using FluentResults; using Microsoft.AspNetCore.SignalR.Client; using NexusReader.Application.Abstractions.Services; using System.Net.Http; namespace NexusReader.UI.Shared.Services; public class SyncService : ISyncService, IAsyncDisposable { private readonly HttpClient _httpClient; private readonly INativeStorageService _storageService; private readonly IPlatformService _platformService; private HubConnection? _hubConnection; private bool _isInitialized; private CancellationTokenSource? _debounceCts; public event Action? OnProgressReceived; public SyncService( HttpClient httpClient, INativeStorageService storageService, IPlatformService platformService) { _httpClient = httpClient; _storageService = storageService; _platformService = platformService; } public async Task InitializeAsync() { if (_isInitialized) return Result.Ok(); var tokenResult = await _storageService.GetSecureString("nexus_auth_token"); if (tokenResult.IsFailed) return Result.Fail("Not authenticated"); var baseUrl = _httpClient.BaseAddress?.ToString() ?? "http://localhost:5000/"; var hubUrl = new Uri(new Uri(baseUrl), "synchub").ToString(); _hubConnection = new HubConnectionBuilder() .WithUrl(hubUrl, options => { options.AccessTokenProvider = () => Task.FromResult(tokenResult.Value); }) .WithAutomaticReconnect() .Build(); _hubConnection.On("ProgressUpdated", (pageId, timestamp) => { OnProgressReceived?.Invoke(pageId, timestamp); }); try { await _hubConnection.StartAsync(); _isInitialized = true; return Result.Ok(); } catch (Exception ex) { return Result.Fail(ex.Message); } } private string? _lastSentPageId; public async Task UpdateProgressAsync(string pageId) { if (pageId == _lastSentPageId) return Result.Ok(); // Proper trailing-edge debounce _debounceCts?.Cancel(); _debounceCts = new CancellationTokenSource(); var token = _debounceCts.Token; _ = Task.Run(async () => { try { await Task.Delay(2000, token); if (!_isInitialized) await InitializeAsync(); if (_hubConnection?.State == HubConnectionState.Connected) { await _hubConnection.SendAsync("UpdateProgress", pageId, token); _lastSentPageId = pageId; } } catch (TaskCanceledException) { /* Ignored, user kept scrolling */ } catch (Exception ex) { Console.WriteLine($"[SyncService] Error sending progress: {ex.Message}"); } }, token); return Result.Ok(); } public async Task DisposeAsync() { _debounceCts?.Cancel(); if (_hubConnection != null) { await _hubConnection.DisposeAsync(); } } async ValueTask IAsyncDisposable.DisposeAsync() { await DisposeAsync(); } }