// tab-pipeline.jsx — Pipeline as a Google-Sheets-style editable grid.
// One sheet (universe folds in as Stage=Identified), pivot tabs that group rows,
// full inline cell editing, dropdown tag pickers, add-column, mocked Google sync.
const { useState, useEffect, useRef, useMemo, useCallback } = React;
const RS = window.RaiseShell;
const Pipe = window.RaisePipe;
const PStore = Pipe.store;

function usePipe(){ const [,f] = useState(0); useEffect(() => PStore.subscribe(() => f(x => x+1)), []); return PStore.getState(); }

// ── small bits ────────────────────────────────────────────────────
function Tag({ optsKey, value, cls }){
  if(!value) return null;
  const m = PStore.optMeta(optsKey, value);
  const c = m ? m.c : '#9b9285';
  return <span className={cls||'gtag'} style={{ color:c, background: c+'24' }}>{value}</span>;
}

const GoogleG = ({ size=14 }) => (
  <svg viewBox="0 0 48 48" width={size} height={size}>
    <path fill="#4285F4" d="M45 24c0-1.6-.1-2.7-.4-3.9H24v7.4h12c-.2 1.9-1.5 4.8-4.3 6.7l6.6 5.1C42.2 41.6 45 33.6 45 24z"/>
    <path fill="#34A853" d="M24 46c5.9 0 10.8-1.9 14.4-5.3l-6.6-5.1c-1.8 1.2-4.2 2-7.8 2-6 0-11-4-12.8-9.5l-6.8 5.2C8 40.6 15.4 46 24 46z"/>
    <path fill="#FBBC05" d="M11.2 28.1c-.5-1.4-.7-2.8-.7-4.1s.3-2.8.7-4.1l-6.8-5.3C2.9 17.5 2 20.6 2 24s.9 6.5 2.4 9.4l6.8-5.3z"/>
    <path fill="#EA4335" d="M24 10.5c3.3 0 5.5 1.4 6.8 2.6l5.9-5.7C33.1 4 28.6 2 24 2 15.4 2 8 7.4 4.4 14.6l6.8 5.3C13 14.5 18 10.5 24 10.5z"/>
  </svg>
);

// ── Google sync pill + popover ────────────────────────────────────
function SyncPill({ sync }){
  const [open, setOpen] = useState(false);
  const [rect, setRect] = useState(null);
  const ref = useRef(null);
  const openPop = () => { const r = ref.current.getBoundingClientRect(); setRect(r); setOpen(o => !o); };
  const doSync = () => PStore.syncNow(() => RS.toast('Pushed to Google · Sheet + Contacts up to date'));
  return (
    <>
      <button className="syncpill" ref={ref} onClick={openPop} title="Google sync">
        <span className="gdot">{sync.syncing ? <svg className="spin" viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="var(--primary)" strokeWidth="2" strokeLinecap="round"><path d="M8 2.5a5.5 5.5 0 1 0 5.5 5.5" /></svg> : <GoogleG />}</span>
        {sync.syncing ? <span>Syncing…</span>
          : sync.pending > 0 ? <><span>Google</span><span className="pend">{sync.pending} pending</span></>
          : <><span>Google</span><span className="ok">· synced</span></>}
      </button>
      {open && rect && (
        <>
          <div className="combo-back" onClick={() => setOpen(false)} />
          <div className="syncpop" style={{ top: rect.bottom+6, right: window.innerWidth - rect.right }} onClick={e => e.stopPropagation()}>
            <h4>Google sync</h4>
            <div className="sub">{sync.pending > 0 ? `${sync.pending} change${sync.pending>1?'s':''} not yet pushed` : `Last synced ${Pipe.relTime(sync.lastSync)}`}</div>
            <div className="synctarget">
              <span className="ti" style={{ background:'#e6f0e3' }}><svg viewBox="0 0 24 24" width="17" height="17" fill="#34A853"><path d="M19 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2zm-9 14H7v-2h3v2zm0-4H7v-2h3v2zm0-4H7V7h3v2zm7 8h-5v-2h5v2zm0-4h-5v-2h5v2zm0-4h-5V7h5v2z"/></svg></span>
              <div className="tm"><div className="tn">Google Sheets</div><div className="ts">Fund II — Pipeline · {PStore.getRows().length} rows</div></div>
              <button className={'toggle'+(sync.sheets?' on':'')} onClick={() => PStore.toggleSyncTarget('sheets')}><i /></button>
            </div>
            <div className="synctarget">
              <span className="ti" style={{ background:'#e7f0fc' }}><svg viewBox="0 0 24 24" width="17" height="17" fill="#4285F4"><path d="M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zm0 2c-3.3 0-8 1.7-8 5v2h16v-2c0-3.3-4.7-5-8-5z"/></svg></span>
              <div className="tm"><div className="tn">Google Contacts</div><div className="ts">Every contact mirrored as a card</div></div>
              <button className={'toggle'+(sync.contacts?' on':'')} onClick={() => PStore.toggleSyncTarget('contacts')}><i /></button>
            </div>
            <div className="syncfoot">
              <button className="btn primary sm" disabled={sync.syncing} onClick={doSync}>{sync.syncing ? 'Syncing…' : 'Sync now'}</button>
            </div>
          </div>
        </>
      )}
    </>
  );
}

// ── dropdown / multi-select cell editor popover ───────────────────
function CellPicker({ pop, rows, onClose }){
  const [q, setQ] = useState('');
  const [confirmDel, setConfirmDel] = useState(null);
  const col = pop.col;
  const opts = PStore.options(col.opts);
  const row = rows.find(r => r.id === pop.rowId);
  const multi = col.type === 'multi';
  const cur = multi ? (row[col.key] || []) : row[col.key];
  const ql = q.trim().toLowerCase();
  const filtered = ql ? opts.filter(o => o.v.toLowerCase().includes(ql)) : opts;
  const exact = opts.some(o => o.v.toLowerCase() === ql);
  // how many rows currently use each option value (so deleting is informed)
  const usage = (v) => rows.reduce((n,r) => n + (multi ? ((r[col.key]||[]).includes(v)?1:0) : (r[col.key]===v?1:0)), 0);
  const pick = (v) => {
    if(multi){
      const set = new Set(cur); set.has(v) ? set.delete(v) : set.add(v);
      PStore.patchCell(pop.rowId, col.key, [...set]); setQ('');
    } else { PStore.patchCell(pop.rowId, col.key, v); onClose(); }
  };
  const createNew = () => { const v = q.trim(); if(!v) return; PStore.addOption(col.opts, v); pick(v); };
  const style = (() => {
    const left = Math.min(pop.rect.left, window.innerWidth - 296);
    const below = window.innerHeight - pop.rect.bottom > 280;
    return below ? { left, top: pop.rect.bottom+4 } : { left, bottom: window.innerHeight - pop.rect.top + 4 };
  })();
  return (
    <>
      <div className="combo-back" style={{ zIndex:209 }} onClick={onClose} />
      <div className="cellpop" style={style} onClick={e => e.stopPropagation()}>
        <input className="psearch" autoFocus placeholder={multi ? 'Search or add…' : 'Search or type new…'} value={q}
          onChange={e => setQ(e.target.value)}
          onKeyDown={e => { if(e.key==='Enter'){ if(filtered.length===1 && !multi) pick(filtered[0].v); else if(ql && !exact) createNew(); } if(e.key==='Escape') onClose(); }} />
        <div className="plist">
          {filtered.map(o => {
            const on = multi ? cur.includes(o.v) : cur === o.v;
            if(confirmDel === o.v){
              const n = usage(o.v);
              return (
                <div key={o.v} className="popopt confirmdel">
                  <span style={{ flex:1 }}>Delete “{o.v}”{n>0?` · clears ${n} row${n===1?'':'s'}`:''}?</span>
                  <button className="cd-yes" onClick={() => { PStore.removeOption(col.opts, o.v); setConfirmDel(null); }}>Delete</button>
                  <button className="cd-no" onClick={() => setConfirmDel(null)}>Cancel</button>
                </div>
              );
            }
            return (
              <div key={o.v} className={'popopt-row'+(on?' on':'')}>
                <button className="popopt" onClick={() => pick(o.v)}>
                  <span className="obox" style={{ background:o.c }} />
                  <span style={{ flex:1 }}>{o.v}</span>
                  {on && <span className="check">✓</span>}
                </button>
                <button className="optdel" title="Delete this option" onClick={(e) => { e.stopPropagation(); setConfirmDel(o.v); }}>
                  <svg viewBox="0 0 14 14" width="13" height="13" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round"><path d="M3 3l8 8M11 3l-8 8"/></svg>
                </button>
              </div>
            );
          })}
          {ql && !exact && <button className="popnew" onClick={createNew}>+ Create “{q.trim()}”</button>}
        </div>
        {!multi && cur && <button className="popclear" onClick={() => { PStore.patchCell(pop.rowId, col.key, ''); onClose(); }}>Clear value</button>}
        {multi && <button className="popclear" onClick={onClose}>Done</button>}
      </div>
    </>
  );
}

// ── column header menu ────────────────────────────────────────────
function ColMenu({ menu, sort, onSort, onClose }){
  const col = menu.col;
  const [name, setName] = useState(col.label);
  const style = { left: Math.min(menu.rect.left, window.innerWidth - 196), top: menu.rect.bottom+4 };
  return (
    <>
      <div className="combo-back" style={{ zIndex:209 }} onClick={onClose} />
      <div className="colmenupop" style={style} onClick={e => e.stopPropagation()}>
        {!col.base && (<>
          <div className="cmlabel">Rename</div>
          <input className="cmrename" value={name} autoFocus onChange={e => setName(e.target.value)}
            onKeyDown={e => { if(e.key==='Enter'){ PStore.renameColumn(col.key, name.trim()||col.label); onClose(); } }} />
        </>)}
        <button onClick={() => { onSort(col.key, 'asc'); onClose(); }}>↑ Sort ascending</button>
        <button onClick={() => { onSort(col.key, 'desc'); onClose(); }}>↓ Sort descending</button>
        {sort.key === col.key && <button onClick={() => { onSort(null); onClose(); }}>✕ Clear sort</button>}
        {!col.base && <button className="del" onClick={() => { PStore.removeColumn(col.key); onClose(); }}>🗑 Delete column</button>}
      </div>
    </>
  );
}

// ── add-column modal ──────────────────────────────────────────────
const COL_TYPES = [
  { t:'text',     i:'T',  l:'Text' },
  { t:'number',   i:'#',  l:'Number' },
  { t:'currency', i:'$',  l:'Currency' },
  { t:'select',   i:'▾',  l:'Dropdown' },
  { t:'multi',    i:'≣',  l:'Multi-select' },
  { t:'date',     i:'▤',  l:'Date' },
  { t:'checkbox', i:'☑',  l:'Checkbox' },
];
function AddColModal({ onClose }){
  const [label, setLabel] = useState('');
  const [type, setType] = useState('text');
  const [options, setOptions] = useState([]);
  const [draft, setDraft] = useState('');
  const needsOpts = type === 'select' || type === 'multi';
  const addOpt = () => { const v = draft.trim(); if(v && !options.includes(v)) setOptions([...options, v]); setDraft(''); };
  const create = () => {
    if(!label.trim()) return;
    PStore.addColumn({ label: label.trim(), type, options, w: type==='checkbox'?90 : type==='select'||type==='multi'?150 : 150 });
    RS.toast('Column added · “'+label.trim()+'”');
    onClose();
  };
  return (
    <div className="modal-back" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        <div className="modal-hd">New column</div>
        <div className="modal-body">
          <div className="mfield">
            <label>Column name</label>
            <input className="minp" autoFocus placeholder="e.g. Accredited, Last contacted, Priority" value={label}
              onChange={e => setLabel(e.target.value)} onKeyDown={e => { if(e.key==='Enter' && !needsOpts) create(); }} />
          </div>
          <div className="mfield">
            <label>Type</label>
            <div className="typegrid">
              {COL_TYPES.map(ct => (
                <button key={ct.t} className={'typebtn'+(type===ct.t?' on':'')} onClick={() => setType(ct.t)}>
                  <span className="tyi">{ct.i}</span>{ct.l}
                </button>
              ))}
            </div>
          </div>
          {needsOpts && (
            <div className="mfield">
              <label>Options</label>
              <div className="optadd">
                {options.map((o,i) => (
                  <span key={o} className="opttag" style={{ color:Pipe.PALETTE[i%Pipe.PALETTE.length], background:Pipe.PALETTE[i%Pipe.PALETTE.length]+'24' }}>
                    {o}<button onClick={() => setOptions(options.filter(x => x!==o))}>✕</button>
                  </span>
                ))}
                <input className="optinp" placeholder="Add option + Enter" value={draft}
                  onChange={e => setDraft(e.target.value)}
                  onKeyDown={e => { if(e.key==='Enter'){ e.preventDefault(); addOpt(); } }} />
              </div>
            </div>
          )}
        </div>
        <div className="modal-foot">
          <button className="btn sm" onClick={onClose}>Cancel</button>
          <button className="btn primary sm" disabled={!label.trim()} onClick={create}>Add column</button>
        </div>
      </div>
    </div>
  );
}

// ── pivot value filter (Excel-style: pick one value of the grouping field) ──
const PIVOT_LABELS = { icp:'ICP', tier:'Tier', stage:'Stage', source:'Source' };
function PivotFilter({ pivot, value, values, counts, total, onChange }){
  const [open, setOpen] = useState(false);
  const [rect, setRect] = useState(null);
  const ref = useRef(null);
  const label = PIVOT_LABELS[pivot] || 'Value';
  const openIt = () => { setRect(ref.current.getBoundingClientRect()); setOpen(true); };
  const meta = (v) => PStore.optMeta(pivot, v) || {};
  const choose = (v) => { onChange(v); setOpen(false); };
  const menuStyle = rect ? { left: Math.min(rect.left, window.innerWidth - 244), top: rect.bottom + 5, minWidth: Math.max(rect.width, 210) } : {};
  return (
    <div className="pivotfilter" ref={ref}>
      <button className={'pf-field'+(open?' open':'')} onClick={openIt} title={'Filter by '+label}>
        <span className="pf-funnel" aria-hidden="true">
          <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M2.5 3.5h11l-4.2 5v4l-2.6 1.3v-5.3z"/></svg>
        </span>
        {value === '__all'
          ? <span className="pf-val all">All {label}s</span>
          : <Tag optsKey={pivot} value={value} cls="pf-tag" />}
        <svg className="pf-chev" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"><path d="M2.5 4.5L6 8l3.5-3.5"/></svg>
      </button>
      {open && rect && (
        <>
          <div className="combo-back" style={{ zIndex:209 }} onClick={() => setOpen(false)} />
          <div className="cellpop pf-menu" style={menuStyle} onClick={e => e.stopPropagation()}>
            <div className="pf-head mono">{label} · {values.length} value{values.length===1?'':'s'}</div>
            <div className="plist">
              <button className={'popopt'+(value==='__all'?' on':'')} onClick={() => choose('__all')}>
                <span className="obox" style={{ background:'var(--ink-3)' }} />
                <span style={{ flex:1 }}>All {label}s</span>
                <span className="pf-count mono">{total}</span>
                {value==='__all' && <span className="check">✓</span>}
              </button>
              {values.map(v => (
                <button key={v} className={'popopt'+(value===v?' on':'')} onClick={() => choose(v)}>
                  <span className="obox" style={{ background: meta(v).c || '#9b9285' }} />
                  <span style={{ flex:1 }}>{v}</span>
                  <span className="pf-count mono">{counts[v] || 0}</span>
                  {value===v && <span className="check">✓</span>}
                </button>
              ))}
            </div>
          </div>
        </>
      )}
    </div>
  );
}

// ── inline cell editor: focuses + shows a blinking caret on every mount ──
function CellEditor({ initial, isNum, isDate, onCommit, onCancel }){
  const ref = useRef(null);
  const [val, setVal] = useState(initial == null ? '' : String(initial));
  useEffect(() => {
    const el = ref.current;
    if(!el) return;
    el.focus();
    // place caret at the end so it blinks ready for typing (don't select-all)
    if(!isDate){ const n = el.value.length; try { el.setSelectionRange(n, n); } catch(e){} }
  }, []);
  return (
    <input ref={ref} className={'gcell-input'+(isNum?' num':'')} value={val}
      type={isDate?'date':'text'}
      onChange={e => setVal(e.target.value)}
      onBlur={() => onCommit(val, null)}
      onKeyDown={e => {
        if(e.key==='Enter'){ e.preventDefault(); onCommit(val, {dr:1,dc:0}); }
        else if(e.key==='Tab'){ e.preventDefault(); onCommit(val, {dr:0,dc:e.shiftKey?-1:1}); }
        else if(e.key==='Escape'){ e.preventDefault(); onCancel(); }
      }} />
  );
}

// ── value display formatting ──────────────────────────────────────
function fmt(col, v){
  if(col.type === 'currency') return v==null||v==='' ? '' : RS.money(v);
  if(col.type === 'number') return v==null||v==='' ? '' : String(v);
  return v==null ? '' : String(v);
}

// ── outreach-track cell + picker (links a contact to a sequence) ───
function trackColor(seq){ return window.OX.ch((seq.steps[0]||{}).ch).color; }

// ── copyable contact-channel cell (value shown + editable + one-click copy) ─
function CopyCell({ row, col }){
  const v = row[col.key];
  if(v==null || v===''){ return <div className="gcell gcopy editable empty"><span className="empty">—</span></div>; }
  const ch = (window.OX && col.channel) ? window.OX.ch(col.channel) : null;
  const copy = (e) => { e.stopPropagation(); try{ navigator.clipboard.writeText(String(v)); }catch(x){} RS.toast(col.label+' copied to clipboard'); };
  return (
    <div className="gcell gcopy editable">
      {ch && <span className="cp-gl mono" style={{ background:ch.color }} title={ch.label}>{ch.glyph}</span>}
      <span className="cp-val" title={String(v)}>{v}</span>
      <button className="cp-btn" title={'Copy '+col.label} onClick={copy} onDoubleClick={e => e.stopPropagation()}>
        <svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" strokeWidth="1.5"><rect x="5.5" y="5.5" width="8" height="9" rx="1.5"/><path d="M11 5.5V4A1.5 1.5 0 0 0 9.5 2.5h-5A1.5 1.5 0 0 0 3 4v7a1.5 1.5 0 0 0 1.5 1.5"/></svg>
      </button>
    </div>
  );
}
function TrackCell({ row }){
  const OX = window.OX;
  if(!row.isContact) return <div className="gcell"><span className="empty">—</span></div>;
  const id = OX.Store.seqOf(row.id);
  if(!id) return <div className="gcell gtrack"><span className="trk-empty">＋ Enroll…</span></div>;
  const s = OX.Store.seq(id);
  if(!s) return <div className="gcell"><span className="empty">—</span></div>;
  const c = trackColor(s);
  return (
    <div className="gcell gtrack">
      <span className="trk-tag" style={{ color:c, background:c+'1f' }}>
        <span className="trk-dot mono" style={{ background:c }}>{s.id}</span>{s.name}
      </span>
    </div>
  );
}
function TrackPicker({ pop, onClose }){
  const OX = window.OX;
  const seqs = OX.Store.seqs();
  const cur = OX.Store.seqOf(pop.rowId);
  const row = pop.row;
  const pick = (seqId) => {
    OX.Store.setEnroll(pop.rowId, seqId);
    const s = seqId ? seqs.find(x => x.id===seqId) : null;
    RS.toast(s ? `${row.name} → ${s.id} · ${s.name}` : `${row.name} removed from sequences`);
    onClose();
  };
  const style = (() => {
    const left = Math.min(pop.rect.left, window.innerWidth - 312);
    const below = window.innerHeight - pop.rect.bottom > 340;
    return below ? { left, top: pop.rect.bottom+4 } : { left, bottom: window.innerHeight - pop.rect.top + 4 };
  })();
  return (
    <>
      <div className="combo-back" style={{ zIndex:209 }} onClick={onClose} />
      <div className="cellpop trackpop" style={style} onClick={e => e.stopPropagation()}>
        <div className="pf-head mono">Outreach track · enroll {row.name}</div>
        <div className="plist">
          <button className={'popopt'+(!cur?' on':'')} onClick={() => pick('')}>
            <span className="obox" style={{ background:'var(--ink-3)' }} />
            <span style={{ flex:1 }}>Not enrolled</span>
            {!cur && <span className="check">✓</span>}
          </button>
          {seqs.map(s => {
            const c = trackColor(s); const on = cur===s.id; const live = OX.contactsFor(s.id).length;
            return (
              <button key={s.id} className={'popopt trackopt'+(on?' on':'')} onClick={() => pick(s.id)}>
                <span className="trk-badge mono" style={{ background:c }}>{s.id}</span>
                <span className="trk-pmeta">
                  <span className="trk-pnm">{s.name}</span>
                  <span className="trk-psub">{s.icp} · {s.replyRate}% reply · {live} live</span>
                </span>
                {on && <span className="check">✓</span>}
              </button>
            );
          })}
        </div>
      </div>
    </>
  );
}

// ── main ──────────────────────────────────────────────────────────
function TabPipeline(){
  const st = usePipe();
  const cols = PStore.cols();
  const rows = st.rows;
  const Store = window.RaiseStore;
  const [,oxBump] = useState(0);
  useEffect(() => { if(window.OX) return window.OX.Store.subscribe(() => oxBump(x => x+1)); }, []);

  const [pivot, setPivot] = useState('all');           // all | icp | tier | stage | source
  const [pivotVal, setPivotVal] = useState('__all');   // Excel-style single-value filter for the active pivot ('__all' = show every value)
  const [query, setQuery] = useState('');
  const [view, setView] = useState(() => localStorage.getItem('raiseos.pipeview') || 'grid');   // grid | board
  const [boardLayout, setBoardLayout] = useState(() => localStorage.getItem('raiseos.boardlayout') || 'comfortable');
  const setViewP = (v) => { setView(v); localStorage.setItem('raiseos.pipeview', v); };
  const setLayoutP = (v) => { setBoardLayout(v); localStorage.setItem('raiseos.boardlayout', v); };
  const [sort, setSort] = useState({ key:null, dir:'asc' });
  const [collapsed, setCollapsed] = useState(() => new Set());
  const [sel, setSel] = useState(null);                // {rowId, colKey}
  const [editing, setEditing] = useState(false);
  const [editVal, setEditVal] = useState('');
  const [pop, setPop] = useState(null);                // dropdown picker
  const [trackPop, setTrackPop] = useState(null);      // outreach-track picker
  const [colMenu, setColMenu] = useState(null);
  const [addCol, setAddCol] = useState(false);
  const [liveW, setLiveW] = useState(null);            // {key,w} while dragging a column edge
  const [tip, setTip] = useState(null);                // {text,x,y} truncated-text hover box
  const [dragCol, setDragCol] = useState(null);        // key of column being dragged to reorder
  const [overCol, setOverCol] = useState(null);        // key of the column we'd drop before
  const gridRef = useRef(null);
  const cellRefs = useRef({});                          // `${rowId}:${colKey}` -> td

  const setSortFn = (key, dir) => setSort(key==null ? { key:null, dir:'asc' } : { key, dir });
  const toggleSort = (key) => setSort(s => s.key!==key ? { key, dir:'asc' } : s.dir==='asc' ? { key, dir:'desc' } : { key:null, dir:'asc' });

  // filter + sort
  const filtered = useMemo(() => {
    let out = rows;
    const q = query.trim().toLowerCase();
    if(q) out = out.filter(r => cols.some(c => String(r[c.key]??'').toLowerCase().includes(q)));
    if(sort.key){
      const col = cols.find(c => c.key === sort.key);
      const num = col && col.num;
      out = [...out].sort((a,b) => {
        let x = a[sort.key], y = b[sort.key];
        if(num){ x = x==null?-Infinity:+x; y = y==null?-Infinity:+y; return sort.dir==='asc'? x-y : y-x; }
        x = String(x??'').toLowerCase(); y = String(y??'').toLowerCase();
        return sort.dir==='asc' ? x.localeCompare(y) : y.localeCompare(x);
      });
    }
    return out;
  }, [rows, cols, query, sort]);

  // distinct values actually present in the grouping column — always reflects the whole table (Excel auto-filter)
  const pivotValues = useMemo(() => {
    if(pivot === 'all') return [];
    const present = []; const seen = new Set();
    rows.forEach(r => { const v = r[pivot]; if(v && !seen.has(v)){ seen.add(v); present.push(v); } });
    const order = PStore.options(pivot).map(o => o.v);
    const ordered = order.filter(v => seen.has(v));
    const extras = present.filter(v => !order.includes(v)).sort((a,b) => a.localeCompare(b));
    return [...ordered, ...extras];
  }, [rows, pivot, st.options]);

  const pivotCounts = useMemo(() => {
    const c = {};
    if(pivot !== 'all') rows.forEach(r => { const v = r[pivot]; if(v) c[v] = (c[v]||0) + 1; });
    return c;
  }, [rows, pivot]);

  // switching pivots picks the first present value, so exactly one value shows (per request)
  const choosePivot = (id) => {
    setPivot(id);
    if(id === 'all'){ setPivotVal('__all'); return; }
    const seen = new Set(); rows.forEach(r => { const v = r[id]; if(v) seen.add(v); });
    const order = PStore.options(id).map(o => o.v).filter(v => seen.has(v));
    const extras = [...seen].filter(v => !order.includes(v)).sort((a,b) => a.localeCompare(b));
    const list = [...order, ...extras];
    setPivotVal(list[0] || '__all');
  };

  // group
  const groups = useMemo(() => {
    if(pivot === 'all') return [{ key:'__all', label:null, color:null, rows: filtered }];
    // single-value filter: show only the chosen value, flat (no group header)
    if(pivotVal !== '__all') return [{ key:'val:'+pivotVal, label:null, color:null, rows: filtered.filter(r => r[pivot] === pivotVal) }];
    const opts = PStore.options(pivot);
    const order = opts.map(o => o.v);
    const map = {}; const none = [];
    filtered.forEach(r => { const v = r[pivot]; if(!v) none.push(r); else (map[v] = map[v]||[]).push(r); });
    const keys = [];
    order.forEach(v => { if(map[v]){ keys.push(v); delete map[v]; } });
    Object.keys(map).forEach(v => keys.push(v));
    const g = keys.map(v => ({ key:v, label:v, color:(PStore.optMeta(pivot,v)||{}).c, rows: map[v] || filtered.filter(r=>r[pivot]===v) }));
    if(none.length) g.push({ key:'__none', label:'(none)', color:null, rows: none, none:true });
    return g;
  }, [filtered, pivot, pivotVal, st.options]);

  // rows actually on screen (respects the pivot value filter) — drives totals + footer count
  const shownRows = useMemo(() => { const s = []; groups.forEach(g => g.rows.forEach(r => s.push(r))); return s; }, [groups]);

  // flat list of visible (non-collapsed) rows for keyboard nav
  const flatRows = useMemo(() => {
    const out = [];
    groups.forEach(g => { if(!collapsed.has(g.key)) g.rows.forEach(r => out.push(r)); });
    return out;
  }, [groups, collapsed]);

  const navRef = useRef({ flatRows, cols });
  navRef.current = { flatRows, cols };

  const focusGrid = () => { if(gridRef.current) gridRef.current.focus(); };
  const selectCell = (rowId, colKey, opts={}) => {
    setSel({ rowId, colKey }); setEditing(false);
    const col = cols.find(c => c.key === colKey);
    if(opts.openEditor){
      if(col.type === 'select' || col.type === 'multi'){ openPicker(rowId, col); }
      else if(col.type === 'track'){ openTrackPicker(rowId, col); }
      else if(col.type === 'checkbox'){ const r = PStore.getRows().find(x=>x.id===rowId) || {}; PStore.patchCell(rowId, colKey, !r[colKey]); }
      else { beginEdit(rowId, col); }   // text / number / currency / date / contact (inline name entry)
    } else { setTimeout(focusGrid, 0); }
  };
  const openPicker = (rowId, col) => {
    const td = cellRefs.current[`${rowId}:${col.key}`];
    if(td) setPop({ rowId, col, rect: td.getBoundingClientRect() });
  };
  const openTrackPicker = (rowId, col) => {
    const row = PStore.getRows().find(r => r.id===rowId);
    if(row && !row.isContact){ RS.toast('Only active contacts can be enrolled — move them out of the universe first'); return; }
    const td = cellRefs.current[`${rowId}:${col.key}`];
    if(td) setTrackPop({ rowId, row, rect: td.getBoundingClientRect() });
  };
  const beginEdit = (rowId, col, initial) => {
    const r = PStore.getRows().find(x => x.id === rowId) || {};
    setSel({ rowId, colKey: col.key });
    setEditVal(initial!=null ? initial : (r[col.key]==null ? '' : String(r[col.key])));
    setEditing(true);
  };
  const commitEdit = (value, move) => {
    if(sel && editing){
      const col = cols.find(c => c.key === sel.colKey);
      let v = value == null ? '' : value;
      if(col.num){ const s = String(v); v = s.trim()===''? null : parseFloat(s.replace(/[^0-9.\-]/g,'')); if(isNaN(v)) v = null; }
      PStore.patchCell(sel.rowId, sel.colKey, v);
    }
    setEditing(false);
    if(move) moveSel(move.dr, move.dc);
    setTimeout(focusGrid, 0);
  };
  // commit a value typed into the Excel-style formula bar (text-like columns only)
  const commitBar = (value) => {
    if(!sel) return;
    const col = cols.find(c => c.key === sel.colKey);
    if(!col || col.type==='select' || col.type==='multi' || col.type==='track' || col.type==='checkbox') return;
    let v = value == null ? '' : value;
    if(col.num){ const s = String(v); v = s.trim()===''? null : parseFloat(s.replace(/[^0-9.\-]/g,'')); if(isNaN(v)) v = null; }
    const row = PStore.getRows().find(r => r.id === sel.rowId);
    const cur = row ? (col.num ? (row[col.key]==null?null:row[col.key]) : (row[col.key]==null?'':String(row[col.key]))) : '';
    if(String(cur) === String(v)) return;   // no-op (e.g. Enter then blur) — don't double-commit or pollute undo
    PStore.patchCell(sel.rowId, sel.colKey, v);
  };
  const moveSel = (dr, dc) => {
    const { flatRows, cols } = navRef.current;
    if(!sel){ if(flatRows[0]) setSel({ rowId: flatRows[0].id, colKey: cols[0].key }); return; }
    let ri = flatRows.findIndex(r => r.id === sel.rowId);
    let ci = cols.findIndex(c => c.key === sel.colKey);
    if(ri<0) ri = 0; if(ci<0) ci = 0;
    ri = Math.max(0, Math.min(flatRows.length-1, ri+dr));
    ci = Math.max(0, Math.min(cols.length-1, ci+dc));
    const next = flatRows[ri];
    if(next) setSel({ rowId: next.id, colKey: cols[ci].key });
  };

  const onGridKey = (e) => {
    if(editing || pop || colMenu || addCol) return;
    if(!sel) return;
    const col = cols.find(c => c.key === sel.colKey);
    const k = e.key;
    if(k==='ArrowDown'){ e.preventDefault(); moveSel(1,0); }
    else if(k==='ArrowUp'){ e.preventDefault(); moveSel(-1,0); }
    else if(k==='ArrowRight'){ e.preventDefault(); moveSel(0,1); }
    else if(k==='ArrowLeft'){ e.preventDefault(); moveSel(0,-1); }
    else if(k==='Tab'){ e.preventDefault(); moveSel(0, e.shiftKey?-1:1); }
    else if(k==='Enter'){
      e.preventDefault();
      if(col.type==='select'||col.type==='multi') openPicker(sel.rowId, col);
      else if(col.type==='track') openTrackPicker(sel.rowId, col);
      else if(col.type==='checkbox'){ const r = rows.find(x=>x.id===sel.rowId); PStore.patchCell(sel.rowId, sel.colKey, !r[sel.colKey]); }
      else if(col.type!=='contact'||true) beginEdit(sel.rowId, col);
    }
    else if(k==='Backspace'||k==='Delete'){
      e.preventDefault();
      if(col.type==='multi') PStore.patchCell(sel.rowId, sel.colKey, []);
      else if(col.type==='checkbox') PStore.patchCell(sel.rowId, sel.colKey, false);
      else PStore.patchCell(sel.rowId, sel.colKey, col.num?null:'');
    }
    else if(k.length===1 && !e.metaKey && !e.ctrlKey && !e.altKey){
      if(col.type==='select'||col.type==='multi'){ openPicker(sel.rowId, col); }
      else if(col.type!=='checkbox'){ e.preventDefault(); beginEdit(sel.rowId, col, k); }
    }
  };

  const toggleGroup = (key) => setCollapsed(s => { const n = new Set(s); n.has(key)?n.delete(key):n.add(key); return n; });

  const openProfile = (row) => { if(row.isContact) Store.openPerson(row.id); };

  // totals (only the rows currently shown)
  const totalCheck = shownRows.reduce((s,r) => s + (typeof r.check==='number'? r.check : 0), 0);

  // width of a column, honoring an in-progress drag
  const wOf = (col) => (liveW && liveW.key===col.key) ? liveW.w : col.w;
  // drag a column's right edge to resize; commit to the store on release
  const startResize = (e, col) => {
    e.preventDefault(); e.stopPropagation();
    const startX = e.clientX, startW = col.w;
    document.body.style.cursor = 'col-resize'; document.body.style.userSelect = 'none';
    const onMove = (ev) => setLiveW({ key: col.key, w: Math.max(72, startW + (ev.clientX - startX)) });
    const onUp = (ev) => {
      const w = Math.max(72, startW + (ev.clientX - startX));
      window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp);
      document.body.style.cursor = ''; document.body.style.userSelect = '';
      setLiveW(null); PStore.setColWidth(col.key, w);
    };
    window.addEventListener('mousemove', onMove); window.addEventListener('mouseup', onUp);
  };
  const autoFit = (col) => {  // double-click the grip → reset to default width
    const def = PStore.cols(); // base widths live in BASE_COLS; clear override
    PStore.setColWidth(col.key, (window.RaisePipe.BASE_COLS.find(b=>b.key===col.key)||{}).w || 150);
  };
  // show a hover box with full text only when the cell content is actually clipped
  const showTip = (e, text) => {
    const el = e.currentTarget;
    if(el.scrollWidth > el.clientWidth + 1){
      const r = el.getBoundingClientRect();
      setTip({ text, x: Math.round(r.left), y: Math.round(r.bottom + 5) });
    }
  };
  const hideTip = () => setTip(null);

  // drag a column header onto another to reorder (the frozen Contact column stays pinned)
  const onColDragStart = (e, col) => {
    if(col.frozen){ e.preventDefault(); return; }
    setDragCol(col.key); hideTip(); e.dataTransfer.effectAllowed = 'move';
    try { e.dataTransfer.setData('text/plain', col.key); } catch(x){}
  };
  const onColDragOver = (e, col) => {
    if(!dragCol || col.frozen) return;
    e.preventDefault(); e.dataTransfer.dropEffect = 'move';
    if(overCol !== col.key) setOverCol(col.key);
  };
  const onColDrop = (e, col) => {
    e.preventDefault();
    const k = dragCol; setOverCol(null); setDragCol(null);
    if(!k || col.frozen || k === col.key) return;
    PStore.moveColumn(k, col.key);
  };
  const onColDragEnd = () => { setDragCol(null); setOverCol(null); };

  const gutterW = 42;
  const tableW = gutterW + cols.reduce((s,c) => s+wOf(c), 0) + 44;
  const colSpanAll = cols.length + 2;

  const PIVOTS = [['all','All'],['icp','By ICP'],['tier','By Tier'],['stage','By Stage'],['source','By Source']];

  // ── cell renderer ──
  const renderCell = (row, col) => {
    const isSel = sel && sel.rowId===row.id && sel.colKey===col.key;
    const v = row[col.key];
    const refKey = `${row.id}:${col.key}`;
    const tdProps = {
      key: col.key, ref: el => { cellRefs.current[refKey] = el; },
      className: (col.frozen?'gfrozen ':'') + (isSel?'sel':'') + (isSel&&editing?' editing':''),
      style: { width: wOf(col), minWidth: wOf(col), maxWidth: wOf(col) },
      onClick: () => selectCell(row.id, col.key, { openEditor: col.type==='select'||col.type==='multi'||col.type==='track' }),
      onDoubleClick: () => { if(col.type==='text'||col.type==='number'||col.type==='currency'||col.type==='date'||col.type==='copy') selectCell(row.id, col.key, { openEditor:true }); },
    };
    // editing input
    if(isSel && editing && (col.type==='text'||col.type==='number'||col.type==='currency'||col.type==='date'||col.type==='contact'||col.type==='copy')){
      return (
        <td {...tdProps}>
          <CellEditor
            key={refKey}
            initial={editVal}
            isNum={!!col.num}
            isDate={col.type==='date'}
            onCommit={(val, move) => commitEdit(val, move)}
            onCancel={() => { setEditing(false); setTimeout(focusGrid,0); }} />
        </td>
      );
    }
    let inner;
    if(col.type==='contact'){
      inner = (
        <div className="gcontact">
          <span className={'ava'+(row.anchor?' anchor':'')}>{RS.initials(v||'· ·')}{row.anchor && <i className="astar">★</i>}</span>
          <span className={'gnm'+(row.isContact?' link':'')+(!v?' placeholder':'')}
            onMouseEnter={e => showTip(e, v || 'Untitled')} onMouseLeave={hideTip}
            onClick={e => { if(row.isContact){ e.stopPropagation(); openProfile(row); } }}>
            {v || 'Untitled'}
          </span>
        </div>
      );
    } else if(col.type==='select'){
      inner = <div className="gcell">{v ? <Tag optsKey={col.opts} value={v} /> : <span className="empty">—</span>}</div>;
    } else if(col.type==='track'){
      inner = <TrackCell row={row} />;
    } else if(col.type==='copy'){
      inner = <CopyCell row={row} col={col} />;
    } else if(col.type==='multi'){
      const arr = Array.isArray(v)?v:[];
      inner = <div className="gcell"><div className="gtags">{arr.length? arr.map(x => <Tag key={x} optsKey={col.opts} value={x} cls="gmtag" />) : <span className="empty">—</span>}</div></div>;
    } else if(col.type==='checkbox'){
      inner = <div className="gcell" style={{justifyContent:'center'}}><span className={'gcheck'+(v?' on':'')} onClick={e => { e.stopPropagation(); PStore.patchCell(row.id, col.key, !v); }}>{v?'✓':''}</span></div>;
    } else {
      const disp = fmt(col, v);
      inner = <div className={'gcell editable'+(col.num?' num':'')+(disp===''?' empty':'')}><span className="ctxt" onMouseEnter={e => disp!=='' && showTip(e, disp)} onMouseLeave={hideTip}>{disp===''?'—':disp}</span></div>;
    }
    return <td {...tdProps}>{inner}</td>;
  };

  return (
    <div className="sheetview">
      <RS.TopBar title="Pipeline" sub={`${rows.length} contacts · ${rows.filter(r=>r.stage!=='Identified').length} active · ${rows.filter(r=>r.stage==='Identified').length} in universe`}>
        <div className="viewtoggle">
          <button className={view==='grid'?'on':''} onClick={() => setViewP('grid')} title="Spreadsheet">
            <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5"><rect x="2" y="2.5" width="12" height="11" rx="1.5"/><path d="M2 6h12M2 9.5h12M6 6v7.5"/></svg>Grid
          </button>
          <button className={view==='board'?'on':''} onClick={() => setViewP('board')} title="Kanban board">
            <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5"><rect x="2" y="2.5" width="3.5" height="11" rx="1"/><rect x="6.5" y="2.5" width="3.5" height="8" rx="1"/><rect x="11" y="2.5" width="3.5" height="6" rx="1"/></svg>Board
          </button>
        </div>
        <SyncPill sync={st.sync} />
      </RS.TopBar>

      {view==='board' ? (
        <>
          <div className="boardtools">
            <span className="bt-hint">Drag a card between columns to advance or regress a stage.</span>
            <div className="layoutseg">
              {[['comfortable','Comfortable'],['compact','Compact'],['swimlanes','Swimlanes']].map(([id,lbl]) =>
                <button key={id} className={boardLayout===id?'on':''} onClick={() => setLayoutP(id)}>{lbl}</button>)}
            </div>
          </div>
          <window.PipelineBoard rows={filtered} layout={boardLayout} onOpen={openProfile} />
        </>
      ) : (
      <>
      <div className="sheetbar">
        <div className="pivot">
          {PIVOTS.map(([id,lbl]) => <button key={id} className={pivot===id?'on':''} onClick={() => choosePivot(id)}>{lbl}</button>)}
        </div>
        {pivot !== 'all' && (
          <PivotFilter pivot={pivot} value={pivotVal} values={pivotValues} counts={pivotCounts} total={rows.length} onChange={setPivotVal} />
        )}
        <div className="sheetsearch">
          <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6"><circle cx="7" cy="7" r="4.5"/><path d="M11 11l3 3" strokeLinecap="round"/></svg>
          <input placeholder="Search the sheet…" value={query} onChange={e => setQuery(e.target.value)} />
          {query && <button className="tdclear" onClick={() => setQuery('')}>✕</button>}
        </div>
        <div className="spacer" />
        <button className="btn sm" onClick={() => setAddCol(true)}>＋ Column</button>
      </div>

      {(() => {
        const selRow = sel && rows.find(r => r.id === sel.rowId);
        const selCol = sel && cols.find(c => c.key === sel.colKey);
        const ref = (selRow && selCol) ? `${selRow.name || 'Untitled'} · ${selCol.label}` : 'No cell selected';
        const isPicker = selCol && (selCol.type==='select' || selCol.type==='multi' || selCol.type==='track' || selCol.type==='checkbox');
        const raw = (selRow && selCol) ? selRow[selCol.key] : '';
        const disp = !selCol ? '' :
          selCol.type==='multi' ? (Array.isArray(raw) ? raw.join(', ') : '') :
          selCol.type==='checkbox' ? (raw ? 'TRUE' : 'FALSE') :
          (raw==null ? '' : String(raw));
        return (
          <div className="formulabar">
            <div className="fb-ref mono" title={ref}>{ref}</div>
            <span className="fb-fx">ƒx</span>
            {!sel ? (
              <div className="fb-empty">Click a cell to edit its content here</div>
            ) : isPicker ? (
              <button className="fb-picker" onClick={() => {
                  if(selCol.type==='track') openTrackPicker(sel.rowId, selCol);
                  else if(selCol.type==='checkbox') PStore.patchCell(sel.rowId, sel.colKey, !raw);
                  else openPicker(sel.rowId, selCol);
                }}>
                {disp ? <span>{disp}</span> : <span className="fb-ph">Empty</span>}
                <span className="fb-tag">{selCol.type==='checkbox' ? 'toggle' : 'choose ▾'}</span>
              </button>
            ) : (
              <input key={sel.rowId+':'+sel.colKey+':'+disp} className="fb-input" defaultValue={disp}
                placeholder="Empty — type to fill this cell"
                onKeyDown={e => { if(e.key==='Enter'){ e.preventDefault(); commitBar(e.target.value); e.target.blur(); } if(e.key==='Escape'){ e.target.value = disp; e.target.blur(); } }}
                onBlur={e => commitBar(e.target.value)} />
            )}
          </div>
        );
      })()}

      <div className="gridwrap" ref={gridRef} tabIndex={0} onKeyDown={onGridKey} style={{ outline:'none' }}>
        <table className="grid" style={{ width: tableW }}>
          <thead>
            <tr>
              <th className="gnum" />
              {cols.map(col => (
                <th key={col.key}
                  className={(col.frozen?'gfrozen ':'')+(col.num?'r ':'')+(overCol===col.key&&dragCol!==col.key?'coldrop ':'')+(dragCol===col.key?'coldragging':'')}
                  style={{ width:wOf(col), minWidth:wOf(col), maxWidth:wOf(col) }}
                  draggable={!col.frozen}
                  onDragStart={e => onColDragStart(e, col)}
                  onDragOver={e => onColDragOver(e, col)}
                  onDrop={e => onColDrop(e, col)}
                  onDragEnd={onColDragEnd}>
                  <div className={'colhd'+(col.frozen?'':' draggable')} onClick={() => toggleSort(col.key)}>
                    <span className="lbl">{col.label}</span>
                    {sort.key===col.key && <span className="sortcaret">{sort.dir==='asc'?'▲':'▼'}</span>}
                    <button className="colmenu" onClick={e => { e.stopPropagation(); setColMenu({ col, rect: e.currentTarget.getBoundingClientRect() }); }}>⋯</button>
                  </div>
                  <span className={'colresize'+(liveW&&liveW.key===col.key?' on':'')} title="Drag to resize · double-click to reset"
                    onMouseDown={e => startResize(e, col)} onDoubleClick={e => { e.stopPropagation(); autoFit(col); }} />
                </th>
              ))}
              <th className="addcolhd" style={{ width:44 }}>
                <button className="addcolbtn" title="Add column" onClick={() => setAddCol(true)}>＋</button>
              </th>
            </tr>
          </thead>
          <tbody>
            {groups.map(g => {
              const isCol = collapsed.has(g.key);
              const sum = g.rows.reduce((s,r) => s + (typeof r.check==='number'?r.check:0), 0);
              return (
                <React.Fragment key={g.key}>
                  {g.label!==null && (
                    <tr className="grouprow">
                      <td colSpan={colSpanAll}>
                        <div className={'grouphd'+(isCol?' collapsed':'')} onClick={() => toggleGroup(g.key)}>
                          <svg className="gcaret" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round"><path d="M4 6l4 4 4-4"/></svg>
                          {g.color ? <Tag optsKey={pivot} value={g.label} /> : <span className="gtitle gnone">{g.label}</span>}
                          <span className="gmeta">{g.rows.length}</span>
                          <span className="gsum">{RS.money(sum)} <small>capital</small></span>
                        </div>
                      </td>
                    </tr>
                  )}
                  {!isCol && g.rows.map((row, i) => (
                    <tr key={row.id}>
                      <td className="gnum">
                        <span className="rn">{i+1}</span>
                        <button className="delrow" title="Delete row" onClick={() => { if(sel&&sel.rowId===row.id) setSel(null); PStore.removeRow(row.id); }}>✕</button>
                      </td>
                      {cols.map(col => renderCell(row, col))}
                      <td style={{ width:44 }} />
                    </tr>
                  ))}
                </React.Fragment>
              );
            })}
          </tbody>
        </table>
      </div>

      <div className="gridfoot">
        <button className="addrowbtn" onClick={() => { const id = PStore.addRow(); if(gridRef.current) gridRef.current.scrollTop = gridRef.current.scrollHeight; setTimeout(() => selectCell(id, 'name', {openEditor:true}), 30); }}>＋ Add contact</button>
        <div className="foottotals">
          <span className="ft">Rows <b>{shownRows.length}</b></span>
          <span className="ft">Total capital <b>{RS.money(totalCheck)}</b></span>
        </div>
      </div>
      </>
      )}

      {pop && <CellPicker pop={pop} rows={rows} onClose={() => { setPop(null); setTimeout(focusGrid,0); }} />}
      {trackPop && <TrackPicker pop={trackPop} onClose={() => { setTrackPop(null); setTimeout(focusGrid,0); }} />}
      {colMenu && <ColMenu menu={colMenu} sort={sort} onSort={setSortFn} onClose={() => setColMenu(null)} />}
      {addCol && <AddColModal onClose={() => setAddCol(false)} />}
      {tip && <div className="gtip" style={{ left: tip.x, top: tip.y }}>{tip.text}</div>}
    </div>
  );
}
window.TabPipeline = TabPipeline;
