import { primeAudioForAutoplay } from './lib/audioAutoplayBridge';

// Cast receiver: capture console output BEFORE any module side effects
// (credentials hydration, fsStore init, etc.) so the on-screen
// CastDebugOverlay can replay everything once it mounts. The overlay
// installs its own console hook after React mounts, which is too late
// for the bootstrap logs the user actually needs to see.
(() => {
  try {
    if (!/^\/cast(\/|$)/.test(window.location.pathname)) return;
    type BootEntry = { level: 'log' | 'warn' | 'error'; text: string; t: number };
    const w = window as unknown as { __castDebugBoot?: BootEntry[] };
    w.__castDebugBoot = w.__castDebugBoot || [];
    const fmt = (args: unknown[]) => args.map((a) => {
      if (a instanceof Error) return `${a.name}: ${a.message}`;
      if (typeof a === 'object' && a !== null) {
        try { return JSON.stringify(a); } catch { return String(a); }
      }
      return String(a);
    }).join(' ');
    const push = (level: BootEntry['level'], args: unknown[]) => {
      try {
        w.__castDebugBoot!.push({ level, text: fmt(args), t: Date.now() });
        if (w.__castDebugBoot!.length > 300) w.__castDebugBoot!.splice(0, w.__castDebugBoot!.length - 300);
        window.dispatchEvent(new CustomEvent('vibeos-cast-debug', { detail: { level, text: fmt(args), t: Date.now() } }));
      } catch { /* ignore */ }
    };
    const orig = { log: console.log, warn: console.warn, error: console.error };
    console.log = (...a: unknown[]) => { try { orig.log.apply(console, a); } catch { /* ignore */ } push('log', a); };
    console.warn = (...a: unknown[]) => { try { orig.warn.apply(console, a); } catch { /* ignore */ } push('warn', a); };
    console.error = (...a: unknown[]) => { try { orig.error.apply(console, a); } catch { /* ignore */ } push('error', a); };
    // Emit useful bootstrap context (instanceId, sid, host) up front.
    try {
      const sp = new URLSearchParams(window.location.search);
      const ctx = {
        path: window.location.pathname,
        instanceId: sp.get('instanceId') || 'default',
        sid: sp.get('sid'),
        host: window.location.hostname,
        hasHash: !!window.location.hash,
      };
      console.log('[cast] receiver boot', JSON.stringify(ctx));
    } catch { /* ignore */ }
  } catch { /* ignore */ }
})();

// Cast receiver: pre-apply session/instance from the URL hash BEFORE
// storage.ts initializes. Otherwise storage.ts runs at module-import time
// while `vos_session_token` / `vos_user_context` haven't been written yet,
// so `_hasAuthAtBoot()` returns false → `_prefix = 'default'` → the OS
// treats this tab as standalone-incognito, uses the synthetic /Volumes/Storage
// placeholder, and credentials hydrate from the wrong volume (`raw=missing`).
(() => {
  try {
    if (!/^\/cast(\/|$)/.test(window.location.pathname)) return;
    const hash = window.location.hash.startsWith('#') ? window.location.hash.slice(1) : '';
    if (!hash) return;
    const sp = new URLSearchParams(hash);
    const st = sp.get('st');
    const uc = sp.get('uc');
    if (st) {
      try { localStorage.setItem('vos_session_token', st); } catch { /* ignore */ }
    }
    if (uc) {
      try { localStorage.setItem('vos_user_context', uc); } catch { /* ignore */ }
      try { sessionStorage.setItem('vos_user_context', uc); } catch { /* ignore */ }
    }
    // instanceId rides on the query string, but only takes effect if storage.ts
    // also sees an auth signal — which we just primed above.
    try {
      const iid = new URLSearchParams(window.location.search).get('instanceId');
      console.log(`[cast] pre-boot primed st=${!!st} uc=${!!uc} instanceId=${iid || 'default'}`);
    } catch { /* ignore */ }
  } catch { /* ignore */ }
})();

// Domain lock: only allow execution on vibeoshub.com / os.vibeoshub.com
// Also allow Lovable preview/published domains for development
const allowedHosts = ['vibeoshub.com', 'os.vibeoshub.com', 'vibeoscloud.com', 'os.vibeoscloud.com'];
const host = window.location.hostname;
const isInIframe = window.self !== window.top;

const isLovableHost = (value: string) =>
  value === 'lovable.dev' ||
  value === 'lovable.app' ||
  value === 'lovableproject.com' ||
  value.endsWith('.lovable.dev') ||
  value.endsWith('.lovable.app') ||
  value.endsWith('.lovableproject.com');

// If embedded in an iframe, only allow from vibeoshub.com domains
const isIframeAllowed = (() => {
  if (!isInIframe) return true; // not in iframe, skip this check
  try {
    const referrerHost = document.referrer ? new URL(document.referrer).hostname : '';
    // If referrer is empty (stripped by policy), allow if the host itself is permitted
    if (!referrerHost) return true;
    return referrerHost === 'vibeoshub.com' || referrerHost.endsWith('.vibeoshub.com') ||
           referrerHost === 'vibeoscloud.com' || referrerHost.endsWith('.vibeoscloud.com') ||
           isLovableHost(referrerHost) || referrerHost === 'localhost';
  } catch {
    return false; // can't determine parent — block
  }
})();

const isAllowed =
  isIframeAllowed && (
    allowedHosts.includes(host) ||
    host === 'localhost' ||
    isLovableHost(host) ||
    // Allow when embedded in an iframe from an allowed parent
    (() => {
      try {
        const parentHost = isInIframe ? new URL(document.referrer).hostname : '';
        return parentHost === 'vibeoshub.com' || parentHost.endsWith('.vibeoshub.com') ||
               parentHost === 'vibeoscloud.com' || parentHost.endsWith('.vibeoscloud.com') ||
               isLovableHost(parentHost);
      } catch {
        return false;
      }
    })()
  );

// Self-heal stale dynamic-import chunk errors after a redeploy by reloading once.
const CHUNK_RELOAD_KEY = '__vos_chunk_reloaded__';
const isChunkLoadError = (msg: string) =>
  /Failed to fetch dynamically imported module|Importing a module script failed|error loading dynamically imported module/i.test(msg);
const tryReloadOnce = () => {
  try {
    if (sessionStorage.getItem(CHUNK_RELOAD_KEY)) return;
    sessionStorage.setItem(CHUNK_RELOAD_KEY, '1');
    window.location.reload();
  } catch { /* ignore reload bookkeeping errors */ }
};
window.addEventListener('error', (e) => {
  if (e?.message && isChunkLoadError(e.message)) tryReloadOnce();
});
window.addEventListener('unhandledrejection', (e: PromiseRejectionEvent) => {
  const msg = e?.reason?.message || String(e?.reason || '');
  if (isChunkLoadError(msg)) tryReloadOnce();
});

// iOS Safari blocks audio playback until a user gesture initiates it.
// Re-prime a silent Audio on EVERY user gesture (not just the first one)
// so any subsequent async play() call — e.g. opening an mp3 which has to
// resolve a signed URL before assigning src — can reuse the primed element.
// This is critical for iOS Home Screen (standalone PWA) where Safari is
// stricter about gesture preservation across async work and one-time
// priming is not enough to cover playback initiated later in the session.
(() => {
  let lastPrimedAt = 0;
  const prime = () => {
    const now = Date.now();
    // Throttle to avoid spamming Audio() creation on rapid pointer events.
    if (now - lastPrimedAt < 250) return;
    lastPrimedAt = now;
    primeAudioForAutoplay();
  };
  window.addEventListener('pointerdown', prime, true);
  window.addEventListener('touchend', prime, true);
  window.addEventListener('keydown', prime, true);
})();

if (!isAllowed) {
  document.body.innerHTML =
    '<div style="display:flex;align-items:center;justify-content:center;height:100vh;font-family:system-ui;color:#888;">' +
    '<p>This application is not authorized to run on this domain.</p></div>';
} else {
  // Wrap in async IIFE — esbuild doesn't support top-level await
  (async () => {
    // Phase 1: Storage namespace (must be first — patches localStorage)
    const storageReady = import("./lib/storage");

    // Phase 2: Start loading React + App immediately (no dependency on storage)
    const reactReady = import("react-dom/client");
    const appReady = import("./App.tsx");

    // Import CSS early (non-blocking)
    import("./index.css");

    // Mail app uses Sora / Manrope — load weights once, app-wide.
    import("@fontsource/sora/400.css");
    import("@fontsource/sora/600.css");
    import("@fontsource/sora/700.css");
    import("@fontsource/manrope/400.css");
    import("@fontsource/manrope/500.css");
    import("@fontsource/manrope/600.css");

    // Wait for storage, then init fsStore
    const storage = await storageReady;

    const [{ initFsStore, setQuotaProvider, getPersistenceMode, setPersistenceMode, resetFsStore }, { getPlanInfo }] = await Promise.all([
      import("./lib/fsStore"),
      import("./lib/planInfo"),
      import("./lib/aiUsageIntercept"),
    ]);

    const applyPersistenceMode = (instanceId = storage.getInstanceId()) => {
      setPersistenceMode(instanceId && instanceId !== 'default' ? 'cloud' : 'session');
    };
    applyPersistenceMode();
    window.addEventListener('vibeos-instance-id-change', (event) => {
      resetFsStore();
      applyPersistenceMode((event as CustomEvent<string>).detail);
    });

    const TIER_QUOTA: Record<string, number> = {
      free: 0.5 * 1024 * 1024 * 1024,
      pro: 50 * 1024 * 1024 * 1024,
      team: 500 * 1024 * 1024 * 1024,
    };

    // Estimate sessionStorage capacity WITHOUT blocking the main thread.
    // The previous implementation wrote ever-growing strings into sessionStorage
    // in a tight loop until the browser threw — a multi-hundred-ms (sometimes
    // multi-second) main-thread freeze that ran during boot AND on every
    // fsStore write in session mode. Browsers universally cap sessionStorage
    // at ~5 MB; a fixed estimate is accurate enough and costs ~0ms.
    let _cachedSessionLimit: number | null = null;
    const SESSION_STORAGE_ESTIMATE_BYTES = 5 * 1024 * 1024;
    const probeSessionLimit = (): number => {
      if (_cachedSessionLimit !== null) return _cachedSessionLimit;
      try {
        const KEY = '__vos_probe__';
        sessionStorage.setItem(KEY, 'x');
        sessionStorage.removeItem(KEY);
        _cachedSessionLimit = SESSION_STORAGE_ESTIMATE_BYTES;
      } catch {
        _cachedSessionLimit = 0;
      }
      return _cachedSessionLimit;
    };

    setQuotaProvider(() => {
      const plan = getPlanInfo();
      const tierKey = (plan.tier || 'free').toLowerCase();
      const tierDefault = TIER_QUOTA[tierKey] ?? TIER_QUOTA.free;

      // Read admin-provided storage limit. Accept either bytes (large number)
      // or megabytes (small number) and normalise to bytes. Anything below
      // 1 MiB is treated as MB to recover from misconfigured admin payloads
      // (and to avoid collapsing the FS to a tiny hard cap on uploads).
      let planQuota = tierDefault;
      const raw = plan.limits as Record<string, unknown> | undefined;
      const candidates: number[] = [];
      const pushNum = (v: unknown) => { if (typeof v === 'number' && isFinite(v) && v > 0) candidates.push(v); };
      if (raw) { pushNum(raw.storage); pushNum(raw.storage_mb); pushNum(raw.storage_MB); pushNum(raw.storageBytes); }
      if (candidates.length > 0) {
        let v = Math.max(...candidates);
        // If value looks like MB (< 1 MiB raw, but plausible MB count), upscale.
        if (v < 1024 * 1024) v = v * 1024 * 1024;
        // Never let admin shrink below the tier minimum — protects against a
        // single bad payload locking users out of all writes.
        planQuota = Math.max(v, tierDefault);
      }

      if (getPersistenceMode() === 'session') {
        // In session/incognito mode, quota is the smaller of: plan quota and what
        // sessionStorage can physically hold. Never report less than the plan
        // tier minimum — the hard sessionStorage cap is enforced separately on
        // write. This avoids confusing "10 KB used of 10 KB" toasts when the
        // browser's session probe collapses to the already-used size.
        const sessionCap = Math.floor(probeSessionLimit() * 0.95);
        return Math.min(planQuota, Math.max(sessionCap, planQuota));
      }
      return planQuota;
    });

    // Must be listening before React effects run: useWindowManager persists
    // the current window list on mount, and missing that first event can leave
    // `.system/open_windows.json` stuck at []/null until another window move.
    await import("./lib/openWindowsPersistence").then(({ startOpenWindowsPersistence }) => startOpenWindowsPersistence());

    // Render React as soon as possible — fsStore init runs in background
    const [{ createRoot }, { default: App }] = await Promise.all([reactReady, appReady]);
    createRoot(document.getElementById("root")!).render(<App />);

    // Init fsStore + volume listener AFTER render (non-blocking)
    await Promise.all([
      initFsStore(),
      import("./lib/volumes").then(({ initVolumeListener }) => initVolumeListener()),
      import("./lib/osStatePersistence").then(({ startOsStatePersistence }) => startOsStatePersistence()),
    ]);
    // Credentials must hydrate AFTER fsStore so the OS volume (and its
    // .system/.api_keys_keymat + .api_keys_salt) is available — otherwise
    // we'd derive a fresh key, fail to decrypt, and lose saved tokens.
    await import("./lib/credentials").then(({ initCredentials }) => initCredentials());
  })();
}
