/* Dump — today.jsx: daily digest + Done archive
   Redesigned (2026-07-01): ONE urgency surface. The Focus hero ("Right now")
   is the only thing above the fold, chosen priority-first (1b). The 2-minute
   list and quick wins collapse into disclosures below (1a). An optional
   warm-up (1c) offers a quick-clear that hands straight back to the one thing. */

/* ---------- priority ladder: what earns the focus card ---------- */
function orderByPriority(list) {
  return list.slice().sort((a, b) => {
    const hiA = a.level === 'high' || a.priority ? 1 : 0;
    const hiB = b.level === 'high' || b.priority ? 1 : 0;
    if (hiB !== hiA) return hiB - hiA;
    const dueA = a.dueDate === DumpUtil.todayISO() ? 1 : 0;
    const dueB = b.dueDate === DumpUtil.todayISO() ? 1 : 0;
    if (dueB !== dueA) return dueB - dueA;
    const actA = (a.minutes && a.big !== true) ? 1 : 0;
    const actB = (b.minutes && b.big !== true) ? 1 : 0;
    if (actB !== actA) return actB - actA;
    return new Date(a.createdAt) - new Date(b.createdAt);
  });
}

/* ============================================================
   Today (redesign, 2026-07-02). One job on open: what do I do now —
   with the least overwhelm and a visible sense of progress.
   · Capture bar first (get the thought out).
   · "Today's shape" ribbon (events + now; messages → one inbox chip).
   · Left = ACT: the one focus task (#1) + what's also on today.
   · Right = MOMENTUM: quick wins (≤10 min, capped at 3), every-day
     routines, and the celebratory "Done today" count.
   Model: hybrid — Today is pre-filled from priority (focus.ids), and the
   user tweaks it via Add / remove. Priority is just a flag, not a 2nd
   commit mechanism. Start = begin (focus session); Done = complete.
   ============================================================ */
function TodaySection() {
  const state = useDumpStore();
  const { tasks, focus, suggestions, google, archive } = state;
  const [rnIdx, setRnIdx] = useState(0);
  const [addOpen, setAddOpen] = useState(false);
  const [quickShown, setQuickShown] = useState(3);
  const [cap, setCap] = useState('');
  const [capBusy, setCapBusy] = useState(false);
  const [capMsg, setCapMsg] = useState(false);
  const capRef = useRef(null);
  const todayIso = DumpUtil.todayISO();

  const active = tasks.filter((t) => isActiveTask(t) && !t.trackDaily);
  const dailyTasks = tasks.filter((t) => t.trackDaily && t.recurrence && DumpAPI.dailyScheduledOn(t, todayIso));
  const doneToday = archive.filter((t) => t.doneAt && t.doneAt.slice(0, 10) === todayIso);
  const pendingSugs = suggestions.filter((s) => !s.dismissed);

  const isHigh = (t) => t.level === 'high' || t.priority;
  const notTwoMin = (t) => !(t.minutes && t.minutes <= 2);

  // ---- hybrid pre-fill: seed Today from priority once per day, never override ----
  useEffect(() => {
    const f = DumpStore.getState().focus;
    if (f.date !== todayIso || f.seededDate === todayIso) return;
    if ((f.ids || []).length === 0) {
      const seed = orderByPriority(active.filter(notTwoMin)).slice(0, 3).map((t) => t.id);
      DumpStore.set((s) => ({ focus: { ...s.focus, ids: seed, seededDate: todayIso } }));
    } else {
      DumpStore.set((s) => ({ focus: { ...s.focus, seededDate: todayIso } }));
    }
  }, []);

  // committed set = the "today list"; candidates = the rest of the board
  const todayList = orderByPriority(active.filter((t) => focus.ids.includes(t.id)));
  const candidates = orderByPriority(active.filter((t) => !focus.ids.includes(t.id) && notTwoMin(t)));
  const idx = Math.min(rnIdx, Math.max(0, todayList.length - 1));
  const rightNow = todayList[idx] || null;
  const upNext = todayList.filter((t) => !rightNow || t.id !== rightNow.id);

  // quick wins: everything ≤10 min not already on today, shortest first, capped
  const quickPool = active
    .filter((t) => t.minutes && t.minutes <= 10 && !t.big && !focus.ids.includes(t.id))
    .sort((a, b) => a.minutes - b.minutes);
  const quickVisible = quickPool.slice(0, quickShown);

  const dateStr = new Date().toLocaleDateString('en-GB', { weekday: 'long', day: 'numeric', month: 'long' });
  const est = rightNow ? (rightNow.minutes || 25) : 0;
  const estLabel = est >= 60 ? Math.round(est / 60 * 10) / 10 + ' hr' : est + ' min';

  const doneHead = (n) => n === 0 ? 'None yet' : n <= 2 ? 'Nice start' : n <= 4 ? 'You’re on a roll' : 'Brilliant day';

  // ---- actions ----
  const startTask = (id) => DumpAPI.startFocusSession([id]);
  const doneTask = (task) => {
    const res = DumpAPI.completeTask(task);
    setRnIdx(0);
    if (res !== 'recur') DumpAPI.toast('Done — that’s one off your plate.');
  };
  const swap = () => { if (todayList.length > 1) setRnIdx((idx + 1) % todayList.length); };
  const removeFromToday = (task) => {
    DumpStore.set((s) => ({ focus: { ...s.focus, ids: s.focus.ids.filter((id) => id !== task.id) } }));
    setRnIdx(0);
    DumpAPI.toast('Taken off today — still on your board.', {
      action: { label: 'Undo', fn: () => DumpStore.set((s) => ({ focus: { ...s.focus, ids: s.focus.ids.concat([task.id]) } })) }
    });
  };
  const addToToday = (task) => {
    DumpStore.set((s) => ({ focus: { ...s.focus, ids: s.focus.ids.concat([task.id]) } }));
    const n = DumpStore.getState().focus.ids.length;
    if (n > 3) DumpAPI.toast('That’s ' + n + ' for today — realistic? Swap one out if not.');
    if (candidates.length <= 1) setAddOpen(false);
  };
  const submitCapture = async () => {
    const text = cap.trim();
    if (!text || capBusy) return;
    setCapBusy(true);
    try {
      const before = DumpStore.getState().tasks.filter((t) => t.bucket === 'inbox').length;
      await DumpAPI.processDump(text);
      const after = DumpStore.getState().tasks.filter((t) => t.bucket === 'inbox').length;
      setCap(''); setCapMsg({ q: Math.max(0, after - before) });
      setTimeout(() => setCapMsg(null), 4500);
    } catch (e) {
      DumpAPI.toast(e && e.friendly ? e.message : 'Couldn’t capture that — try again.');
    } finally { setCapBusy(false); }
  };

  return (
    <div className="section td" data-screen-label="Today">
      <div className="td-head">
        <h1>Today</h1>
        <span className="date">{dateStr}</span>
        <span className="sp"></span>
        <span className="td-donepill">{Icons.check(13)} {doneToday.length} done{doneToday.length ? ' · ' + doneHead(doneToday.length).toLowerCase() : ''}</span>
      </div>

      {/* capture — the first interactive thing */}
      <div className="td-caprow">
        {capMsg ? (
          <div className="td-capture"><span className="td-capdone">{Icons.check(16)} {capMsg.q > 0
            ? <React.Fragment>Got it — filed. {capMsg.q === 1 ? 'One quick question' : capMsg.q + ' quick questions'} waiting · <button className="linklike" onClick={() => DumpStore.set({ route: 'clarify' })}>answer now →</button></React.Fragment>
            : 'Got it — filed and sorted.'}</span></div>
        ) : (
          <div className="td-capture">
            <span className="cp">{Icons.plus(18)}</span>
            <input ref={capRef} value={cap} onChange={(e) => setCap(e.target.value)}
              onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); submitCapture(); } }}
              placeholder="Offload a thought — I’ll sort it later" autoComplete="off" />
            <button className="cbtn" onClick={submitCapture} disabled={capBusy || !cap.trim()}>{Icons.capture(14)} {capBusy ? 'Sorting…' : 'Capture'}</button>
          </div>
        )}
        {pendingSugs.length ? (
          <button className="td-inbox-chip" onClick={() => DumpStore.set({ route: 'connect' })}>{Icons.inbox(15)} Inbox · {pendingSugs.length} actionable</button>
        ) : null}
      </div>

      <ShapeRibbon google={google} />

      <TodayPlanStrip />

      <div className="td-grid">
        <div className="td-col">
          {/* focus */}
          {rightNow ? (
            <div className="td-card td-focus">
              <div className="kicker">
                <span className="td-ford">{idx + 1}</span>
                {isHigh(rightNow) ? <span className="hi">{Icons.flag(13)} Right now · high priority</span> : <span>Right now</span>}
              </div>
              <div className="title">{rightNow.text}</div>
              <div className="meta">About {estLabel} · just the first step is enough</div>
              <div className="acts">
                <button className="td-btn primary" onClick={() => startTask(rightNow.id)}>{Icons.play(15)} Start this</button>
                <button className="td-btn ghost" onClick={() => doneTask(rightNow)}>Mark done</button>
                {todayList.length > 1 ? (<><span style={{ flex: 1 }}></span>
                  <button className="td-link" onClick={swap}>{Icons.swap(14)} Not this? Show another</button></>) : null}
              </div>
            </div>
          ) : (
            <div className="td-card td-focus">
              <div className="td-clear">
                <span className="mk">{Icons.check(24)}</span>
                <h4>{doneToday.length ? 'Today’s list is clear.' : 'Nothing on today yet.'}</h4>
                <p>{doneToday.length ? 'Everything you took on is done — anything else is a bonus.' : 'Pull something over from your board to get going.'}</p>
                {candidates.length ? <button className="td-btn ghost sm" onClick={() => setAddOpen(true)}>{Icons.plus(15)} Add to today</button> : null}
              </div>
            </div>
          )}

          {/* also on today */}
          {(rightNow || addOpen) ? (
            <div>
              <div className="td-sec-h">
                <span className="t">Also on today</span>
                {todayList.length ? <span className="note">{todayList.some(isHigh) ? '· picked from your ⚑ flags — swap any' : '· lined up for you — swap any'}</span> : null}
                <span className="add"><button className="td-addbtn" onClick={() => setAddOpen((o) => !o)}>{Icons.plus(13)} Add to today</button></span>
              </div>
              {upNext.length ? upNext.map((t, i) => (
                <div className="td-card td-unc" key={t.id}>
                  <div className="ord">{idx + 1 + i + 1 > todayList.length ? i + 2 : todayList.indexOf(t) + 1}</div>
                  <div className="ub">
                    <div className="ut">{isHigh(t) ? <span className="td-flag">{Icons.flag(13)}</span> : null}{t.text}</div>
                    <div className="um">About {t.minutes ? (t.minutes >= 60 ? Math.round(t.minutes / 60 * 10) / 10 + ' hr' : t.minutes + ' min') : '25 min'}</div>
                  </div>
                  <button className="td-btn ghost sm" onClick={() => startTask(t.id)}>{Icons.play(14)} Start</button>
                  <button className="td-x" title="Remove from today" onClick={() => removeFromToday(t)}>{Icons.x(15)}</button>
                </div>
              )) : (rightNow ? <div className="td-hint" style={{ padding: '2px' }}>Nothing else queued — add one when you’re ready.</div> : null)}
              {addOpen ? (
                <div className="td-card td-picker">
                  <div className="ph">From your board</div>
                  {candidates.length ? candidates.slice(0, 6).map((c) => (
                    <div className="td-prow" key={c.id} onClick={() => addToToday(c)}>
                      <span className="pt">{isHigh(c) ? <span className="td-flag">{Icons.flag(13)}</span> : null}{c.text}</span>
                      <span className="pm">{c.minutes ? c.minutes + ' min' : ''}</span>
                      <span className="padd">{Icons.plus(13)} Add</span>
                    </div>
                  )) : <div className="td-prow" style={{ cursor: 'default', color: 'var(--ink-faint)' }}><span className="pt">Nothing else pressing on your board.</span></div>}
                </div>
              ) : null}
            </div>
          ) : null}

          <SweepNudge />
        </div>

        {/* right rail: momentum */}
        <div className="td-rail">
          <QuickWinsCard visible={quickVisible} pool={quickPool} shown={quickShown}
            onDone={(t) => { DumpAPI.completeTask(t); setQuickShown((n) => Math.max(0, n - 1)); }}
            onRepop={() => setQuickShown(3)} />
          <DailyChecklist tasks={dailyTasks} />
          <MomentumCard done={doneToday} head={doneHead(doneToday.length)} />
        </div>
      </div>
    </div>
  );
}

/* ---------- Today's shape: one plain-language line (no timeline graphic) ---------- */
function ShapeRibbon({ google }) {
  const connected = google && google.connected;
  const toMin = (hm) => { const m = /(\d{1,2}):(\d{2})/.exec(hm || ''); return m ? (+m[1]) * 60 + (+m[2]) : null; };
  const events = (connected && Array.isArray(google.events) ? google.events : [])
    .map((e) => ({ ...e, min: toMin(e.time) }))
    .filter((e) => e.min != null)
    .sort((a, b) => a.min - b.min);
  const now = new Date().getHours() * 60 + new Date().getMinutes();
  const n = events.length;
  const countSuffix = n > 1 ? <span className="dim"> · {n} events today</span> : null;

  let body;
  if (!connected) {
    body = <span className="dim">Connect a calendar to see your day’s shape.</span>;
  } else if (n === 0) {
    body = <span>Clear day ahead — nothing scheduled.</span>;
  } else {
    const upcoming = events.filter((e) => e.min >= now);
    if (!upcoming.length) {
      body = <span>That’s the last of today’s calendar — you’re clear.</span>;
    } else {
      const next = upcoming[0];
      const somePassed = events.some((e) => e.min < now);
      body = somePassed
        ? <span>Next: <b>{next.title}</b> at <b>{next.time}</b>{countSuffix}</span>
        : <span>Clear until <b>{next.time}</b> — then <b>{next.title}</b>{countSuffix}</span>;
    }
  }
  return (
    <button className="td-shape" onClick={() => DumpStore.set({ route: 'calendar' })} title="Open the calendar">
      <span className="ic">{Icons.calendar(15)}</span>
      <span className="txt">{body}</span>
      <span className="td-shape-go" aria-hidden="true">{Icons.chevR(14)}</span>
    </button>
  );
}

/* ---------- Planned-for-today strip ----------
   Mirrors the calendar's Day Plan on Today so the two screens reconcile (a plan
   made on the calendar is visible where you actually act). Suggestions only —
   nothing is written to the calendar; done/removed tasks drop off. */
function TodayPlanStrip() {
  const { dayPlan, tasks } = useDumpStore();
  const iso = DumpUtil.todayISO();
  const plan = (dayPlan || {})[iso];
  if (!plan || !Array.isArray(plan.entries) || !plan.entries.length) return null;
  const rows = plan.entries
    .map((e) => { const t = tasks.find((x) => x.id === e.taskId); return (t && !t.done) ? { id: t.id, text: t.text, time: e.time, reason: e.reason } : null; })
    .filter(Boolean)
    .sort((a, b) => (a.time || '').localeCompare(b.time || ''));
  if (!rows.length) return null;
  return (
    <div className="td-plan">
      <div className="td-plan-head">
        <span className="td-plan-label">{Icons.sparkle(13)} Planned for today</span>
        <span className="td-plan-note">suggestions · your calendar stays free</span>
        <button className="linklike td-plan-clear" onClick={() => DumpAPI.clearDayPlan(iso)}>Clear</button>
      </div>
      <ul className="td-plan-list">
        {rows.map((r) => (
          <li key={r.id} className="td-plan-row">
            <span className="td-plan-time">{r.time || 'any'}</span>
            <span className="td-plan-text">{r.text}</span>
            {r.reason ? <span className="td-plan-reason">{r.reason}</span> : null}
          </li>
        ))}
      </ul>
    </div>
  );
}

/* ---------- Quick wins: merged ≤10-min pool, capped at 3, repopulate ---------- */
function QuickWinsCard({ visible, pool, shown, onDone, onRepop }) {
  const label = (m) => m <= 2 ? '2 min' : m + ' min';
  let body;
  if (!visible.length) {
    body = pool.length
      ? <><div className="td-qd">{Icons.check(13)} Batch cleared</div><button className="td-repop" onClick={onRepop}>{Icons.plus(13)} Pull in {Math.min(3, pool.length)} more</button></>
      : <p className="td-hint" style={{ margin: '2px 0' }}>All quick wins done today. Nice.</p>;
  } else {
    body = <><p className="td-hint">Got a spare moment? Knock one out.</p>
      {visible.map((q) => (
        <div className="td-row" key={q.id}>
          <button className="td-ring" onClick={() => onDone(q)} aria-label="Complete"></button>
          <span className="rt">{(q.level === 'high' || q.priority) ? <span className="td-flag">{Icons.flag(13)}</span> : null}{q.text}</span>
          <span className="rm">{label(q.minutes)}</span>
        </div>
      ))}</>;
  }
  return <div className="td-card"><p className="td-clabel">{Icons.bolt(14)} Quick wins<span className="n">under 10 min</span></p>{body}</div>;
}

/* ---------- Done today: the reward ---------- */
function MomentumCard({ done, head }) {
  const n = done.length;
  return (
    <div className="td-card td-mom">
      <p className="td-clabel">{Icons.check(13)} Done today</p>
      <div className="td-celebrate">
        <span className="num">{n}</span>
        <div><div className="ch">{head}</div><div className="cs">{n === 1 ? 'thing' : 'things'} off your plate</div></div>
      </div>
      {n ? (
        <div className="td-dlist">
          {done.slice(0, 6).map((t, i) => <div className="td-drow" key={t.id || i}><span className="tk">{Icons.check(11)}</span><span className="tx">{t.text}</span></div>)}
          {n > 6 ? <div className="td-drow more"><span className="tk"></span><span>+{n - 6} more</span></div> : null}
        </div>
      ) : <p className="empty">Finish one thing and watch this fill up.</p>}
    </div>
  );
}

/* ---------- a small reusable disclosure (below-the-fold content) ---------- */
function Disclosure({ open, onToggle, icon, label, count, className, children }) {
  return (
    <div className={'disc' + (open ? ' open' : '') + (className ? ' ' + className : '')}>
      <button className="disc-head" onClick={onToggle} aria-expanded={open}>
        <span className="disc-ic">{icon}</span>
        <span className="disc-label">{label}</span>
        {count != null ? <span className="disc-count">{count}</span> : null}
        <span className="disc-chev">{open ? Icons.chevD ? Icons.chevD(16) : '▾' : '▸'}</span>
      </button>
      {open ? <div className="disc-body">{children}</div> : null}
    </div>
  );
}

function InboxCard({ pendingSugs }) {
  return (
    <div className="digest-card">
      <h3>{Icons.inbox(16)} Waiting in your inbox</h3>
      {['gmail', 'whatsapp', 'slack', 'file'].map((src) => {
        const n = pendingSugs.filter((s) => s.source === src).length;
        if (!n) return null;
        const label = src === 'gmail' ? 'Gmail' : src === 'whatsapp' ? 'WhatsApp' : src === 'slack' ? 'Slack' : 'Files';
        return (
          <div className="inbox-line" key={src}>
            {src === 'gmail' ? Icons.mail(15) : src === 'whatsapp' ? Icons.chat(15) : src === 'slack' ? Icons.slack(15) : Icons.file(15)}
            <span><strong>{n}</strong> suggestion{n === 1 ? '' : 's'} from {label}</span>
          </div>
        );
      })}
      <button className="linklike" onClick={() => DumpStore.set({ route: 'connect' })}>Review in Connect →</button>
    </div>
  );
}

/* ---------- Quick wins (the 2-minute rule) ---------- */
function QuickWinRow({ task, variant }) {
  const [leaving, setLeaving] = useState(false);
  const finish = () => {
    const res = DumpAPI.completeTask(task);
    if (res !== 'recur') DumpAPI.toast('Done — that’s one off your mind.');
  };
  const complete = () => {
    if (task.recurrence) { finish(); return; }
    setLeaving(true);
    setTimeout(finish, 360);
  };
  const mins = task.minutes <= 2 ? '2 min' : '~' + task.minutes + ' min';
  return (
    <div className={'qw-row' + (variant === 'twomin' ? ' twomin-row' : '') + (leaving ? ' qw-leaving' : '')}>
      <button className={'task-check' + (leaving ? ' checked' : '')} onClick={complete} aria-label="Complete task">
        {Icons.check(12)}
      </button>
      <span className="qw-text">{task.text}</span>
      <span className="qw-chip">{Icons.bolt(11)} {mins}</span>
    </div>
  );
}

/* The 2-minute rule, made literal: these never sit on the board. Only undated
   or due-today ones surface; a future-dated 2-min task waits on its day. */
function twoMinuteNow(active) {
  const today = DumpUtil.todayISO();
  return active
    .filter((t) => t.minutes && t.minutes <= 2 && (!t.dueDate || t.dueDate === today))
    .sort((a, b) => (a.priority ? 0 : 1) - (b.priority ? 0 : 1));
}

function QuickWinsDisclosure({ active, focusIds, open, onToggle }) {
  const wins = active
    .filter((t) => t.minutes && t.minutes > 2 && t.minutes <= 10 && !focusIds.includes(t.id))
    .sort((a, b) => a.minutes - b.minutes)
    .slice(0, 4);
  if (!wins.length) return null;
  return (
    <Disclosure open={open} onToggle={onToggle} icon={Icons.bolt(15)} label="Got a few minutes?" count={wins.length}>
      <p className="disc-hint">Five or ten minutes each — quick to knock out between the bigger things.</p>
      {wins.map((t) => <QuickWinRow key={t.id} task={t} />)}
    </Disclosure>
  );
}

/* ---------- Done archive ---------- */
function DoneSection() {
  const { archive, settings, doneArchivedTotal } = useDumpStore();
  const allTime = (doneArchivedTotal || 0) + archive.length;
  const retention = settings.doneRetention;

  const restore = (task) => {
    DumpStore.set((s) => ({
      archive: s.archive.filter((t) => t.id !== task.id),
      tasks: s.tasks.concat([{ ...task, done: false, doneAt: null, fresh: false }])
    }));
    DumpAPI.toast('Back on the board.');
  };

  const groups = [];
  const byDay = {};
  archive.forEach((t) => {
    const day = (t.doneAt || t.createdAt || '').slice(0, 10) || 'unknown';
    if (!byDay[day]) { byDay[day] = []; groups.push(day); }
    byDay[day].push(t);
  });
  groups.sort().reverse();

  return (
    <div className="section" data-screen-label="Done">
      <div className="section-head">
        <h1 className="section-title">Done</h1>
      </div>
      <p className="section-sub">{archive.length === 0
        ? (allTime > 0 ? allTime + ' finished all-time — the recent ones show below once you complete more.' : 'Finished things land here.')
        : <span>{archive.length} shown{retention > 0 ? <span> · kept {retention} days</span> : null}{allTime > archive.length ? <span> · {allTime} all-time</span> : null} — proof it happens.</span>}</p>

      {archive.length === 0 ? (
        <EmptyState icon={Icons.done(26)} title="Nothing here yet">
          Complete a task on the board and it shows up here, day by day.
        </EmptyState>
      ) : (
        groups.map((day) => (
          <div className="done-group" key={day}>
            <h2 className="done-day">{DumpUtil.dayLabel(day)}</h2>
            {byDay[day].map((t) => (
              <div className="done-item" key={t.id}>
                <span className="done-check">{Icons.done(18)}</span>
                <span className="done-text">{t.text}</span>
                <button className="linklike" onClick={() => restore(t)}>Restore</button>
              </div>
            ))}
          </div>
        ))
      )}
    </div>
  );
}

/* ---------- Daily tracking: opt-in per-occurrence checks ----------
   Tracked recurring tasks (meds, vitamins) live here, not on the board. Each
   scheduled day gets its own tick; a missed day shows as a hollow dot rather
   than silently vanishing. Neutral by design — no red, no shame, re-check anytime. */
function DailyChecklist({ tasks }) {
  if (!tasks || !tasks.length) return null;
  const todayIso = DumpUtil.todayISO();
  const doneCount = tasks.filter((t) => DumpAPI.dailyDoneOn(t, todayIso)).length;
  const allDone = doneCount === tasks.length;
  return (
    <div className="daily-card">
      <div className="daily-head">
        <h3>{Icons.repeat(16)} Every day</h3>
        <span className={'daily-progress' + (allDone ? ' all' : '')}>
          {allDone ? Icons.check(12) : null} {doneCount}/{tasks.length} done
        </span>
      </div>
      <div className="daily-list">
        {tasks.map((t) => <DailyRow key={t.id} task={t} />)}
      </div>
    </div>
  );
}

function DailyRow({ task }) {
  const todayIso = DumpUtil.todayISO();
  const done = DumpAPI.dailyDoneOn(task, todayIso);
  const streak = DumpAPI.dailyStreak(task);
  const history = DumpAPI.dailyHistory(task, 7);
  const course = DumpAPI.dailyCourseInfo(task);
  const [menu, setMenu] = useState(false);
  const [durOpen, setDurOpen] = useState(false);
  const [durVal, setDurVal] = useState(course ? course.remaining || course.total : 10);
  const menuRef = useRef(null);
  useEffect(() => {
    if (!menu) return;
    const onDoc = (e) => { if (menuRef.current && !menuRef.current.contains(e.target)) setMenu(false); };
    const onEsc = (e) => { if (e.key === 'Escape') setMenu(false); };
    document.addEventListener('mousedown', onDoc);
    document.addEventListener('keydown', onEsc);
    return () => { document.removeEventListener('mousedown', onDoc); document.removeEventListener('keydown', onEsc); };
  }, [menu]);

  const stopTracking = () => {
    setMenu(false);
    DumpStore.set((s) => ({ tasks: s.tasks.map((t) => t.id === task.id ? { ...t, trackDaily: false, fresh: false } : t) }));
    DumpAPI.toast('Back on your board — no longer tracked daily.', {
      action: { label: 'View', fn: () => DumpStore.set({ route: 'tasks', tasksView: 'board' }) }
    });
  };
  const remove = () => {
    setMenu(false);
    const snapshot = task;
    DumpStore.set((s) => ({ tasks: s.tasks.filter((t) => t.id !== task.id) }));
    DumpAPI.toast('Removed.', { action: { label: 'Undo', fn: () => DumpStore.set((s) => ({ tasks: s.tasks.concat([snapshot]) })) } });
  };
  const saveDuration = () => {
    const n = parseInt(durVal, 10);
    if (!n || n < 1) return;
    DumpAPI.setDailyDuration(task.id, n);
    setDurOpen(false);
  };

  return (
    <div className={'daily-row' + (done ? ' is-done' : '')}>
      <button className={'daily-check' + (done ? ' checked' : '')}
        onClick={() => DumpAPI.toggleDailyDone(task.id, todayIso)}
        aria-label={done ? 'Undo today' : 'Mark done for today'}>
        {done ? Icons.check(13) : null}
      </button>
      <div className="daily-body">
        <span className="daily-text">{task.text}</span>
        <div className="daily-meta">
          <span className="daily-dots" title="Recent days — filled = done, hollow = missed">
            {history.map((h) => (
              <span key={h.iso} className={'ddot' + (h.done ? ' on' : '') + (h.today ? ' today' : '')}></span>
            ))}
          </span>
          {course
            ? <span className="daily-course">{'Day ' + course.dayNum + ' of ' + course.total}</span>
            : (streak >= 2 ? <span className="daily-streak">{Icons.repeat(11)} {streak}-day streak</span> : null)}
        </div>
        {durOpen ? (
          <div className="daily-dur">
            <span>For</span>
            <input type="number" min="1" max="365" value={durVal}
              onChange={(e) => setDurVal(e.target.value)}
              onKeyDown={(e) => { if (e.key === 'Enter') saveDuration(); if (e.key === 'Escape') setDurOpen(false); }} />
            <span>more days</span>
            <button className="btn btn-primary btn-sm" onClick={saveDuration}>Set</button>
            <button className="linklike" onClick={() => setDurOpen(false)}>Cancel</button>
          </div>
        ) : null}
      </div>
      <div className="overflow-wrap daily-menu-wrap" ref={menuRef}>
        <button className="daily-menu-btn" aria-haspopup="true" aria-expanded={menu} aria-label="Options"
          onClick={() => setMenu((o) => !o)}>{Icons.more(16)}</button>
        {menu ? (
          <div className="overflow-menu down" role="menu">
            <button role="menuitem" onClick={() => { setMenu(false); setDurVal(course ? (course.remaining || course.total) : 10); setDurOpen(true); }}>
              {Icons.calendar(15)} {course ? 'Change duration' : 'Only for a while…'}
            </button>
            {course ? (
              <button role="menuitem" onClick={() => { setMenu(false); DumpAPI.setDailyOngoing(task.id); }}>{Icons.repeat(15)} Make it ongoing</button>
            ) : null}
            <button role="menuitem" onClick={stopTracking}>{Icons.pause(15)} Stop tracking daily</button>
            <div className="menu-sep"></div>
            <button role="menuitem" className="danger" onClick={remove}>{Icons.trash(15)} Remove</button>
          </div>
        ) : null}
      </div>
    </div>
  );
}

Object.assign(window, { TodaySection, DoneSection, Disclosure, DailyChecklist });
