using BotPages.Core.Abstractions; using BotPages.Core.Routing; using System.Collections.Concurrent; namespace BotPages.Core; /// /// Сервис навигации между страницами. /// Позволяет выполнять переходы, замену страниц и передачу аргументов. /// public sealed class NavigationService { private readonly RoutesRegistry _routes; private readonly Dictionary _singletonPages = new(); private readonly ConcurrentDictionary _sessionPages = new(); private Type? _defaultPage; /// /// Создать сервис навигации. /// internal NavigationService(RoutesRegistry routes) { _routes = routes; } internal void AddDefaultPage() where TPage : SingletonPage { _defaultPage = typeof(TPage); } /// /// Перейти по маршруту без аргументов. /// public Task GoToHomeAsync(PageContext ctx, CancellationToken ct) { return NavigateAsync(_defaultPage!, ctx, null, ct); } /// /// Перейти по маршруту без аргументов. /// public Task GoToAsync(string route, PageContext ctx, CancellationToken ct) { var pageType = _routes.Resolve(route); return NavigateAsync(pageType!, ctx, null, ct); } /// /// Перейти по маршруту с аргументами. /// public Task GoToAsync(string route, TArgs args, PageContext ctx, CancellationToken ct) { var pageType = _routes.Resolve(route); return NavigateAsync(pageType!, ctx, args!, ct); } /// /// Перейти на страницу без аргументов. /// public Task GoToAsync(PageContext ctx, CancellationToken ct) where TPage : Page => NavigateAsync(typeof(TPage), ctx, null, ct); /// /// Перейти на страницу с аргументами. /// public Task GoToAsync(PageContext ctx, TArgs args, CancellationToken ct) where TPage : StatefullPage => NavigateAsync(typeof(TPage), ctx, args!, ct); /// /// Заменить текущую страницу. /// public Task ReplaceWithAsync(PageContext ctx, CancellationToken ct) where TPage : Page => NavigateAsync(typeof(TPage), ctx, null, ct, replace: true); internal async Task NavigateAsync(Type pageType, PageContext ctx, object? args, CancellationToken ct, bool replace = false) { Page? page; if (typeof(SingletonPage).IsAssignableFrom(pageType)) { // Singleton: один объект на всё приложение if (!_singletonPages.TryGetValue(pageType, out page)) { page = (Page)Activator.CreateInstance(pageType)!; _singletonPages[pageType] = page; } } else { // Stateful: новый объект на пользователя page = (Page)Activator.CreateInstance(pageType)!; } if (_sessionPages.TryGetValue(ctx.SessionKey, out var currentPage)) { if (currentPage.GetType() != pageType || replace) { await currentPage.OnLeave(ctx, ct); } } _sessionPages[ctx.SessionKey] = page; if (args is null) await page.OnEnter(ctx, ct); else await (page as dynamic).OnEnter(ctx, (dynamic)args, ct); } /// /// Восстановить текущую страницу из StateStorage. /// public Page? ResolveCurrentPage(PageContext ctx) => _sessionPages.TryGetValue(ctx.SessionKey, out var page) ? page : null; }