// NOTE: Do NOT install a global window.onerror handler that overwrites
// document.body — a single benign script error (e.g. third-party widget,
// browser extension) would wipe the entire app and produce a blank page.
// Errors are handled by the React ErrorBoundary below and the listeners
// further down (which only call preventDefault for known recoverable
// asset-loading errors).

import { createRoot } from "react-dom/client";
import React from "react";
import App from "./App.tsx";
import { AppErrorBoundary } from "./components/AppErrorBoundary";
import "./index.css";

const ASSET_RECOVERY_KEY = "mediforums:asset-recovery-attempted";
const ASSET_ERROR_PATTERNS = [
  "failed to fetch dynamically imported module",
  "importing a module script failed",
  "expected javascript",
  "javascript mime type",
  "text/html is not a valid javascript mime type",
  "loading css chunk",
  "loading chunk",
];

function getErrorText(error: unknown) {
  if (error instanceof Error) return error.message;
  return String(error ?? "");
}

function isRecoverableAssetError(error: unknown) {
  const message = getErrorText(error).toLowerCase();
  return ASSET_ERROR_PATTERNS.some((pattern) => message.includes(pattern));
}

async function clearClientCaches() {
  if ("serviceWorker" in navigator) {
    const registrations = await navigator.serviceWorker.getRegistrations().catch(() => []);
    await Promise.all(registrations.map((registration) => registration.unregister())).catch(() => {});
  }

  const cacheKeys = await (globalThis as { caches?: CacheStorage }).caches?.keys?.().catch(() => []);
  if (cacheKeys?.length) {
    await Promise.all(cacheKeys.map((key) => (globalThis as { caches?: CacheStorage }).caches?.delete(key))).catch(() => {});
  }
}

async function recoverFromAssetError(error: unknown) {
  if (!isRecoverableAssetError(error)) return false;

  if (sessionStorage.getItem(ASSET_RECOVERY_KEY) === "1") {
    return false;
  }

  sessionStorage.setItem(ASSET_RECOVERY_KEY, "1");
  await clearClientCaches();
  window.location.reload();
  return true;
}

class ErrorBoundary extends React.Component<{ children: React.ReactNode }, { error?: unknown }> {
  state: { error?: unknown } = {};
  static getDerivedStateFromError(error: unknown) {
    return { error };
  }
  render() {
    if (this.state.error) {
      const message = getErrorText(this.state.error);
      const assetError = isRecoverableAssetError(this.state.error);

      return (
        <div style={{ padding: 16, fontFamily: "ui-sans-serif, system-ui", color: "#111" }}>
          <div style={{ fontSize: 14, opacity: 0.8 }}>App failed to load.</div>
          {assetError && (
            <div style={{ marginTop: 8, fontSize: 12, opacity: 0.8 }}>
              A cached app bundle is out of date. Retry will refresh local caches and reload the latest version.
            </div>
          )}
          <pre style={{ marginTop: 8, fontSize: 12, whiteSpace: "pre-wrap" }}>
            {message}
          </pre>
          <button
            type="button"
            onClick={() => {
              void clearClientCaches().finally(() => {
                sessionStorage.removeItem(ASSET_RECOVERY_KEY);
                window.location.reload();
              });
            }}
            style={{
              marginTop: 12,
              borderRadius: 8,
              border: "1px solid #d1d5db",
              background: "#111827",
              color: "#fff",
              padding: "8px 12px",
              cursor: "pointer",
            }}
          >
            Retry
          </button>
        </div>
      );
    }
    return this.props.children;
  }
}

// Register service worker for PWA — only in production, not in iframes/preview
const isInIframe = (() => {
  try { return window.self !== window.top; } catch { return true; }
})();
const isPreviewHost =
  window.location.hostname.includes("id-preview--") ||
  window.location.hostname.includes("lovableproject.com");

window.addEventListener("error", (event) => {
  if (event instanceof ErrorEvent) {
    const source = event.error ?? event.message;
    if (isRecoverableAssetError(source)) {
      event.preventDefault();
      void recoverFromAssetError(source);
    }
  }
});

window.addEventListener("unhandledrejection", (event) => {
  if (isRecoverableAssetError(event.reason)) {
    event.preventDefault();
    void recoverFromAssetError(event.reason);
  }
});

// DEV: if a service worker was previously registered, unregister it to prevent stale-cache blank screens.
if (import.meta.env.DEV && "serviceWorker" in navigator) {
  navigator.serviceWorker.getRegistrations()
    .then((regs) => Promise.all(regs.map((r) => r.unregister())))
    .catch(() => {});
  // Best-effort cache clear
  (globalThis as any).caches?.keys?.()
    ?.then((keys: string[]) => Promise.all(keys.map((k) => (globalThis as any).caches.delete(k))))
    .catch(() => {});
}

if (import.meta.env.PROD && !isPreviewHost && !isInIframe && "serviceWorker" in navigator) {
  window.addEventListener("load", () => {
    sessionStorage.removeItem(ASSET_RECOVERY_KEY);
    const swUrl = `${import.meta.env.BASE_URL}sw.js?v=${encodeURIComponent(__APP_BUILD_ID__)}`;
    navigator.serviceWorker.register(swUrl).catch(() => {});
  });
}

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