import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
// Consumer typography pilot font faces (SimplerPro Alte, four exact weights).
import "./styles/fonts.css";
import { applyAppSurface } from "@/lib/app-surface-font-scope";
// NOTE: the canonical splash artwork (`assets/splash.png`) is consumed by the
// Capacitor asset-generation flow for the native iOS splash layers. Android no
// longer renders a WebView launch cover, so it is not imported here.

import { isNativePlatform, isAndroid, isIOS } from "@/lib/capacitor-utils";
import { LaunchCover } from "@/plugins/launch-cover";
import { markStatusBarOverlayApplied } from "@/hooks/useStatusBarTint";


import { initGlobalErrorLogger, isExpectedAvatarStatMiss } from "@/lib/global-error-logger";

// Pre-init boot error capture — runs BEFORE initGlobalErrorLogger so any JS
// exception thrown during very early bootstrap (Capacitor bridge init, plugin
// registration, image/webp hooks, error-logger setup itself) is preserved on
// window.__bootErrors for later inspection. Logging-only; behavior unchanged.
(() => {
  try {
    const w = window as any;
    if (w.__bootErrorsInstalled) return;
    w.__bootErrorsInstalled = true;
    w.__bootErrors = [];
    w.__bootPhase = 'pre-init';
    const push = (entry: Record<string, unknown>) => {
      try {
        // Skip expected Capacitor Filesystem avatar cache misses. Canonical
        // signal remains [ImageCacheTrace] Filesystem.stat miss.
        if (isExpectedAvatarStatMiss([entry, (entry as any)?.message, (entry as any)?.stack])) return;
        w.__bootErrors.push({ t: Math.round(performance.now()), phase: w.__bootPhase, ...entry });
        // Mirror to console so native log shows it too
        // eslint-disable-next-line no-console
        console.error('[BootError]', JSON.stringify(entry));
      } catch { /* noop */ }
    };
    window.addEventListener('error', (e: ErrorEvent) => {
      push({
        kind: 'error',
        message: e.message,
        filename: e.filename,
        lineno: e.lineno,
        colno: e.colno,
        stack: e.error?.stack,
      });
    }, true);
    window.addEventListener('unhandledrejection', (e: PromiseRejectionEvent) => {
      const r: any = e.reason;
      push({
        kind: 'unhandledrejection',
        message: r?.message ?? String(r),
        stack: r?.stack,
      });
    });
    // eslint-disable-next-line no-console
    console.log('[BootPhase] pre-init: boot-error capture installed');
  } catch { /* noop */ }
})();

const markBootPhase = (phase: string) => {
  try {
    (window as any).__bootPhase = phase;
    // eslint-disable-next-line no-console
    console.log(`[BootPhase] ${phase} t=${Math.round(performance.now())}`);
  } catch { /* noop */ }
};

markBootPhase('error-logger-init');
// Initialize global error logger BEFORE anything else (captures JS Eval errors)
initGlobalErrorLogger();


import { prefetchComplete, destinationReady, launchComplete, signalLaunchComplete } from "@/lib/prefetch-signals";
// TEMP-LAUNCH-TRACE: temporary instrumentation, remove after validation
import { markLaunchFlag, emitLaunchContractComplete } from "@/lib/debug/launchTrace";

import { getSnapshot as getSessionSnapshot, subscribe as subscribeSession } from "@/lib/auth/sessionStore";

/** Single upper bound on the whole launch handoff (pre-existing contract). */

const LAUNCH_FAILSAFE_MS = 6000;

/** Hard bound on the native `LaunchCover.present()` bridge call (iOS). */
const PRESENT_TIMEOUT_MS = 1200;

/** Hard bound on the native `LaunchCover.dismiss()` bridge call (iOS). */
const DISMISS_TIMEOUT_MS = 900;

/**
 * Bounded wait for the Consumer pilot webfonts to be decoded and ready.
 *
 * The pilot faces use `font-display: block`, so text is never painted in the
 * system font first — but the launch cover is the product's visual readiness
 * boundary, so the cover must not be released while glyphs are still invisible.
 * Hard-capped: a font that never resolves releases the cover anyway (the
 * pre-existing failure-release semantics are preserved) and the event is
 * reported through `[Launch] fonts` rather than stalling startup.
 */
const FONTS_READY_NATIVE_MS = 2500;
const FONTS_READY_WEB_MS = 1500;

function fontsReady(maxMs: number): Promise<void> {
  const startedAt = performance.now();
  const report = (verdict: 'ready' | 'timeout' | 'unsupported') => {
    console.log(
      `[Launch] fonts ${verdict} after ${Math.round(performance.now() - startedAt)}ms`,
    );
  };
  const api = (document as Document & { fonts?: FontFaceSet }).fonts;
  if (!api || typeof api.ready?.then !== 'function') {
    report('unsupported');
    return Promise.resolve();
  }
  return new Promise<void>((resolve) => {
    let done = false;
    const finish = (verdict: 'ready' | 'timeout') => {
      if (done) return;
      done = true;
      clearTimeout(timer);
      report(verdict);
      resolve();
    };
    const timer = setTimeout(() => finish('timeout'), maxMs);
    api.ready.then(() => finish('ready'), () => finish('timeout'));
  });
}

/**
 * Bound a bridge promise. A native call that never resolves must never be able
 * to stall the launch: on timeout the returned promise rejects and the caller
 * treats it exactly like a failure. The underlying call is left to settle on
 * its own — the native plugin's generation token makes a late `present`
 * harmless once `dismiss` has been requested.
 */
function withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T> {
  return new Promise<T>((resolve, reject) => {
    const timer = setTimeout(
      () => reject(new Error(`LaunchCover.${label} timed out after ${ms}ms`)),
      ms,
    );
    promise.then(
      (value) => { clearTimeout(timer); resolve(value); },
      (error) => { clearTimeout(timer); reject(error); },
    );
  });
}

/**
 * Resolves once the canonical session store reaches a terminal verdict
 * (`authenticated` | `unauthenticated`). Read-only: no second auth listener,
 * no Supabase call, no state mutation, no timeout of its own (the launch
 * fail-safe bounds it). Unsubscribes immediately after resolving once.
 */
function sessionVerdictSettled(): Promise<void> {
  if (getSessionSnapshot().status !== "unknown") return Promise.resolve();
  return new Promise<void>((resolve) => {
    let done = false;
    const unsubscribe = subscribeSession(() => {
      if (done) return;
      if (getSessionSnapshot().status === "unknown") return;
      done = true;
      unsubscribe();
      resolve();
    });
    // Guard against a verdict committed between the snapshot read and the
    // subscription being installed.
    if (!done && getSessionSnapshot().status !== "unknown") {
      done = true;
      unsubscribe();
      resolve();
    }
  });
}

/** Resolves after two animation frames, i.e. once the ready frame is painted. */
function nextPaint(): Promise<void> {
  return new Promise<void>((resolve) => {
    requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
  });
}

/** The static launch cover parsed from index.html. */
function getLaunchCover(): HTMLElement | null {
  return document.getElementById('initial-loader');
}

/**
 * Single canonical native-splash dismissal.
 *
 * iOS calls this behind the already-attached native LaunchCover (unchanged
 * timing). Android calls it at the END of the readiness handoff, because on
 * Android the Capacitor / core-splashscreen surface IS the only pre-app owner
 * and must stay retained until the destination surface is ready to draw.
 */
async function hideNativeSplash(): Promise<void> {
  try {
    const { SplashScreen } = await import("@capacitor/splash-screen");
    await SplashScreen.hide({ fadeOutDuration: 0 });
    console.log('[Launch] Native splash dismissed (zero fade)');
  } catch (error) {
    console.warn('[Launch] SplashScreen.hide failed:', error);
  }
}




/**
 * Bounded wait for the WebView viewport to stop resizing.
 *
 * Applying the steady-state status-bar contract (notably iOS
 * `setOverlaysWebView({ overlay: false })`) resizes the WebView frame and
 * recomputes `env(safe-area-inset-*)`. Doing that behind the opaque cover and
 * waiting here is what removes the shell displacement / geometry jump: the
 * user never sees the intermediate layouts. Hard-capped — never open-ended.
 */
function viewportSettled(maxMs = 800): Promise<void> {
  return new Promise<void>((resolve) => {
    const startedAt = performance.now();
    let lastHeight = -1;
    let stableFrames = 0;
    let done = false;

    const finish = () => {
      if (done) return;
      done = true;
      window.removeEventListener('resize', onResize);
      resolve();
    };
    const onResize = () => { stableFrames = 0; };
    window.addEventListener('resize', onResize);

    const tick = () => {
      if (done) return;
      const height = window.innerHeight;
      if (height === lastHeight) stableFrames += 1;
      else { stableFrames = 0; lastHeight = height; }
      if (stableFrames >= 3 || performance.now() - startedAt >= maxMs) {
        finish();
        return;
      }
      requestAnimationFrame(tick);
    };
    requestAnimationFrame(tick);
  });
}

/**
 * Bounded wait for the safe-area CSS variable to be populated by the native
 * bridge. Preserves the pre-existing 50 × 20ms budget.
 */
async function safeAreaSettled(): Promise<void> {
  for (let attempt = 0; attempt < 50; attempt++) {
    const top = getComputedStyle(document.documentElement)
      .getPropertyValue('--safe-area-inset-top');
    if (top && top !== '0px' && !top.includes('undefined')) {
      console.log('[Launch] Safe area ready:', top);
      return;
    }
    await new Promise((resolve) => setTimeout(resolve, 20));
  }
}





import { setupRealtimeCacheInvalidation } from "@/lib/realtime-cache-invalidation";

// Start the global realtime cache invalidation listener exactly once for the
// app lifecycle. The function has an internal `isSubscribed` guard, so even if
// React StrictMode double-invokes effects this remains a single subscription.
//
// C3 — the websocket handshake is not required for Discovery first paint, so
// it is held until the LaunchCover is PHYSICALLY dismissed (`launchComplete`
// resolves on every exit path in this file: native, web, degraded, fail-safe).
launchComplete.then(() => {
  setupRealtimeCacheInvalidation();
  markBootPhase('realtime-listener-installed');
});


async function initializeApp() {
  markBootPhase('initializeApp-start');

  // ANDROID launch ownership (canonical, explicit): the native
  // core-splashscreen surface (green field + canonical WePlay launcher mark)
  // is the ONLY pre-app owner on Android. No HTML launch cover is armed, no
  // WebView reveal animation runs, and no native launch-phase bridge exists.
  // iOS continues to use the native LaunchCover rendering port, because only a
  // native view can stay above the status-bar background view that
  // `setOverlaysWebView(false)` inserts.



  // Configure native platform basics (non-blocking)
  if (isNativePlatform()) {
    const [
      nativeImageUtils,
      iosWebpDebug,
      splashScreenModule,
      statusBarModule,
    ] = await Promise.all([
      import("@/lib/native-image-utils"),
      import("@/lib/ios-webp-debug"),
      import("@capacitor/splash-screen"),
      import("@capacitor/status-bar"),
    ]);

    const { purgeImageCacheIfNeeded, installNativeImageRewriteGuards } = nativeImageUtils;
    const { installIOSWebpDebugHook } = iosWebpDebug;
    const { SplashScreen } = splashScreenModule;
    const { StatusBar, Style } = statusBarModule;

    document.body.classList.add('native-app');
    if (isAndroid()) {
      document.body.classList.add('android');
    }
    if (isIOS()) {
      document.body.classList.add('ios');
    }

    markBootPhase('native-modules-loaded');
    // Install native iOS image URL rewrite guards early
    installNativeImageRewriteGuards();
    installIOSWebpDebugHook();
    markBootPhase('image-hooks-installed');
    // Re-assert the retained launch splash (iOS only).
    //
    // Android MUST NOT call `show()` here: the installed plugin resolves
    // `show()` through the LEGACY WindowManager overlay path, while dismissal
    // is owned by the core-splashscreen pre-draw gate installed at launch —
    // `hide()` returns as soon as it releases that gate and would never remove
    // a legacy overlay. Skipping `show()` on Android keeps exactly one native
    // splash owner, the one installed by `showOnLaunch()`.
    if (!isAndroid()) {
      try {
        await SplashScreen.show({
          showDuration: 0,  // Don't auto-hide
          autoHide: false,
        });
        console.log('[Init] SplashScreen shown manually');
      } catch (error) {
        console.warn('[Init] Failed to show SplashScreen:', error);
      }
    }

    markBootPhase('splash-shown');

    
    // Configure Status Bar for the LAUNCH PHASE (platform-specific).
    // The native splash and the web launch cover are both full-bleed brand
    // green (#01A375); the status bar must overlay them so no white strip is
    // painted above. The steady-state contract is applied later in this file,
    // behind the still-opaque cover, before the slide-down reveal.

    try {
      if (isIOS()) {
        // iOS: overlay only. setBackgroundColor is Android-only and must not
        // be called here.
        await StatusBar.setOverlaysWebView({ overlay: true });
        await StatusBar.setStyle({ style: Style.Dark }); // Dark = light icons
        console.log('[Init] iOS status bar configured for splash (overlay: true)');
      } else if (isAndroid()) {
        await StatusBar.setOverlaysWebView({ overlay: true });
        await StatusBar.setBackgroundColor({ color: '#00000000' }); // transparent
        await StatusBar.setStyle({ style: Style.Dark }); // Dark = light icons
        console.log('[Init] Android status bar configured for splash (transparent overlay)');
      }
    } catch (error) {
      // Non-fatal: a visual status-bar call must never block startup.
      console.warn('[Init] Splash status bar config failed:', error);
    }
    markBootPhase('statusbar-configured');
  }



  // Purge stale cached images (WEBP cleanup) - non-blocking, native only
  if (isNativePlatform()) {
    const { purgeImageCacheIfNeeded } = await import("@/lib/native-image-utils");
    purgeImageCacheIfNeeded().catch(() => {});
  }
  
  markBootPhase('react-render');
  // Canonical typography surface, applied BEFORE the first React paint so the
  // PSC / Club OS system-font pin is already in effect on an admin deep link.
  applyAppSurface(window.location.pathname);
  // Render the app immediately
  createRoot(document.getElementById("root")!).render(
    <React.StrictMode>
      <App />
    </React.StrictMode>
  );
  markBootPhase('react-rendered');

  queueMicrotask(() => {
    console.log('[GlobalError] POST-BOOT marker');
  });

  // ---------------------------------------------------------------------
  // Web path (Desktop Web / Mobile Web): previous browser behavior.
  // The dark spinner loader is removed at the first React paint — no native
  // launch cover, no minimum display time, no prefetch gate, no fail-safe,
  // no viewport settlement, no StatusBar call, no slide animation. Nothing
  // native can block the browser.
  //
  // The one addition is a hard-bounded `document.fonts.ready` wait so the
  // spinner is not swapped for a screen whose glyphs are still invisible
  // (`font-display: block`). On timeout the loader is removed regardless.
  // ---------------------------------------------------------------------
  if (!isNativePlatform()) {
    await fontsReady(FONTS_READY_WEB_MS);
    getLaunchCover()?.remove();
    markBootPhase('web-loader-removed');
    // TEMP-LAUNCH-TRACE — web has no launch gates; the contract is emitted for
    // parity from a detached observer that nothing awaits.
    markLaunchFlag('minimumDisplaySatisfied');
    markLaunchFlag('launchCoverDismissed');
    void (async () => {
      const settled = sessionVerdictSettled();
      settled.then(() => markLaunchFlag('sessionReady'));
      destinationReady.then(() => markLaunchFlag('destinationReady'));
      await Promise.race([
        Promise.all([settled, prefetchComplete, destinationReady]),
        new Promise((r) => setTimeout(r, LAUNCH_FAILSAFE_MS)),
      ]);
      emitLaunchContractComplete();
    })();

    signalLaunchComplete();
    return;
  }





  // ---------------------------------------------------------------------
  // Native launch handoff (iOS / Android only — the web path returned above).

  //
  //   1. Confirm the static launch cover has painted.
  //   2. Dismiss the native splash with fadeOutDuration: 0 (hard cut onto an
  //      identical, already-painted green field — no seam, no white frame).
  //   3. Behind the still-opaque cover: wait for the canonical session verdict
  //      and the honest terminal prefetch verdict.
  //   4. Behind the still-opaque cover: apply the steady-state StatusBar
  //      contract and let the resulting viewport/safe-area geometry settle.
  //   5. Confirm the first application frame is painted, slide the cover down,
  //      remove it, and release StatusBar ownership.
  //
  // Every wait is bounded, and the whole path is additionally capped by the
  // pre-existing 6s fail-safe.
  // ---------------------------------------------------------------------
  const minDisplayTime = new Promise((resolve) => setTimeout(resolve, 1000));

  // Track if prefetch completed to suppress spurious timeout warning
  let prefetchCompleted = false;
  prefetchComplete.then(() => { prefetchCompleted = true; });

  let failSafeWon = true;
  const failSafe = new Promise<void>((resolve) => setTimeout(() => {
    if (!prefetchCompleted) {
      console.warn('[Launch] Fail-safe reached (6s), proceeding without full prefetch');
    }
    resolve();
  }, LAUNCH_FAILSAFE_MS));

  // 1 + 2 + 3 — present the launch cover and prove it is attached, laid out
  // and ready to paint BEFORE the native splash is dismissed.
  //
  // The bridge call is hard-bounded: a missing plugin, a rejection, a
  // `presented: false` result or a call that never comes back all collapse to
  // the same DEGRADED verdict. iOS never falls back to the HTML/WebView cover —
  // that cover is Android-owned and animating it here would reintroduce the
  // white status strip, the cover resize and the artwork recrop that the
  // native port exists to remove.
  let nativeCoverPresented = false;
  let iosCoverDegraded = false;
  if (isIOS()) {
    try {
      const result = await withTimeout(
        LaunchCover.present(),
        PRESENT_TIMEOUT_MS,
        'present',
      );
      nativeCoverPresented = result?.presented === true;
      console.log('[Launch] Native LaunchCover present ->', JSON.stringify(result));
    } catch (error) {
      console.warn('[Launch] Native LaunchCover.present unavailable:', error);
    }
    iosCoverDegraded = !nativeCoverPresented;
    if (iosCoverDegraded) {
      console.warn('[Launch] iOS launch cover DEGRADED — immediate, non-animated handoff');
    }
  }
  await nextPaint();

  // Canonical splash dismissal — iOS ONLY at this point, behind the already
  // attached (or degraded) native LaunchCover.
  //
  // Android deliberately does NOT dismiss here: the native splash is its only
  // pre-app owner, so it stays retained through readiness and is dismissed
  // once, at step 7, after the two settled `nextPaint()` frames.
  if (isNativePlatform() && !isAndroid()) {
    await hideNativeSplash();
  }

  markBootPhase('native-splash-dismissed');

  if (iosCoverDegraded) {
    // ---------------------------------------------------------------------
    // iOS DEGRADED handoff — no cover exists, so nothing may be hidden behind
    // one. No HTML cover exists on any platform, no readiness gate is held and

    // contract in guarded non-fatal calls and reveal immediately.
    // ---------------------------------------------------------------------
    try {
      const { StatusBar, Style } = await import('@capacitor/status-bar');
      await StatusBar.setStyle({ style: Style.Light }); // Light = dark icons
      // Same canonical steady state as the normal path (see below): overlay
      // mode ON, so no post-reveal resize can be triggered by tinted surfaces.
      await StatusBar.setOverlaysWebView({ overlay: true });
      markStatusBarOverlayApplied();
    } catch (error) {
      console.warn('[Launch] Degraded steady-state status bar config failed:', error);
    }

    await safeAreaSettled();
    await viewportSettled();

    // Cancel any native present that may still be in flight: dismiss()
    // invalidates the pending generation so a late main-thread present can
    // never attach a cover after this point.
    try {
      await withTimeout(LaunchCover.dismiss(), DISMISS_TIMEOUT_MS, 'dismiss');
    } catch (error) {
      console.warn('[Launch] Degraded LaunchCover.dismiss cleanup skipped:', error);
    }
    // (No HTML launch-cover state exists on any platform anymore.)
    // The static HTML loader is never armed on iOS, so it still carries its
    // default in-flow black/spinner styling. It must be removed on EVERY iOS
    // exit path or it occupies a full viewport ahead of #root and pushes the
    // whole app below the fold.
    getLaunchCover()?.remove();
    markBootPhase('launch-cover-degraded-reveal');
    markLaunchFlag('launchCoverDismissed'); // TEMP-LAUNCH-TRACE
    signalLaunchComplete();
    console.log('[Launch] Degraded reveal complete (no animation)');
    emitLaunchContractComplete(); // TEMP-LAUNCH-TRACE

  } else {

  // 3 — readiness, entirely behind the opaque cover.
  //
  // `destinationReady` is the strengthened contract: it resolves only when the
  // FINAL destination surface is rendering (ProtectedRoute's terminal children
  // render, or LoginPage's phone surface). Without it, `prefetchComplete` can
  // win while ProtectedRoute is still in its `profile_pending` "טוען…" branch,
  // which is what exposed loading text during the slide.
  console.log('[Launch] Waiting for session verdict, destination, prefetch, fonts and minimum display time...');
  const readyPath = (async () => {
    // TEMP-LAUNCH-TRACE: passive observers. Each awaits the SAME promise the
    // Promise.all below awaits, so ordering and timing are unchanged.
    const sessionSettled = sessionVerdictSettled();
    sessionSettled.then(() => markLaunchFlag('sessionReady'));
    destinationReady.then(() => markLaunchFlag('destinationReady'));
    const minDisplay = minDisplayTime;
    Promise.resolve(minDisplay).then(() => markLaunchFlag('minimumDisplaySatisfied'));

    await Promise.all([
      sessionSettled,
      prefetchComplete,
      destinationReady,
      fontsReady(FONTS_READY_NATIVE_MS),
      minDisplay,
    ]);
    failSafeWon = false;
  })();


  await Promise.race([readyPath, failSafe]);

  if (failSafeWon) {
    console.warn('[Launch] Fail-safe reveal — app may briefly show a loading state');
  }

  // 6 — steady-state status bar + geometry settlement, still fully behind the
  // opaque cover. iOS restores the canonical `overlay: false` contract here.
  if (isNativePlatform()) {
    try {
      const { StatusBar, Style } = await import('@capacitor/status-bar');
      if (isIOS()) {
        // Canonical iOS steady state: dark icons with the WebView extending
        // UNDER the status bar (`overlay: true`), so every surface paints its
        // own page tint into the top safe area (`useStatusBarTint`).
        //
        // This must be the value applied HERE, behind the still-opaque cover.
        // Previously the launch applied `overlay: false` and the first tinted
        // surface flipped it back to `true` right after the reveal — that
        // WebView resize (top inset 0px → 62px) was the one-time boot jump.
        await StatusBar.setStyle({ style: Style.Light }); // Light = dark icons
        await StatusBar.setOverlaysWebView({ overlay: true });
        markStatusBarOverlayApplied();
      } else if (isAndroid()) {
        // Android: keep the LAUNCH-COMPATIBLE bar for the entire cover
        // interval. Writing the opaque white steady-state background here
        // painted a white status bar on top of the still-visible green WePlay
        // cover. The steady-state white contract is already owned by
        // `App.tsx` after `launchComplete` (which resolves only after
        // `revealApp()` + `LaunchCover.dismiss()`), so no writer is added here
        // — the premature duplicate is simply removed.
        await StatusBar.setOverlaysWebView({ overlay: true });
        await StatusBar.setBackgroundColor({ color: '#00000000' }); // transparent over the green cover
      }
    } catch (error) {
      // Non-fatal: never block the reveal on a cosmetic call.
      console.warn('[Launch] Steady-state status bar config failed:', error);
    }
    await safeAreaSettled();
  }
  await viewportSettled();

  // The static `#initial-loader` is never armed as a cover on any native
  // platform, so it still carries its default in-flow dark/spinner styling.
  // It MUST be detached before the pre-app owner goes away — on iOS before the
  // native cover slides, on Android before the native splash is dismissed —
  // otherwise it occupies a full viewport ahead of #root.
  getLaunchCover()?.remove();


  // Two final painted frames of the settled geometry, with the loader already
  // gone, before anything moves.
  await nextPaint();
  await nextPaint();

  // 7 — release the pre-app owner for this platform.
  //   iOS: bounded native LaunchCover dismiss (a stalled bridge call must
  //        never block `launchComplete`).
  //   Android: the single explicit native splash dismissal. The prepared
  //        application surface draws for the first time immediately after.
  if (nativeCoverPresented) {
    try {
      await withTimeout(LaunchCover.dismiss(), DISMISS_TIMEOUT_MS, 'dismiss');
    } catch (error) {
      console.warn('[Launch] Native LaunchCover.dismiss failed:', error);
    }
  } else if (isAndroid()) {
    await hideNativeSplash();
  }

  // Idempotent safety net (`?.remove()` is a no-op once detached above).
  getLaunchCover()?.remove();

  markBootPhase('launch-cover-removed');
  markLaunchFlag('launchCoverDismissed'); // TEMP-LAUNCH-TRACE


  // 8 — launchComplete.
  signalLaunchComplete();
  console.log('[Launch] Reveal complete, StatusBar ownership released');
  emitLaunchContractComplete(); // TEMP-LAUNCH-TRACE


  }




  if (isNativePlatform()) {


    
    // Background verification tasks (non-blocking)
    setTimeout(async () => {
      // Verify layout
      const topBar = document.querySelector('[data-layout="top-bar"]');
      const bottomNav = document.querySelector('[data-layout="bottom-nav"]');
      const fixedElements = Array.from(document.querySelectorAll('*'))
        .filter(el => {
          const s = getComputedStyle(el as HTMLElement);
          if (!s || s.position !== 'fixed') return false;
          const top = s.top || '';
          const isTopZero = top === '0px' || top.startsWith('0');
          const hasBackground = s.backgroundColor !== 'rgba(0, 0, 0, 0)';
          const z = parseInt(s.zIndex || '0', 10);
          return isTopZero && hasBackground && z >= 0;
        });

      if (topBar && bottomNav) {
        const topRect = (topBar as HTMLElement).getBoundingClientRect();
        const bottomRect = (bottomNav as HTMLElement).getBoundingClientRect();
        
        console.log('[Init] Layout verification:', {
          topBar: { top: topRect.top, height: topRect.height },
          bottomNav: { bottom: window.innerHeight - bottomRect.bottom, height: bottomRect.height },
          windowHeight: window.innerHeight
        });
      }
      
      // Scan for unwanted fixed elements with visible backgrounds (skip intentional layout elements)
      const fixedTopElements = fixedElements.filter(el => {
        const htmlEl = el as HTMLElement;
        // Skip our intentional layout elements
        if (htmlEl.dataset.layout) return false;
        const s = getComputedStyle(htmlEl);
        const top = s.top || '';
        const isTopZero = top === '0px' || top.startsWith('0');
        return isTopZero;
      });

      if (fixedTopElements.length > 0) {
        console.warn('[Layout Scan] Found unexpected fixed@top elements:');
        fixedTopElements.forEach(el => {
          const s = getComputedStyle(el as HTMLElement);
          console.log(' - Element:', {
            tag: el.tagName,
            class: (el as HTMLElement).className,
            id: (el as HTMLElement).id,
            top: s.top,
            z: s.zIndex,
          });
        });
      }
    }, 100);
  }
}

// Start initialization. `signalLaunchComplete` is idempotent, and this
// fallback guarantees steady-state StatusBar owners are never blocked if the
// orchestrator throws before its own release point. Any failure — before or
// after native splash dismissal, during readiness, geometry settlement or the
// slide — hard-removes the cover and clears the native-launch state so no
// green field can ever be retained.
initializeApp()
  .catch((error) => {
    console.error('[Launch] initializeApp failed:', error);
    getLaunchCover()?.remove();
    // Failure exit: the pre-app owner must be released, otherwise Android
    // would stay behind the retained native splash forever. Idempotent and
    // non-blocking; iOS is unaffected (its cover is released by its own path).
    if (isAndroid()) void hideNativeSplash();
  })


  .finally(() => {
    signalLaunchComplete();
  });


