const DEFAULT_RETRY_DELAYS = [1000, 2000, 5000, 10000];

export function createReconnectingEventSource({
  url,
  onSource,
  onError,
  eventSourceFactory = (sourceUrl, options) => new globalThis.EventSource(sourceUrl, options),
  schedule = (callback, delay) => globalThis.setTimeout(callback, delay),
  cancel = (timer) => globalThis.clearTimeout(timer),
  retryDelays = DEFAULT_RETRY_DELAYS
}) {
  let source = null;
  let retryTimer = null;
  let retryAttempt = 0;
  let stopped = false;

  function scheduleReconnect() {
    if (stopped || retryTimer !== null) return;
    const delay = retryDelays[Math.min(retryAttempt, retryDelays.length - 1)];
    retryAttempt += 1;
    retryTimer = schedule(() => {
      retryTimer = null;
      connect();
    }, delay);
  }

  function connect() {
    if (stopped) return;
    const current = eventSourceFactory(url, { withCredentials: true });
    source = current;
    onSource(current);
    current.onopen = () => {
      if (!stopped && source === current) retryAttempt = 0;
    };
    current.onerror = () => {
      if (stopped || source !== current) return;
      current.close();
      source = null;
      try {
        void Promise.resolve(onError?.()).catch(() => {});
      } catch {
        // Reconnection must not depend on the diagnostic request.
      }
      scheduleReconnect();
    };
  }

  connect();
  return {
    stop() {
      if (stopped) return;
      stopped = true;
      if (retryTimer !== null) {
        cancel(retryTimer);
        retryTimer = null;
      }
      source?.close();
      source = null;
    }
  };
}
