using System.Text.Json; using System.Text.Json.Serialization; using ChessCubing.App.Models; namespace ChessCubing.App.Services; public sealed class MatchStore(BrowserBridge browser) { public const string StorageKey = "chesscubing-arena-state-v2"; public const string WindowNameKey = "chesscubing-arena-state-v2:"; private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web) { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, }; private bool _dirty; private long _lastPersistedAt; public MatchState? Current { get; private set; } public bool IsLoaded { get; private set; } public async Task EnsureLoadedAsync() { if (IsLoaded) { return; } try { var raw = await browser.ReadMatchJsonAsync(); if (!string.IsNullOrWhiteSpace(raw)) { var parsed = JsonSerializer.Deserialize(raw, JsonOptions); if (parsed is not null && MatchEngine.IsSupportedSchemaVersion(parsed.SchemaVersion)) { MatchEngine.NormalizeRecoveredMatch(parsed); Current = parsed; } } } catch { Current = null; } IsLoaded = true; _lastPersistedAt = MatchEngine.NowUnixMs(); } public void SetCurrent(MatchState? match) { Current = match; MarkDirty(); } public void MarkDirty() => _dirty = true; public async Task SaveAsync() { if (!IsLoaded) { return; } if (Current is null) { await browser.ClearMatchAsync(); _dirty = false; _lastPersistedAt = MatchEngine.NowUnixMs(); return; } var json = JsonSerializer.Serialize(Current, JsonOptions); await browser.WriteMatchJsonAsync(json); _dirty = false; _lastPersistedAt = MatchEngine.NowUnixMs(); } public async Task FlushIfDueAsync(long minimumIntervalMs = 1_000) { if (!_dirty) { return; } if (MatchEngine.NowUnixMs() - _lastPersistedAt < minimumIntervalMs) { return; } await SaveAsync(); } public async Task ClearAsync() { Current = null; IsLoaded = true; _dirty = false; _lastPersistedAt = MatchEngine.NowUnixMs(); await browser.ClearMatchAsync(); } }