using System; using System.Collections.Generic; using FlowAPI.Application.DTOs.Graph; using FlowAPI.Domain.Entities; using FlowAPI.Domain.Enums; namespace FlowAPI.Application.Services { public static class TemplateLibrary { private class TemplateNodeDef { public string TempId { get; set; } = string.Empty; public string Label { get; set; } = string.Empty; public double PosX { get; set; } public double PosY { get; set; } public string Type { get; set; } = "sketch"; public string Color { get; set; } = "white"; public string Description { get; set; } = string.Empty; public string DecisionJson { get; set; } = string.Empty; public double? Width { get; set; } public double? Height { get; set; } } private class TemplateEdgeDef { public string FromTempId { get; set; } = string.Empty; public string ToTempId { get; set; } = string.Empty; public string Condition { get; set; } = "True"; } private class TemplateDef { public string Key { get; set; } = string.Empty; public string Title { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; public string Icon { get; set; } = string.Empty; public string Category { get; set; } = string.Empty; public List Nodes { get; set; } = new(); public List Edges { get; set; } = new(); } private static readonly Dictionary Templates = new() { { "startup", new TemplateDef { Key = "startup", Title = "Запуск Стартапу", Description = "Шлях від формулювання ціннісної пропозиції та CustDev до запуску MVP та перших 100 користувачів.", Icon = "Rocket", Category = "Business", Nodes = new List { new() { TempId = "vprop", Label = "Формулювання ціннісної пропозиції", PosX = 250, PosY = 50, Color = "purple", Description = "Чітко опишіть, яку проблему вирішує ваш продукт і для кого." }, new() { TempId = "custdev", Label = "CustDev: Інтерв'ю з клієнтами", PosX = 250, PosY = 170, Color = "blue", Description = "Проведіть принаймні 10 інтерв'ю з потенційними користувачами." }, new() { TempId = "fit", Label = "Чи підтверджено проблему?", PosX = 250, PosY = 290, Type = "condition", DecisionJson = "{\"options\":[{\"id\":\"opt-yes\",\"label\":\"Так (Є інтерес)\"},{\"id\":\"opt-no\",\"label\":\"Ні (Потрібен півот)\"}],\"selected\":[],\"multiSelect\":false}" }, new() { TempId = "mvp", Label = "Розробка MVP", PosX = 100, PosY = 420, Color = "green", Description = "Створіть мінімально життєздатний продукт з однією ключовою функцією." }, new() { TempId = "pivot", Label = "Півот ідеї", PosX = 400, PosY = 420, Color = "orange", Description = "Змініть напрямок або цільову аудиторію на основі фідбеку з інтерв'ю." }, new() { TempId = "landing", Label = "Лендинг та збір заявок", PosX = 100, PosY = 550, Color = "pink", Description = "Налаштуйте просту сторінку для збору контактів зацікавлених клієнтів." }, new() { TempId = "users", Label = "Залучення перших 100 користувачів", PosX = 100, PosY = 680, Color = "yellow", Description = "Використовуйте безкоштовні канали (соцмережі, профільні форуми, зв'язки)." } }, Edges = new List { new() { FromTempId = "vprop", ToTempId = "custdev" }, new() { FromTempId = "custdev", ToTempId = "fit" }, new() { FromTempId = "fit", ToTempId = "mvp", Condition = "True" }, new() { FromTempId = "fit", ToTempId = "pivot", Condition = "False" }, new() { FromTempId = "pivot", ToTempId = "vprop" }, new() { FromTempId = "mvp", ToTempId = "landing" }, new() { FromTempId = "landing", ToTempId = "users" } } } }, { "software", new TemplateDef { Key = "software", Title = "Розробка ПЗ (Web App)", Description = "Стандартний життєвий цикл розробки веб-додатку: вимоги, дизайн, UI/UX, API, інтеграція та реліз.", Icon = "Code", Category = "Development", Nodes = new List { new() { TempId = "req", Label = "Збір та аналіз вимог", PosX = 250, PosY = 50, Color = "blue", Description = "Опишіть функціонал проекту та підготуйте ТЗ." }, new() { TempId = "arch", Label = "Архітектура та проектування БД", PosX = 250, PosY = 170, Color = "purple", Description = "Спроектуйте схему бази даних та виберіть стек технологій." }, new() { TempId = "uiux", Label = "UI/UX макети та дизайн", PosX = 100, PosY = 300, Color = "pink", Description = "Створіть клікабельний прототип у Figma." }, new() { TempId = "api", Label = "Розробка API та Бекенду", PosX = 400, PosY = 300, Color = "yellow", Description = "Реалізуйте серверу логіку, авторизацію та базу даних." }, new() { TempId = "integ", Label = "Інтеграція та тестування", PosX = 250, PosY = 440, Color = "green", Description = "З'єднайте фронтенд з бекендом та проведіть ручне тестування." }, new() { TempId = "staging", Label = "Деплой на Staging сервер", PosX = 250, PosY = 560, Color = "orange", Description = "Розгорніть тестову версію додатку для фінальної перевірки." }, new() { TempId = "prod", Label = "Реліз у Production та моніторинг", PosX = 250, PosY = 680, Color = "green", Description = "Запустіть додаток для реальних користувачів та підключіть аналітику." } }, Edges = new List { new() { FromTempId = "req", ToTempId = "arch" }, new() { FromTempId = "arch", ToTempId = "uiux" }, new() { FromTempId = "arch", ToTempId = "api" }, new() { FromTempId = "uiux", ToTempId = "integ" }, new() { FromTempId = "api", ToTempId = "integ" }, new() { FromTempId = "integ", ToTempId = "staging" }, new() { FromTempId = "staging", ToTempId = "prod" } } } }, { "language", new TemplateDef { Key = "language", Title = "Вивчення Іноземної Мови", Description = "Методика освоєння мови через щоденні звички, проходження базового курсу та вихід у практику.", Icon = "Languages", Category = "Education", Nodes = new List { new() { TempId = "goal", Label = "Визначення цілей та ресурсів", PosX = 250, PosY = 50, Color = "purple", Description = "Оберіть додатки, підручники та поставте чітку ціль (наприклад, рівень B1)." }, new() { TempId = "words", Label = "Вчити 15 нових слів щодня", PosX = 100, PosY = 170, Type = "habit", Color = "blue", Description = "Використовуйте картки Anki або Quizlet для регулярного повторення.", DecisionJson = "{\"resetRule\":{\"type\":\"daily\",\"time\":\"00:00\"},\"createdAt\":\"" + DateTime.UtcNow.ToString("o") + "\",\"description\":\"Використовуйте картки Anki або Quizlet для регулярного повторення.\",\"tags\":[],\"status\":\"Todo\",\"progress\":0}" }, new() { TempId = "listen", Label = "Слухати подкаст / дивитись відео", PosX = 400, PosY = 170, Type = "habit", Color = "pink", Description = "Принаймні 20 хвилин фонового або активного слухання щодня.", DecisionJson = "{\"resetRule\":{\"type\":\"daily\",\"time\":\"00:00\"},\"createdAt\":\"" + DateTime.UtcNow.ToString("o") + "\",\"description\":\"Принаймні 20 хвилин фонового або активного слухання щодня.\",\"tags\":[],\"status\":\"Todo\",\"progress\":0}" }, new() { TempId = "course", Label = "Завершити курс для початківців", PosX = 250, PosY = 300, Color = "yellow", Description = "Пройти базову граматику та отримати розуміння структури мови." }, new() { TempId = "speak", Label = "Розмовна сесія (1 раз на тиждень)", PosX = 100, PosY = 430, Type = "habit", Color = "green", Description = "Знайдіть партнера для спілкування на Tandem або викладача.", DecisionJson = "{\"resetRule\":{\"type\":\"weekly\",\"time\":\"00:00\"},\"createdAt\":\"" + DateTime.UtcNow.ToString("o") + "\",\"description\":\"Знайдіть партнера для спілкування на Tandem або викладача.\",\"tags\":[],\"status\":\"Todo\",\"progress\":0}" }, new() { TempId = "read", Label = "Прочитати першу коротку розповідь", PosX = 400, PosY = 430, Color = "orange", Description = "Оберіть адаптовану книгу під ваш поточний рівень." }, new() { TempId = "exam", Label = "Скласти тестовий іспит", PosX = 250, PosY = 560, Color = "green", Description = "Перевірте свій прогрес за допомогою офіційного пробного тесту." } }, Edges = new List { new() { FromTempId = "goal", ToTempId = "words" }, new() { FromTempId = "goal", ToTempId = "listen" }, new() { FromTempId = "words", ToTempId = "course" }, new() { FromTempId = "listen", ToTempId = "course" }, new() { FromTempId = "course", ToTempId = "speak" }, new() { FromTempId = "course", ToTempId = "read" }, new() { FromTempId = "speak", ToTempId = "exam" }, new() { FromTempId = "read", ToTempId = "exam" } } } }, { "fitness", new TemplateDef { Key = "fitness", Title = "Фітнес та Здорове Тіло", Description = "Збалансований план тренувань, контролю харчування та циклічного аналізу результатів.", Icon = "Activity", Category = "Health", Nodes = new List { new() { TempId = "setup", Label = "Поставити ціль та розрахувати КБЖВ", PosX = 250, PosY = 50, Color = "blue", Description = "Визначте добову норму калорій залежно від мети (схуднення чи набір маси)." }, new() { TempId = "plan", Label = "Скласти план тренувань та раціон", PosX = 250, PosY = 170, Color = "purple", Description = "Виберіть 3 дні для занять та підготуйте перелік здорових страв." }, new() { TempId = "diet", Label = "Трекати щоденне харчування та воду", PosX = 100, PosY = 300, Type = "habit", Color = "green", Description = "Записуйте все з'їдене в MyFitnessPal та пийте достатньо води.", DecisionJson = "{\"resetRule\":{\"type\":\"daily\",\"time\":\"00:00\"},\"createdAt\":\"" + DateTime.UtcNow.ToString("o") + "\",\"description\":\"Записуйте все з'їдене в MyFitnessPal та пийте достатньо води.\",\"tags\":[],\"status\":\"Todo\",\"progress\":0}" }, new() { TempId = "workout", Label = "Силове або кардіо тренування", PosX = 400, PosY = 300, Type = "habit", Color = "orange", Description = "Виконуйте заплановане тренування 3 рази на тиждень.", DecisionJson = "{\"resetRule\":{\"type\":\"weekly\",\"time\":\"00:00\"},\"createdAt\":\"" + DateTime.UtcNow.ToString("o") + "\",\"description\":\"Виконуйте заплановане тренування 3 рази на тиждень.\",\"tags\":[],\"status\":\"Todo\",\"progress\":0}" }, new() { TempId = "check", Label = "Контрольне зважування та заміри (4 тижні)", PosX = 250, PosY = 430, Color = "yellow", Description = "Порівняйте поточну вагу та об'єми тіла з початковими показниками." }, new() { TempId = "satisfied", Label = "Чи є прогрес задовільним?", PosX = 250, PosY = 550, Type = "condition", DecisionJson = "{\"options\":[{\"id\":\"opt-yes\",\"label\":\"Так (Продовжуємо)\"},{\"id\":\"opt-no\",\"label\":\"Ні (Потрібні коригування)\"}],\"selected\":[],\"multiSelect\":false}" }, new() { TempId = "advance", Label = "Ускладнити програму тренувань", PosX = 100, PosY = 680, Color = "pink", Description = "Додайте вагу на снарядах або збільшіть інтенсивність." }, new() { TempId = "adjust", Label = "Скоригувати КБЖВ та план їжі", PosX = 400, PosY = 680, Color = "yellow", Description = "Зменшіть або збільшіть калорійність раціону на 10%." } }, Edges = new List { new() { FromTempId = "setup", ToTempId = "plan" }, new() { FromTempId = "plan", ToTempId = "diet" }, new() { FromTempId = "plan", ToTempId = "workout" }, new() { FromTempId = "diet", ToTempId = "check" }, new() { FromTempId = "workout", ToTempId = "check" }, new() { FromTempId = "check", ToTempId = "satisfied" }, new() { FromTempId = "satisfied", ToTempId = "advance", Condition = "True" }, new() { FromTempId = "satisfied", ToTempId = "adjust", Condition = "False" }, new() { FromTempId = "adjust", ToTempId = "plan" } } } }, { "gamedev", new TemplateDef { Key = "gamedev", Title = "Створення Інді-Гри", Description = "Повний цикл геймдеву від ідеї та прототипу до створення сторінки в Steam та релізу.", Icon = "Gamepad", Category = "Creative", Nodes = new List { new() { TempId = "concept", Label = "Концепт та написання GDD", PosX = 250, PosY = 50, Color = "pink", Description = "Сформулюйте основну ідею гри та опишіть її механіки в дизайн-документі." }, new() { TempId = "proto", Label = "Прототип базового геймплею", PosX = 250, PosY = 170, Color = "purple", Description = "Створіть «сіру коробку» на рушії, щоб перевірити, чи весело грати." }, new() { TempId = "art", Label = "Стилістика та створення артів", PosX = 100, PosY = 300, Color = "orange", Description = "Визначте художній стиль та підготуйте перші 2D/3D моделі." }, new() { TempId = "level", Label = "Дизайн рівнів та звуковий супровід", PosX = 400, PosY = 300, Color = "blue", Description = "Створіть тестовий рівень та додайте базові звукові ефекти." }, new() { TempId = "playable", Label = "Перша грабельна демо-версія", PosX = 250, PosY = 430, Color = "yellow", Description = "Зберіть стабільний білд для тестування друзями." }, new() { TempId = "test", Label = "Плейтести та виправлення багів", PosX = 100, PosY = 560, Type = "habit", Color = "pink", Description = "Щодня грайте, збирайте фідбек та виправляйте помилки.", DecisionJson = "{\"resetRule\":{\"type\":\"daily\",\"time\":\"00:00\"},\"createdAt\":\"" + DateTime.UtcNow.ToString("o") + "\",\"description\":\"Щодня грайте, збирайте фідбек та виправляйте помилки.\",\"tags\":[],\"status\":\"Todo\",\"progress\":0}" }, new() { TempId = "steam", Label = "Створення сторінки в Steam", PosX = 400, PosY = 560, Color = "yellow", Description = "Підготуйте красиві скріншоти, трейлер та запустіть сторінку для збору Wishlists." }, new() { TempId = "release", Label = "Реліз гри в Steam", PosX = 250, PosY = 690, Color = "green", Description = "Опублікуйте фінальний білд та підтримайте запуск маркетинговою кампанією." } }, Edges = new List { new() { FromTempId = "concept", ToTempId = "proto" }, new() { FromTempId = "proto", ToTempId = "art" }, new() { FromTempId = "proto", ToTempId = "level" }, new() { FromTempId = "art", ToTempId = "playable" }, new() { FromTempId = "level", ToTempId = "playable" }, new() { FromTempId = "playable", ToTempId = "test" }, new() { FromTempId = "playable", ToTempId = "steam" }, new() { FromTempId = "test", ToTempId = "release" }, new() { FromTempId = "steam", ToTempId = "release" } } } }, { "finance", new TemplateDef { Key = "finance", Title = "Фінансова Свобода", Description = "Покроковий план досягнення фінансової незалежності: облік витрат, подушка безпеки та інвестиції.", Icon = "DollarSign", Category = "Finance", Nodes = new List { new() { TempId = "audit", Label = "Аудит активів та боргових зобов'язань", PosX = 250, PosY = 50, Color = "yellow", Description = "Запишіть усі свої збереження, майно та кредити." }, new() { TempId = "track", Label = "Щоденний облік усіх витрат", PosX = 100, PosY = 170, Type = "habit", Color = "blue", Description = "Записуйте кожну покупку в мобільний додаток або Excel таблицю.", DecisionJson = "{\"resetRule\":{\"type\":\"daily\",\"time\":\"00:00\"},\"createdAt\":\"" + DateTime.UtcNow.ToString("o") + "\",\"description\":\"Записуйте кожну покупку в мобільний додаток або Excel таблицю.\",\"tags\":[],\"status\":\"Todo\",\"progress\":0}" }, new() { TempId = "emergency", Label = "Сформувати подушку на 3 місяці", PosX = 300, PosY = 170, Color = "orange", Description = "Розрахуйте мінімальний бюджет життя та накопичте цю суму на депозиті." }, new() { TempId = "debt", Label = "Виплатити всі споживчі кредити", PosX = 250, PosY = 290, Color = "pink", Description = "Спрямуйте вільні кошти на швидке закриття боргів з найвищими відсотками." }, new() { TempId = "invest", Label = "Інвестувати 20% від доходу", PosX = 100, PosY = 420, Type = "habit", Color = "green", Description = "Регулярно купуйте надійні індекси (ETF) або облігації.", DecisionJson = "{\"resetRule\":{\"type\":\"monthly\",\"time\":\"00:00\"},\"createdAt\":\"" + DateTime.UtcNow.ToString("o") + "\",\"description\":\"Регулярно купуйте надійні індекси (ETF) або облігації.\",\"tags\":[],\"status\":\"Todo\",\"progress\":0}" }, new() { TempId = "passive", Label = "Створити джерело пасивного доходу", PosX = 400, PosY = 420, Color = "purple", Description = "Створіть власний блог, курс, невеликий онлайн-сервіс чи придбайте дивідендні акції." }, new() { TempId = "freedom", Label = "Досягти фінансової незалежності", PosX = 250, PosY = 550, Color = "green", Description = "Коли пасивний дохід повністю перекриє ваші щомісячні витрати." } }, Edges = new List { new() { FromTempId = "audit", ToTempId = "track" }, new() { FromTempId = "audit", ToTempId = "emergency" }, new() { FromTempId = "track", ToTempId = "debt" }, new() { FromTempId = "emergency", ToTempId = "debt" }, new() { FromTempId = "debt", ToTempId = "invest" }, new() { FromTempId = "debt", ToTempId = "passive" }, new() { FromTempId = "invest", ToTempId = "freedom" }, new() { FromTempId = "passive", ToTempId = "freedom" } } } }, { "writing", new TemplateDef { Key = "writing", Title = "Написання та Публікація Книги", Description = "Шлях автора: від розробки сюжету та персонажів до щоденного написання, редактури та релізу.", Icon = "BookOpen", Category = "Creative", Nodes = new List { new() { TempId = "outline", Label = "Побудова сюжету та анкет персонажів", PosX = 250, PosY = 50, Color = "pink", Description = "Напишіть синопсис та детально розпишіть структуру розділів книги." }, new() { TempId = "words", Label = "Писати по 1000 слів щодня", PosX = 100, PosY = 170, Type = "habit", Color = "purple", Description = "Формуйте звичку писати без критики та внутрішнього редактора.", DecisionJson = "{\"resetRule\":{\"type\":\"daily\",\"time\":\"00:00\"},\"createdAt\":\"" + DateTime.UtcNow.ToString("o") + "\",\"description\":\"Формуйте звичку писати без критики та внутрішнього редактора.\",\"tags\":[],\"status\":\"Todo\",\"progress\":0}" }, new() { TempId = "draft", Label = "Завершити першу чернетку", PosX = 350, PosY = 170, Color = "yellow", Description = "Напишіть повну історію від початку до кінця (орієнтовно 50-80 тис. слів)." }, new() { TempId = "edit", Label = "Самостійна редактура та вичитка", PosX = 250, PosY = 300, Color = "orange", Description = "Виправте логічні помилки, покращіть діалоги та стиль оповідання." }, new() { TempId = "beta", Label = "Отримати фідбек від бета-рідерів", PosX = 250, PosY = 420, Color = "blue", Description = "Дайте почитати книгу кільком людям із вашої цільової аудиторії." }, new() { TempId = "professional", Label = "Професійна коректура та верстка", PosX = 250, PosY = 540, Color = "pink", Description = "Знайдіть професійного редактора для виправлення граматики та підготовки макету." }, new() { TempId = "cover", Label = "Створення обкладинки та форматування", PosX = 100, PosY = 660, Color = "purple", Description = "Замовте обкладинку в дизайнера та підготуйте формати ePub, PDF." }, new() { TempId = "publish", Label = "Публікація та запуск маркетингу", PosX = 350, PosY = 660, Color = "green", Description = "Опублікуйте книгу на Amazon / Litres та запустіть промо-акції." } }, Edges = new List { new() { FromTempId = "outline", ToTempId = "words" }, new() { FromTempId = "words", ToTempId = "draft" }, new() { FromTempId = "draft", ToTempId = "edit" }, new() { FromTempId = "edit", ToTempId = "beta" }, new() { FromTempId = "beta", ToTempId = "professional" }, new() { FromTempId = "professional", ToTempId = "cover" }, new() { FromTempId = "professional", ToTempId = "publish" } } } } }; public static IEnumerable GetTemplateInfos() { var list = new List(); foreach (var kv in Templates) { list.Add(new TemplateInfoDto { Key = kv.Value.Key, Title = kv.Value.Title, Description = kv.Value.Description, Icon = kv.Value.Icon, Category = kv.Value.Category }); } return list; } public static (List Nodes, List Edges) GetTemplateContent(string key, Guid graphId) { if (!Templates.TryGetValue(key.ToLower().Trim(), out var template)) { return (new List(), new List()); } var nodes = new List(); var edges = new List(); var idMapping = new Dictionary(); // 1. Instantiate nodes and map their IDs foreach (var nodeDef in template.Nodes) { var newId = Guid.NewGuid(); idMapping[nodeDef.TempId] = newId; var taskNode = new TaskNode { Id = newId, GraphId = graphId, Label = nodeDef.Label, PosX = nodeDef.PosX, PosY = nodeDef.PosY, Type = nodeDef.Type, State = NodeState.Pending, CreatedAt = DateTime.UtcNow, Description = nodeDef.Description, Color = nodeDef.Color, IsPinned = false, Decision = string.IsNullOrEmpty(nodeDef.DecisionJson) ? null : nodeDef.DecisionJson }; // For normal sketch nodes without custom Decision JSON, populate standard default JSON if (nodeDef.Type == "sketch" && string.IsNullOrEmpty(nodeDef.DecisionJson)) { taskNode.Decision = $"{{\"description\":\"{nodeDef.Description}\",\"progress\":0,\"showProgress\":false,\"decision\":\"bottom\",\"assignees\":[],\"color\":\"{nodeDef.Color}\",\"isPinned\":false,\"tags\":[],\"status\":\"Todo\"}}"; } nodes.Add(taskNode); } // 2. Instantiate edges foreach (var edgeDef in template.Edges) { if (idMapping.TryGetValue(edgeDef.FromTempId, out var fromId) && idMapping.TryGetValue(edgeDef.ToTempId, out var toId)) { var edge = new Edge { Id = Guid.NewGuid(), GraphId = graphId, FromNodeId = fromId, ToNodeId = toId, Condition = edgeDef.Condition }; edges.Add(edge); } } return (nodes, edges); } } }