using System; using System.Collections.Generic; using System.Text; using GmRelay.Shared.Domain; using GmRelay.Shared.Features.Sessions.CreateSession.Wizard; using Telegram.Bot.Types.ReplyMarkups; namespace GmRelay.Bot.Features.Sessions.CreateSession.Wizard; public static class WizardStep { public const int MaxTitleLength = 200; public const int MaxDescriptionLength = 4000; public const int MaxSystemLength = 100; public const int MaxCapacity = 50; public const int MinCapacity = 1; public const int MinDurationHours = 1; public const int MaxDurationHours = 12; public static (string text, InlineKeyboardMarkup keyboard) Render( WizardDraft draft, WizardPayload payload, IReadOnlyList? clubs = null) { return draft.Step switch { WizardStepNames.Type => RenderType(), WizardStepNames.Title => RenderTitle(), WizardStepNames.Description => RenderDescription(), WizardStepNames.Cover => RenderCover(), WizardStepNames.System => RenderSystem(), WizardStepNames.Duration => RenderDuration(), WizardStepNames.DateTime => RenderDateTime(), WizardStepNames.Capacity => RenderCapacity(), WizardStepNames.Visibility => RenderVisibility(), WizardStepNames.PickClub => RenderPickClub(clubs ?? Array.Empty()), WizardStepNames.Publish => RenderPublish(), WizardStepNames.Confirm => RenderSingleConfirm(payload), WizardStepNames.PoolSystemDuration => RenderPoolSystemDuration(), WizardStepNames.PoolAddSlots => RenderPoolAddSlots(payload), WizardStepNames.PoolSlotDateTime => RenderPoolSlotDateTime(), WizardStepNames.PoolSlotCapacity => RenderPoolSlotCapacity(), WizardStepNames.PoolConfirm => RenderPoolConfirm(payload), _ => throw new InvalidOperationException($"Unknown wizard step: {draft.Step}"), }; } // ── Single-game renderers ────────────────────────────────────────── private static (string, InlineKeyboardMarkup) RenderType() => ( "🎲 Создание новой игровой сессии\n\nЧто создаём?", new InlineKeyboardMarkup(new[] { new[] { InlineKeyboardButton.WithCallbackData("🎯 Одну игру", WizardCallbackData.Choice(WizardStepNames.Type, "single")) }, new[] { InlineKeyboardButton.WithCallbackData("📅 Пул игр", WizardCallbackData.Choice(WizardStepNames.Type, "pool")) }, new[] { InlineKeyboardButton.WithCallbackData("❌ Отмена", WizardCallbackData.Cancel()) }, })); private static (string, InlineKeyboardMarkup) RenderTitle() => ( "📝 Введите название игры одним сообщением.", BackCancel()); private static (string, InlineKeyboardMarkup) RenderDescription() => ( "📄 Введите описание (или «-», чтобы пропустить).", SkipBackCancel()); private static (string, InlineKeyboardMarkup) RenderCover() => ( "🖼 Пришлите картинку как вложение или URL (или «-»).", SkipBackCancel()); private static (string, InlineKeyboardMarkup) RenderSystem() { var buttons = new List { new[] { InlineKeyboardButton.WithCallbackData("D&D 5e", WizardCallbackData.Choice(WizardStepNames.System, "Dnd5e")) }, new[] { InlineKeyboardButton.WithCallbackData("Pathfinder 2e", WizardCallbackData.Choice(WizardStepNames.System, "Pathfinder2e")) }, new[] { InlineKeyboardButton.WithCallbackData("Call of Cthulhu",WizardCallbackData.Choice(WizardStepNames.System, "CallOfCthulhu7e")) }, new[] { InlineKeyboardButton.WithCallbackData("GURPS", WizardCallbackData.Choice(WizardStepNames.System, "GURPS")) }, new[] { InlineKeyboardButton.WithCallbackData("Fate", WizardCallbackData.Choice(WizardStepNames.System, "Fate")) }, new[] { InlineKeyboardButton.WithCallbackData("Другое… ✏️", WizardCallbackData.Choice(WizardStepNames.System, "_other")) }, new[] { InlineKeyboardButton.WithCallbackData("⏭ Пропустить", WizardCallbackData.Choice(WizardStepNames.System, "_skip")) }, }; return ("🎲 Выберите систему.", new InlineKeyboardMarkup(buttons).AppendBackCancel()); } private static (string, InlineKeyboardMarkup) RenderDuration() => ( "⏱ Выберите длительность.", new InlineKeyboardMarkup(new[] { new[] { InlineKeyboardButton.WithCallbackData("3 часа", WizardCallbackData.Choice(WizardStepNames.Duration, "180")) }, new[] { InlineKeyboardButton.WithCallbackData("4 часа", WizardCallbackData.Choice(WizardStepNames.Duration, "240")) }, new[] { InlineKeyboardButton.WithCallbackData("5 часов", WizardCallbackData.Choice(WizardStepNames.Duration, "300")) }, new[] { InlineKeyboardButton.WithCallbackData("6 часов", WizardCallbackData.Choice(WizardStepNames.Duration, "360")) }, new[] { InlineKeyboardButton.WithCallbackData("Другое… ✏️", WizardCallbackData.Choice(WizardStepNames.Duration, "_other")) }, new[] { InlineKeyboardButton.WithCallbackData("⏭ Пропустить", WizardCallbackData.Choice(WizardStepNames.Duration, "_skip")) }, }).AppendBackCancel()); private static (string, InlineKeyboardMarkup) RenderDateTime() => ( "📅 Введите дату и время в формате ДД.ММ.ГГГГ ЧЧ:ММ (Москва).", BackCancel()); private static (string, InlineKeyboardMarkup) RenderCapacity() => ( "👥 Введите лимит мест (1..50) одним числом.\nЗатем нажмите кнопку waitlist.", new InlineKeyboardMarkup(new[] { new[] { InlineKeyboardButton.WithCallbackData("✅ Waitlist вкл", WizardCallbackData.Choice(WizardStepNames.Capacity, "waitlist:on")) }, new[] { InlineKeyboardButton.WithCallbackData("❌ Без waitlist", WizardCallbackData.Choice(WizardStepNames.Capacity, "waitlist:off")) }, }).AppendBackCancel()); private static (string, InlineKeyboardMarkup) RenderVisibility() => ( "🔒 Выберите видимость.", new InlineKeyboardMarkup(new[] { new[] { InlineKeyboardButton.WithCallbackData("🌐 Публичная в общем showcase", WizardCallbackData.Choice(WizardStepNames.Visibility, "public")) }, new[] { InlineKeyboardButton.WithCallbackData("🏠 Публичная в витрине клуба", WizardCallbackData.Choice(WizardStepNames.Visibility, "club")) }, new[] { InlineKeyboardButton.WithCallbackData("🔐 Только для членов клуба", WizardCallbackData.Choice(WizardStepNames.Visibility, "members")) }, new[] { InlineKeyboardButton.WithCallbackData("🏷 Выбрать клуб…", WizardCallbackData.Choice(WizardStepNames.Visibility, "pickclub")) }, }).AppendBackCancel()); private static (string, InlineKeyboardMarkup) RenderPickClub(IReadOnlyList clubs) { if (clubs.Count == 0) { return ( "🏷 У вас нет клубов. Создайте клуб в Web dashboard и вернитесь.", BackCancel()); } var rows = new List(); foreach (var club in clubs) { rows.Add(new[] { InlineKeyboardButton.WithCallbackData(club.Name, WizardCallbackData.Choice(WizardStepNames.PickClub, club.ClubId.ToString())) }); } return ("🏷 Выберите клуб:", new InlineKeyboardMarkup(rows).AppendBackCancel()); } private static (string, InlineKeyboardMarkup) RenderPublish() => ( "✨ Опубликовать в витрине сейчас?", new InlineKeyboardMarkup(new[] { new[] { InlineKeyboardButton.WithCallbackData("✅ Опубликовать", WizardCallbackData.Choice(WizardStepNames.Publish, "yes")) }, new[] { InlineKeyboardButton.WithCallbackData("📝 Только в чате", WizardCallbackData.Choice(WizardStepNames.Publish, "no")) }, }).AppendBackCancel()); private static (string, InlineKeyboardMarkup) RenderSingleConfirm(WizardPayload p) { var sb = new StringBuilder(); sb.AppendLine("👀 Проверьте перед созданием:"); sb.AppendLine(); sb.AppendLine($"🎲 {p.Title}"); if (!string.IsNullOrEmpty(p.Description)) sb.AppendLine($"📄 {p.Description}"); if (!string.IsNullOrEmpty(p.System)) sb.AppendLine($"🎲 Система: {p.System}"); if (p.DurationMinutes.HasValue) sb.AppendLine($"⏱ Длительность: {p.DurationMinutes / 60} ч"); if (p.Single?.ScheduledAt is { } at) sb.AppendLine($"📅 {at.FormatMoscow()} (МСК)"); if (p.Single?.MaxPlayers is { } mp) sb.AppendLine($"👥 Мест: {mp}, waitlist {(p.Waitlist == true ? "вкл" : "выкл")}"); sb.AppendLine($"🔒 Видимость: {RenderVisibilityText(p.Visibility)}"); return (sb.ToString(), new InlineKeyboardMarkup(new[] { new[] { InlineKeyboardButton.WithCallbackData("✅ Создать", WizardCallbackData.Create()) }, new[] { InlineKeyboardButton.WithCallbackData("⬅️ Назад", WizardCallbackData.Back()) }, new[] { InlineKeyboardButton.WithCallbackData("❌ Отмена", WizardCallbackData.Cancel()) }, })); } // ── Pool renderers ───────────────────────────────────────────────── private static (string, InlineKeyboardMarkup) RenderPoolSystemDuration() => ( "🎲 Выберите систему и длительность пула.", new InlineKeyboardMarkup(new[] { new[] { InlineKeyboardButton.WithCallbackData("D&D 5e · 4 ч", WizardCallbackData.Choice(WizardStepNames.PoolSystemDuration, "Dnd5e:240")) }, new[] { InlineKeyboardButton.WithCallbackData("Pathfinder 2e · 4 ч", WizardCallbackData.Choice(WizardStepNames.PoolSystemDuration, "Pathfinder2e:240")) }, new[] { InlineKeyboardButton.WithCallbackData("Call of Cthulhu · 3 ч",WizardCallbackData.Choice(WizardStepNames.PoolSystemDuration, "CallOfCthulhu7e:180")) }, new[] { InlineKeyboardButton.WithCallbackData("GURPS · 4 ч", WizardCallbackData.Choice(WizardStepNames.PoolSystemDuration, "GURPS:240")) }, new[] { InlineKeyboardButton.WithCallbackData("Другое… ✏️", WizardCallbackData.Choice(WizardStepNames.PoolSystemDuration, "_custom")) }, }).AppendBackCancel()); private static (string, InlineKeyboardMarkup) RenderPoolAddSlots(WizardPayload p) => ( $"📅 Слоты пула «{p.Title}»\n\nДобавлено: {(p.Pool?.Slots.Count ?? 0)}", new InlineKeyboardMarkup(new[] { new[] { InlineKeyboardButton.WithCallbackData("➕ Добавить слот", WizardCallbackData.Choice(WizardStepNames.PoolAddSlots, "add")) }, new[] { InlineKeyboardButton.WithCallbackData("✅ Готово, к превью", WizardCallbackData.Choice(WizardStepNames.PoolAddSlots, "done")) }, }).AppendBackCancel()); private static (string, InlineKeyboardMarkup) RenderPoolSlotDateTime() => ( "📅 Введите дату/время слота (ДД.ММ.ГГГГ ЧЧ:ММ).", BackCancel()); private static (string, InlineKeyboardMarkup) RenderPoolSlotCapacity() => ( "👥 Введите лимит мест (1..50) и выберите waitlist.", new InlineKeyboardMarkup(new[] { new[] { InlineKeyboardButton.WithCallbackData("✅ Waitlist вкл", WizardCallbackData.Choice(WizardStepNames.PoolSlotCapacity, "waitlist:on")) }, new[] { InlineKeyboardButton.WithCallbackData("❌ Без waitlist", WizardCallbackData.Choice(WizardStepNames.PoolSlotCapacity, "waitlist:off")) }, }).AppendBackCancel()); private static (string, InlineKeyboardMarkup) RenderPoolConfirm(WizardPayload p) { var sb = new StringBuilder(); sb.AppendLine("👀 Проверьте пул перед созданием:"); sb.AppendLine(); sb.AppendLine($"📝 {p.Title}"); if (!string.IsNullOrEmpty(p.Description)) sb.AppendLine($"📄 {p.Description}"); if (!string.IsNullOrEmpty(p.System)) sb.AppendLine($"🎲 Система: {p.System}"); if (p.DurationMinutes.HasValue) sb.AppendLine($"⏱ Длительность: {p.DurationMinutes / 60} ч"); sb.AppendLine($"🔒 Видимость: {RenderVisibilityText(p.Visibility)}"); sb.AppendLine(); sb.AppendLine($"Слоты ({p.Pool?.Slots.Count ?? 0}):"); if (p.Pool is not null) { foreach (var s in p.Pool.Slots) { sb.AppendLine($" • {s.ScheduledAt.FormatMoscow()} — мест {s.MaxPlayers}, waitlist {(s.Waitlist ? "вкл" : "выкл")}"); } } return (sb.ToString(), new InlineKeyboardMarkup(new[] { new[] { InlineKeyboardButton.WithCallbackData("✅ Создать пул", WizardCallbackData.Create()) }, new[] { InlineKeyboardButton.WithCallbackData("⬅️ Назад", WizardCallbackData.Back()) }, new[] { InlineKeyboardButton.WithCallbackData("❌ Отмена", WizardCallbackData.Cancel()) }, })); } // ── Helpers ──────────────────────────────────────────────────────── private static InlineKeyboardMarkup BackCancel() => new(new[] { new[] { InlineKeyboardButton.WithCallbackData("⬅️ Назад", WizardCallbackData.Back()) }, new[] { InlineKeyboardButton.WithCallbackData("❌ Отмена", WizardCallbackData.Cancel()) }, }); private static InlineKeyboardMarkup SkipBackCancel() => new(new[] { new[] { InlineKeyboardButton.WithCallbackData("⏭ Пропустить", WizardCallbackData.Choice("Skip", "1")) }, new[] { InlineKeyboardButton.WithCallbackData("⬅️ Назад", WizardCallbackData.Back()) }, new[] { InlineKeyboardButton.WithCallbackData("❌ Отмена", WizardCallbackData.Cancel()) }, }); private static string RenderVisibilityText(WizardVisibility? v) => v switch { WizardVisibility.Public => "публичная в общем showcase", WizardVisibility.Club => "публичная в витрине клуба", WizardVisibility.Members => "только для членов клуба", _ => "не задана", }; } internal static class InlineKeyboardMarkupExtensions { public static InlineKeyboardMarkup AppendBackCancel(this InlineKeyboardMarkup kb) => kb; }