export interface BundleFreshnessMonitorOptions {
  readonly appName: string;
  readonly currentModuleUrl: string;
  readonly enabled?: boolean;
  readonly intervalMs?: number;
  readonly documentUrl?: string;
  readonly fetchHtml?: (url: string) => Promise<string>;
  readonly reload?: () => void;
}

const DEFAULT_BUNDLE_FRESHNESS_INTERVAL_MS = 30_000;
const MIN_BUNDLE_FRESHNESS_INTERVAL_MS = 5_000;

export function htmlReferencesBundle(
  html: string,
  currentModuleUrl: string,
  documentUrl: string,
): boolean {
  const currentPath = normalizeUrlPath(currentModuleUrl, documentUrl);
  const scriptPattern = /<script\b[^>]*\bsrc=(["'])(.*?)\1/gi;
  for (const match of html.matchAll(scriptPattern)) {
    const rawSrc = match[2]?.trim();
    if (!rawSrc) {
      continue;
    }
    if (normalizeUrlPath(rawSrc, documentUrl) === currentPath) {
      return true;
    }
  }
  const inlineImportPattern = /\bimport\s+(?:[^'"]+\s+from\s+)?(["'])(.*?)\1/gi;
  for (const match of html.matchAll(inlineImportPattern)) {
    const rawImport = match[2]?.trim();
    if (!rawImport) {
      continue;
    }
    if (normalizeUrlPath(rawImport, documentUrl) === currentPath) {
      return true;
    }
  }
  return false;
}

export function installBundleFreshnessMonitor(
  options: BundleFreshnessMonitorOptions,
): () => void {
  if (options.enabled === false || typeof window === 'undefined' || typeof document === 'undefined') {
    return () => {};
  }

  const documentUrl = options.documentUrl ?? window.location.href;
  const fetchHtml = options.fetchHtml ?? fetchDocumentHtml;
  const reload = options.reload ?? (() => window.location.reload());
  const intervalMs = Math.max(
    options.intervalMs ?? DEFAULT_BUNDLE_FRESHNESS_INTERVAL_MS,
    MIN_BUNDLE_FRESHNESS_INTERVAL_MS,
  );
  let checking = false;
  let disposed = false;

  const check = async () => {
    if (checking || disposed) {
      return;
    }
    checking = true;
    try {
      const html = await fetchHtml(documentUrl);
      if (!htmlReferencesBundle(html, options.currentModuleUrl, documentUrl)) {
        console.warn(
          `[${options.appName}] Loaded bundle is no longer the served entrypoint; reloading.`,
        );
        reload();
      }
    } catch {
      // Keep the running app active if the entrypoint probe itself fails.
    } finally {
      checking = false;
    }
  };

  const onVisible = () => {
    if (document.visibilityState === 'visible') {
      void check();
    }
  };
  const timer = window.setInterval(() => {
    void check();
  }, intervalMs);
  window.addEventListener('focus', check);
  document.addEventListener('visibilitychange', onVisible);

  return () => {
    disposed = true;
    window.clearInterval(timer);
    window.removeEventListener('focus', check);
    document.removeEventListener('visibilitychange', onVisible);
  };
}

function normalizeUrlPath(value: string, baseUrl: string): string {
  return new URL(value, baseUrl).pathname;
}

async function fetchDocumentHtml(url: string): Promise<string> {
  const response = await fetch(url, {
    cache: 'no-store',
    credentials: 'same-origin',
    headers: {
      accept: 'text/html',
    },
  });
  return response.text();
}
