// Root app — orchestrates screens and flows.
const { useState: useS, useEffect: useE } = React;

function App() {
  // Server-backed user state (null while loading, {} on network error)
  const [user, setUser] = useS(null);

  // onboarded starts as null (unknown). Shown as "loading" until /api/user resolves.
  // Only flip to true after the server confirms — never just on local optimism.
  const [onboarded, setOnboarded] = useS(null);
  const [voiceChosen, setVoiceChosen] = useS(() => localStorage.getItem("fs.voice") || "");
  const [installed, setInstalled] = useS(() => localStorage.getItem("fs.installed") === "1");
  const [notifDecided, setNotifDecided] = useS(() => localStorage.getItem("fs.notif") || "");
  const [notifOn, setNotifOn] = useS(() => localStorage.getItem("fs.notif") === "on");

  // UI state
  const [tab, setTab] = useS(() => localStorage.getItem("fs.tab") || "today");
  const [showSettings, setShowSettings] = useS(false);
  const [showVoice, setShowVoice] = useS(false);
  const [splashGone, setSplashGone] = useS(false);
  const [onboardError, setOnboardError] = useS("");

  useE(() => { const t = setTimeout(() => setSplashGone(true), 1400); return () => clearTimeout(t); }, []);
  useE(() => { localStorage.setItem("fs.tab", tab); }, [tab]);

  // Version check + ghost-SW kill switch. Two layers:
  //   1. Build version drift: if server's BUILD_VERSION differs from our cached
  //      one, call reg.update() to encourage the SW to fetch the new assets.
  //   2. SW version cross-check: ask the running SW directly what its VERSION
  //      constant is. If it reports an old value (e.g. "fs-v9" while server
  //      wants "fs-v10"), we're stuck. Try reg.update() once; if still stuck
  //      on next load (second consecutive mismatch), unregister all SWs and
  //      reload — nuclear option to recover users with a zombie SW.
  useE(() => {
    api.getVersion().then((info) => {
      const { version, sw_version } = info || {};
      if (version) {
        const prev = localStorage.getItem("fs.version") || "";
        if (prev && prev !== version) {
          navigator.serviceWorker?.getRegistration?.().then(r => r?.update?.());
        }
        localStorage.setItem("fs.version", version);
      }

      if (!sw_version || !("serviceWorker" in navigator)) return;
      const controller = navigator.serviceWorker.controller;
      if (!controller) return;

      // MessageChannel is a two-way pipe dedicated to this one question.
      // Direct postMessage wouldn't give us a reply path; channels do.
      const chan = new MessageChannel();
      const timer = setTimeout(() => { try { chan.port1.close(); } catch {} }, 3000);
      chan.port1.onmessage = (ev) => {
        clearTimeout(timer);
        const running = ev.data && ev.data.version;
        if (!running) return;
        if (running === sw_version) {
          localStorage.setItem("fs.sw_stuck", "0");
          return;
        }
        const stuck = (parseInt(localStorage.getItem("fs.sw_stuck") || "0", 10) || 0) + 1;
        localStorage.setItem("fs.sw_stuck", String(stuck));
        if (stuck >= 2) {
          navigator.serviceWorker.getRegistrations().then(regs =>
            Promise.all(regs.map(r => r.unregister().catch(() => false)))
          ).then(() => {
            localStorage.setItem("fs.sw_stuck", "0");
            location.reload();
          }).catch(() => {});
        } else {
          navigator.serviceWorker.getRegistration?.().then(r => r?.update?.());
        }
      };
      try {
        controller.postMessage({ type: "version" }, [chan.port2]);
      } catch {}
    }).catch(() => {});
  }, []);

  // Load user on mount. Server is the source of truth for onboarded state.
  // If no token → LoginScreen. If token but /api/user 401s → clear token, LoginScreen.
  useE(() => {
    if (!api.hasToken()) { setUser({}); setOnboarded(false); return; }
    api.getUser().then(u => {
      setUser(u);
      setOnboarded(!!u.onboarded);
      if (u.voice_id) { localStorage.setItem("fs.voice", "mine"); setVoiceChosen("mine"); }
    }).catch(e => {
      console.warn("[app] getUser failed:", e);
      if (e?.status === 401) {
        // Stale/invalid token — fall back to LoginScreen cleanly.
        api.clearToken();
      }
      setUser({});
      setOnboarded(false);
    });
  }, []);

  // Flow handlers — only mark onboarded locally after server confirms.
  const completeOnboarding = async (answers = {}) => {
    const profile = mapOnboardingAnswers(answers);
    setOnboardError("");
    try {
      await api.onboard(profile);
    } catch (e) {
      console.warn("onboard failed:", e);
      setOnboardError(e.message || "Couldn't save. Check your connection and try again.");
      return; // do NOT advance — user retries
    }
    // Fresh onboarding implies fresh setup — drop any sticky flags from a prior
    // install so voice/install/notif prompts all get a clean shot to appear.
    ["fs.voice", "fs.installed", "fs.notif"].forEach(k => localStorage.removeItem(k));
    setVoiceChosen(""); setInstalled(false); setNotifDecided(""); setNotifOn(false);
    try {
      const u = await api.getUser();
      setUser(u);
    } catch {}
    setOnboarded(true);
  };
  const chooseVoice = (v) => { localStorage.setItem("fs.voice", v); setVoiceChosen(v); setShowVoice(false); };
  const didInstall   = () => { localStorage.setItem("fs.installed", "1"); setInstalled(true); };
  const laterInstall = () => { localStorage.setItem("fs.installed", "1"); setInstalled(true); };
  const enableNotif  = async () => {
    try { await enablePushNotifications(); } catch (e) { console.warn("push enable failed:", e); }
    localStorage.setItem("fs.notif", "on"); setNotifDecided("on"); setNotifOn(true);
  };
  const laterNotif   = () => { localStorage.setItem("fs.notif", "later"); setNotifDecided("later"); };

  const booting         = onboarded === null;
  const showLogin       = !api.hasToken();
  const showOnboarding  = !showLogin && onboarded === false;
  const showVoiceFlow   = !showLogin && onboarded === true && (!voiceChosen || showVoice);
  const showInstall     = !showLogin && onboarded === true && voiceChosen && !installed && !showVoice && isIOSSafari() && !isStandalone();
  const showNotif       = !showLogin && onboarded === true && voiceChosen && (installed || !isIOSSafari()) && !notifDecided && !showVoice;

  return (
    <>
      <div className="desktop-bg"/>
      <div className="app-frame">
        {booting ? (
          <div className="screen" aria-busy="true"/>
        ) : showLogin ? (
          <LoginScreen/>
        ) : showSettings ? (
          <SettingsScreen
            onBack={() => setShowSettings(false)}
            onRerecord={() => { setShowSettings(false); setShowVoice(true); }}
            hasCloned={voiceChosen === "mine"}
            setHasCloned={(v) => setVoiceChosen(v ? "mine" : "charlie")}
            notifOn={notifOn}
            setNotifOn={(v) => { setNotifOn(v); localStorage.setItem("fs.notif", v ? "on" : "later"); setNotifDecided(v ? "on" : "later"); }}
            user={user}
          />
        ) : onboarded ? (
          <>
            {tab === "today"   && <TodayScreen variant="C" onReflect={() => setTab("reflect")} onSettings={() => setShowSettings(true)}/>}
            {tab === "reflect" && <ReflectScreen onSubmitted={() => setTab("today")}/>}
            {tab === "journey" && <JourneyScreen onSettings={() => setShowSettings(true)}/>}
            <TabBar active={tab} onChange={setTab}/>
          </>
        ) : null}

        {!showLogin && showOnboarding && <Onboarding onComplete={completeOnboarding} errorMessage={onboardError}/>}
        {!showLogin && !showOnboarding && showVoiceFlow && (
          <div className="onboard" style={{ padding: 0 }}>
            <VoiceCloneScreen onDone={() => chooseVoice("mine")} onSkip={() => chooseVoice("charlie")}/>
          </div>
        )}
        {!showLogin && !showOnboarding && !showVoiceFlow && showInstall && (
          <InstallPrompt onDone={didInstall} onLater={laterInstall}/>
        )}
        {!showLogin && !showOnboarding && !showVoiceFlow && !showInstall && showNotif && (
          <div className="onboard" style={{ padding: 0 }}>
            <NotificationsScreen onEnable={enableNotif} onLater={laterNotif}/>
          </div>
        )}

        <div className={`splash ${splashGone ? "hide" : ""}`}><div className="mark">f</div></div>
      </div>
    </>
  );
}

function isIOSSafari() {
  const ua = navigator.userAgent;
  const iOS = /iPad|iPhone|iPod/.test(ua) && !window.MSStream;
  const webkit = /WebKit/.test(ua) && !/CriOS|FxiOS|OPiOS|mercury/i.test(ua);
  return iOS && webkit;
}
function isStandalone() {
  return window.matchMedia("(display-mode: standalone)").matches || window.navigator.standalone === true;
}

// Onboarding answers come back as {name, age, situation, struggles, goals,
// lang_pref, delivery_hour}. These map 1:1 to the columns Claude reads each
// night (engine/context_builder.py:34-45). Keep the user's own words verbatim.
function mapOnboardingAnswers(ans) {
  const ageBracketToInt = {
    "Under 25": 22, "25 – 34": 30, "35 – 44": 39, "45 – 54": 49, "55 +": 58,
  };
  const listenToHour = {
    "Before 7 AM": 6,
    "7 – 9 AM":    8,
    "Commute":     8,
    "Evening":     20,
    "No fixed time": 8,
  };
  return {
    name: (ans.name || "Friend").trim(),
    age: ageBracketToInt[ans.age],
    situation: (ans.situation || "").trim(),
    struggles: (ans.struggles || "").trim(),
    goals:     (ans.goals || "").trim(),
    lang_pref: (ans.lang_pref || "Hinglish").toLowerCase(),
    delivery_hour: listenToHour[ans.delivery_hour] ?? 8,
  };
}

async function enablePushNotifications() {
  if (!("serviceWorker" in navigator) || !("PushManager" in window)) {
    throw new Error("Push not supported");
  }
  const perm = await Notification.requestPermission();
  if (perm !== "granted") throw new Error("Permission denied");
  const reg = await navigator.serviceWorker.ready;
  const { public_key } = await api.getVapidKey();
  if (!public_key) throw new Error("No VAPID key on server");
  const sub = await reg.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: api.urlBase64ToUint8Array(public_key),
  });
  await api.subscribePush(sub);
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App/>);
