// Vanta station — the page. One responsive layout, three screens (today,
// review, settings), plain words. Every number on it comes from one payload,
// /vanta/station, and every write refetches that payload so the plan, the
// lists and the week board never disagree.

// ── Data ──────────────────────────────────────────────────────────────────
function useStation() {
  const [st, setSt] = React.useState(window.VANTA_STATION);
  const [status, setStatus] = React.useState(window.VANTA_STATION ? 'ready' : 'loading');
  React.useEffect(() => {
    const onStation = () => { setSt(window.VANTA_STATION); setStatus(window.VANTA_STATION ? 'ready' : 'down'); };
    let t = 0;
    const onChanged = () => { clearTimeout(t); t = setTimeout(() => vantaLoadStation(), 150); };
    const onVis = () => { if (document.visibilityState === 'visible') vantaLoadStation(); };
    window.addEventListener('vanta:station', onStation);
    window.addEventListener('vanta:changed', onChanged);
    document.addEventListener('visibilitychange', onVis);
    if (!window.VANTA_STATION) vantaLoadStation();
    const iv = setInterval(() => vantaLoadStation(), 5 * 60 * 1000);   // the plan follows the clock
    return () => {
      clearTimeout(t); clearInterval(iv);
      window.removeEventListener('vanta:station', onStation);
      window.removeEventListener('vanta:changed', onChanged);
      document.removeEventListener('visibilitychange', onVis);
    };
  }, []);
  return [st, status];
}

function useWide(bp = 900) {
  const [wide, setWide] = React.useState(window.innerWidth >= bp);
  React.useEffect(() => {
    const on = () => setWide(window.innerWidth >= bp);
    window.addEventListener('resize', on);
    return () => window.removeEventListener('resize', on);
  }, [bp]);
  return wide;
}

// ── Primitives ────────────────────────────────────────────────────────────
const toneColor = (P, t) => (t === 'attention' ? P.rust : t === 'good' ? P.accent : P.ink4);

// Drop optimistic overrides the server has caught up with, or that are old
// enough to be a lost write. `agrees(key, entry)` says the server matches.
function vPruneOverrides(map, agrees, maxAgeMs = 20000) {
  const now = Date.now();
  const out = {};
  for (const [k, v] of Object.entries(map)) {
    const entry = typeof v === 'object' && v !== null ? v : { value: true, at: v };
    if (agrees(k, entry)) continue;
    if (now - entry.at > maxAgeMs) continue;
    out[k] = entry;
  }
  return out;
}

function Card({ P, title, aside, children, style, id }) {
  const S = vStyles(P);
  return (
    <section id={id} style={{ background: P.tile, borderRadius: 20, padding: '16px 18px 18px', boxSizing: 'border-box', ...style }}>
      {(title || aside) && (
        <div style={{ display: 'flex', alignItems: 'baseline', gap: 12, marginBottom: 12 }}>
          {title && <span style={{ ...S.caps, color: P.ink3 }}>{title}</span>}
          <span style={{ flex: 1 }} />
          {aside}
        </div>
      )}
      {children}
    </section>
  );
}

// Seven dots for the last seven days. null = nothing logged (outline).
function Dots({ P, series, color, max }) {
  const m = max || Math.max(1, ...series.filter(v => v != null));
  return (
    <span style={{ display: 'inline-flex', gap: 4, alignItems: 'center' }} aria-hidden="true">
      {series.map((v, i) => (
        <span key={i} style={{
          width: 8, height: 8, borderRadius: '50%',
          background: v == null || v === 0 ? 'transparent' : color,
          opacity: v == null || v === 0 ? 1 : Math.max(0.25, Math.min(1, v / m)),
          boxShadow: v == null ? `inset 0 0 0 1px ${P.ink4}` : v === 0 ? `inset 0 0 0 1px ${P.rule}` : 'none',
        }} />
      ))}
    </span>
  );
}

function TextButton({ P, children, onClick, strong = false, style }) {
  return (
    <button onClick={onClick} className="g-chip" style={{
      appearance: 'none', border: 0, cursor: 'pointer', fontFamily: 'inherit',
      padding: strong ? '8px 14px' : '6px 10px', borderRadius: 999, fontSize: 12, fontWeight: 500,
      background: strong ? P.ink : P.chip, color: strong ? P.bg : P.ink2, ...style,
    }}>{children}</button>
  );
}

function Empty({ P, children }) {
  return <div style={{ fontSize: 12.5, color: P.ink3, padding: '4px 0' }}>{children}</div>;
}

const inputStyle = (P) => ({
  appearance: 'none', border: 0, outline: 'none', fontFamily: 'inherit', fontSize: 13,
  background: P.bg2, color: P.ink, borderRadius: 12, padding: '9px 12px', boxSizing: 'border-box', width: '100%',
});

// ── Header ────────────────────────────────────────────────────────────────
function StationHeader({ P, screen, onScreen, st }) {
  const S = vStyles(P);
  const wide = useWide(640);
  const nav = [['today', 'Today'], ['review', 'Review'], ['settings', 'Settings']];
  const w = st?.weather;
  return (
    <header style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 10, marginBottom: 18 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 9 }}>
        <VantaM P={P} size={28} fontSize={24} />
        <span style={{ ...S.serif, fontSize: 22, color: P.ink }}>vanta</span>
      </div>
      <nav style={{ display: 'flex', gap: 2, padding: 3, background: P.chip, borderRadius: 999, marginLeft: wide ? 14 : 0 }}>
        {nav.map(([k, label]) => (
          <button key={k} onClick={() => onScreen(k)} aria-current={screen === k ? 'page' : undefined} style={{
            appearance: 'none', border: 0, cursor: 'pointer', fontFamily: 'inherit', fontSize: 12.5,
            padding: '6px 14px', borderRadius: 999,
            background: screen === k ? P.tile : 'transparent', color: screen === k ? P.ink : P.ink3,
            fontWeight: screen === k ? 500 : 400, transition: 'background .15s',
          }}>{label}</button>
        ))}
      </nav>
      <span style={{ flex: 1 }} />
      <span style={{ ...S.caps, color: P.ink3 }}>
        {fmtDate(new Date())}{w && typeof w.temp === 'number' ? ` · ${w.temp}° ${w.sky || ''}` : ''}
      </span>
      <a href="/vantage" style={{ ...S.caps, color: P.ink2, textDecoration: 'none', padding: '6px 12px', borderRadius: 999, background: P.chip }}>vantage ↗</a>
    </header>
  );
}

// ── Work on next ──────────────────────────────────────────────────────────
function PlanCard({ P, st }) {
  const S = vStyles(P);
  const plan = st.plan;
  // Optimistic ticks are kept until the server agrees (the item leaves the
  // plan) or they go stale; a refetch that raced the write must not undo them.
  const [done, setDone] = React.useState({});
  React.useEffect(() => {
    setDone(d => vPruneOverrides(d, (k) => !plan.items.some(it => it.key === k)));
  }, [plan]);
  const mark = (key) => setDone(d => ({ ...d, [key]: Date.now() }));

  const act = (item) => {
    const a = item.act;
    if (!a) return;
    if (a.type === 'habit') { mark(item.key); vantaPost('/vanta/habit', { habit: a.key, completed: true }); }
    else if (a.type === 'chore') { mark(item.key); vantaPost('/vanta/chore/done', { id: a.id }); }
    else if (a.type === 'task') { mark(item.key); vantaPost('/vanta/task/toggle', { id: a.id, done: true }); }
    else if (a.type === 'link') { window.open(a.href, '_blank', 'noreferrer'); }
    else if (a.type === 'reading') { const el = document.getElementById('reading-log'); if (el) { el.scrollIntoView({ behavior: 'smooth', block: 'center' }); el.focus({ preventScroll: true }); } }
  };
  const kindColor = { deadline: P.rust, brief: P.rust, task: P.sky, study: P.rust, apps: P.rust, pitch: P.rust, habit: P.accent, chore: P.sky, hobby: P.accent, reading: P.accent };

  return (
    <Card P={P} style={{ background: P.tileWarm }}>
      <div style={{ ...S.caps, color: P.rust, marginBottom: 8 }}>work on next</div>
      <h1 style={{ ...S.serif, fontSize: 24, lineHeight: 1.15, margin: '0 0 14px', color: P.ink, textWrap: 'pretty' }}>{plan.headline}</h1>
      {plan.items.length === 0 ? (
        <Empty P={P}>Nothing is asking for you right now. Add a task below, or get ahead on the week.</Empty>
      ) : (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
          {plan.items.map((it, i) => {
            const checkable = it.act && ['habit', 'chore', 'task'].includes(it.act.type);
            const isDone = !!done[it.key];
            return (
              <div key={it.key} className="g-rise" style={{ animationDelay: `${i * 40}ms`, display: 'grid', gridTemplateColumns: '24px 1fr auto', gap: 12, alignItems: 'center', padding: '7px 0', borderTop: i ? `1px solid ${P.rule}` : 'none' }}>
                {checkable ? (
                  <button onClick={() => !isDone && act(it)} aria-label={`done: ${it.label}`} style={{ appearance: 'none', border: 0, background: 'transparent', cursor: 'pointer', padding: 0, display: 'grid', placeItems: 'center' }}>
                    <VCheck P={P} done={isDone} color={kindColor[it.kind] || P.accent} size={20} />
                  </button>
                ) : (
                  <span style={{ width: 8, height: 8, borderRadius: '50%', background: kindColor[it.kind] || P.ink4, justifySelf: 'center' }} />
                )}
                <span style={{ minWidth: 0 }}>
                  <span style={{ display: 'block', fontSize: 15, color: isDone ? P.ink4 : P.ink, textDecoration: isDone ? 'line-through' : 'none', transition: 'color .15s' }}>{it.label}</span>
                  <span style={{ display: 'block', fontSize: 11.5, color: P.ink3, marginTop: 1 }}>{it.why}</span>
                </span>
                {it.act && it.act.type === 'link' && <TextButton P={P} onClick={() => act(it)}>open ↗</TextButton>}
                {it.act && it.act.type === 'reading' && <TextButton P={P} onClick={() => act(it)}>log pages</TextButton>}
                {!it.act && <span style={{ ...S.caps, color: P.ink4 }}>{it.kind === 'brief' ? 'telegram' : it.kind === 'study' ? '/study' : it.kind === 'apps' ? '/apply' : it.kind === 'pitch' ? '/pitch' : ''}</span>}
              </div>
            );
          })}
        </div>
      )}
      {plan.more > 0 && <div style={{ ...S.caps, color: P.ink4, marginTop: 10 }}>+ {plan.more} more in the lists below</div>}
    </Card>
  );
}

// ── Today ─────────────────────────────────────────────────────────────────
function CheckRow({ P, done, label, sub, right, color, onClick, muted }) {
  return (
    <button onClick={onClick} style={{
      appearance: 'none', border: 0, background: 'transparent', cursor: onClick ? 'pointer' : 'default', fontFamily: 'inherit', textAlign: 'left',
      display: 'grid', gridTemplateColumns: '20px 1fr auto', gap: 10, alignItems: 'center', minHeight: 30, padding: 0, width: '100%',
    }}>
      <VCheck P={P} done={done} color={color} size={18} />
      <span style={{ minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontSize: 13.5, color: done || muted ? P.ink4 : P.ink, textDecoration: done ? 'line-through' : 'none' }}>
        {label}{sub && <span style={{ color: P.ink4, fontSize: 11.5 }}> · {sub}</span>}
      </span>
      <span style={{ fontSize: 11.5, color: P.ink4 }}>{right}</span>
    </button>
  );
}

function TodayCard({ P, st }) {
  const S = vStyles(P);
  const wide = useWide(560);
  const [tasks, setTasks] = React.useState(st.tasks ? st.tasks.map(vCloneTask) : null);
  React.useEffect(() => { setTasks(st.tasks ? st.tasks.map(vCloneTask) : null); }, [st.tasks]);
  // Optimistic state: an override lives until the server agrees with it or
  // it is 20s old (a lost write). A refetch that raced the write cannot
  // flip a fresh tick back — that was the "checkbox fights me" glitch.
  const [habitOverride, setHabitOverride] = React.useState({});
  const [choreDone, setChoreDone] = React.useState({});
  React.useEffect(() => {
    setHabitOverride(o => vPruneOverrides(o, (k, v) => { const h = (st.habits || []).find(x => x.key === k); return !h || h.done === v.value; }));
  }, [st.habits]);
  React.useEffect(() => {
    setChoreDone(o => vPruneOverrides(o, (k) => { const c = (st.chores || []).find(x => String(x.id) === k); return !c || !c.due; }));
  }, [st.chores]);
  const habitDone = (h) => (habitOverride[h.key] ? habitOverride[h.key].value : h.done);

  const toggleHabit = (h) => {
    const next = !habitDone(h);
    setHabitOverride(o => ({ ...o, [h.key]: { value: next, at: Date.now() } }));
    vantaPost('/vanta/habit', { habit: h.key, completed: next });
  };
  const doChore = (c) => { setChoreDone(d => ({ ...d, [c.id]: { value: true, at: Date.now() } })); vantaPost('/vanta/chore/done', { id: c.id }); };
  const choresDue = (st.chores || []).filter(c => c.due);
  const events = (st.events || []).filter(e => e.when === st.today);
  const fmtT = (iso) => { const d = new Date(iso); return isNaN(d) ? '' : d.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' }).toLowerCase(); };
  const habitsDone = (st.habits || []).filter(habitDone).length;

  return (
    <Card P={P} title="today" aside={<span style={{ ...S.caps, color: P.ink4 }}>{tasks ? `${tasks.filter(t => t.done).length} / ${tasks.length} tasks` : ''}</span>}>
      {tasks ? <VTaskList P={P} tasks={tasks} setTasks={setTasks} /> : <Empty P={P}>tasks unreachable right now.</Empty>}

      {st.pm && st.pm.next && st.pm.next.length > 0 && (
        <div style={{ marginTop: 14 }}>
          <div style={{ ...S.caps, color: P.ink4, marginBottom: 6 }}>pm track · due</div>
          {st.pm.next.map(t => (
            <div key={t.key} style={{ padding: '4px 0' }}>
              <div style={{ display: 'flex', gap: 10, alignItems: 'baseline' }}>
                <span style={{ fontSize: 13.5, color: P.ink }}>{t.label}</span>
                <span style={{ flex: 1 }} />
                <span style={{ ...S.caps, color: P.ink4, textAlign: 'right' }}>{t.source}</span>
              </div>
              <div style={{ fontSize: 11.5, color: P.ink3 }}>{t.reasons.filter(r => !r.startsWith('tier')).join(' · ') || 'due'}</div>
            </div>
          ))}
        </div>
      )}

      <div style={{ height: 1, background: P.rule, margin: '14px 0' }} />
      <div style={{ display: 'flex', alignItems: 'baseline', gap: 10, marginBottom: 6 }}>
        <span style={{ ...S.caps, color: P.ink4 }}>habits</span>
        <span style={{ flex: 1 }} />
        {st.habits && <span style={{ ...S.caps, color: P.ink4 }}>{habitsDone} / {st.habits.length}{st.streak ? ` · ${st.streak}-day streak` : ''}</span>}
      </div>
      {st.habits ? (
        <div style={{ display: 'grid', gridTemplateColumns: wide ? 'repeat(2, minmax(0, 1fr))' : '1fr', columnGap: 18, rowGap: 2 }}>
          {st.habits.map(h => (
            <CheckRow key={h.key} P={P} done={habitDone(h)} label={h.label} right={h.streak > 0 ? `${h.streak}d` : ''} color={P.accent} onClick={() => toggleHabit(h)} />
          ))}
        </div>
      ) : <Empty P={P}>habits unreachable right now.</Empty>}

      {(choresDue.length > 0 || events.length > 0) && <div style={{ height: 1, background: P.rule, margin: '14px 0' }} />}
      {choresDue.length > 0 && (
        <div>
          <div style={{ ...S.caps, color: P.ink4, marginBottom: 6 }}>chores due</div>
          {choresDue.map(c => (
            <CheckRow key={c.id} P={P} done={!!choreDone[c.id]} label={c.label} right={`every ${c.cadenceDays}d`} color={P.sky} onClick={() => !choreDone[c.id] && doChore(c)} />
          ))}
        </div>
      )}
      {events.length > 0 && (
        <div style={{ marginTop: choresDue.length ? 12 : 0 }}>
          <div style={{ ...S.caps, color: P.ink4, marginBottom: 6 }}>on the calendar</div>
          {events.map((e, i) => (
            <div key={i} style={{ display: 'flex', gap: 10, alignItems: 'baseline', fontSize: 13, padding: '3px 0' }}>
              <span style={{ ...S.num, color: P.ink3, minWidth: 64 }}>{e.allDay ? 'all day' : fmtT(e.start)}</span>
              <span style={{ color: P.ink, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{e.title}</span>
              {e.location && <span style={{ color: P.ink4, fontSize: 11.5 }}>{e.location}</span>}
            </div>
          ))}
        </div>
      )}
    </Card>
  );
}

// ── Reading ───────────────────────────────────────────────────────────────
function BookForm({ P, onDone }) {
  const [title, setTitle] = React.useState('');
  const [author, setAuthor] = React.useState('');
  const [pages, setPages] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const submit = () => {
    const t = title.trim();
    if (!t || busy) return;
    setBusy(true);
    const totalPages = parseInt(pages, 10);
    vantaPost('/vanta/book', { title: t, author: author.trim() || undefined, totalPages: Number.isInteger(totalPages) && totalPages > 0 ? totalPages : undefined })
      .then(() => { setTitle(''); setAuthor(''); setPages(''); setBusy(false); onDone && onDone(); })
      .catch(() => setBusy(false));
  };
  return (
    <div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 2fr) minmax(0, 1.4fr) 80px auto', gap: 8, alignItems: 'center' }}>
      <input value={title} onChange={e => setTitle(e.target.value)} placeholder="title" style={inputStyle(P)} onKeyDown={e => e.key === 'Enter' && submit()} />
      <input value={author} onChange={e => setAuthor(e.target.value)} placeholder="author" style={inputStyle(P)} onKeyDown={e => e.key === 'Enter' && submit()} />
      <input value={pages} onChange={e => setPages(e.target.value)} placeholder="pages" inputMode="numeric" style={{ ...inputStyle(P), textAlign: 'center' }} onKeyDown={e => e.key === 'Enter' && submit()} />
      <TextButton P={P} strong onClick={submit}>start</TextButton>
    </div>
  );
}

function ReadingCard({ P, st }) {
  const S = vStyles(P);
  const book = st.book;
  const [page, setPage] = React.useState(book ? String(book.currentPage) : '');
  const [saved, setSaved] = React.useState(null);
  const [finishing, setFinishing] = React.useState(false);
  React.useEffect(() => { setPage(book ? String(book.currentPage) : ''); setFinishing(false); }, [book && book.id, book && book.currentPage]);

  const log = () => {
    if (!book) return;
    const p = parseInt(page, 10);
    if (!Number.isInteger(p) || p < 0) return;
    vantaPost('/vanta/book/progress', { id: book.id, page: p }).then(() => setSaved(p));
  };
  const bump = (n) => setPage(String(Math.max(0, (parseInt(page, 10) || 0) + n)));
  const finish = () => { if (!book) return; setFinishing(true); vantaPost('/vanta/book/finish', { id: book.id }); };
  const pct = book && book.totalPages ? Math.min(1, book.currentPage / book.totalPages) : null;

  return (
    <Card P={P} title="reading" aside={book && <span style={{ ...S.caps, color: P.ink4 }}>{(st.readingDays || []).slice(-7).reduce((a, r) => a + r.pages, 0)} pages this week</span>}>
      {book ? (
        <React.Fragment>
          <div style={{ display: 'flex', alignItems: 'baseline', gap: 10, flexWrap: 'wrap' }}>
            <span style={{ ...S.serif, fontSize: 20, color: P.ink }}>{book.title}</span>
            {book.author && <span style={{ fontSize: 12.5, color: P.ink3 }}>{book.author}</span>}
            <span style={{ flex: 1 }} />
            <span style={{ ...S.num, fontSize: 12.5, color: P.ink2 }}>p. {book.currentPage}{book.totalPages ? ` of ${book.totalPages}` : ''}</span>
          </div>
          {pct != null && <div style={{ marginTop: 8 }}><ProgBar pct={pct} h={3} color={P.accent} track={P.rule} /></div>}
          <div style={{ display: 'flex', gap: 8, alignItems: 'center', marginTop: 12, flexWrap: 'wrap' }}>
            <span style={{ fontSize: 12.5, color: P.ink3 }}>stopped at page</span>
            <input id="reading-log" value={page} onChange={e => setPage(e.target.value)} inputMode="numeric" aria-label="stopped at page"
              onKeyDown={e => e.key === 'Enter' && log()} style={{ ...inputStyle(P), width: 84, textAlign: 'center' }} />
            <TextButton P={P} onClick={() => bump(10)}>+10</TextButton>
            <TextButton P={P} onClick={() => bump(25)}>+25</TextButton>
            <TextButton P={P} strong onClick={log}>log</TextButton>
            <span style={{ flex: 1 }} />
            {!finishing ? <TextButton P={P} onClick={finish}>finished</TextButton> : <span style={{ ...S.caps, color: P.accent }}>finished ✓</span>}
          </div>
          {saved != null && saved === book.currentPage && <div style={{ ...S.caps, color: P.accent, marginTop: 8 }}>logged</div>}
        </React.Fragment>
      ) : (
        <React.Fragment>
          <Empty P={P}>No book open. Start one and log the page you stop at; the week board and the review keep the rest.</Empty>
          <div style={{ marginTop: 10 }}><BookForm P={P} /></div>
        </React.Fragment>
      )}
    </Card>
  );
}

// ── This week ─────────────────────────────────────────────────────────────
function WeekCard({ P, st }) {
  const S = vStyles(P);
  const rows = st.board || [];
  return (
    <Card P={P} title="this week" aside={<span style={{ ...S.caps, color: P.ink4 }}>vs pace by {['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'][new Date(st.today + 'T12:00:00').getDay()]}</span>}>
      {rows.length === 0 ? <Empty P={P}>nothing to pace yet.</Empty> : (
        <div style={{ display: 'flex', flexDirection: 'column' }}>
          {rows.map((r, i) => (
            <div key={r.key} style={{ display: 'grid', gridTemplateColumns: '8px 1fr auto', gap: 12, alignItems: 'center', padding: '8px 0', borderTop: i ? `1px solid ${P.rule}` : 'none' }}>
              <span style={{ width: 8, height: 8, borderRadius: '50%', background: toneColor(P, r.tone) }} title={r.tone} />
              <span style={{ minWidth: 0 }}>
                <span style={{ display: 'block', fontSize: 13.5, color: P.ink }}>{r.label}</span>
                <span style={{ display: 'block', fontSize: 11.5, color: P.ink3 }}>{r.sub}</span>
              </span>
              <span style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 4 }}>
                <span style={{ ...S.num, fontSize: 15, fontWeight: 500, color: r.tone === 'attention' ? P.rust : P.ink }}>{r.value}</span>
                {r.series && <Dots P={P} series={r.series} color={toneColor(P, r.tone === 'calm' ? 'good' : r.tone)} max={r.key === 'habits' || r.key === 'sleep' ? 1 : null} />}
              </span>
            </div>
          ))}
        </div>
      )}
    </Card>
  );
}

// ── Coming up ─────────────────────────────────────────────────────────────
function ComingCard({ P, st }) {
  const S = vStyles(P);
  const today = st.today;
  const plus = (iso, n) => { const d = new Date(iso + 'T12:00:00Z'); d.setUTCDate(d.getUTCDate() + n); return d.toISOString().slice(0, 10); };
  const limit = plus(today, 7);
  const items = [];
  for (const d of st.deadlines || []) if (d.due && d.due > today && d.due <= plus(today, 14)) items.push({ when: d.due, label: d.title, sub: d.course || 'deadline', color: P.rust, href: d.url });
  for (const e of st.events || []) if (e.when && e.when > today && e.when <= limit) items.push({ when: e.when, label: e.title, sub: e.location || e.feed || 'calendar', color: P.sky });
  for (const c of st.chores || []) {
    if (c.due || !c.lastDone) continue;
    const next = plus(c.lastDone, c.cadenceDays);
    if (next > today && next <= limit) items.push({ when: next, label: c.label, sub: 'chore', color: P.ink4 });
  }
  items.sort((a, b) => (a.when < b.when ? -1 : a.when > b.when ? 1 : 0));
  return (
    <Card P={P} title="coming up" aside={<span style={{ ...S.caps, color: P.ink4 }}>next 7 days · deadlines to 14</span>}>
      {items.length === 0 ? <Empty P={P}>nothing scheduled.</Empty> : items.slice(0, 10).map((it, i) => (
        <div key={i} style={{ display: 'grid', gridTemplateColumns: '84px 1fr', gap: 10, alignItems: 'baseline', padding: '6px 0', borderTop: i ? `1px solid ${P.rule}` : 'none' }}>
          <span style={{ ...S.num, fontSize: 11.5, color: P.ink3 }}>{vantaDueLabel(it.when)}</span>
          <span style={{ minWidth: 0 }}>
            {it.href ? <a href={it.href} target="_blank" rel="noreferrer" style={{ color: P.ink, textDecoration: 'none', fontSize: 13.5 }}>{it.label} ↗</a>
              : <span style={{ fontSize: 13.5, color: P.ink }}>{it.label}</span>}
            <span style={{ fontSize: 11.5, color: it.color === P.ink4 ? P.ink4 : it.color, marginLeft: 8 }}>{it.sub}</span>
          </span>
        </div>
      ))}
    </Card>
  );
}

// ── Settings ──────────────────────────────────────────────────────────────
function SettingsScreen({ P, st, store, update }) {
  const S = vStyles(P);
  const [label, setLabel] = React.useState('');
  const [cadence, setCadence] = React.useState('7');
  const addChore = () => {
    const l = label.trim(); const cd = parseInt(cadence, 10);
    if (!l || !Number.isInteger(cd) || cd < 1) return;
    vantaPost('/vanta/chore', { label: l, cadenceDays: cd }).then(() => { setLabel(''); });
  };
  const sources = st?.sources || [];
  const hobbies = st?.hobbies || [];
  return (
    <div style={{ display: 'grid', gap: 14 }}>
      <Card P={P} title="palette">
        <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
          {GARDEN_PALETTE_ORDER.map(k => {
            const Q = GARDEN_PALETTES[k];
            const on = store.palette === k;
            return (
              <button key={k} onClick={() => update({ palette: k })} aria-pressed={on} style={{
                appearance: 'none', cursor: 'pointer', fontFamily: 'inherit', fontSize: 12.5,
                border: `1.5px solid ${on ? P.ink : P.rule}`, borderRadius: 14, padding: '10px 14px', background: Q.bg, color: Q.ink,
                display: 'flex', alignItems: 'center', gap: 10,
              }}>
                <span style={{ display: 'flex', gap: 3 }}>{[Q.accent, Q.rust, Q.sky].map((c, i) => <span key={i} style={{ width: 10, height: 10, borderRadius: '50%', background: c }} />)}</span>
                {k}
              </button>
            );
          })}
        </div>
      </Card>

      <Card P={P} title="chores" aside={<span style={{ ...S.caps, color: P.ink4 }}>done from the today card · history on review</span>}>
        {(st?.chores || []).length === 0 && <Empty P={P}>no chores yet.</Empty>}
        {(st?.chores || []).map(c => (
          <div key={c.id} style={{ display: 'grid', gridTemplateColumns: '1fr auto auto', gap: 12, alignItems: 'center', padding: '6px 0', borderBottom: `1px solid ${P.rule}` }}>
            <span style={{ fontSize: 13.5, color: P.ink }}>{c.label}<span style={{ color: P.ink4, fontSize: 11.5 }}> · every {c.cadenceDays}d{c.lastDone ? ` · last ${vantaDueLabel(c.lastDone)}` : ' · never done'}</span></span>
            <span style={{ ...S.caps, color: c.due ? P.rust : P.ink4 }}>{c.due ? 'due' : 'ok'}</span>
            <TextButton P={P} onClick={() => vantaFetchDelete(`/vanta/chore/${c.id}`)}>remove</TextButton>
          </div>
        ))}
        <div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) 90px auto', gap: 8, alignItems: 'center', marginTop: 10 }}>
          <input value={label} onChange={e => setLabel(e.target.value)} placeholder="new chore" style={inputStyle(P)} onKeyDown={e => e.key === 'Enter' && addChore()} />
          <input value={cadence} onChange={e => setCadence(e.target.value)} inputMode="numeric" aria-label="every N days" style={{ ...inputStyle(P), textAlign: 'center' }} />
          <TextButton P={P} strong onClick={addChore}>add</TextButton>
        </div>
        <div style={{ ...S.caps, color: P.ink4, marginTop: 6 }}>cadence in days</div>
      </Card>

      <Card P={P} title="reading" aside={st?.book ? <span style={{ ...S.caps, color: P.ink4 }}>finish the open book from the today card first</span> : null}>
        {st?.book ? <Empty P={P}>Open: {st.book.title}. Start the next one after you mark it finished.</Empty> : <BookForm P={P} />}
      </Card>

      <Card P={P} title="hobbies" aside={<span style={{ ...S.caps, color: P.ink4 }}>days per week · goals set in server config</span>}>
        {hobbies.map(h => (
          <div key={h.key} style={{ display: 'flex', gap: 10, alignItems: 'baseline', padding: '4px 0', fontSize: 13.5 }}>
            <span style={{ color: P.ink }}>{h.label}</span><span style={{ flex: 1 }} /><span style={{ ...S.num, color: P.ink3 }}>goal {h.goal} / wk</span>
          </div>
        ))}
        {hobbies.length === 0 && <Empty P={P}>none configured.</Empty>}
      </Card>

      <Card P={P} title="sources">
        {sources.map(s => (
          <div key={s.key} style={{ display: 'grid', gridTemplateColumns: '1fr auto', gap: 12, alignItems: 'center', padding: '7px 0', borderBottom: `1px solid ${P.rule}` }}>
            <span><span style={{ display: 'block', fontSize: 13.5, color: P.ink }}>{s.name}</span><span style={{ display: 'block', fontSize: 11.5, color: P.ink3 }}>{s.desc}</span></span>
            <span style={{ ...S.caps, color: s.linked ? P.accent : P.ink4 }}>{s.linked ? 'linked' : 'quiet'}</span>
          </div>
        ))}
        <p style={{ fontSize: 12, color: P.ink4, margin: '12px 0 0', lineHeight: 1.5 }}>
          Study hours, applications and pitches are logged on Telegram (/study, /apply, /pitch); the market brief and its review run there too. This page reads the same tables.
        </p>
      </Card>
    </div>
  );
}

// ── Page ──────────────────────────────────────────────────────────────────
function VantaStation() {
  const [store, update] = useVantaStore();
  const P = GARDEN_PALETTES[store.palette] || GARDEN_PALETTES.dusk;
  const S = vStyles(P);
  const [screen, setScreen] = React.useState(() => (location.hash === '#review' ? 'review' : location.hash === '#settings' ? 'settings' : 'today'));
  const go = (k) => { setScreen(k); try { history.replaceState(null, '', k === 'today' ? location.pathname : `#${k}`); } catch {} window.scrollTo({ top: 0 }); };
  const [st, status] = useStation();
  const wide = useWide(900);
  const [notice, setNotice] = React.useState(null);
  React.useEffect(() => {
    let t = 0;
    const on = (e) => { setNotice(`a change didn't save (${e.detail?.why || 'unknown'}) — try again`); clearTimeout(t); t = setTimeout(() => setNotice(null), 7000); };
    window.addEventListener('vanta:error', on);
    return () => { clearTimeout(t); window.removeEventListener('vanta:error', on); };
  }, []);

  let riseIdx = 0;
  const rise = (extra = {}) => ({ className: 'g-rise', style: { animationDelay: `${(riseIdx++) * 60}ms`, ...extra } });

  return (
    <div style={{
      minHeight: '100%', background: `radial-gradient(1000px 600px at 30% -10%, ${P.bg2}, ${P.bg} 60%)`, color: P.ink,
      fontFamily: '"Bricolage Grotesque", ui-sans-serif, system-ui, sans-serif', fontSize: 13, lineHeight: 1.45,
      padding: wide ? '20px 28px 48px' : '14px 16px 40px', boxSizing: 'border-box',
    }}>
      <div style={{ maxWidth: 1200, margin: '0 auto' }}>
        <div {...rise()}><StationHeader P={P} screen={screen} onScreen={go} st={st} /></div>
        {notice && <div role="status" style={{ background: P.rust, color: P.bg, borderRadius: 12, padding: '8px 14px', fontSize: 12.5, marginBottom: 14 }}>{notice}</div>}

        {screen === 'today' && status === 'loading' && <Empty P={P}>reading the day…</Empty>}
        {screen === 'today' && status === 'down' && (
          <Card P={P}><div style={{ fontSize: 13.5, color: P.ink2 }}>The server is unreachable, so there is nothing honest to show. Try again in a moment.</div></Card>
        )}
        {screen === 'today' && st && (
          <div style={{ display: 'grid', gap: 14 }}>
            <div {...rise()}><PlanCard P={P} st={st} /></div>
            <div style={{ display: 'grid', gridTemplateColumns: wide ? 'minmax(0, 7fr) minmax(0, 5fr)' : '1fr', gap: 14, alignItems: 'start' }}>
              <div style={{ display: 'grid', gap: 14 }}>
                <div {...rise()}><TodayCard P={P} st={st} /></div>
                <div {...rise()}><ReadingCard P={P} st={st} /></div>
              </div>
              <div style={{ display: 'grid', gap: 14 }}>
                <div {...rise()}><WeekCard P={P} st={st} /></div>
                <div {...rise()}><ComingCard P={P} st={st} /></div>
              </div>
            </div>
          </div>
        )}

        {screen === 'review' && <div {...rise()}><VantaReview P={P} /></div>}
        {screen === 'settings' && <div {...rise()}><SettingsScreen P={P} st={st} store={store} update={update} /></div>}

        <div style={{ ...S.caps, color: P.ink4, marginTop: 28, display: 'flex', gap: 14, flexWrap: 'wrap' }}>
          <span>vanta · you</span>
          <a href="/vantage" style={{ color: P.ink4, textDecoration: 'none' }}>vantage · markets ↗</a>
          <a href="/agents" style={{ color: P.ink4, textDecoration: 'none' }}>agents ↗</a>
          {st && <span style={{ marginLeft: 'auto' }}>as of {new Date().toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' }).toLowerCase()}</span>}
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { VantaStation, Card, Dots, TextButton, Empty, inputStyle, toneColor, useWide });
