/**
 * Dispatch.IO SSE client for Blazor interop.
 * Connects to the SSE endpoint and invokes a C# callback on matching events.
 */
const DispatchSse = {
    /** @type {Map<string, EventSource>} */
    _connections: new Map(),

    /**
     * Connect to the SSE endpoint and filter events by type and key.
     * @param {string} id - Unique connection ID (for cleanup).
     * @param {string} url - SSE endpoint URL.
     * @param {string} eventType - SSE event type to listen for (e.g., "FocusRealtimeEvent").
     * @param {string} filterKey - Value to match in event payload's focusKey field. Empty = no filter.
     * @param {object} dotnetRef - DotNetObjectReference for C# callback.
     * @param {string} callbackMethod - Name of the [JSInvokable] method to invoke.
     */
    connect(id, url, eventType, filterKey, dotnetRef, callbackMethod) {
        this.disconnect(id);

        const source = new EventSource(url, { withCredentials: true });

        source.addEventListener(eventType, (e) => {
            if (!filterKey) {
                dotnetRef.invokeMethodAsync(callbackMethod);
                return;
            }
            try {
                const data = JSON.parse(e.data);
                if (data.focusKey === filterKey) {
                    dotnetRef.invokeMethodAsync(callbackMethod);
                }
            } catch { /* malformed JSON — skip */ }
        });

        source.addEventListener("dispatch:overflow", (e) => {
            try {
                const data = JSON.parse(e.data);
                console.warn(`[DispatchSse] Overflow: dropped ${data.dropped} events — triggering re-sync`);
                dotnetRef.invokeMethodAsync(callbackMethod);
            } catch { /* skip */ }
        });

        source.onerror = () => {
            // EventSource auto-reconnects; retry interval set by server.
            // No action needed — just log for debugging.
            if (source.readyState === EventSource.CLOSED) {
                console.warn(`[DispatchSse] Connection ${id} closed by server`);
            }
        };

        this._connections.set(id, source);
    },

    /**
     * Connect and hand the raw event payload to C#.
     *
     * SEPARATE FROM connect() ON PURPOSE. connect() invokes its callback with NO arguments, and its
     * existing consumer's [JSInvokable] takes none — Blazor matches on arity, so passing a payload
     * there would break every current subscriber. A caller that needs to say WHAT arrived rather
     * than merely THAT something did uses this instead.
     *
     * @param {string} id - Unique connection ID (for cleanup).
     * @param {string} url - SSE endpoint URL.
     * @param {string} eventType - SSE event type to listen for.
     * @param {object} dotnetRef - DotNetObjectReference for the C# callback.
     * @param {string} callbackMethod - Name of a [JSInvokable] method taking one string.
     */
    connectWithData(id, url, eventType, dotnetRef, callbackMethod) {
        this.disconnect(id);
        const source = new EventSource(url, { withCredentials: true });
        source.addEventListener(eventType, (e) => {
            // Deliberately unparsed here: the payload shape belongs to C#, and a parse failure in
            // this listener would silently drop the event with nothing on either side reporting it.
            dotnetRef.invokeMethodAsync(callbackMethod, e.data).catch(() => { /* circuit gone */ });
        });
        source.onerror = () => {
            if (source.readyState === EventSource.CLOSED) {
                console.warn(`[DispatchSse] Connection ${id} closed by server`);
            }
        };
        this._connections.set(id, source);
    },

    /**
     * Disconnect and clean up an SSE connection.
     * @param {string} id - Connection ID to close.
     */
    disconnect(id) {
        const source = this._connections.get(id);
        if (source) {
            source.close();
            this._connections.delete(id);
        }
    }
};

window.DispatchSse = DispatchSse;
