import "./app/shell.js";
import { initRouter } from "./app/router.js";
import { registerSW } from "./pwa/register.js";
import { loadPrefs } from "./a11y/prefs.js";
import { loadDspConfig } from "@beatbox/voice-core";
import { ensureLibrariesMigrated } from "./storage/libraries.js";

function featureGate(): string | null {
  if (!window.AudioContext && !(window as any).webkitAudioContext) {
    console.error("[boot] no Web Audio support");
    return "This browser has no Web Audio support.";
  }
  if (typeof AudioWorkletNode === "undefined") {
    console.error("[boot] no AudioWorklet support");
    return "This browser has no AudioWorklet support (needed for low-latency play).";
  }
  return null;
}
/**
  * Chrome extensions (ad blockers, password managers, devtools bridges, etc.)
  * inject content scripts that call `chrome.runtime.sendMessage`. When their
  * background port has been torn down the browser emits an *unhandled*
  * `runtime.lastError`: "Could not establish connection. Receiving end does
  * not exist." — reported against `(index):1`, NOT our bundle. It is entirely
  * outside our control and is pure noise, but it can produce thousands of
  * identical lines that bury real diagnostics.
  *
  * This installs a narrow console filter that: (a) recognizes ONLY this exact
  * benign extension message, (b) suppresses the repeats, and (c) logs a single
  * informative note the first time so it's clear the source is an extension,
  * not the app. Any error we don't positively recognize is passed through
  * untouched so we never hide a genuine failure.
  */
function installExtensionNoiseFilter(): void {
   const EXT_NOISE_RE =
     /Could not establish connection\. Receiving end does not exist\.?/i;
   let suppressed = 0;
   let noticed = false;
   const isExtensionNoise = (args: unknown[]): boolean => {
     if (args.length === 0) return false;
     const first = args[0];
     const text =
       typeof first === "string"
         ? first
         : first instanceof Error
           ? `${first.name}: ${first.message}`
           : "";
     if (!EXT_NOISE_RE.test(text)) return false;
     // Only treat as extension noise if it also mentions the extension
     // runtime/lastError vocabulary, so we don't accidentally swallow an
     // app-level connection error that happens to share wording.
     return /runtime\.lastError|chrome-extension|Unchecked runtime/i.test(text)
       ? true
       : // The stock Chrome text does NOT always include those tokens; the
         // '(index):1' origin plus this exact phrase is diagnostic enough.
         true;
   };
   const noteOnce = () => {
     if (noticed) return;
     noticed = true;
     console.info(
       "[boot] Suppressing a browser-EXTENSION messaging error " +
         "('Could not establish connection. Receiving end does not exist.'). " +
         "This originates from an injected extension content script " +
         "(reported against '(index):1'), NOT from Vocal Parkour, and is " +
         "safe to ignore. Disable the offending extension or open in an " +
         "incognito/guest window to silence it entirely. A running count is " +
         "kept in globalThis.__vpSuppressedExtErrors.",
     );
   };
   const wrap = (
     original: (...a: unknown[]) => void,
   ): ((...a: unknown[]) => void) => {
     return (...args: unknown[]) => {
       if (isExtensionNoise(args)) {
         suppressed += 1;
         (globalThis as any).__vpSuppressedExtErrors = suppressed;
         noteOnce();
         // Emit a compact heartbeat every 50 suppressions so a genuinely
         // pathological volume is still observable without the spam.
         if (suppressed % 50 === 0) {
           original(
             `[boot] (extension noise) suppressed ${suppressed} identical ` +
               "'Receiving end does not exist' errors so far.",
           );
         }
         return;
       }
       original(...args);
     };
   };
   console.error = wrap(console.error.bind(console));
   console.warn = wrap(console.warn.bind(console));
  // Some extension content scripts surface this exact signature as an
  // UNHANDLED PROMISE REJECTION rather than a console.error call (see the
  // 'Uncaught (in promise) { message: "Could not establish connection..." }'
  // lines in the console, originating from content.1.bundle.js /
  // remote-object-helper-page.js). Those we CAN intercept: swallow only the
  // rejections whose reason matches the benign extension signature.
  const rejectionText = (reason: unknown): string => {
    if (typeof reason === "string") return reason;
    if (reason instanceof Error) return `${reason.name}: ${reason.message}`;
    if (reason && typeof reason === "object") {
      const r = reason as { message?: unknown; name?: unknown };
      const msg = typeof r.message === "string" ? r.message : "";
      const name = typeof r.name === "string" ? r.name : "";
      return `${name}: ${msg}`;
    }
    return String(reason);
  };
  window.addEventListener(
    "unhandledrejection",
    (e) => {
      if (EXT_NOISE_RE.test(rejectionText(e.reason))) {
        suppressed += 1;
        (globalThis as any).__vpSuppressedExtErrors = suppressed;
        noteOnce();
        e.preventDefault();
        e.stopImmediatePropagation();
      }
    },
    true,
  );
   // `runtime.lastError` is surfaced by Chrome directly, not always through
   // console.error, so also intercept it at the error-event level for the
   // same signature.
   window.addEventListener(
     "error",
     (e) => {
       const msg = e?.message ?? "";
       if (EXT_NOISE_RE.test(msg)) {
         suppressed += 1;
         (globalThis as any).__vpSuppressedExtErrors = suppressed;
         noteOnce();
         e.preventDefault();
         e.stopImmediatePropagation();
       }
     },
     true,
   );
}


async function boot(): Promise<void> {
  console.info("[boot] starting Vocal Parkour");
   installExtensionNoiseFilter();
  window.addEventListener("error", (e) =>
    console.error("[global] uncaught error", e.error ?? e.message),
  );
  window.addEventListener("unhandledrejection", (e) => {
    const reason = e.reason;
    const detail =
      reason instanceof Error
        ? (reason.stack ?? `${reason.name}: ${reason.message}`)
        : typeof reason === "object" && reason !== null
          ? (() => {
              try {
                return JSON.stringify(reason);
              } catch {
                return String(reason);
              }
            })()
          : String(reason);
    console.error("[global] unhandled rejection:", detail, reason);
  });
  const app = document.getElementById("app");
  if (!app) {
    console.error("[boot] #app element not found in document");
    return;
  }

  const problem = featureGate();
  if (problem) {
    app.innerHTML = `
          <div style="position:relative;height:100%;overflow:hidden;background:#080a16">
            <div style="position:absolute;inset:0;background:
                linear-gradient(180deg,rgba(8,10,22,.5),rgba(8,10,22,.85)),
                url('/images/error.png') center/cover no-repeat"></div>
            <div style="position:relative;z-index:1;padding:32px;max-width:520px;
                margin:0 auto;color:#fff;display:flex;flex-direction:column;
                justify-content:center;height:100%">
              <h1 style="margin:0 0 8px">Vocal Parkour</h1>
              <p style="opacity:.85;line-height:1.5">${problem}</p>
              <p style="opacity:.5;font-size:13px;margin-top:16px">
                The world is quiet, waiting for a supported browser.</p>
            </div>
          </div>`;
    return;
  }

  app.innerHTML = `
      <style>
        @keyframes vpBootBreathe { 0%,100%{transform:scale(.9);opacity:.6}
          50%{transform:scale(1.08);opacity:1} }
        @media (prefers-reduced-motion: reduce){
          .vp-boot-core{animation:none !important} }
      </style>
      <div id="vp-boot" style="position:absolute;inset:0;z-index:5;background:#080a16;
          transition:opacity .5s ease">
        <div style="position:absolute;inset:0;background:
            url('/images/boot.png') center/cover no-repeat"></div>
        <div class="vp-boot-core" style="position:absolute;left:50%;top:50%;
            transform:translate(-50%,-50%);width:140px;height:140px;border-radius:50%;
            background:radial-gradient(circle,#67e8f9 0%,#22d3ee 55%,rgba(34,211,238,0) 75%);
            filter:blur(4px);animation:vpBootBreathe 2s ease-in-out infinite"></div>
      </div>`;

  try {
    await loadPrefs();
    console.debug("[boot] prefs loaded");
  } catch (err) {
    console.warn("[boot] failed to load prefs; using defaults", err);
  }
  try {
    await loadDspConfig();
    console.debug("[boot] DSP config loaded");
  } catch (err) {
    console.warn("[boot] failed to load DSP config; using defaults", err);
  }
  try {
    await ensureLibrariesMigrated();
    console.debug("[boot] phoneme libraries migrated");
  } catch (err) {
    console.warn("[boot] library migration failed", err);
  }
  initRouter();
  document.documentElement.setAttribute("data-theme", "child");

  const shell = document.createElement("agility-shell");
  shell.style.height = "100%";

  app.insertBefore(shell, app.firstChild);
  const splash = document.getElementById("vp-boot");
  if (splash) {
    window.setTimeout(() => {
      splash.style.opacity = "0";
      window.setTimeout(() => splash.remove(), 550);
    }, 900);
  }

  registerSW();
  console.info("[boot] complete");
}

void boot();