/** biome-ignore-all lint/suspicious/noConfusingVoidType: <> */
import { getBytes, getLines, getMessages } from './parse.js';
export const EventStreamContentType = 'text/event-stream';
const DefaultRetryInterval = 1000;
const LastEventId = 'last-event-id';
function isBrowser() {
    return typeof window !== 'undefined';
}
export function fetchEventSource(input, { signal: inputSignal, headers: inputHeaders, onopen: inputOnOpen, onmessage, onclose, onerror, openWhenHidden, fetch: inputFetch, ...rest }) {
    return new Promise((resolve, reject) => {
        // make a copy of the input headers since we may modify it below:
        const headers = { ...inputHeaders };
        if (!headers.accept) {
            headers.accept = EventStreamContentType;
        }
        let curRequestController;
        function onVisibilityChange() {
            curRequestController?.abort(); // close existing request on every visibility change
            if (!document.hidden) {
                create(); // page is now visible again, recreate request.
            }
        }
        if (!openWhenHidden && isBrowser()) {
            document.addEventListener('visibilitychange', onVisibilityChange);
        }
        let retryInterval = DefaultRetryInterval;
        let retryTimer;
        function dispose() {
            if (isBrowser()) {
                document.removeEventListener('visibilitychange', onVisibilityChange);
            }
            clearTimeout(retryTimer);
            curRequestController?.abort();
        }
        // if the incoming signal aborts, dispose resources and resolve:
        inputSignal?.addEventListener('abort', () => {
            dispose();
            resolve(); // don't waste time windconstructing/logging errors
        });
        const outputFetch = inputFetch ?? fetch;
        const onopen = inputOnOpen ?? defaultOnOpen;
        async function create() {
            curRequestController = AbortController ? new AbortController() : null;
            try {
                const response = await outputFetch(input, {
                    ...rest,
                    headers,
                    signal: curRequestController?.signal,
                });
                await onopen(response);
                await getBytes(response.body, getLines(getMessages((id) => {
                    if (id) {
                        // store the id and send it back on the next retry:
                        headers[LastEventId] = id;
                    }
                    else {
                        // don't send the last-event-id header anymore:
                        delete headers[LastEventId];
                    }
                }, (retry) => {
                    retryInterval = retry;
                }, onmessage)));
                onclose?.();
                dispose();
                resolve();
            }
            catch (err) {
                if (!curRequestController?.signal?.aborted) {
                    // if we haven't aborted the request ourselves:
                    try {
                        // check if we need to retry:
                        const interval = onerror?.(err) ?? retryInterval;
                        clearTimeout(retryTimer);
                        retryTimer = setTimeout(create, interval);
                    }
                    catch (innerErr) {
                        // we should not retry anymore:
                        dispose();
                        reject(innerErr);
                    }
                }
            }
        }
        create();
    });
}
function defaultOnOpen(response) {
    const contentType = response.headers.get('content-type');
    if (!contentType?.startsWith(EventStreamContentType)) {
        throw new Error(`Expected content-type to be ${EventStreamContentType}, Actual: ${contentType}`);
    }
}
