using System.Collections.Concurrent;
using System.Threading.Channels;

namespace JobsMedical.Web.Services;

/// <summary>
/// In-memory pub/sub for live notifications over Server-Sent Events (SSE).
///
/// Why SSE instead of Web Push here: Web Push is delivered by the *browser vendor's*
/// push service (Chrome → Google FCM), which is filtered in Iran. SSE streams over our
/// OWN origin (hamkadr.ir), so it always reaches users while the tab/PWA is open — no
/// Google dependency. The client then shows an in-page toast and (if permission is
/// granted) a LOCAL OS notification via the service worker — also no push server.
///
/// Singleton, process-local. Each open tab = one subscription. Web Push stays as the
/// best-effort closed-app channel for users who can reach the push endpoint.
/// </summary>
public class NotificationHub
{
    private readonly ConcurrentDictionary<int, ConcurrentDictionary<Guid, Channel<LiveNotice>>> _subs = new();

    /// <summary>Open a stream for a user. Returns the reader + an unsubscribe callback.</summary>
    public (ChannelReader<LiveNotice> Reader, Action Unsubscribe) Subscribe(int userId)
    {
        // Bounded + DropOldest so a stalled/slow client can never grow memory unbounded.
        var ch = Channel.CreateBounded<LiveNotice>(new BoundedChannelOptions(50)
        {
            FullMode = BoundedChannelFullMode.DropOldest,
            SingleReader = true,
            SingleWriter = false,
        });
        var id = Guid.NewGuid();
        var map = _subs.GetOrAdd(userId, _ => new());
        map[id] = ch;

        void Unsub()
        {
            if (_subs.TryGetValue(userId, out var m))
            {
                m.TryRemove(id, out _);
                if (m.IsEmpty) _subs.TryRemove(userId, out _);
            }
            ch.Writer.TryComplete();
        }
        return (ch.Reader, Unsub);
    }

    /// <summary>Fan a notice out to every open tab of the given user (no-op if none online).</summary>
    public void Publish(int userId, LiveNotice notice)
    {
        if (_subs.TryGetValue(userId, out var m))
            foreach (var ch in m.Values)
                ch.Writer.TryWrite(notice);
    }

    /// <summary>True if the user has at least one open SSE stream right now.</summary>
    public bool IsOnline(int userId) => _subs.ContainsKey(userId);
}

/// <summary>Payload pushed to the browser over SSE (serialized to the event's data line).</summary>
public record LiveNotice(string title, string? body, string url);
