// Synced from packages/transport-realtime/src/runtime-events.ts
// Extracted from legacy source: src/runtime/transports/runtime-events-client.ts
import { RUNTIME_EVENT_DOMAINS } from '../contracts/index.js';
import { resolveAuthToken, openRawServerSentEventStream as openServerSentEventStream } from '../transport-http/index.js';
import { buildUrl, normalizeBaseUrl } from '../transport-http/index.js';
import { createRemoteDomainEvents, } from './domain-events.js';
async function resolveAuthTokenSource(source) {
    if (typeof source === 'function') {
        return await resolveAuthToken(null, source);
    }
    return source ?? null;
}
export function createRemoteRuntimeEvents(connect) {
    return createRemoteDomainEvents(RUNTIME_EVENT_DOMAINS, connect);
}
export function buildEventSourceUrl(baseUrl, domain) {
    const url = new URL(buildUrl(baseUrl, '/api/control-plane/events'));
    url.searchParams.set('domains', domain);
    return url.toString();
}
export function buildWebSocketUrl(baseUrl, domains) {
    const base = normalizeBaseUrl(baseUrl);
    const url = new URL('/api/control-plane/ws', base.replace(/^http(s?):\/\//, 'ws$1://'));
    url.searchParams.set('clientKind', 'web');
    if (domains.length > 0) {
        url.searchParams.set('domains', domains.join(','));
    }
    return url.toString();
}
export function createEventSourceConnector(baseUrl, token, fetchImpl, options = {}) {
    const handleError = options.onError ?? (options.reconnect?.enabled ? (() => { }) : undefined);
    return async (domain, onEnvelope) => {
        const url = buildEventSourceUrl(baseUrl, domain);
        return await openServerSentEventStream(fetchImpl, url, {
            onEvent: (eventName, payload) => {
                if (eventName !== domain)
                    return;
                if (!payload || typeof payload !== 'object')
                    return;
                onEnvelope(payload);
            },
            onError: handleError,
        }, {
            reconnect: options.reconnect,
            getAuthToken: typeof token === 'function' ? token : undefined,
            authToken: typeof token === 'function' ? null : token,
        });
    };
}
export function createWebSocketConnector(baseUrl, token, WebSocketImpl, options = {}) {
    return async (domain, onEnvelope) => {
        const url = buildWebSocketUrl(baseUrl, [domain]);
        const reconnect = options.reconnect;
        const enabled = reconnect?.enabled ?? false;
        let stopped = false;
        let reconnectAttempt = 0;
        let socket = null;
        let reconnectTimer = null;
        const closeSocket = () => {
            if (!socket)
                return;
            socket.removeEventListener('open', onOpen);
            socket.removeEventListener('message', onMessage);
            socket.removeEventListener('close', onClose);
            socket.removeEventListener('error', onError);
            socket.close();
            socket = null;
        };
        const scheduleReconnect = () => {
            if (!enabled || stopped)
                return;
            const nextAttempt = reconnectAttempt + 1;
            const maxAttempts = reconnect?.maxAttempts ?? Number.POSITIVE_INFINITY;
            if (nextAttempt >= maxAttempts)
                return;
            reconnectAttempt = nextAttempt;
            const baseDelayMs = reconnect?.baseDelayMs ?? 500;
            const maxDelayMs = reconnect?.maxDelayMs ?? 5_000;
            const backoffFactor = reconnect?.backoffFactor ?? 2;
            const delayMs = Math.min(maxDelayMs, Math.floor(baseDelayMs * (backoffFactor ** Math.max(0, nextAttempt - 1))));
            reconnectTimer = setTimeout(() => {
                reconnectTimer = null;
                void connect();
            }, delayMs);
        };
        const onOpen = async () => {
            reconnectAttempt = 0;
            const authToken = await resolveAuthTokenSource(token);
            if (!authToken || !socket)
                return;
            socket.send(JSON.stringify({
                type: 'auth',
                token: authToken,
                domains: [domain],
            }));
        };
        const onMessage = (event) => {
            try {
                const frame = JSON.parse(event.data);
                if (frame.type === 'event' && frame.event === domain && frame.payload && typeof frame.payload === 'object') {
                    onEnvelope(frame.payload);
                }
            }
            catch {
                // Ignore malformed frames.
            }
        };
        const onClose = () => {
            closeSocket();
            scheduleReconnect();
        };
        const onError = (event) => {
            options.onError?.(event);
        };
        const connect = async () => {
            if (stopped)
                return;
            closeSocket();
            const nextSocket = new WebSocketImpl(url);
            socket = nextSocket;
            nextSocket.addEventListener('open', onOpen);
            nextSocket.addEventListener('message', onMessage);
            nextSocket.addEventListener('close', onClose);
            nextSocket.addEventListener('error', onError);
        };
        await connect();
        return () => {
            stopped = true;
            if (reconnectTimer) {
                clearTimeout(reconnectTimer);
            }
            closeSocket();
        };
    };
}
