// app.jsx — root: routing, opt-in modal, vault transition, persistence, tweaks
const { useState: useS, useEffect: useE, useRef: useR } = React;

const STORE_KEY = "hyro70.member.v1";
const PAGE_KEY = "hyro70.page.v1";

function loadMember() {
  try { return JSON.parse(localStorage.getItem(STORE_KEY)); } catch (e) { return null; }
}
function saveMember(m) { try { localStorage.setItem(STORE_KEY, JSON.stringify(m)); } catch (e) {} }

/* Local-midnight day math. Day 1 = the user's LOCAL calendar day they started.
   The clock ticks over to the next box at 00:01 local time each new day. */
function startOfLocalDay(d) {
  return new Date(d.getFullYear(), d.getMonth(), d.getDate());
}
function dayFromStart(startISO, now) {
  if (!startISO) return 1;
  const start = startOfLocalDay(new Date(startISO));
  const today = startOfLocalDay(now || new Date());
  const elapsed = Math.floor((today - start) / 86400000); // whole local days since start
  return Math.min(70, Math.max(1, elapsed + 1)); // Day 1 on start day
}
/* ms until 00:01:00 local tomorrow (the tick-over moment for a new box) */
function msUntilNextLocalTick(now) {
  const n = now || new Date();
  const next = new Date(n.getFullYear(), n.getMonth(), n.getDate() + 1, 0, 1, 0, 0);
  return Math.max(1000, next - n);
}

/* ---------- Opt-in modal ---------- */
function OptinModal({ open, onClose, onSubmit, prefill }) {
  const [first, setFirst] = useS(prefill.firstName || "");
  const [email, setEmail] = useS(prefill.email || "");
  const [errs, setErrs] = useS({});

  useE(() => { if (open) { setFirst(prefill.firstName || ""); setEmail(prefill.email || ""); setErrs({}); } }, [open]);

  function submit(e) {
    e.preventDefault();
    const next = {};
    if (!first.trim()) next.first = "Pop your first name in";
    if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email.trim())) next.email = "That email does not look right";
    setErrs(next);
    if (Object.keys(next).length === 0) onSubmit({ firstName: first.trim(), email: email.trim() });
  }

  return (
    <div className={"modal-back" + (open ? " show" : "")} onClick={onClose}>
      <form className="modal" onClick={e => e.stopPropagation()} onSubmit={submit}>
        <button type="button" className="close" onClick={onClose} aria-label="Close">×</button>
        <div className="lock-ic">{Icon.unlock({})}</div>
        <h3>Start your 70 days</h3>
        <p className="sub">First name and email. That is it. Your tracker is on the other side.</p>
        <div className={"field" + (errs.first ? " err" : "")}>
          <label>First name</label>
          <input value={first} onChange={e => setFirst(e.target.value)} placeholder="Alex" autoComplete="given-name" />
          {errs.first && <div className="msg">{errs.first}</div>}
        </div>
        <div className={"field" + (errs.email ? " err" : "")}>
          <label>Email</label>
          <input value={email} onChange={e => setEmail(e.target.value)} placeholder="you@email.com" type="email" autoComplete="email" />
          {errs.email && <div className="msg">{errs.email}</div>}
        </div>
        <button className="cta block big" type="submit" style={{ marginTop: 6 }}>Open my tracker</button>
        <p className="fine">Free to join. We will only email you about your challenge.</p>
      </form>
    </div>
  );
}

/* ---------- Vault transition overlay ---------- */
function VaultOverlay({ phase }) {
  if (!phase) return null;
  return (
    <div className={"vault-overlay " + phase}>
      <div className="vault-door left"><div className="edge"></div></div>
      <div className="vault-door right"><div className="edge"></div></div>
      <div className="vault-seam">
        <div className="ring">{Icon.unlock({})}</div>
        <div className="t">Unlocking your 70</div>
      </div>
    </div>
  );
}

/* ---------- Press bar (toggled by showPress tweak) ---------- */
function PressBar() {
  return (
    <div style={{ background: "var(--hyro-white)", borderTop: "1px solid var(--hyro-line)", borderBottom: "1px solid var(--hyro-line)", padding: "22px 0" }}>
      <div className="wrap" style={{ display: "flex", flexWrap: "wrap", alignItems: "center", justifyContent: "center", gap: "16px 34px" }}>
        <span style={{ fontFamily: "var(--font-display)", textTransform: "uppercase", letterSpacing: "0.08em", fontSize: 12, color: "var(--hyro-grey-500)" }}>As seen in</span>
        {[0,1,2,3].map(i => (
          <span key={i} style={{ fontFamily: "var(--font-sans)", fontSize: 12, color: "var(--hyro-grey-300)", border: "1px dashed var(--hyro-grey-300)", borderRadius: 8, padding: "8px 18px" }}>[ Press logo ]</span>
        ))}
      </div>
    </div>
  );
}

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "heroCopy": "Offer-led",
  "previewDay": 39,
  "previewDayOverride": false,
  "showPress": false,
  "stickyCta": true
}/*EDITMODE-END*/;

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [member, setMember] = useS(() => loadMember());
  const [page, setPage] = useS(() => (localStorage.getItem(PAGE_KEY) === "tracker" && loadMember()) ? "tracker" : "vault");
  const [optin, setOptin] = useS(false);
  const [phase, setPhase] = useS(null); // vault transition
  const [checked, setChecked] = useS(() => (loadMember()?.checkedDays) || []);
  const [claimed, setClaimed] = useS(() => loadMember()?.claimStatus === "submitted");
  const [justChecked, setJustChecked] = useS(null);
  const [stickyShow, setStickyShow] = useS(false);
  const heroRef = useR(null);

  // Designer override: when the Preview-day slider is touched it pins the view
  // to that day. Real members run on a real clock derived from their start date.
  const previewOverride = t.previewDayOverride === true;

  // `tick` is bumped at each local-midnight rollover to force a recompute.
  const [tick, setTick] = useS(0);

  // Live current day: Day 1 on the local calendar day the member started, then
  // +1 at 00:01 local time every new day, capped at 70. Preview slider wins when
  // explicitly engaged (design tool), otherwise we always use the real clock.
  const liveDay = member ? dayFromStart(member.startDate, new Date()) : 1;
  const currentDay = previewOverride ? Math.round(t.previewDay || 1) : liveDay;

  // Roll the clock over at 00:01 local time each new calendar day. Reschedules
  // itself so it keeps ticking one box per day for the life of the session.
  useE(() => {
    if (page !== "tracker" || !member || previewOverride) return;
    let timer;
    const schedule = () => {
      timer = setTimeout(() => {
        setTick(x => x + 1); // recompute currentDay from the new local date
        schedule();
      }, msUntilNextLocalTick(new Date()));
    };
    schedule();
    return () => clearTimeout(timer);
  }, [page, member, previewOverride]);

  // Also recompute if the tab was backgrounded across a midnight (timers can be
  // throttled/suspended), so the day is correct the moment the user returns.
  useE(() => {
    if (previewOverride) return;
    const onVis = () => { if (!document.hidden) setTick(x => x + 1); };
    document.addEventListener("visibilitychange", onVis);
    return () => document.removeEventListener("visibilitychange", onVis);
  }, [previewOverride]);

  useE(() => { localStorage.setItem(PAGE_KEY, page); }, [page]);

  // sticky CTA visibility (mobile) — show once hero is scrolled past
  useE(() => {
    if (page !== "vault") { setStickyShow(false); return; }
    const el = heroRef.current;
    if (!el || !("IntersectionObserver" in window)) return;
    const io = new IntersectionObserver(([e]) => setStickyShow(!e.isIntersecting), { threshold: 0 });
    io.observe(el);
    return () => io.disconnect();
  }, [page]);

  function openCta() { setOptin(true); }

  function handleSubmit({ firstName, email }) {
    const existing = loadMember();
    const m = existing
      ? { ...existing, firstName, email }
      : { firstName, email, startDate: new Date().toISOString(), checkedDays: [], subscriptionStatus: "active", claimStatus: "none", memberId: "mbr_" + Math.random().toString(36).slice(2, 9) };
    saveMember(m);
    setMember(m);
    setChecked(m.checkedDays || []);
    setClaimed(m.claimStatus === "submitted");
    setOptin(false);
    runTransition();
  }

  function runTransition() {
    setPhase("closing");
    setTimeout(() => setPhase("closed"), 820);
    setTimeout(() => { setPage("tracker"); window.scrollTo(0, 0); }, 1320);
    setTimeout(() => setPhase("opening"), 1380);
    setTimeout(() => setPhase(null), 2200);
  }

  function toggleDay(day) {
    if (day > currentDay) return;
    setChecked(prev => {
      const has = prev.includes(day);
      const next = has ? prev.filter(d => d !== day) : [...prev, day].sort((a, b) => a - b);
      const m = loadMember(); if (m) { m.checkedDays = next; saveMember(m); }
      if (!has) { setJustChecked(day); setTimeout(() => setJustChecked(null), 360); }
      return next;
    });
  }

  function claim() {
    const m = loadMember(); if (m) { m.claimStatus = "submitted"; saveMember(m); }
    setClaimed(true);
  }

  function retake() {
    const m = loadMember();
    if (m) { m.checkedDays = []; m.claimStatus = "none"; m.startDate = new Date().toISOString(); saveMember(m); }
    setChecked([]); setClaimed(false); setTweak("previewDayOverride", false);
    window.scrollTo(0, 0);
  }

  function resetProgress() {
    localStorage.removeItem(STORE_KEY);
    localStorage.removeItem(PAGE_KEY);
    setMember(null); setChecked([]); setClaimed(false); setPage("vault");
    window.scrollTo(0, 0);
  }

  return (
    <>
      {page === "vault" && (
        <>
          <VaultDoor onCta={openCta} t={t} heroRef={heroRef} />
          {t.showPress && (
            <div style={{ position: "relative", marginTop: -1 }}><PressBar /></div>
          )}
          {/* sticky bottom CTA bar removed on mobile per Nathan 2026-06-03 */}
        </>
      )}

      {page === "tracker" && member && (
        <Tracker
          member={member} currentDay={currentDay} checked={checked}
          onToggle={toggleDay} claimed={claimed} onClaim={claim} onRetake={retake} justChecked={justChecked}
        />
      )}
      {page === "tracker" && !member && (
        /* safety: no member but on tracker — send back */
        <div style={{ padding: 60, textAlign: "center" }}>
          <button className="cta" onClick={() => setPage("vault")}>Back to the challenge</button>
        </div>
      )}

      <OptinModal open={optin} onClose={() => setOptin(false)} onSubmit={handleSubmit} prefill={member || {}} />
      <VaultOverlay phase={phase} />

      {/* TWEAKS */}
      <TweaksPanel>
        <TweakSection label="Page 1 — Hero" />
        <TweakSelect label="Hero headline" value={t.heroCopy}
          options={["Default", "Problem-led", "Offer-led", "Returning subscriber"]}
          onChange={v => setTweak("heroCopy", v)} />
        <TweakToggle label="Show ‘as seen in’ bar" value={t.showPress} onChange={v => setTweak("showPress", v)} />
        <TweakToggle label="Sticky mobile CTA" value={t.stickyCta} onChange={v => setTweak("stickyCta", v)} />

        <TweakSection label="Page 2 — Tracker preview" />
        <TweakToggle label="Override clock (preview day)" value={t.previewDayOverride} onChange={v => setTweak("previewDayOverride", v)} />
        <TweakSlider label="Preview day" value={t.previewDay} min={1} max={70} step={1} unit=" / 70"
          onChange={v => { setTweak("previewDay", v); setTweak("previewDayOverride", true); }} />
        <TweakButton label="View the tracker" onClick={() => { if (!member) { handleSubmit({ firstName: "Alex", email: "alex@email.com" }); } else { setPage("tracker"); window.scrollTo(0,0); } }} />
        <TweakButton label="Reset progress · back to start" onClick={resetProgress} />
      </TweaksPanel>
    </>
  );
}

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