/**
 * SSE (Server-Sent Events) streaming utilities for Hono.
 *
 * Provides generic helpers for creating SSE response streams
 * and a notification type aligned with JSON-RPC 2.0.
 *
 * @module
 */
import { streamSSE } from 'hono/streaming';
import { z } from 'zod';
import { DEV } from 'esm-env';
/**
 * Create an SSE response for a Hono context.
 *
 * Wraps Hono's `streamSSE` to provide a `{response, stream}` API
 * compatible with `SubscriberRegistry` push-based broadcasting.
 * The callback suspends via a promise that resolves on client disconnect
 * or explicit `close()`, keeping the stream alive for external sends.
 *
 * Uses `hono_stream.write()` directly (not `writeSSE`) to avoid
 * Hono's HTML callback resolution — keeps the same `data: JSON\n\n` format.
 *
 * @param log - logger for serialization and `on_close` listener errors
 * @returns object with the streaming `Response` and an `SseStream` controller
 */
export const create_sse_response = (c, log) => {
    const { promise, resolve } = Promise.withResolvers();
    const close_listeners = [];
    let resolved = false;
    const do_close = () => {
        if (resolved)
            return;
        resolved = true;
        resolve();
        for (const fn of close_listeners) {
            try {
                fn();
            }
            catch (e) {
                log.error('on_close listener threw:', e);
            }
        }
    };
    let sse_stream;
    const response = streamSSE(c, async (hono_stream) => {
        sse_stream = {
            send(data) {
                if (resolved || hono_stream.aborted)
                    return;
                try {
                    // JSON.stringify (no space arg) never produces literal newlines,
                    // so single-line `data:` framing is safe per the SSE spec.
                    void hono_stream.write(`data: ${JSON.stringify(data)}\n\n`);
                }
                catch (e) {
                    log.error('send failed to serialize data:', e);
                }
            },
            comment(text) {
                if (resolved || hono_stream.aborted)
                    return;
                void hono_stream.write(`: ${text}\n`);
            },
            close: do_close,
            on_close(fn) {
                close_listeners.push(fn);
            },
        };
        hono_stream.onAbort(do_close);
        // flush an SSE comment to push headers through proxies (Vite, nginx),
        // ensuring EventSource fires onopen without waiting for the first data event
        void hono_stream.write(SSE_CONNECTED_COMMENT);
        await promise;
    });
    return { response, stream: sse_stream };
};
/** SSE comment sent on connect to flush headers through proxies. Exported for test assertions. */
export const SSE_CONNECTED_COMMENT = `: connected\n\n`;
/**
 * Create a broadcaster that validates events in DEV mode.
 *
 * In DEV: warns on unknown methods and invalid params.
 * In production: passes through with zero overhead.
 *
 * @param broadcaster - duck-typed broadcaster (e.g. `SubscriberRegistry`)
 * @param event_specs - event specs to validate against
 * @param log - logger used to emit DEV warnings on unknown methods or param mismatches
 * @returns validated broadcaster wrapper (passthrough in production)
 */
export const create_validated_broadcaster = (broadcaster, event_specs, log) => {
    if (!DEV) {
        return broadcaster;
    }
    const spec_map = new Map(event_specs.map((s) => [s.method, s]));
    return {
        broadcast: (channel, data) => {
            const spec = spec_map.get(data.method);
            if (!spec) {
                log.warn(`Unknown event method: '${data.method}'`);
            }
            else {
                const result = spec.params.safeParse(data.params);
                if (!result.success) {
                    log.warn(`Params mismatch for '${data.method}':`, result.error.issues);
                }
            }
            broadcaster.broadcast(channel, data);
        },
    };
};
