import { createRuntimeEventFeeds } from '../transport-core/index.js';
function addListener(map, type, listener) {
    const listeners = map.get(type) ?? new Set();
    listeners.add(listener);
    map.set(type, listeners);
    return () => {
        const existing = map.get(type);
        if (!existing)
            return;
        existing.delete(listener);
        if (existing.size === 0) {
            map.delete(type);
        }
    };
}
function hasAnyListener(map) {
    for (const listeners of map.values()) {
        if (listeners.size > 0)
            return true;
    }
    return false;
}
function isExpectedDisconnectError(error) {
    return (error instanceof DOMException && error.name === 'AbortError') || (typeof error === 'object'
        && error !== null
        && 'name' in error
        && error.name === 'AbortError');
}
function toEventEnvelope(envelope) {
    return {
        type: envelope.type,
        ts: typeof envelope.ts === 'number'
            ? envelope.ts
            : typeof envelope.timestamp === 'number'
                ? envelope.timestamp
                : Date.now(),
        traceId: typeof envelope.traceId === 'string' ? envelope.traceId : 'transport-trace',
        sessionId: typeof envelope.sessionId === 'string' ? envelope.sessionId : 'transport',
        source: typeof envelope.source === 'string' ? envelope.source : 'transport',
        payload: envelope.payload,
    };
}
function createRemoteDomainEventFeed(domain, connect) {
    const payloadListeners = new Map();
    const envelopeListeners = new Map();
    let disconnect = null;
    let connectPromise = null;
    let disconnectPending = false;
    const hasListeners = () => (hasAnyListener(payloadListeners)
        || hasAnyListener(envelopeListeners));
    const maybeConnect = () => {
        if (disconnect || connectPromise)
            return;
        connectPromise = Promise.resolve(connect(domain, (envelope) => {
            const eventType = typeof envelope.type === 'string' ? envelope.type : '';
            if (!eventType)
                return;
            const payload = envelope.payload;
            const typedEnvelope = toEventEnvelope(envelope);
            for (const listener of payloadListeners.get(eventType) ?? []) {
                listener(payload);
            }
            for (const listener of envelopeListeners.get(eventType) ?? []) {
                listener(typedEnvelope);
            }
        })).then((cleanup) => {
            if (typeof cleanup !== 'function')
                return;
            if (disconnectPending && !hasListeners()) {
                cleanup();
                return;
            }
            disconnect = cleanup;
        }).catch((error) => {
            if (!isExpectedDisconnectError(error)) {
                throw error;
            }
        }).finally(() => {
            connectPromise = null;
            disconnectPending = false;
        });
    };
    const maybeDisconnect = () => {
        if (hasListeners())
            return;
        if (disconnect) {
            disconnect();
            disconnect = null;
            return;
        }
        if (connectPromise) {
            disconnectPending = true;
        }
    };
    return {
        on(type, listener) {
            const unsubscribe = addListener(payloadListeners, type, listener);
            maybeConnect();
            return () => {
                unsubscribe();
                maybeDisconnect();
            };
        },
        onEnvelope(type, listener) {
            const unsubscribe = addListener(envelopeListeners, type, listener);
            maybeConnect();
            return () => {
                unsubscribe();
                maybeDisconnect();
            };
        },
    };
}
export function createRemoteDomainEvents(domains, connect) {
    return createRuntimeEventFeeds(domains, (domain) => createRemoteDomainEventFeed(domain, connect));
}
