using System; using System.Collections.Generic; using System.Linq; using MareSynchronos.Services.Mediator; using MareSynchronos.MareConfiguration; using MareSynchronos.MareConfiguration.Configurations; using MareSynchronos.MareConfiguration.Models; namespace MareSynchronos.Services.Notifications; public enum NotificationCategory { AutoDetect, Syncshell, } public sealed record NotificationEntry(NotificationCategory Category, string Id, string Title, string? Description, DateTime CreatedAt) { public static NotificationEntry AutoDetect(string uid, string displayName) => new(NotificationCategory.AutoDetect, uid, displayName, "Nouvelle demande d'appairage via AutoDetect.", DateTime.UtcNow); public static NotificationEntry SyncshellPublic(string gid, string aliasOrGid) => new(NotificationCategory.Syncshell, gid, $"Syncshell publique: {aliasOrGid}", "La Syncshell est désormais visible via AutoDetect.", DateTime.UtcNow); public static NotificationEntry SyncshellNotPublic(string gid, string aliasOrGid) => new(NotificationCategory.Syncshell, gid, $"Syncshell non publique: {aliasOrGid}", "La Syncshell n'est plus visible via AutoDetect.", DateTime.UtcNow); } public sealed class NotificationTracker { private const int MaxStored = 100; private readonly MareMediator _mediator; private readonly NotificationsConfigService _configService; private readonly Dictionary<(NotificationCategory Category, string Id), NotificationEntry> _entries = new(); private readonly object _lock = new(); public NotificationTracker(MareMediator mediator, NotificationsConfigService configService) { _mediator = mediator; _configService = configService; LoadPersisted(); PublishState(); } public void Upsert(NotificationEntry entry) { lock (_lock) { _entries[(entry.Category, entry.Id)] = entry; TrimIfNecessary_NoLock(); Persist_NoLock(); } PublishState(); } public void Remove(NotificationCategory category, string id) { lock (_lock) { _entries.Remove((category, id)); Persist_NoLock(); } PublishState(); } public IReadOnlyList GetEntries() { lock (_lock) { return _entries.Values .OrderBy(e => e.CreatedAt) .ToList(); } } public int Count { get { lock (_lock) { return _entries.Count; } } } private void PublishState() { _mediator.Publish(new NotificationStateChanged(Count)); } private void LoadPersisted() { try { var list = _configService.Current.Notifications ?? new List(); foreach (var s in list) { if (!Enum.TryParse(s.Category, out var cat)) continue; var entry = new NotificationEntry(cat, s.Id, s.Title, s.Description, s.CreatedAtUtc); _entries[(entry.Category, entry.Id)] = entry; } TrimIfNecessary_NoLock(); } catch { // ignore load errors, start empty } } private void Persist_NoLock() { try { var stored = _entries.Values .OrderBy(e => e.CreatedAt) .Select(e => new StoredNotification { Category = e.Category.ToString(), Id = e.Id, Title = e.Title, Description = e.Description, CreatedAtUtc = e.CreatedAt }) .ToList(); _configService.Current.Notifications = stored; _configService.Save(); } catch { // ignore persistence errors } } private void TrimIfNecessary_NoLock() { if (_entries.Count <= MaxStored) return; foreach (var kv in _entries.Values.OrderByDescending(v => v.CreatedAt).Skip(MaxStored).ToList()) { _entries.Remove((kv.Category, kv.Id)); } } }