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

// Coach login persistence: the coach session token is stored only in
// sessionStorage (per-tab), so it is lost when a link opens a new tab or iOS
// discards the tab. The signed token itself is valid server-side for 30 days,
// so mirror writes/removals to localStorage and restore on startup.
(() => {
  const KEY = "fussbally_coach_session";
  const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;

  // 1. Mirror sessionStorage writes/removals for the coach key to localStorage.
  const originalSetItem = Storage.prototype.setItem;
  const originalRemoveItem = Storage.prototype.removeItem;
  const isAdminOverride = () => {
    try { return new URLSearchParams(window.location.search).get("admin_override") === "true"; } catch { return false; }
  };
  Storage.prototype.setItem = function (key: string, value: string) {
    originalSetItem.call(this, key, value);
    if (this === window.sessionStorage && key === KEY && !isAdminOverride()) {
      try { originalSetItem.call(window.localStorage, key, value); } catch {}
    }
  };
  Storage.prototype.removeItem = function (key: string) {
    originalRemoveItem.call(this, key);
    if (this === window.sessionStorage && key === KEY) {
      try { originalRemoveItem.call(window.localStorage, key); } catch {}
    }
  };

  // 2. On startup, restore a recent-enough token from localStorage into
  // sessionStorage; drop stale or unparseable copies.
  try {
    if (!sessionStorage.getItem(KEY)) {
      const stored = localStorage.getItem(KEY);
      if (stored) {
        try {
          const token = stored.trim().startsWith("{")
            ? (JSON.parse(stored)?.token as string | undefined)
            : stored;
          const dot = token ? token.indexOf(".") : -1;
          const payload = dot > 0 ? token!.slice(0, dot) : "";
          const b64 = payload.replace(/-/g, "+").replace(/_/g, "/");
          const json = atob(b64 + "=".repeat((4 - (b64.length % 4)) % 4));
          const ts = Number(JSON.parse(json)?.ts);
          if (Number.isFinite(ts) && Date.now() - ts < THIRTY_DAYS_MS) {
            sessionStorage.setItem(KEY, stored);
          } else {
            localStorage.removeItem(KEY);
          }
        } catch {
          localStorage.removeItem(KEY);
        }
      }
    }
  } catch {}
})();

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);
});
