import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { HelmetProvider } from "react-helmet-async";
import App from "./App.tsx";
import "./index.css";
import { cleanupLegacyStorage } from "./core/days/legacyCleanup";
import { initTextScale } from "./core/textScale";
import posthog from "posthog-js";

// NOTE: Capgo's notifyAppReady() is deliberately NOT called here. Calling it at
// file-load time would mark a bundle "healthy" before React has rendered, which
// defeats Capgo's auto-rollback. It fires from src/lib/otaUpdater.ts via
// App.tsx only AFTER the React tree renders successfully (proof-of-health).

// CORE V2: Clean up legacy non-user-scoped keys on app boot
// This prevents cross-account data bleed before any auth events fire
console.log('🧹 APP BOOT: Running legacy storage cleanup...');
cleanupLegacyStorage();

// Note: Storage initialization moved to App.tsx useEffect for safety
// This ensures React and ErrorBoundary are active before storage access

// Recover from stale chunk references after a deploy. Vite fires
// 'vite:preloadError' when a hashed chunk URL 404s (user's tab still
// holds the previous index.html). One-shot hard reload, guarded by
// sessionStorage so a truly broken build can't loop.
const CHUNK_RELOAD_KEY = 'gdr:chunk-reload-attempt';

// Reveal-aware reload deferral: while the Recipe Reveal cinematic is playing
// (or just finished within ~10s), do NOT hard-reload — it would tear down the
// reveal, music, and orb. Poll until the reveal completes, then reload.
function isRevealActive(): boolean {
  try {
    if (sessionStorage.getItem('gdr-reveal-in-progress') === '1') return true;
    const finishedAt = Number(sessionStorage.getItem('gdr-reveal-finished-at') || 0);
    return finishedAt > 0 && (Date.now() - finishedAt) < 10000;
  } catch { return false; }
}
function reloadWhenRevealIdle(reason: string) {
  if (!isRevealActive()) {
    console.warn('[ChunkRecovery] Reloading now:', reason);
    window.location.reload();
    return;
  }
  console.warn('[ChunkRecovery] Reveal active — deferring reload:', reason);
  const iv = setInterval(() => {
    if (!isRevealActive()) {
      clearInterval(iv);
      console.warn('[ChunkRecovery] Reveal idle — reloading now:', reason);
      window.location.reload();
    }
  }, 300);
}
// Exposed for other reload sites (lazyWithRetry, ErrorBoundary).
(window as any).__gdrReloadWhenRevealIdle = reloadWhenRevealIdle;
(window as any).__gdrIsRevealActive = isRevealActive;

window.addEventListener('vite:preloadError', (event: any) => {
  if (sessionStorage.getItem(CHUNK_RELOAD_KEY)) {
    console.error('[ChunkRecovery] Reload already attempted, surfacing error', event);
    return; // let ErrorBoundary render
  }
  // Diagnostics: capture the failing chunk URL so we can pinpoint the source.
  let url = '';
  try {
    const payload: any = event && (event as any).payload;
    url = String(payload?.toString?.() || payload || (event && (event as any).target?.src) || '');
  } catch {}
  console.warn('[ChunkRecovery] preloadError url=', url);
  try { posthog.capture('dbg_chunk_preload_error', { url }); } catch {}

  sessionStorage.setItem(CHUNK_RELOAD_KEY, String(Date.now()));
  event.preventDefault(); // suppress default rethrow so we can reload cleanly
  console.warn('[ChunkRecovery] Stale chunk detected, scheduling reload');
  reloadWhenRevealIdle('vite:preloadError ' + url);
});
// Clear the guard a few seconds after successful load — proof recovery worked.
window.addEventListener('load', () => {
  setTimeout(() => sessionStorage.removeItem(CHUNK_RELOAD_KEY), 5000);
});

function renderApp() {
  createRoot(document.getElementById("root")!).render(
    <StrictMode>
      <HelmetProvider>
        <App />
      </HelmetProvider>
    </StrictMode>,
  );
}

// TEXT SIZE: read the phone's text-size setting and apply it BEFORE the first
// render, so onboarding screen 1 paints at the size this person actually reads
// at instead of visibly reflowing a moment later. This is a single native
// bridge call (milliseconds) and the native splash covers it.
//
// initTextScale never throws — on any failure it falls back to 1x, which is
// exactly today's behaviour — so this can never block boot. The .catch is a
// second belt anyway, because nothing is allowed to stop the app rendering.
initTextScale()
  .catch(() => { /* initTextScale already handles its own failures */ })
  .finally(renderApp);
