using NexusReader.Application.Abstractions.Services; using FluentResults; using NexusReader.Application.Queries.Graph; using NexusReader.Application.Queries.Quiz; using NexusReader.UI.Shared.Services; using NexusReader.Application.DTOs.AI; using Microsoft.Extensions.Logging; namespace NexusReader.UI.Shared.Services; public sealed partial class KnowledgeCoordinator : IDisposable { private readonly IKnowledgeService _knowledgeService; private readonly IKnowledgeGraphService _graphService; private readonly IQuizStateService _quizService; private readonly IPlatformService _platformService; private readonly IReaderInteractionService _interactionService; private readonly ILogger _logger; /// /// Raised when the knowledge graph has been updated with new data. /// Subscribers must return a Task to enable proper async handling. /// public event Func? OnGraphUpdated; public KnowledgeCoordinator( IKnowledgeService knowledgeService, IKnowledgeGraphService graphService, IQuizStateService quizService, IPlatformService platformService, IReaderInteractionService interactionService, ILogger logger) { _knowledgeService = knowledgeService; _graphService = graphService; _quizService = quizService; _platformService = platformService; _interactionService = interactionService; _logger = logger; _interactionService.OnNodeSelected += HandleNodeSelected; } private async Task HandleNodeSelected(string nodeId) { string? targetBlockId = nodeId; var graph = _graphService.CurrentGraphData; if (graph != null) { var selectedNode = graph.Nodes.FirstOrDefault(n => n.Id == nodeId); if (selectedNode != null && selectedNode.Group == "concept") { // Look for connected block nodes (group: "current") in the links var connectedLinks = graph.Links.Where(l => l.Source == nodeId || l.Target == nodeId).ToList(); foreach (var link in connectedLinks) { var otherId = link.Source == nodeId ? link.Target : link.Source; var otherNode = graph.Nodes.FirstOrDefault(n => n.Id == otherId); if (otherNode != null && otherNode.Group == "current") { targetBlockId = otherId; break; } } } } if (!string.IsNullOrEmpty(targetBlockId)) { await _interactionService.RequestScrollToBlock(targetBlockId); await _interactionService.RequestHighlightBlock(targetBlockId); } } public async Task ProcessFullPageAsync(string fullContent, string tenantId = "global") { if (string.IsNullOrWhiteSpace(fullContent)) return; LogGeneratingGraph(tenantId); await _graphService.Clear(); await _graphService.SetLoading(true); try { var result = await _knowledgeService.GetGraphDataAsync(fullContent, tenantId); if (result.IsSuccess) { var packet = result.Value; if (packet.Graph != null) { await _graphService.UpdateGraph(packet.Graph); if (OnGraphUpdated != null) await OnGraphUpdated.Invoke(packet.Graph); await _platformService.VibrateSuccessAsync(); } } } catch (Exception ex) { LogGraphError(ex, tenantId); } } public async Task OnBlockReachedAsync(string blockId, string content) { // Only update active node for "TU JESTEĊš" logic, do NOT trigger highlight here await _graphService.SetActiveNode(blockId); } public async Task> RequestSummaryAndQuizAsync(string content, string tenantId = "global") { await _quizService.SetHydrating(true); LogRequestingSummary(tenantId); try { var result = await _knowledgeService.GetSummaryAndQuizAsync(content, tenantId); if (result.IsSuccess) { var packet = result.Value; var quizQuestions = packet.Quizzes .Select(q => new QuizQuestionDto(q.Question, q.Options, q.CorrectIndex)) .ToList(); await _quizService.SetQuiz(null, new QuizDto(quizQuestions)); await _platformService.VibrateSuccessAsync(); return Result.Ok(packet); } LogSummaryWarning(tenantId); return Result.Fail(result.Errors); } catch (Exception ex) { LogSummaryError(ex, tenantId); return Result.Fail(new Error("Error requesting summary and quiz").CausedBy(ex)); } finally { await _quizService.SetHydrating(false); } } public async Task ClearAsync() { await _graphService.Clear(); await _quizService.SetQuiz(null, null); } public void Dispose() { _interactionService.OnNodeSelected -= HandleNodeSelected; } [LoggerMessage(Level = LogLevel.Information, Message = "[KnowledgeCoordinator] Generating full page graph for tenant: {TenantId}")] private partial void LogGeneratingGraph(string tenantId); [LoggerMessage(Level = LogLevel.Error, Message = "[KnowledgeCoordinator] Error generating graph for tenant: {TenantId}")] private partial void LogGraphError(Exception ex, string tenantId); [LoggerMessage(Level = LogLevel.Information, Message = "[KnowledgeCoordinator] Requesting summary and quiz for tenant: {TenantId}")] private partial void LogRequestingSummary(string tenantId); [LoggerMessage(Level = LogLevel.Warning, Message = "[KnowledgeCoordinator] Failed to get summary and quiz for tenant: {TenantId}")] private partial void LogSummaryWarning(string tenantId); [LoggerMessage(Level = LogLevel.Error, Message = "[KnowledgeCoordinator] Error requesting summary and quiz for tenant: {TenantId}")] private partial void LogSummaryError(Exception ex, string tenantId); }