/* scope guard: file-scoped IIFE — see index.html */
(function () {
/* Stoic+ — app shell & router.

   Replaces the prototype's phone-frame harness with a real responsive
   web app (CLAUDE.md §0 + Design Law 10): one centered ~440px column,
   no bezel / notch / fake status bar. Owns navigation, the four routed
   tabs, the check-in overlay, the bottom sheets, and the gating states
   (sign-in → first-launch welcome → app · lapsed → renew wall).

   State is seeded from and written through Store (localStorage in a
   hosted build, in-memory otherwise). The dev "Prototype states" menu
   is mounted only when the URL carries ?dev. */

const { useState, useEffect } = React;

const NAV = [
  { key: "home",    label: "Today",   icon: "Home" },
  { key: "journey", label: "Journey", icon: "Trend" },
  { key: "coach",   label: "Coach",   icon: "Message" },
  { key: "settings",label: "Settings",icon: "Settings" },
];
const TAB_KEYS = NAV.map((n) => n.key);

const IS_DEV = (() => { try { return new URLSearchParams(window.location.search).has("dev"); } catch (e) { return false; } })();

// Subscriptions are billed & auto-renewed by Loop; "Manage subscription" and
// the lapsed Renew wall both hand off to Loop's hosted customer portal. Its URL
// is NOT hardcoded here — it carries the storefront domain, so it comes from
// /api/config (STORE_DOMAIN). Hardcoding it is what pinned the app to the old
// store; keep it configuration.

// Admin emails allowed in regardless of subscription state — also the path that
// works when the entitlement backend is unreachable (e.g. local dev / DB down).
// Mirror of the server's DEV_BYPASS_EMAILS in .env; keep the two in sync.
const ADMIN_EMAILS = ["moneylytictrades@gmail.com"];

// Format the server's renewal date (YYYY-MM-DD) → "Jul 12, 2026". Parsed by
// parts to avoid the UTC-midnight day-shift of new Date("YYYY-MM-DD").
const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
function fmtRenew(d) {
  if (!d || typeof d !== "string") return null;
  const m = d.match(/^(\d{4})-(\d{2})-(\d{2})$/);
  if (!m) return null;
  return MONTHS[parseInt(m[2], 10) - 1] + " " + parseInt(m[3], 10) + ", " + m[1];
}

/* Hash router for the four tabs → linkable screens, working back button. */
function useTabRoute() {
  const read = () => window.location.hash.replace(/^#\/?/, "").split("/")[0];
  const [raw, setRaw] = useState(read);
  useEffect(() => {
    const on = () => setRaw(read());
    window.addEventListener("hashchange", on);
    return () => window.removeEventListener("hashchange", on);
  }, []);
  const tab = TAB_KEYS.indexOf(raw) >= 0 ? raw : "home";
  const setTab = (t) => {
    const v = document.querySelector(".viewport");
    if (v) v.scrollTop = 0;
    if (read() === t) setRaw(t); else window.location.hash = "/" + t;
  };
  return [tab, setTab];
}

function App() {
  const boot = window.Store.load();
  const Sheet = window.Sheet;
  const I = window.Icons;

  const [tab, setTab] = useTabRoute();
  const [authed, setAuthed] = useState(boot.authed);
  const [welcomeSeen, setWelcomeSeen] = useState(boot.welcomeSeen);
  const [email, setEmail] = useState(boot.email || "");
  const [sheet, setSheet] = useState(null);
  const [overlay, setOverlay] = useState(null);                 // 'checkin' | 'edit'
  const [values, setValues] = useState(boot.values);
  const [logged, setLogged] = useState(boot.logged);
  const [committedAction, setCommittedAction] = useState(boot.committedAction);
  const [sub, setSub] = useState(boot.sub);                     // 'active' | 'offline'(dev) | 'lapsed'
  const [renewDate, setRenewDate] = useState(null);            // next billing date (YYYY-MM-DD) from Loop, via bootstrap
  const [portalUrl, setPortalUrl] = useState(boot.portalUrl || ""); // Loop portal, from /api/config (cached seed)
  const [rangeIdx, setRangeIdx] = useState(1);
  const [online, setOnline] = useState(typeof navigator === "undefined" ? true : navigator.onLine);
  const [scenario, setScenario] = useState("normal");          // DEV-only QA control; always 'normal' in prod
  const [hydrated, setHydrated] = useState(false);             // pulled real server state yet?

  // Real connectivity → fail-open offline indicator (never lock a member out).
  useEffect(() => {
    const up = () => setOnline(true), down = () => setOnline(false);
    window.addEventListener("online", up);
    window.addEventListener("offline", down);
    return () => { window.removeEventListener("online", up); window.removeEventListener("offline", down); };
  }, []);

  // Public store config (Loop portal URL). Fetched on mount rather than with
  // the authed bootstrap: the lapsed Renew wall renders before bootstrap is
  // meaningful and its only CTA is the portal hand-off.
  useEffect(() => {
    window.Store.api.fetchConfig()
      .then((c) => { if (c && c.portalUrl) setPortalUrl(c.portalUrl); })
      .catch(() => {});
  }, []);

  // Persist the durable slices through the store on change.
  useEffect(() => { window.Store.save({ authed, welcomeSeen, email, values, logged, committedAction, sub }); },
    [authed, welcomeSeen, email, values, logged, committedAction, sub]);

  // Hydrate real server state once signed in (sets window.__HIST / __BOOT for
  // the charts). Fail-open: on error we keep the cached/seed view.
  useEffect(() => {
    if (!authed || !welcomeSeen || hydrated) return;
    window.Store.hydrate().then((b) => {
      setHydrated(true);
      if (b.today) { setValues(b.today.values); setLogged(!!b.today.logged); }
      if (b.coach) setCommittedAction(b.coach.committedAction || null);
      if (b.subscription && b.subscription.sub) setSub(b.subscription.sub);
      if (b.subscription) setRenewDate(b.subscription.renewalDate || null);
      if (b.user && b.user.email) setEmail(b.user.email);
    }).catch(() => setHydrated(true));
  }, [authed, welcomeSeen, hydrated]);

  const go = setTab;

  // ---- flows (each routes through a Store.api seam) ----
  // Entitlement gate: only recognised members (active → app, lapsed → Renew
  // wall) and admin emails may enter; unknown emails are rejected. The backend
  // (/api/entitlement → Loop) is the source of truth; admins are also let in
  // when it's unreachable. Resolves to { ok, error } for the sign-in screen.
  const signIn = (addr) => {
    const norm = (addr || "").trim().toLowerCase();
    const isAdmin = ADMIN_EMAILS.includes(norm);
    return window.Store.api.signIn(norm).then((r) => {
      r = r || {};
      if (r.found || isAdmin) {                       // known customer (active/lapsed) or admin
        setEmail(r.email || norm);
        setSub(r.sub || "active");
        setAuthed(true);
        return { ok: true };
      }
      if (r.offline) return { ok: false, error: "Couldn't verify your subscription. Check your connection and try again." };
      return { ok: false, error: "No active Stoic+ subscription found for this email." };
    }).catch(() => ({ ok: false, error: "Sign-in failed. Try again." }));
  };
  const beginFromWelcome = () => { setWelcomeSeen(true); setOverlay("checkin"); };
  const startCheckin = () => setOverlay("checkin");
  const editCheckin = () => setOverlay("edit");
  const submitCheckin = (vals) => {
    window.Store.api.submitCheckin(vals).then(() => { setValues(vals); setLogged(true); setOverlay(null); setHydrated(false); go("home"); });
  };
  const commit = (action) => { window.Store.api.commitAction(action).then(() => setCommittedAction(action)); };
  const signOut = () => { window.Store.api.signOut().then(() => { setAuthed(false); setSheet(null); go("home"); }); };
  // Billing/renewal lives in Loop — open its hosted portal in a new tab. No URL
  // (STORE_DOMAIN unset / config unreachable and nothing cached) → do nothing
  // rather than navigate somewhere wrong.
  const openLoopPortal = () => { if (portalUrl) window.open(portalUrl, "_blank", "noopener"); };

  const offline = sub === "offline" || !online;

  /* The dev "Prototype states" trigger + sheet — only when ?dev is set. */
  const DevTrigger = IS_DEV ? (
    <button className="dev-beaker press" title="Prototype states" onClick={() => setSheet("demo")}>
      <I.Beaker size={16} />
    </button>
  ) : null;
  const DemoSheet = IS_DEV ? (
    <Sheet open={sheet === "demo"} onClose={() => setSheet(null)} title="Prototype states">
      <window.DemoMenu {...{ scenario, setScenario, sub, setSub, logged, setLogged, welcome: !welcomeSeen, setWelcome: (w) => setWelcomeSeen(!w), authed, setAuthed }} onClose={() => setSheet(null)} />
    </Sheet>
  ) : null;

  // ---- Gating states ----
  if (!authed) {
    return (
      <div className="app stoic-root">
        <div className="ambient"></div>
        <div className="viewport flush"><window.SignInScreen onEnter={signIn} /></div>
        {DevTrigger}{DemoSheet}
      </div>
    );
  }

  if (!welcomeSeen) {
    return (
      <div className="app stoic-root">
        <div className="ambient"></div>
        <div className="viewport flush"><window.WelcomeScreen onBegin={beginFromWelcome} /></div>
        {DevTrigger}{DemoSheet}
      </div>
    );
  }

  if (sub === "lapsed") {
    return (
      <div className="app stoic-root">
        <window.RenewWall onRenew={openLoopPortal} />
        {DevTrigger}{DemoSheet}
      </div>
    );
  }

  // ---- Main app ----
  let Body;
  if (overlay) {
    Body = <window.Checkin initial={overlay === "edit" ? values : null} onSubmit={submitCheckin} onCancel={() => setOverlay(null)} />;
  } else if (tab === "home") {
    Body = <window.TodayScreen {...{ values, logged, scenario, offline, committedAction, rangeIdx, setRangeIdx }} onStartCheckin={startCheckin} onEditCheckin={editCheckin} openSheet={setSheet} />;
  } else if (tab === "journey") {
    Body = <window.JourneyScreen scenario={scenario} openSheet={setSheet} />;
  } else if (tab === "coach") {
    Body = <window.CoachScreen scenario={scenario} committedAction={committedAction} onCommit={commit} onAsk={() => setSheet("ask")} />;
  } else {
    Body = <window.SettingsScreen sub={sub} email={email} renewDate={fmtRenew(renewDate)} onSignOut={signOut} onManageSub={openLoopPortal} />;
  }

  const hideNav = !!overlay;

  return (
    <div className="app stoic-root">
      <div className="ambient"></div>
      <div className="viewport" key={tab + (overlay || "") + scenario + logged}>{Body}</div>

      {!hideNav && (
        <nav className="nav glass glass-lit glass-strong" aria-label="Primary">
          <svg width="0" height="0" style={{ position: "absolute" }} aria-hidden="true">
            <defs>
              <linearGradient id="navGold" x1="0" y1="0" x2="0" y2="1">
                <stop offset="0%" stopColor="#D8CC8A" />
                <stop offset="55%" stopColor="#BBB065" />
                <stop offset="100%" stopColor="#9A8F4E" />
              </linearGradient>
            </defs>
          </svg>
          {NAV.map((n) => {
            const Ico = I[n.icon];
            const active = tab === n.key;
            return (
              <button key={n.key} className={active ? "active" : ""} aria-current={active ? "page" : undefined} onClick={() => go(n.key)}>
                <Ico size={22} sw={active ? 2.3 : 2} />
                <span className="nav-lbl">{n.label}</span>
              </button>
            );
          })}
        </nav>
      )}

      <Sheet open={sheet === "monitor"} onClose={() => setSheet(null)} title="Live Monitor">
        <window.MonitorBreakdownBody values={values} />
      </Sheet>

      <Sheet open={sheet === "day"} onClose={() => setSheet(null)} title="Day detail">
        <window.DayDetailBody onEdit={() => { setSheet(null); go("home"); editCheckin(); }} />
      </Sheet>

      <Sheet open={sheet === "ask"} onClose={() => setSheet(null)} title="Ask the Coach">
        <window.AskCoachBody />
      </Sheet>

      {DevTrigger}{DemoSheet}
    </div>
  );
}

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