// vote-app.jsx — 5to9 public A/B voting page.
// Ballots are real product forks from the decision log (window.FIVE_TO_NINE.BALLOTS).
const { useState, useEffect, useCallback, useRef } = React;
const DATA = window.FIVE_TO_NINE;
const BALLOTS = DATA.BALLOTS;
const { useTweaks, TweaksPanel, TweakSection, TweakRadio } = window;

const LS_KEY = '5to9_vote_v1';
const pad2 = n => String(n).padStart(2, '0');

const Check = () => (
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round"><path d="M20 6 9 17l-5-5" /></svg>
);
const Arrow = () => (
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14" /><path d="m12 5 7 7-7 7" /></svg>
);
const ChevL = () => (
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m15 18-6-6 6-6" /></svg>
);
const ChevR = () => (
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m9 18 6-6-6-6" /></svg>
);

// Anonymous, per-browser id so one person counts once per ballot. Not PII.
const VOTE_TOKEN = (() => {
  try {
    let t = localStorage.getItem('5to9_vote_token');
    if (!t) {
      t = (window.crypto && crypto.randomUUID)
        ? crypto.randomUUID()
        : 'a' + Math.random().toString(36).slice(2) + Date.now();
      localStorage.setItem('5to9_vote_token', t);
    }
    return t;
  } catch (e) { return 'anon'; }
})();

// Baked starting tallies — shown until the live store answers, and as the offline fallback.
const seedTallies = () => {
  const o = {};
  for (const b of BALLOTS) o[b.id] = { a: b.a.seed, b: b.b.seed };
  return o;
};

function pcts(t) {
  const total = t.a + t.b || 1;
  let a = Math.round((t.a / total) * 100);
  return { a, b: 100 - a, total };
}

// ---- option: a phone showing the real product screen for this choice -------
function PhoneOption({ side, opt, voted, vote, pct, onVote }) {
  const { MiniPhone, Scene } = window;
  const isPick = vote === side;
  const share = side === 'a' ? pct.a : pct.b;
  const cls = ['popt'];
  if (voted) cls.push('locked');
  if (voted && isPick) cls.push('picked');
  if (voted && !isPick) cls.push('dim');
  return (
    <div className={cls.join(' ')} role="button" tabIndex={voted ? -1 : 0}
         onClick={voted ? undefined : onVote}
         onKeyDown={voted ? undefined : (ev => { if (ev.key === 'Enter' || ev.key === ' ') { ev.preventDefault(); onVote(); } })}>
      <div className="popt-phone"><div className="popt-phone-inner"><MiniPhone><Scene id={opt.scene} /></MiniPhone></div></div>
      <div className="popt-cap">
        <span className="popt-letter">{side.toUpperCase()}{voted ? ` · ${share}%` : ''}</span>
        <span className="popt-label">{opt.label}</span>
        <span className="popt-blurb">{opt.blurb}</span>
        {voted && isPick && <span className="opt-you"><Check /> your pick</span>}
      </div>
    </div>
  );
}

// ---- a ballot --------------------------------------------------------------
function Ballot({ b, index, total, vote, tally, onVote, onNext, onBack, counting, layout }) {
  const voted = !!vote;
  const pct = pcts(tally || { a: b.a.seed, b: b.b.seed });
  const last = index === total - 1;
  return (
    <div className="anim" key={b.id}>
      <div className="prog">
        <span className="ix">{pad2(index + 1)} / {pad2(total)}</span>
        <span className="track"><i style={{ width: `${((index + 1) / total) * 100}%` }} /></span>
        <span className="kind">{b.kind}</span>
      </div>

      <h2 className="q">{b.question}</h2>
      {b.sub && <p className="sub">{b.sub}</p>}

      <div className={`opts ${layout}`}>
        <PhoneOption side="a" opt={b.a} voted={voted} vote={vote} pct={pct} onVote={() => onVote('a')} />
        <PhoneOption side="b" opt={b.b} voted={voted} vote={vote} pct={pct} onVote={() => onVote('b')} />
      </div>

      {voted && (
        <div className="splitbar anim">
          <i style={{ width: pct.a + '%', background: 'var(--accent)' }} />
          <i style={{ width: pct.b + '%', background: 'var(--accent-soft)' }} />
        </div>
      )}
      {voted && (
        <div className="tally anim">{pct.total.toLocaleString('en')} people have weighed in · based on {b.source}</div>
      )}

      <div className={`navbar ${counting ? 'counting' : ''}`}>
        {voted ? (
          <React.Fragment>
            <button className="navmini" disabled={index === 0} onClick={onBack} aria-label="Previous question"><ChevL /> back</button>
            <span className="cd"><i /></span>
            <button className="navmini next" onClick={onNext}>{last ? 'results' : 'next'} <ChevR /></button>
          </React.Fragment>
        ) : <div className="fine" style={{ width: '100%', textAlign: 'center' }}>Tap the screen you&rsquo;d want.</div>}
      </div>
    </div>
  );
}

// ---- intro -----------------------------------------------------------------
function Intro({ onStart }) {
  return (
    <div className="anim">
      <span className="eyebrow">Shape 5to9 · Amsterdam</span>
      <h1 className="lead">Two ways to build it. You pick.</h1>
      <p className="sub">5to9 helps you sort the hours after work, then close the app. We&rsquo;re still deciding how a few things should work — so here are the real forks. Tap the one you&rsquo;d want.</p>
      <div className="actions">
        <button className="btn btn-primary" onClick={onStart}>Start <Arrow /></button>
      </div>
      <div className="fine">{BALLOTS.length} quick either/ors · anonymous · about two minutes</div>
    </div>
  );
}

// ---- done ------------------------------------------------------------------
function Done({ votes, tallies, onRestart }) {
  const [email, setEmail] = useState('');
  const [consent, setConsent] = useState(false);
  const [company, setCompany] = useState(''); // honeypot — bots fill this
  const [status, setStatus] = useState('idle'); // idle | loading | done | error
  const [errMsg, setErrMsg] = useState('');
  const emailOk = /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email);
  const canSubmit = emailOk && consent && status !== 'loading';
  const submit = async e => {
    e.preventDefault();
    if (!canSubmit) return;
    setStatus('loading'); setErrMsg('');
    try {
      // Reuse the landing's waitlist route (Resend). Same-origin on vote.5to9.live.
      const res = await fetch('/api/waitlist', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email, consent, company }),
      });
      const data = await res.json().catch(() => ({}));
      if (res.ok) setStatus('done');
      else { setStatus('error'); setErrMsg((data && data.error) || 'Something went wrong. Try again in a moment.'); }
    } catch (err) { setStatus('error'); setErrMsg('Network hiccup. Try again in a moment.'); }
  };
  return (
    <div className="anim">
      <span className="eyebrow">That&rsquo;s everything</span>
      <h1 className="lead">Go enjoy your evening.</h1>
      <p className="sub">Your picks are in. Here&rsquo;s where the crowd landed on each — yours marked.</p>

      <div className="recap">
        {BALLOTS.map(b => {
          const vote = votes[b.id];
          const pct = pcts(tallies[b.id] || { a: b.a.seed, b: b.b.seed });
          return (
            <div className="recap-row" key={b.id}>
              <div className="rq">{b.question}</div>
              <div className="poll">
                {['a', 'b'].map(side => {
                  const share = side === 'a' ? pct.a : pct.b;
                  const mine = vote === side;
                  return (
                    <div key={side} className={`poll-opt ${mine ? 'mine' : ''}`}>
                      <span className="poll-fill" style={{ width: share + '%' }} />
                      <span className="poll-top">
                        <span className="poll-label">
                          {b[side].label}
                          {mine && <span className="poll-you"><Check /> your pick</span>}
                        </span>
                        <span className="poll-pct">{share}%</span>
                      </span>
                    </div>
                  );
                })}
              </div>
            </div>
          );
        })}
      </div>

      <div className="email">
        {status === 'done' ? (
          <div className="done-msg"><Check /> You&rsquo;re on the Amsterdam list. Now close the app.</div>
        ) : (
          <form onSubmit={submit} noValidate>
            <h3>Want first dibs on the beta?</h3>
            <p>5to9 opens in Amsterdam first. Leave an email and we&rsquo;ll tell you when it&rsquo;s your turn — nothing else.</p>
            <div className="field">
              <input type="email" placeholder="you@email.com" value={email} autoComplete="email"
                     onChange={e => setEmail(e.target.value)} />
              <input type="text" name="company" tabIndex={-1} autoComplete="off" aria-hidden
                     value={company} onChange={e => setCompany(e.target.value)}
                     style={{ position: 'absolute', left: '-9999px', width: 1, height: 1, opacity: 0 }} />
              <button className="btn btn-primary" type="submit" disabled={!canSubmit}
                      style={{ opacity: canSubmit ? 1 : 0.55, cursor: canSubmit ? 'pointer' : 'not-allowed' }}>
                {status === 'loading' ? 'Joining…' : 'Join'}
              </button>
            </div>
            <label className="consent">
              <input type="checkbox" checked={consent} onChange={e => setConsent(e.target.checked)} />
              <span>Add me to the 5to9 Amsterdam beta waitlist. I&rsquo;m OK with the odd email about the beta until it opens — nothing else.</span>
            </label>
            {status === 'error' && <div className="err" role="alert">{errMsg}</div>}
            <div className="opt-note">Optional. Skipping is fine — your votes already counted.</div>
          </form>
        )}
      </div>

      <a className="feedback" href="mailto:feedback@5to9.live?subject=5to9%20%E2%80%94%20a%20sharper%20idea">
        Got a better idea than either of these? <span>feedback@5to9.live</span>
      </a>

      <div className="actions center">
        <button className="btn btn-ghost" onClick={onRestart}>Start over</button>
      </div>
    </div>
  );
}

// ---- app -------------------------------------------------------------------
function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [state, setState] = useState(() => {
    try {
      const s = JSON.parse(localStorage.getItem(LS_KEY));
      if (s && typeof s === 'object') return { stage: s.stage ?? 'intro', votes: s.votes ?? {} };
    } catch (e) {}
    return { stage: 'intro', votes: {} };
  });

  useEffect(() => {
    try { localStorage.setItem(LS_KEY, JSON.stringify(state)); } catch (e) {}
  }, [state]);

  // Live tallies from /api/vote (Neon). Seeded first so the page renders instantly and
  // still works with no backend; the GET replaces seeds with the real counts.
  const [tallies, setTallies] = useState(seedTallies);
  useEffect(() => {
    let off = false;
    fetch('/api/vote')
      .then(r => (r.ok ? r.json() : null))
      .then(d => { if (!off && d && d.tallies) setTallies(prev => ({ ...prev, ...d.tallies })); })
      .catch(() => {});
    return () => { off = true; };
  }, []);

  const timers = useRef([]);
  const [pending, setPending] = useState(null); // index whose result is counting down to auto-advance
  const clearTimers = () => { timers.current.forEach(clearTimeout); timers.current = []; };
  useEffect(() => () => clearTimers(), []);

  const advance = idx => setState(s => ({ ...s, stage: idx + 1 >= BALLOTS.length ? 'done' : idx + 1 }));
  const start = () => setState(s => ({ ...s, stage: 0 }));
  const restart = () => { clearTimers(); setPending(null); setState({ stage: 'intro', votes: {} }); };
  const castVote = (idx, side) => {
    const b = BALLOTS[idx];
    setState(s => ({ ...s, votes: { ...s.votes, [b.id]: side } }));
    // Optimistic bump so the result feels instant; the POST response reconciles to truth.
    setTallies(prev => ({ ...prev, [b.id]: { ...prev[b.id], [side]: ((prev[b.id] && prev[b.id][side]) || 0) + 1 } }));
    fetch('/api/vote', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ ballotId: b.id, side, token: VOTE_TOKEN }),
    })
      .then(r => (r.ok ? r.json() : null))
      .then(d => { if (d && d.tallies && d.tallies[b.id]) setTallies(prev => ({ ...prev, [b.id]: d.tallies[b.id] })); })
      .catch(() => {});
    clearTimers();
    setPending(idx); // show the result, then glide on
    timers.current.push(setTimeout(() => { setPending(null); advance(idx); }, 1800));
  };
  const goNext = idx => { clearTimers(); setPending(null); advance(idx); };
  const goBack = idx => { clearTimers(); setPending(null); setState(s => ({ ...s, stage: Math.max(0, idx - 1) })); };

  let content;
  if (state.stage === 'intro') content = <Intro onStart={start} />;
  else if (state.stage === 'done') content = <Done votes={state.votes} tallies={tallies} onRestart={restart} />;
  else {
    const idx = state.stage;
    const b = BALLOTS[idx];
    content = (
      <Ballot b={b} index={idx} total={BALLOTS.length} vote={state.votes[b.id]}
        tally={tallies[b.id]}
        counting={pending === idx}
        onVote={side => castVote(idx, side)} onNext={() => goNext(idx)} onBack={() => goBack(idx)}
        layout={t.optionLayout} />
    );
  }

  return (
    <div className="stage" data-theme={t.theme}>
      <div className="vtop">
        <span className="wordmark">5<span className="to">to</span>9<span className="dot">.</span></span>
      </div>
      <div className="col">{content}</div>

      <TweaksPanel>
        <TweakSection label="Look" />
        <TweakRadio label="Theme" value={t.theme}
          options={[{ value: 'paper', label: 'Paper' }, { value: 'dusk', label: 'Dusk' }]}
          onChange={v => setTweak('theme', v)} />
        <TweakSection label="Ballot" />
        <TweakRadio label="Layout" value={t.optionLayout}
          options={[{ value: 'side', label: 'Side by side' }, { value: 'stacked', label: 'Stacked' }]}
          onChange={v => setTweak('optionLayout', v)} />
      </TweaksPanel>
    </div>
  );
}

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "theme": "paper",
  "optionLayout": "side"
}/*EDITMODE-END*/;

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