import React from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import { i18nReady } from "./i18n";

const container = document.getElementById("root")!;

const escapeHtml = (input: string) =>
  input
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/\"/g, "&quot;")
    .replace(/'/g, "&#039;");

const formatUnknownError = (error: unknown) => {
  if (error instanceof Error) {
    return `${error.name}: ${error.message}${error.stack ? `\n\n${error.stack}` : ""}`;
  }
  try {
    return JSON.stringify(error, null, 2);
  } catch {
    return String(error);
  }
};

const BOOT_RETRY_KEY = "boot_retry_v1";

const isLikelyModuleImportFailure = (error: unknown) => {
  const msg = formatUnknownError(error);
  return /Importing a module script failed|Failed to fetch dynamically imported module|Load failed/i.test(msg);
};

const trySelfHealAndReload = async (error: unknown) => {
  if (!isLikelyModuleImportFailure(error)) return false;

  try {
    if (sessionStorage.getItem(BOOT_RETRY_KEY)) return false;
    sessionStorage.setItem(BOOT_RETRY_KEY, "1");
  } catch {
    // sessionStorage unavailable; continue with a best-effort recovery
  }

  // Stale/buggy service workers (including old PWA tooling) are a common cause of module load failures.
  // Keep OneSignal service worker intact.
  try {
    if ("serviceWorker" in navigator) {
      const regs = await navigator.serviceWorker.getRegistrations();
      await Promise.all(
        regs.map((r) => r.unregister().catch(() => false)),
      );
    }
  } catch {
    // ignore
  }

  // Clear Cache Storage to avoid serving an old HTML pointing to missing hashed JS chunks.
  try {
    if ("caches" in window) {
      const keys = await window.caches.keys();
      await Promise.all(keys.map((k) => window.caches.delete(k).catch(() => false)));
    }
  } catch {
    // ignore
  }

  const url = new URL(window.location.href);
  url.searchParams.set("__reload", String(Date.now()));
  window.location.replace(url.toString());
  return true;
};

const renderFatalError = (title: string, error: unknown) => {
  const safe = escapeHtml(formatUnknownError(error));
  container.innerHTML = `
    <div style="padding: 20px; font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial; max-width: 960px; margin: 0 auto;">
      <h1 style="color: #dc2626; font-size: 20px; margin: 0 0 8px;">${escapeHtml(title)}</h1>
      <p style="margin: 0 0 16px; color: #374151;">The app failed to load. Please refresh — if it keeps happening, copy the error details below.</p>
      <pre style="background: #f3f4f6; color: #111827; padding: 12px; border-radius: 8px; overflow: auto; white-space: pre-wrap;">${safe}</pre>
      <button onclick="location.reload()" style="margin-top: 12px; padding: 10px 16px; border-radius: 8px; border: 1px solid #d1d5db; background: white; cursor: pointer;">Reload</button>
    </div>
  `;
};

// Capture boot-time errors to avoid "blank page" with no signal
let booting = true;
window.setTimeout(() => {
  booting = false;
}, 15000);

window.addEventListener("error", (event) => {
  if (!booting) return;
  const e = event as ErrorEvent;

  // Safari/Brave/etc can emit a generic cross-origin "Script error." when a third-party script is blocked.
  // Avoid flashing a fatal screen for a non-actionable, non-fatal boot-time signal.
  if (e.message === "Script error." && !e.filename && !e.error) return;

  renderFatalError("앱 로딩 오류", e.error ?? e.message);
});

window.addEventListener("unhandledrejection", (event) => {
  if (!booting) return;

  // Same rationale as above: ignore generic, cross-origin "Script error." strings.
  const reason = (event as PromiseRejectionEvent).reason;
  if (typeof reason === "string" && reason === "Script error.") return;

  renderFatalError("앱 로딩 오류", reason);
});

// Async initialization to properly catch import errors
// Add platform classes to body for CSS targeting
if ((window as any).Capacitor) {
  document.body.classList.add('capacitor-app');
  if (/android/i.test(navigator.userAgent)) {
    document.body.classList.add('platform-android');

    // Compensate for Android system text scaling (textZoom).
    try {
      const probe = document.createElement('div');
      probe.style.cssText = 'position:absolute;visibility:hidden;font-size:100px;line-height:1;margin:0;padding:0;width:auto;height:auto;';
      probe.textContent = 'M';
      document.body.appendChild(probe);
      const actualHeight = probe.getBoundingClientRect().height;
      document.body.removeChild(probe);
      const expectedHeight = 100;
      const scaleFactor = actualHeight / expectedHeight;
      if (scaleFactor > 1.05 || scaleFactor < 0.95) {
        document.documentElement.style.zoom = String(1 / scaleFactor);
      }
    } catch {
      // Silently ignore measurement errors
    }

    // Android safe-area-inset-top fallback for devices where env() returns 0.
    // Detect actual status bar height via StatusBar plugin or use a sensible default.
    (async () => {
      try {
        // Wait a frame so layout is settled
        await new Promise(r => requestAnimationFrame(r));

        // Check if env(safe-area-inset-top) is actually reporting a value
        const testEl = document.createElement('div');
        testEl.style.cssText = 'position:fixed;top:0;left:0;padding-top:env(safe-area-inset-top,0px);visibility:hidden;pointer-events:none;';
        document.body.appendChild(testEl);
        const computedPadding = parseFloat(getComputedStyle(testEl).paddingTop) || 0;
        document.body.removeChild(testEl);

        if (computedPadding < 1) {
          // env(safe-area-inset-top) is 0 — need fallback
          const statusBarHeight = 24; // default Android status bar = 24dp

          // Set CSS custom property as fallback
          document.documentElement.style.setProperty(
            '--safe-area-top-fallback',
            `${statusBarHeight}px`
          );
          document.body.classList.add('safe-area-fallback');
        }
      } catch {
        // ignore
      }
    })();
  }
}

const startApp = async () => {
  try {
    // Ensure i18n detection finishes before the first React render
    await i18nReady;

    // Dynamic import to catch module loading errors
    const { default: App } = await import("./App.tsx");

    createRoot(container).render(<App />);

    // Let index.html boot watchdog know the app mounted even if skeleton removal is delayed.
    (window as any).__APP_STARTED = true;

    // Use requestAnimationFrame to ensure React has committed to the DOM
    requestAnimationFrame(() => {
      const initialSkeleton = document.getElementById("initial-skeleton");
      if (initialSkeleton && initialSkeleton.parentNode) {
        try {
          initialSkeleton.remove();
        } catch {
          // Already removed by index.html watchdog — safe to ignore
        }
      }
    });

    // Hide Capacitor SplashScreen after app mount (native only)
    try {
      const { Capacitor } = await import('@capacitor/core');
      if (Capacitor.isNativePlatform()) {
        try {
          const { SplashScreen } = await import('@capacitor/splash-screen');
          await SplashScreen.hide({ fadeOutDuration: 300 });
        } catch {
          console.log('SplashScreen not available, skipping');
        }
      }
    } catch {
      // Not in native environment, ignore
    }

    // If we recovered previously in this tab session, clear the flag after a successful mount.
    try {
      sessionStorage.removeItem(BOOT_RETRY_KEY);
    } catch {
      // ignore
    }
  } catch (error) {
    const healed = await trySelfHealAndReload(error);
    if (healed) return;

    renderFatalError("앱 로딩 오류", error);
    console.error("React mount error:", error);
  }
};

startApp();

// Defer analytics loading to reduce unused JS impact on initial load
const loadAnalytics = () => {
  // Microsoft Clarity
  (function (c: any, l: Document, a: string, r: string, i: string) {
    c[a] =
      c[a] ||
      function (...args: any[]) {
        (c[a].q = c[a].q || []).push(args);
      };
    const t = l.createElement(r) as HTMLScriptElement;
    t.async = true;
    t.src = "https://www.clarity.ms/tag/" + i;
    const y = l.getElementsByTagName(r)[0];
    y.parentNode?.insertBefore(t, y);
  })(window, document, "clarity", "script", "v558cemgxa");

  // Google Analytics 4 (GA4)
  (function (w: any, d: Document, s: string, id: string) {
    // 중복 로드 방지
    if (d.querySelector(`script[src="https://www.googletagmanager.com/gtag/js?id=${id}"]`)) return;

    // gtag 스크립트 로드
    const js = d.createElement(s) as HTMLScriptElement;
    js.async = true;
    js.src = `https://www.googletagmanager.com/gtag/js?id=${id}`;
    d.head.appendChild(js);

    // gtag 초기화
    w.dataLayer = w.dataLayer || [];
    function gtag(...args: any[]) {
      w.dataLayer.push(args);
    }
    w.gtag = w.gtag || gtag;

    w.gtag("js", new Date());
    w.gtag("config", id);
  })(window, document, "script", "G-PMQ91W6ZPL");
};

// Load analytics after page is interactive using requestIdleCallback or setTimeout fallback
if ("requestIdleCallback" in window) {
  (window as any).requestIdleCallback(() => loadAnalytics(), { timeout: 5000 });
} else {
  setTimeout(loadAnalytics, 3000);
}
