import { createRoot } from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import App from './App.tsx'
import './index.css'
import { TrackingProvider } from './components/tracking/TrackingProvider'
import { GlobalErrorBoundary } from './components/ErrorBoundary/GlobalErrorBoundary'
import { ScrollToTop } from './components/common/ScrollToTop'
import { setupInternalLinkTracking } from './utils/seoInterlinking'
import { initWebVitals } from './utils/webVitals'
import { installContentProtection } from './utils/contentProtection'
import { assertProjectBinding } from './integrations/supabase/projectConfig'
import { installDataLayerEnricher } from './intelligence/dataLayerEnricher'
import { recordTouchOnce } from './intelligence/attribution'
import { refreshScores } from './intelligence/customerContext'
import { preloadCurrencyRates } from './services/currencyService'

// 🛡️  Verify the app is talking to the right Supabase project before anything else.
// See README-PROJECTS.md for why this matters.
assertProjectBinding();

// Preload live exchange rates the moment the app boots so every price shown
// sitewide (instant quotes, featured packages, hotel cards, checkout) uses
// realtime numbers. Idempotent + non-blocking.
preloadCurrencyRates();

// Customer Intelligence bootstrap — additive to existing analytics.
// Installs a dataLayer.push wrapper that merges customer-context enrichment
// into every event BEFORE GTM observes it. Original call sites untouched.
try {
  installDataLayerEnricher();
  recordTouchOnce();
  refreshScores();
} catch { /* never break the app */ }

// Disable right-click / copy sitewide (does NOT affect crawlers — they don't run JS listeners)
installContentProtection();

// PERFORMANCE: Defer non-critical work using requestIdleCallback
const deferWork = (fn: () => void) => {
  if ('requestIdleCallback' in window) {
    (window as any).requestIdleCallback(fn, { timeout: 3000 });
  } else {
    setTimeout(fn, 2000);
  }
};

// Defer SEO and performance monitoring until after page is interactive
window.addEventListener('load', () => {
  deferWork(() => {
    setupInternalLinkTracking();
    initWebVitals();
  });
}, { once: true, passive: true });

// BULLETPROOF: Auto-recover from stale chunk hashes after deploys.
// Old tabs / cached SWs hold an index.html pointing to JS chunks that no longer
// exist on the CDN, producing "Importing a module script failed" / "Failed to
// fetch dynamically imported module". A plain reload can hit the same broken
// SW response, so we do a NUCLEAR recovery: unregister the SW, purge every
// cache, then hard-reload. Guarded by sessionStorage to prevent loops.
const CHUNK_ERR_RE = /Failed to fetch dynamically imported module|Importing a module script failed|error loading dynamically imported module/i;

async function nukeAndReload(reason: string) {
  const flag = '__chunk_reloaded__';
  const attempts = Number(sessionStorage.getItem(flag) || '0');
  if (attempts >= 2) {
    // Give up after 2 tries — let the ErrorBoundary render a friendly screen
    console.error('[chunk-recovery] giving up after', attempts, 'attempts:', reason);
    return;
  }
  sessionStorage.setItem(flag, String(attempts + 1));
  console.warn('[chunk-recovery] nuking SW + caches, reason:', reason);
  try {
    if ('serviceWorker' in navigator) {
      const regs = await navigator.serviceWorker.getRegistrations();
      await Promise.all(regs.map((r) => r.unregister().catch(() => false)));
    }
  } catch {}
  try {
    if ('caches' in window) {
      const keys = await caches.keys();
      await Promise.all(keys.map((k) => caches.delete(k).catch(() => false)));
    }
  } catch {}
  // Force a fully fresh document — bypass HTTP cache with a cache-busting query.
  const url = new URL(window.location.href);
  url.searchParams.set('_r', Date.now().toString(36));
  window.location.replace(url.toString());
}

window.addEventListener('error', (e: ErrorEvent) => {
  const msg = e?.message ?? '';
  // Also catch <script>/<link> load failures which fire error events on window
  // with an empty message but a target element.
  const tgt = e?.target as HTMLElement | null;
  const isAssetTag =
    tgt && (tgt.tagName === 'SCRIPT' || tgt.tagName === 'LINK') &&
    /\.(js|mjs|css)(\?|$)/i.test((tgt as HTMLScriptElement).src || (tgt as HTMLLinkElement).href || '');
  if (CHUNK_ERR_RE.test(msg) || isAssetTag) {
    void nukeAndReload(msg || 'asset-tag-load-failure');
  }
}, true); // capture phase so we see resource errors too

window.addEventListener('unhandledrejection', (e) => {
  const msg = e?.reason?.message ?? String(e?.reason ?? '');
  if (CHUNK_ERR_RE.test(msg)) {
    void nukeAndReload(msg);
  }
});

window.addEventListener('load', () => {
  // Clear the attempts counter on any successful load so future stale-chunk
  // events can recover again on the next deploy.
  setTimeout(() => sessionStorage.removeItem('__chunk_reloaded__'), 2000);
}, { once: true });


// Production-optimized error handling (always track - consent granted by default)
window.addEventListener('error', (event) => {
  if (window.gtag) {
    window.gtag('event', 'exception', {
      description: event.error?.message || 'Unknown error',
      fatal: true,
    })
  }
})

window.addEventListener('unhandledrejection', (event) => {
  if (window.gtag) {
    window.gtag('event', 'exception', {
      description: event.reason?.message || 'Promise rejection',
      fatal: false,
    })
  }
})

const root = document.getElementById('root')

// Clear chunk reload flag on successful app load
if (typeof sessionStorage !== 'undefined') {
  sessionStorage.removeItem('chunk_reload_attempted')
}

/**
 * BULLETPROOF SEO SHELL (Deploy 1 — homepage recovery)
 *
 * SSG output now places the crawlable content in `#seo-static-fallback`
 * (a sibling of `#root`), NOT inside `#root`. That means:
 *   - React crashes cannot remove the SEO hero from the DOM.
 *   - Provider / router throws cannot remove it either.
 *   - We no longer need hydrateRoot / hydration-mismatch guards.
 *
 * Flow:
 *   1. React renders into an empty #root.
 *   2. On first successful commit (`onRecoverableError` is fine; only a
 *      thrown error skips this), we add `html.app-hydrated` which hides
 *      the SEO fallback via CSS.
 *   3. If React throws before mounting, the fallback stays visible and
 *      Googlebot + human users both see the commercial landing page.
 */
function markAppHydrated() {
  try {
    document.documentElement.classList.add('app-hydrated');
    // Legacy loader (only present in the SPA-shell index.html for
    // non-SSG routes) — remove if it's here.
    document.getElementById('initial-loader')?.remove();
  } catch {
    // no-op
  }
}

/**
 * SEO DE-DUPLICATION (Aug 2026 — homepage "hotels in makkah" recovery)
 *
 * The SSG shell used to stay in the DOM forever, merely hidden with CSS.
 * Googlebot renders JS, so the rendered DOM contained TWO copies of the
 * homepage: two <h1>, 33 <h2>, duplicated FAQ / directory / reviews blocks
 * and duplicated JSON-LD. That reads as a low-quality keyword-repetitive
 * page and was the single biggest drag on the homepage.
 *
 * Fix: after the crossfade completes we DETACH the shell from the DOM.
 *   - No-JS crawlers still receive the full static copy in the HTML.
 *   - Rendering crawlers see exactly one copy.
 *   - The node is kept in memory so ErrorBoundary recovery can re-attach it
 *     if React crashes after mount (see window.__restoreSeoShell).
 */
const SEO_SHELL_ID = 'seo-static-fallback';
let detachedSeoShell: { node: Element; parent: Node; next: Node | null } | null = null;

function detachSeoShell() {
  try {
    const node = document.getElementById(SEO_SHELL_ID);
    if (!node || !node.parentNode) return;
    detachedSeoShell = { node, parent: node.parentNode, next: node.nextSibling };
    node.parentNode.removeChild(node);
  } catch {
    // never break the app for an SEO optimisation
  }
}

function restoreSeoShell() {
  try {
    if (!detachedSeoShell) return;
    const { node, parent, next } = detachedSeoShell;
    if (node.isConnected) return;
    parent.insertBefore(node, next);
    detachedSeoShell = null;
  } catch {
    // no-op
  }
}

if (typeof window !== 'undefined') {
  (window as any).__restoreSeoShell = restoreSeoShell;
}


if (!root) {
  // Do NOT wipe body — the static SEO fallback is a sibling of #root and
  // must stay in the DOM. Log and let the fallback do its job.
  console.error('[SEO] #root not found — falling back to static SEO shell only.')
} else {
  try {
    const appElement = (
      <GlobalErrorBoundary>
        <TrackingProvider>
          <BrowserRouter>
            <ScrollToTop />
            <App />
          </BrowserRouter>
        </TrackingProvider>
      </GlobalErrorBoundary>
    )

    createRoot(root).render(appElement)

    // Mark hydrated on the very next paint frame — this is what hides the
    // static SEO fallback and reveals the React tree. We used to wait two
    // rAFs which added a visible flash of the SSG shell (~150-300ms extra
    // on mobile). One frame is enough: by then React has committed the
    // initial subtree (Suspense boundaries for below-the-fold code split
    // continue to load in the background without blocking the swap).
    requestAnimationFrame(() => {
      markAppHydrated()
      // Detach the static shell once the 180ms crossfade + 200ms hold has
      // finished, so the rendered DOM contains a single copy of the page.
      setTimeout(detachSeoShell, 600)
    })

  } catch (error) {
    console.error('[SEO] Render error:', error)
    // Do NOT clear the SEO fallback. Leave it visible so Google + users
    // still see a real commercial landing page. Optional one-shot reload
    // if this was a stale-chunk error (handled by the global listeners
    // registered above).
  }
}

