// SheetsPage — Google-Sheets-style multi-list view. All of the user's
// roots appear as tabs along the bottom edge; the selected list renders
// as a full Spreadsheet view above the tab strip, with a dashboard
// header showing per-list progress + all-lists totals.
//
// Design goals:
//   - One page to see the state of every list at a glance.
//   - Bottom tab-strip matches spreadsheet UX (Excel / Google Sheets).
//   - Reuses the existing SpreadsheetView — every column, filter, group
//     header, drag-reorder, custom-column, click-cycle behaviour comes
//     with it. New planner metadata columns light up automatically on
//     lists that carry the metadata.
//   - Opens item modal on row click (same as list detail).
//
// Route: `#sheets` (all lists, first-list default) or `#sheets/{rootId}`
// (deep-link to a specific list).

const { useState: _sp_s, useEffect: _sp_e, useMemo: _sp_m, useRef: _sp_r } = React;

function progressForRoot(root) {
  // Walk the tree; count every leaf (type='item') by status.
  // Returns { total, want, need, have, done_pct, actual_spent, planned_cost }.
  let total = 0, want = 0, need = 0, have = 0, ordered = 0;
  let actual = 0, planned = 0;
  const walk = (n) => {
    if (!n || !n.children) return;
    for (const c of n.children) {
      if (c.type === 'item') {
        total++;
        const s = c.status || null;
        if (s === 'have') have++;
        else if (s === 'need') need++;
        else if (s === 'ordered') ordered++;
        else if (s === 'want') want++;
        const meta = c.metadata || {};
        const actualN = Number(meta.actual_cost);
        if (Number.isFinite(actualN)) actual += actualN;
        const price = c.custom && c.custom.price;
        if (Number.isFinite(Number(price))) planned += Number(price);
      }
      if (c.children && c.children.length) walk(c);
    }
  };
  walk(root);
  const done_pct = total ? Math.round((have / total) * 100) : 0;
  return { total, want, need, have, ordered, done_pct, actual, planned };
}

// Status palette — validated (light surface #fffaf0): CVD ΔE ≥ 13, normal ≥ 21.
// Amber sits under 3:1 contrast, so every chart pairs colour with labels.
// Validated triad (light surface): Done green, Ordered blue, Chosen amber.
const SH_STATUS = {
  have:    { label: 'Done',    color: '#4f8a56' },
  ordered: { label: 'Ordered', color: '#4a63c9' },
  need:    { label: 'Chosen',  color: '#d9a33a' },
};
const SH_SEGS = ['have', 'ordered', 'need'];
const SH_BAR = '#7a6a52';   // single hue for magnitude charts

function shTimeAgo(iso) {
  const t = new Date(iso).getTime(); if (!t) return '';
  const s = Math.max(0, Math.round((Date.now() - t) / 1000));
  if (s < 60) return 'just now';
  const m = Math.round(s / 60); if (m < 60) return `${m}m ago`;
  const h = Math.round(m / 60); if (h < 24) return `${h}h ago`;
  const d = Math.round(h / 24); if (d < 7) return `${d}d ago`;
  return new Date(iso).toLocaleDateString(undefined, { day: 'numeric', month: 'short' });
}
function shVerb(a) {
  const m = a.meta || {};
  const st = (x) => x === 'have' ? 'Done' : x === 'need' ? 'Chosen' : x === 'ordered' ? 'Ordered' : x === 'want' ? 'Loved' : x;
  const what = m.product_name || m.custom_name || 'an item';
  switch (a.action) {
    case 'added_item':   return `added ${what}`;
    case 'updated_item': return m.status_to ? `moved ${what === 'an item' ? 'an item' : what} to ${st(m.status_to)}` : (m.picked ? 'picked an alternative' : 'updated an item');
    case 'removed_item': return `removed ${what}`;
    case 'voted':        return `voted ${m.vote === 'up' ? '👍' : '👎'}`;
    case 'noted':        return 'added a note';
    case 'commented':    return 'commented';
    case 'joined': case 'partner_joined': return 'joined';
    case 'invited': case 'invite_sent':   return `invited ${a.target_email || 'someone'}`;
    case 'created_list': return 'created the list';
    case 'shortlist_decided': return `decided "${m.slot_name || 'a shortlist'}"`;
    case 'list_completed':    return 'finished the list 🎉';
    default: return String(a.action || '').replace(/_/g, ' ');
  }
}
// Walk one root for the dashboard: counts by status / when / owner /
// priority, groups, placeholders, and money.
function shStats(root) {
  const out = { total: 0, have: 0, need: 0, ordered: 0, want: 0, planned: 0, spent: 0, groups: 0, placeholders: 0, when: {}, owner: {}, priority: {} };
  const walk = (n) => {
    (n.children || []).forEach(c => {
      if (c.type === 'slot') { out.groups++; walk(c); return; }
      if (c.type !== 'item') return;
      out.total++;
      const st = c.status; if (st && st in SH_STATUS) out[st]++; else out.unset = (out.unset || 0) + 1;
      const md = c.metadata || {}, cu = c.custom || {};
      if (cu.placeholder) out.placeholders++;
      const price = Number(cu.price); if (Number.isFinite(price)) out.planned += price;
      const spent = Number(md.actual_cost); if (Number.isFinite(spent)) out.spent += spent;
      const w = md.when_needed || 'Unscheduled'; out.when[w] = (out.when[w] || 0) + 1;
      const o = md.owner ? String(md.owner).toLowerCase() : 'unassigned'; out.owner[o] = (out.owner[o] || 0) + 1;
      const pr = md.priority ? String(md.priority).toLowerCase() : 'unset'; out.priority[pr] = (out.priority[pr] || 0) + 1;
      if (c.children && c.children.length) walk(c);
    });
  };
  walk(root);
  out.pct = out.total ? Math.round((out.have / out.total) * 100) : 0;
  return out;
}

// Stacked status bar with 2px gaps; labels live in the legend / title.
function ShStatusBar({ have, need, ordered = 0, total, height = 10 }) {
  if (!total) return <div className="sh-bar sh-bar--empty" style={{ height }} />;
  const segs = [['have', have], ['ordered', ordered], ['need', need]].filter(([, v]) => v > 0);
  return (
    <div className="sh-bar" style={{ height }} title={`${have} done · ${ordered} ordered · ${need} chosen · ${total - have - ordered - need} not started`}>
      {segs.map(([k, v]) => <span key={k} style={{ flex: `${v} 0 0`, background: SH_STATUS[k].color }} />)}
    </div>
  );
}
// Horizontal single-hue bars for a small categorical breakdown of counts.
function ShBars({ data, order, labels = {}, total }) {
  const keys = order ? order.filter(k => data[k]) : Object.keys(data);
  const extra = Object.keys(data).filter(k => !keys.includes(k));
  const all = [...keys, ...extra];
  const max = Math.max(1, ...all.map(k => data[k]));
  if (!all.length) return <div className="rooms-hint">Nothing tagged yet.</div>;
  return (
    <ul className="sh-bars">
      {all.map(k => (
        <li key={k} title={`${labels[k] || k}: ${data[k]} of ${total}`}>
          <span className="sh-bars-lbl">{labels[k] || k}</span>
          <span className="sh-bars-track"><span className="sh-bars-fill" style={{ width: `${(data[k] / max) * 100}%` }} /></span>
          <span className="sh-bars-val">{data[k]}</span>
        </li>
      ))}
    </ul>
  );
}

// Click-to-edit text (title / description). Enter or blur saves, Esc cancels.
function ShEditable({ value, onSave, as = 'span', className = '', placeholder = 'Add…', multiline = false }) {
  const [editing, setEditing] = _sp_s(false);
  const [draft, setDraft] = _sp_s(value || '');
  _sp_e(() => { if (!editing) setDraft(value || ''); }, [value, editing]);
  const Tag = as;
  if (!editing) {
    return (
      <Tag className={`${className} sh-editable${value ? '' : ' is-empty'}`} onClick={() => setEditing(true)} title="Click to edit">
        {value || placeholder}
      </Tag>
    );
  }
  const commit = () => { setEditing(false); const v = draft.trim(); if (v !== (value || '')) onSave(v); };
  const props = {
    autoFocus: true, className: `${className} sh-editable-input`, value: draft,
    onChange: (e) => setDraft(e.target.value), onBlur: commit,
    onKeyDown: (e) => { if (e.key === 'Escape') { setEditing(false); setDraft(value || ''); } else if (e.key === 'Enter' && (!multiline || e.metaKey || e.ctrlKey)) { e.preventDefault(); commit(); } },
  };
  return multiline ? <textarea rows={2} {...props} /> : <input type="text" {...props} />;
}

// Move `fromId` before/after `toId` in an id list.
function shMoveId(ids, fromId, toId, pos) {
  const out = ids.filter(x => x !== fromId);
  const i = out.indexOf(toId);
  if (i < 0) return ids;
  out.splice(pos === 'before' ? i : i + 1, 0, fromId);
  return out;
}

// HTML5 drag-to-reorder over a list of ids. Returns per-id props to
// spread on the draggable element plus the current drag state for styling.
function useShDragReorder(ids, onReorder, enabled = true) {
  const [drag, setDrag] = _sp_s(null);   // { id, overId, pos }
  const propsFor = (id) => enabled ? ({
    draggable: true,
    onDragStart: (e) => { try { e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', 'root:' + id); } catch {} setDrag({ id, overId: null, pos: null }); },
    onDragOver: (e) => { if (!drag || drag.id === id) return; e.preventDefault(); const r = e.currentTarget.getBoundingClientRect(); const horiz = r.width > r.height * 2 && r.height < 60; const pos = (horiz ? (e.clientX - r.left) < r.width / 2 : (e.clientY - r.top) < r.height / 2) ? 'before' : 'after'; if (drag.overId !== id || drag.pos !== pos) setDrag({ ...drag, overId: id, pos }); },
    onDrop: (e) => { e.preventDefault(); if (drag && drag.id !== id) onReorder(shMoveId(ids, drag.id, id, drag.pos || 'after')); setDrag(null); },
    onDragEnd: () => setDrag(null),
  }) : {};
  const classFor = (id) => `${drag && drag.id === id ? ' is-dragging' : ''}${drag && drag.overId === id ? ' is-over-' + drag.pos : ''}`;
  return { propsFor, classFor, dragging: !!drag };
}

function SheetsDashboard({ roots, myUserId, onSelectRoot, onOpenList, onCreateList, onReorderRoots }) {
  const rows = _sp_m(() => roots.map(r => ({ r, s: shStats(r) })), [roots]);
  const all = _sp_m(() => {
    const t = { total: 0, have: 0, need: 0, ordered: 0, want: 0, planned: 0, spent: 0, groups: 0, placeholders: 0, when: {}, owner: {}, priority: {} };
    rows.forEach(({ s }) => {
      ['total', 'have', 'need', 'ordered', 'want', 'planned', 'spent', 'groups', 'placeholders'].forEach(k => { t[k] += s[k]; });
      ['when', 'owner', 'priority'].forEach(k => Object.entries(s[k]).forEach(([kk, v]) => { t[k][kk] = (t[k][kk] || 0) + v; }));
    });
    t.pct = t.total ? Math.round((t.have / t.total) * 100) : 0;
    return t;
  }, [rows]);
  const [sort, setSort] = _sp_s({ key: 'manual', dir: 'asc' });
  const manual = sort.key === 'manual';
  const dnd = useShDragReorder(roots.map(r => r.id), (ids) => onReorderRoots && onReorderRoots(ids), manual && !!onReorderRoots);
  const sorted = _sp_m(() => {
    if (sort.key === 'manual') return rows;
    const v = (x) => {
      switch (sort.key) {
        case 'name': return String(x.r.name || '').toLowerCase();
        case 'total': return x.s.total;
        case 'pct': return x.s.pct;
        case 'have': return x.s.have;
        case 'need': return x.s.need;
        case 'ordered': return x.s.ordered;
        case 'planned': return x.s.planned;
        case 'spent': return x.s.spent;
        case 'placeholders': return x.s.placeholders;
        default: return 0;
      }
    };
    return [...rows].sort((a, b) => { const A = v(a), B = v(b); return (A > B ? 1 : A < B ? -1 : 0) * (sort.dir === 'asc' ? 1 : -1); });
  }, [rows, sort]);
  const th = (key, label, num) => (
    <th className={`${num ? 'num ' : ''}${sort.key === key ? 'is-sorted' : ''}`} onClick={() => setSort(s => (s.key === key && s.dir === 'desc') ? { key: 'manual', dir: 'asc' } : { key, dir: s.key === key && s.dir === 'asc' ? 'desc' : 'asc' })} title={sort.key === key && sort.dir === 'desc' ? 'Back to your order' : 'Sort'}>
      {label}{sort.key === key ? <span className="sh-sort">{sort.dir === 'asc' ? '↑' : '↓'}</span> : null}
    </th>
  );

  // Loved-by-both: items with ≥2 distinct up-voters across every list.
  const [lovedBoth, setLovedBoth] = _sp_s(null);
  const [lovedAny, setLovedAny] = _sp_s(null);
  _sp_e(() => {
    let cancelled = false;
    (async () => {
      if (!window.MR || !window.MR.supabase || !roots.length) { setLovedBoth(0); setLovedAny(0); return; }
      const ids = [];
      const walk = (n) => (n.children || []).forEach(c => { if (c.type === 'item') ids.push(c.id); if (c.children) walk(c); });
      roots.forEach(walk);
      if (!ids.length) { setLovedBoth(0); setLovedAny(0); return; }
      try {
        const { data } = await window.MR.supabase.from('list_node_votes').select('node_id, voter_id').eq('vote', 'up').in('node_id', ids);
        if (cancelled) return;
        const per = {}; (data || []).forEach(v => { (per[v.node_id] = per[v.node_id] || new Set()).add(v.voter_id); });
        setLovedAny(Object.keys(per).length);
        setLovedBoth(Object.values(per).filter(sset => sset.size >= 2).length);
      } catch { if (!cancelled) { setLovedBoth(0); setLovedAny(0); } }
    })();
    return () => { cancelled = true; };
  }, [roots]);

  // Recent activity across every list.
  const [activity, setActivity] = _sp_s(null);
  _sp_e(() => {
    let cancelled = false;
    (async () => {
      if (!window.MR || !window.MR.supabase || !roots.length) { setActivity([]); return; }
      try {
        const { data, error } = await window.MR.supabase.from('list_activity').select('*').in('list_id', roots.map(r => r.id)).order('created_at', { ascending: false }).limit(30);
        if (!cancelled) setActivity(error ? [] : (data || []));
      } catch { if (!cancelled) setActivity([]); }
    })();
    return () => { cancelled = true; };
  }, [roots.map(r => r.id).join(',')]);
  const nameOf = (id) => { const r = roots.find(x => x.id === id); return r ? r.name : 'a list'; };
  const who = (a) => a.actor_id && myUserId && a.actor_id === myUserId ? 'You' : (a.actor_email ? a.actor_email.split('@')[0] : 'Someone');

  const money = (n) => n ? `A$${Math.round(n).toLocaleString()}` : '—';
  const WHEN_ORDER = ['Before birth', '0–1 month', '1–3 months', 'After birth', 'Later', 'Unscheduled'];

  return (
    <div className="sh-dash">
      {/* Hero numbers */}
      <div className="sh-tiles">
        <div className="sh-tile sh-tile--hero">
          <div className="sh-tile-lbl">Done</div>
          <div className="sh-tile-val">{all.pct}<small>%</small></div>
          <div className="sh-tile-sub">{all.have} of {all.total} items across {roots.length} {roots.length === 1 ? 'list' : 'lists'}</div>
          <ShStatusBar have={all.have} need={all.need} ordered={all.ordered} total={all.total} height={12} />
          <div className="sh-legend">
            {SH_SEGS.map(k => <span key={k}><i style={{ background: SH_STATUS[k].color }} />{SH_STATUS[k].label} <b>{all[k]}</b></span>)}
            <span><i style={{ background: 'var(--rule, #e8e0d2)' }} />Not started <b>{Math.max(0, all.total - all.have - all.ordered - all.need)}</b></span>
          </div>
        </div>
        <div className="sh-tile"><div className="sh-tile-lbl">Loved by both</div><div className="sh-tile-val">{lovedBoth == null ? '…' : lovedBoth}</div><div className="sh-tile-sub">{lovedAny == null ? '' : `${lovedAny} loved by at least one of you`}</div></div>
        <div className="sh-tile"><div className="sh-tile-lbl">Placeholders</div><div className="sh-tile-val">{all.placeholders}</div><div className="sh-tile-sub">still need a real product</div></div>
        <div className="sh-tile"><div className="sh-tile-lbl">Spent</div><div className="sh-tile-val">{money(all.spent)}</div><div className="sh-tile-sub">of {money(all.planned)} planned</div></div>
      </div>

      {/* Lists table */}
      <div className="sh-section">
        <div className="sh-section-head">
          <h2 className="sh-h2">Lists</h2>
          <button type="button" className="rooms-btn" onClick={onCreateList}>+ New list</button>
        </div>
        <div className="sh-table-wrap">
          <table className="sh-table">
            <thead><tr>
              <th className="sh-th-grip" onClick={() => setSort({ key: 'manual', dir: 'asc' })} title="Your order"></th>
              {th('name', 'List')}
              <th style={{ minWidth: 180 }}>Progress</th>
              {th('pct', '%', true)}
              {th('have', 'Done', true)}
              {th('ordered', 'Ordered', true)}
              {th('need', 'Chosen', true)}
              {th('total', 'Items', true)}
              {th('placeholders', 'Placeholders', true)}
              {th('planned', 'Planned', true)}
              {th('spent', 'Spent', true)}
              <th></th>
            </tr></thead>
            <tbody>
              {sorted.map(({ r, s }) => (
                <tr key={r.id} className={`sh-row${manual ? ' is-manual' : ''}${dnd.classFor(r.id)}`} onClick={() => onSelectRoot(r.id)} {...dnd.propsFor(r.id)}>
                  <td className="sh-td-grip" onClick={(e) => e.stopPropagation()} title={manual ? 'Drag to reorder — the tabs follow this order' : 'Clear the sort to reorder'}>{manual ? '⋮⋮' : ''}</td>
                  <td className="sh-td-name">{r.coverIcon && <span className="sh-icon">{r.coverIcon}</span>}<span className="sh-name">{r.name}</span>{r.isMine === false && <span className="sh-shared">shared</span>}</td>
                  <td><ShStatusBar have={s.have} need={s.need} ordered={s.ordered} total={s.total} /></td>
                  <td className="num"><b>{s.pct}%</b></td>
                  <td className="num">{s.have || '—'}</td>
                  <td className="num">{s.ordered || '—'}</td>
                  <td className="num">{s.need || '—'}</td>
                  <td className="num">{s.total}</td>
                  <td className="num">{s.placeholders || '—'}</td>
                  <td className="num">{money(s.planned)}</td>
                  <td className="num">{money(s.spent)}</td>
                  <td className="sh-td-act" onClick={(e) => e.stopPropagation()}>
                    <button type="button" className="rooms-btn" onClick={() => onSelectRoot(r.id)} title="Open as a sheet">Sheet</button>
                    {onOpenList && <button type="button" className="rooms-btn" onClick={() => onOpenList(r.id)} title="Open the list page">List ↗</button>}
                  </td>
                </tr>
              ))}
            </tbody>
            <tfoot><tr>
              <td></td>
              <td className="sh-td-name"><b>All lists</b></td>
              <td><ShStatusBar have={all.have} need={all.need} ordered={all.ordered} total={all.total} /></td>
              <td className="num"><b>{all.pct}%</b></td>
              <td className="num">{all.have}</td><td className="num">{all.ordered}</td><td className="num">{all.need}</td>
              <td className="num">{all.total}</td><td className="num">{all.placeholders}</td>
              <td className="num">{money(all.planned)}</td><td className="num">{money(all.spent)}</td><td></td>
            </tr></tfoot>
          </table>
        </div>
      </div>

      {/* Breakdowns + activity */}
      <div className="sh-grid">
        <div className="sh-card">
          <h3 className="sh-h3">When it's needed</h3>
          <ShBars data={all.when} order={WHEN_ORDER} total={all.total} />
        </div>
        <div className="sh-card">
          <h3 className="sh-h3">Who's on it</h3>
          <ShBars data={all.owner} order={['lucie', 'simon', 'both', 'unassigned']} labels={{ lucie: 'Lucie', simon: 'Simon', both: 'Both', unassigned: 'Unassigned' }} total={all.total} />
        </div>
        <div className="sh-card">
          <h3 className="sh-h3">Priority</h3>
          <ShBars data={all.priority} order={['essential', 'useful', 'bonus', 'unset']} labels={{ essential: 'Essential', useful: 'Useful', bonus: 'Bonus', nice: 'Bonus', unset: 'Unset' }} total={all.total} />
        </div>
        <div className="sh-card sh-card--activity">
          <h3 className="sh-h3">Recent activity</h3>
          {activity === null ? <div className="rooms-hint">Loading…</div>
            : activity.length === 0 ? <div className="rooms-hint">Nothing yet — changes to any list will show up here.</div>
            : (
              <ul className="sh-activity">
                {activity.map(a => (
                  <li key={a.id}>
                    <span className="sh-act-who">{who(a)}</span> {shVerb(a)}
                    <button type="button" className="sh-act-list" onClick={() => onSelectRoot(a.list_id)}>{nameOf(a.list_id)}</button>
                    <span className="sh-act-time">{shTimeAgo(a.created_at)}</span>
                  </li>
                ))}
              </ul>
            )}
        </div>
      </div>
    </div>
  );
}

function SheetsPage({
  roots,                // Array of root nodes (with .children trees) — for tabs + dashboard
  rootId,               // Currently-selected root id (or null → default to first)
  onSelectRoot,         // (rootId) => void
  productMap,
  onCreateList,         // () => open the create-list modal
  onOpenList,           // (rootId) => navigate into the full list detail page
  // Pass-through to the embedded ListDetailV2 (it owns the item modal,
  // comments, votes, inline edits, placeholder flow, etc.)
  onOpenProduct,
  onUpdateListContext,
  onNavigateAdd,
  onShare,
  myUserId,
  userLists,
  onPatchRoot,          // (rootId, { name?, description?, coverIcon? }) => void
  onReorderRoots,       // (orderedRootIds) => void — persists tab / dashboard order
}) {
  const tabDnd = useShDragReorder((roots || []).map(r => r.id), (ids) => onReorderRoots && onReorderRoots(ids), !!onReorderRoots);
  // No rootId → the Dashboard. A rootId that no longer exists also falls
  // back to the Dashboard rather than silently opening another list.
  const activeRoot = _sp_m(
    () => (rootId && roots.find(r => r.id === rootId)) || null,
    [rootId, roots]
  );
  const showDashboard = !activeRoot;

  // Aggregate progress across every list — small dashboard strip.
  const allProgress = _sp_m(() => {
    const summed = { total: 0, want: 0, need: 0, have: 0, actual: 0, planned: 0 };
    for (const r of roots) {
      const p = progressForRoot(r);
      summed.total += p.total;
      summed.want += p.want;
      summed.need += p.need;
      summed.have += p.have;
      summed.actual += p.actual;
      summed.planned += p.planned;
    }
    summed.done_pct = summed.total ? Math.round((summed.have / summed.total) * 100) : 0;
    return summed;
  }, [roots]);

  const activeProgress = _sp_m(
    () => activeRoot ? progressForRoot(activeRoot) : null,
    [activeRoot]
  );

  if (!roots || !roots.length) {
    return (
      <div className="page sheets-page">
        <div className="page-head">
          <div>
            <div className="page-eyebrow">Sheets</div>
            <h1 className="page-title">Your lists as <em>spreadsheets</em></h1>
            <p className="page-sub">Once you create a list, it'll appear here alongside the others — one tab per list, one row per item, every column you might want.</p>
          </div>
          <div className="page-head-action">
            <button className="btn" onClick={onCreateList} style={{ width: 'auto', padding: '12px 20px' }}>
              <span>New list</span><span className="arrow">+</span>
            </button>
          </div>
        </div>
      </div>
    );
  }

  return (
    <div className="page sheets-page">
      {/* Header dashboard — small so it stays out of the way. */}
      <div className="sheets-topbar">
        <div className="sheets-topbar-left">
          <div className="page-eyebrow">Sheets</div>
          {showDashboard ? (
            <>
              <h1 className="sheets-title">Dashboard</h1>
              <p className="sheets-desc">Every list at a glance — progress, what's left, who's doing what.</p>
            </>
          ) : (
            <>
              <h1 className="sheets-title">
                <button type="button" className={`sheets-title-icon sheets-icon-btn${activeRoot.coverIcon ? '' : ' is-empty'}`} title="Change icon"
                  onClick={() => { const v = window.prompt('Emoji for this list (leave blank to remove):', activeRoot.coverIcon || ''); if (v !== null && onPatchRoot) onPatchRoot(activeRoot.id, { coverIcon: v.trim() || null }); }}>
                  {activeRoot.coverIcon || '＋'}
                </button>
                <ShEditable value={activeRoot.name} placeholder="Untitled list" onSave={(v) => v && onPatchRoot && onPatchRoot(activeRoot.id, { name: v })} />
              </h1>
              <ShEditable as="p" className="sheets-desc" value={activeRoot.description} placeholder="Add a description…" multiline onSave={(v) => onPatchRoot && onPatchRoot(activeRoot.id, { description: v })} />
            </>
          )}
        </div>
        <div className="sheets-topbar-right">
          {activeProgress && (
            <>
              <div className="sheets-stat">
                <div className="sheets-stat-lbl">Progress</div>
                <div className="sheets-stat-val">
                  <span className="sheets-stat-num">{activeProgress.have}</span>
                  <span className="sheets-stat-sep">/</span>
                  <span className="sheets-stat-tot">{activeProgress.total}</span>
                  <span className="sheets-stat-pct">{activeProgress.done_pct}%</span>
                </div>
                <div className="sheets-stat-bar" title={`${activeProgress.have} done · ${activeProgress.ordered || 0} ordered · ${activeProgress.need} chosen`}>
                  <span className="sheets-stat-bar-have" style={{ width: `${activeProgress.total ? (activeProgress.have/activeProgress.total*100) : 0}%` }} />
                  <span className="sheets-stat-bar-ordered" style={{ width: `${activeProgress.total ? ((activeProgress.ordered || 0)/activeProgress.total*100) : 0}%` }} />
                  <span className="sheets-stat-bar-need" style={{ width: `${activeProgress.total ? (activeProgress.need/activeProgress.total*100) : 0}%` }} />
                </div>
              </div>
              {activeProgress.actual > 0 && (
                <div className="sheets-stat">
                  <div className="sheets-stat-lbl">Spent</div>
                  <div className="sheets-stat-val">
                    <span className="sheets-stat-num">A${Math.round(activeProgress.actual)}</span>
                    {activeProgress.planned > 0 && (
                      <span className="sheets-stat-tot"> / A${Math.round(activeProgress.planned)}</span>
                    )}
                  </div>
                </div>
              )}
            </>
          )}
          <div className="sheets-stat sheets-stat--all">
            <div className="sheets-stat-lbl">All lists</div>
            <div className="sheets-stat-val">
              <span className="sheets-stat-num">{allProgress.have}</span>
              <span className="sheets-stat-sep">/</span>
              <span className="sheets-stat-tot">{allProgress.total}</span>
              <span className="sheets-stat-pct">{allProgress.done_pct}%</span>
            </div>
          </div>
          {onOpenList && activeRoot && (
            <button
              type="button"
              className="btn btn-ghost sheets-open-btn"
              style={{ width: 'auto', padding: '10px 16px' }}
              onClick={() => onOpenList(activeRoot.id)}
              title="Open this list in the classic detail view"
            >
              <span>Open</span><span className="arrow">↗</span>
            </button>
          )}
        </div>
      </div>

      {/* The list itself — the full ListDetailV2 locked to spreadsheet
          mode and stripped of its own header/dashboard (the topbar above
          covers that). Row clicks open the same item modal as everywhere
          else, with prev/next, comments, votes, images and inline edits. */}
      <div className="sheets-body">
        {showDashboard ? (
          <SheetsDashboard onReorderRoots={onReorderRoots} roots={roots} myUserId={myUserId} onSelectRoot={onSelectRoot} onOpenList={onOpenList} onCreateList={onCreateList} />
        ) : typeof window.ListDetailV2 === 'function' ? (
          <window.ListDetailV2
            key={activeRoot.id}
            rootId={activeRoot.id}
            embedded
            forceViewMode="sheet"
            productMap={productMap}
            myUserId={myUserId}
            onBack={null}
            onOpenProduct={onOpenProduct}
            onUpdateListContext={onUpdateListContext}
            onNavigateAdd={onNavigateAdd}
            onShare={onShare}
            userLists={userLists}
            onSwitchList={onSelectRoot}
          />
        ) : (
          <div className="empty"><p>Loading list…</p></div>
        )}
      </div>

      {/* Bottom tab strip — one chip per list, Excel-style. */}
      <div className="sheets-tabs" role="tablist" aria-label="Your lists">
        <button type="button" role="tab" aria-selected={showDashboard} className={`sheets-tab sheets-tab--dash${showDashboard ? ' is-active' : ''}`} onClick={() => onSelectRoot(null)} title="Overview of every list">
          <span className="sheets-tab-icon" aria-hidden="true">▦</span><span className="sheets-tab-name">Dashboard</span>
        </button>
        {roots.map(r => {
          const p = progressForRoot(r);
          const active = activeRoot && activeRoot.id === r.id;
          return (
            <button
              key={r.id}
              role="tab"
              aria-selected={active}
              className={`sheets-tab${active ? ' is-active' : ''}${tabDnd.classFor(r.id)}`}
              onClick={() => onSelectRoot(r.id)}
              title={`${r.name} — ${p.have}/${p.total} done · drag to reorder`}
              {...tabDnd.propsFor(r.id)}
            >
              {r.coverIcon && <span className="sheets-tab-icon">{r.coverIcon}</span>}
              <span className="sheets-tab-name">{r.name}</span>
              <span className="sheets-tab-count">{p.have}/{p.total}</span>
            </button>
          );
        })}
        <button
          type="button"
          className="sheets-tab sheets-tab--add"
          onClick={onCreateList}
          title="Create a new list"
        >
          <span>+</span>
        </button>
      </div>
    </div>
  );
}

window.SheetsPage = SheetsPage;
