import { createRoot } from "react-dom/client";
import { HelmetProvider } from "react-helmet-async";
import App from "./App.tsx";
import "./index.css";

const STALE_MODULE_RECOVERY_PENDING_KEY = "fussbally:stale-module-recovery-pending";
const STALE_MODULE_RECOVERY_ATTEMPTED_KEY = "fussbally:stale-module-recovery-attempted";

const getAppVersionKey = () => {
  const viteVersion = import.meta.env.VITE_APP_VERSION || import.meta.env.VITE_COMMIT_SHA || "";
  const entrySrc = document.querySelector<HTMLScriptElement>('script[type="module"][src]')?.src || "";
  return [import.meta.env.MODE, viteVersion, entrySrc].filter(Boolean).join("|") || "unknown";
};

const APP_VERSION_KEY = getAppVersionKey();

const readRecoveryValue = (key: string) => {
  try {
    const value = sessionStorage.getItem(key);
    if (value) return value;
  } catch {}
  try { return localStorage.getItem(key); } catch { return null; }
};

const writeRecoveryValue = (key: string, value: string) => {
  try { sessionStorage.setItem(key, value); return; } catch {}
  try { localStorage.setItem(key, value); } catch {}
};

const removeRecoveryValue = (key: string) => {
  try { sessionStorage.removeItem(key); } catch {}
  try { localStorage.removeItem(key); } catch {}
};

let staleModuleRecoveryStarted = readRecoveryValue(STALE_MODULE_RECOVERY_PENDING_KEY) === APP_VERSION_KEY;

const shouldRecoverModuleError = (value: unknown) => {
  const message = value instanceof Error ? value.message : String(value ?? "");
  return /Importing a module script failed|Failed to fetch dynamically imported module|error loading dynamically imported module|Loading chunk \d+ failed|ChunkLoadError/i.test(message);
};

const isRecoverableResourceTarget = (target: EventTarget | null) => {
  if (!(target instanceof HTMLScriptElement || target instanceof HTMLLinkElement)) return false;
  const url = target instanceof HTMLScriptElement ? target.src : target.href;
  const rel = target instanceof HTMLLinkElement ? target.rel : "";
  return Boolean(url) && (
    target instanceof HTMLScriptElement ||
    rel === "modulepreload" ||
    rel === "preload"
  ) && /\/(@vite|node_modules\/\.vite\/deps|src\/|assets\/).+\.(m?js|tsx?)(\?|$)|\/assets\/.+\.js(\?|$)/i.test(url);
};

const recoverFromStaleModule = () => {
  if (staleModuleRecoveryStarted) return;
  staleModuleRecoveryStarted = true;
  try {
    if (readRecoveryValue(STALE_MODULE_RECOVERY_ATTEMPTED_KEY) === APP_VERSION_KEY) {
      removeRecoveryValue(STALE_MODULE_RECOVERY_PENDING_KEY);
      return;
    }
    writeRecoveryValue(STALE_MODULE_RECOVERY_ATTEMPTED_KEY, APP_VERSION_KEY);
    writeRecoveryValue(STALE_MODULE_RECOVERY_PENDING_KEY, APP_VERSION_KEY);
  } catch {
    // If storage is unavailable, the in-memory guard still prevents duplicate
    // reload attempts during the current page lifetime.
  }
  window.setTimeout(() => window.location.reload(), 50);
};

const clearStaleModuleRecoveryPending = () => {
  removeRecoveryValue(STALE_MODULE_RECOVERY_PENDING_KEY);
  staleModuleRecoveryStarted = false;
};

const installReplaceStateGuard = () => {
  const originalReplaceState = window.history.replaceState.bind(window.history);
  const calls: number[] = [];
  let lastWarningAt = 0;
  window.history.replaceState = ((data: unknown, unused: string, url?: string | URL | null) => {
    const now = Date.now();
    calls.push(now);
    while (calls.length && now - calls[0] > 10_000) calls.shift();
    if (calls.length > 80) {
      if (now - lastWarningAt > 10_000) {
        lastWarningAt = now;
        console.warn("Suppressed repeated history.replaceState calls to avoid a browser navigation safety error.");
      }
      return;
    }
    return originalReplaceState(data, unused, url);
  }) as History["replaceState"];
};

installReplaceStateGuard();

window.addEventListener("error", (event) => {
  if (shouldRecoverModuleError(event.error ?? event.message) || isRecoverableResourceTarget(event.target)) {
    event.preventDefault();
    recoverFromStaleModule();
  }
}, true);

window.addEventListener("unhandledrejection", (event) => {
  if (shouldRecoverModuleError(event.reason)) {
    event.preventDefault();
    recoverFromStaleModule();
  }
});

const preloadRouteChunk = (loader: () => Promise<unknown>) => {
  void loader().catch((error) => {
    if (shouldRecoverModuleError(error)) recoverFromStaleModule();
  });
};

// Route-chunk preload: kick off the dynamic import for the current route in
// parallel with React mounting, eliminating the main-bundle → route-chunk
// waterfall that delays first paint on lazy routes (Speed Index killer).
// Each entry must use a static string literal so Vite can statically resolve
// it to the same chunk used by App.tsx's React.lazy() calls.
(() => {
  try {
    const path = (location.pathname || "/").replace(/\/+$/, "") || "/";
    switch (path) {
      case "/":
        preloadRouteChunk(() => import("./pages/Index")); break;
      case "/training":
        preloadRouteChunk(() => import("./pages/Training")); break;
      case "/camps":
        preloadRouteChunk(() => import("./pages/Camps")); break;
      case "/3v3-tournaments":
        preloadRouteChunk(() => import("./pages/Tournaments3v3")); break;
      case "/player-pathway":
        preloadRouteChunk(() => import("./pages/PlayerPathway")); break;
      case "/start-here":
        preloadRouteChunk(() => import("./pages/StartHere")); break;
    }
  } catch {}
})();

createRoot(document.getElementById("root")!).render(
  <HelmetProvider>
    <App />
  </HelmetProvider>
);

// Mark body as ready so the static hero shell (if any) hides and React's
// rendered hero takes over without a visible flicker.
// Also clear the stale-module recovery flag on successful mount so a future
// stale-bundle error can trigger a fresh reload instead of being suppressed.
queueMicrotask(() => {
  document.body.classList.add("js-ready");
  window.setTimeout(clearStaleModuleRecoveryPending, 4000);
});
