/* Leads List — table with presets, filters, sort, bulk actions, inline edit. */
const { useState: useStateLL, useMemo: useMemoLL, useEffect: useEffectLL } = React;

/* Status funnel order for sorting (BR-SO-008): New > In Progress > Qualified > Disqualified > Converted. */
const LEAD_STATUS_ORDER = { new: 0, in_progress: 1, qualified: 2, disqualified: 3, converted: 4 };

// overdue excludes Disqualified (lead-sla AC-SLA-017); problem is computed live.
function leadFlagMatch(f, l) {
  if (f === 'unassigned') return !l.owner;
  if (f === 'overdue') return l.status !== 'disqualified' && l.nextStep && new Date(l.nextStep.due) < window.MonolitData.NOW;
  if (f === 'nonext') return !l.nextStep;
  if (f === 'problem') return window.MonolitData.isProblemLead(l);
  return true;
}
// Filtering uses a field catalog + AND/OR/NOT query — see `leadFields` below + LeadFilterDrawer.

function LeadsList({ leads, role, onOpenLead, onCreate, onToast, onUpdate, onAssign, onDisqualify, onArchive, onRestore, onImport, bulkLock, onRunBulk }) {
  const RB = window.MonolitRBAC;
  const isViewer = role === 'Viewer';
  const canBulk = RB.can(role, 'leads.bulk');
  const isMgrPlus = role === 'Admin' || role === 'Ops Admin' || role === 'Manager';
  const scoped = useMemoLL(() => {
    if (role === 'Sales Rep') return leads.filter(l => l.owner && l.owner.id === RB.ME);
    if (role === 'Manager') return leads.filter(l => !l.owner || RB.TEAM_IDS.includes(l.owner.id));
    if (role === 'Viewer') return leads.filter(l => l.status !== 'archived');
    return leads;
  }, [leads, role]);
  // Persisted per-user view state (filters / sort / columns) — prd-leads-filters Persistence.
  const LEADS_VIEW_KEY = 'mn_leads_view';
  const saved = useMemoLL(() => { try { return JSON.parse(localStorage.getItem(LEADS_VIEW_KEY) || '{}'); } catch (e) { return {}; } }, []);
  const [preset, setPreset] = useStateLL(saved.preset || 'all');
  const [hidden, setHidden] = useStateLL(saved.hidden || ['city', 'country', 'sourceName']); // off by default (prd-leads-columns BR-CL-002A)
  // Quick filters (multi-toggle pills) + Status quick popover + advanced AND/OR/NOT query — one canonical view.
  const [quick, setQuick] = useStateLL(saved.quick || []);
  const [advq, setAdvq] = useStateLL(saved.advq && saved.advq.groups ? saved.advq : advEmpty());
  const toggleQuick = (k) => setQuick(a => a.includes(k) ? a.filter(x => x !== k) : [...a, k]);
  // Status near search edits the canonical query (group 0) → visible in the Filters tab and Advanced too.
  const statusSel = (advGet(advq, 'status') || {}).value || [];
  const toggleStatus = (s) => { const next = statusSel.includes(s) ? statusSel.filter(x => x !== s) : [...statusSel, s]; setAdvq(next.length ? advSet(advq, 'status', 'in', next) : advRemove(advq, 'status')); };
  const removeAdvCond = (gi, ci) => setAdvq(s => { const g = s.groups.map(x => ({ not: x.not, conditions: x.conditions.slice() })); g[gi].conditions.splice(ci, 1); let gg = g.filter((grp, i) => grp.conditions.length || i === 0); if (!gg.length) gg = [{ conditions: [] }]; return { groups: gg }; });
  // Canonical column catalog (prd-leads-columns BR-CL-001/002). Labels MUST match the spec verbatim.
  const COLS = [
    { k: 'id', label: 'Lead ID', locked: true, pinned: true }, { k: 'name', label: 'Lead Name', locked: true, pinned: true },
    { k: 'company', label: 'Company' }, { k: 'status', label: 'Status', locked: true }, { k: 'owner', label: 'Owner' },
    { k: 'score', label: 'Score' }, { k: 'phone', label: 'Phone' }, { k: 'email', label: 'Email' },
    { k: 'sourceType', label: 'Source Type' }, { k: 'sourceName', label: 'Source Name' }, { k: 'tags', label: 'Tags' }, { k: 'leadTime', label: 'Lead Current Time' },
    { k: 'nextStep', label: 'Next Step' }, { k: 'lastActivity', label: 'Last Activity Date' }, { k: 'createdAt', label: 'Created Date' },
    { k: 'city', label: 'City' }, { k: 'country', label: 'Country' },
  ];
  // Identity columns (id/name) stay pinned left; the rest are drag-reorderable in the Columns popover (prd-leads-columns).
  const REORDERABLE = COLS.filter(c => !c.pinned).map(c => c.k);
  const [colOrder, setColOrder] = useStateLL(() => { const s = (saved.colOrder || []).filter(k => REORDERABLE.includes(k)); return s.concat(REORDERABLE.filter(k => !s.includes(k))); });
  const colOrderSafe = colOrder.filter(k => REORDERABLE.includes(k)).concat(REORDERABLE.filter(k => !colOrder.includes(k)));
  const show = (k) => !hidden.includes(k);
  const toggleCol = (k) => setHidden(h => h.includes(k) ? h.filter(x => x !== k) : [...h, k]);
  const resetCols = () => { setHidden(['city', 'country', 'sourceName']); setColOrder(REORDERABLE); };
  const SORT_OPTS = [{ k: 'createdAt', label: 'Created Date' }, { k: 'name', label: 'Lead Name' }, { k: 'score', label: 'Score' }, { k: 'status', label: 'Status' }, { k: 'nextStep', label: 'Next Step' }, { k: 'lastActivity', label: 'Last Activity Date' }];
  // Selection descriptor (BR-BA-003/004): explicit ids, or all_matching + exclusion ids.
  const [sel, setSel] = useStateLL({ mode: 'explicit', ids: new Set(), excluded: new Set() });
  const [bulkModal, setBulkModal] = useStateLL(null); // 'assign' | 'status' | 'tags' | 'archive' | 'export' | { kind: 'deals-block', … }
  const clearSel = () => setSel({ mode: 'explicit', ids: new Set(), excluded: new Set() });
  const [sortKey, setSortKey] = useStateLL(saved.sortKey || 'createdAt');
  const [sortDir, setSortDir] = useStateLL(saved.sortDir || 'desc');
  const [query, setQuery] = useStateLL(saved.query || '');

  // Live faceted option lists (with counts over the scoped set) — power the builder value editors + bulk menus.
  const opts = useMemoLL(() => {
    const cnt = (pred) => scoped.reduce((a, l) => a + (pred(l) ? 1 : 0), 0);
    const owners = (() => { const seen = {}; const o = [{ value: 'none', label: 'No owner', count: cnt(l => !l.owner) }]; scoped.forEach(l => { if (l.owner && !seen[l.owner.id]) { seen[l.owner.id] = 1; o.push({ value: l.owner.id, label: l.owner.name, user: l.owner, count: cnt(x => x.owner && x.owner.id === l.owner.id) }); } }); return o; })();
    const uniq = (arr) => Array.from(new Set(arr.filter(Boolean)));
    return {
      status: ['new', 'in_progress', 'qualified', 'disqualified', 'converted'].map(s => ({ value: s, label: STATUS_CFG[s].label, count: cnt(l => l.status === s) })),
      owner: owners,
      source: uniq(scoped.map(l => l.source && l.source.type)).map(s => ({ value: s, label: s, count: cnt(l => l.source && l.source.type === s) })),
      tag: uniq(scoped.flatMap(l => l.tags || [])).map(t => ({ value: t, label: t, count: cnt(l => (l.tags || []).includes(t)) })),
      country: uniq(scoped.map(l => l.country)).map(c => ({ value: c, label: c, count: cnt(l => l.country === c) })),
    };
  }, [scoped]);
  // Field catalog for the advanced AND/OR/NOT builder (UI: key/label/type/options · eval: get/known).
  const leadFields = useMemoLL(() => [
    { key: 'status', label: 'Status', icon: 'circle-dot', type: 'enum', options: opts.status, get: l => l.status, known: l => !!l.status },
    { key: 'owner', label: 'Owner', icon: 'user', type: 'people', options: opts.owner, get: l => l.owner ? l.owner.id : 'none', known: l => !!l.owner },
    { key: 'source', label: 'Source type', icon: 'git-branch', type: 'enum', options: opts.source, get: l => l.source ? l.source.type : null, known: l => !!(l.source && l.source.type) },
    { key: 'score', label: 'Score', icon: 'gauge', type: 'number', slider: true, min: 0, max: 100, gradient: 'linear-gradient(to right,var(--error-low) 0 40%,var(--warning-low) 40% 70%,var(--success-low) 70% 100%)', bands: [{ label: 'Low', from: 0, to: 39, tone: 'error' }, { label: 'Medium', from: 40, to: 69, tone: 'warning' }, { label: 'High', from: 70, to: 100, tone: 'success' }], format: (n) => String(n), get: l => l.score, known: l => l.score != null },
    { key: 'created', label: 'Created', icon: 'calendar', type: 'date', get: l => l.createdAt, known: l => !!l.createdAt },
    { key: 'nextStep', label: 'Next step', icon: 'calendar-clock', type: 'date', get: l => l.nextStep ? l.nextStep.due : null, known: l => !!l.nextStep },
    { key: 'tags', label: 'Tags', icon: 'tag', type: 'multi', options: opts.tag, get: l => l.tags || [], known: l => !!(l.tags && l.tags.length) },
    { key: 'company', label: 'Company', icon: 'building-2', type: 'text', get: l => l.company, known: l => !!l.company },
    { key: 'email', label: 'Email', icon: 'mail', type: 'text', get: l => l.email, known: l => !!l.email },
    { key: 'phone', label: 'Phone', icon: 'phone', type: 'text', get: l => l.phone, known: l => !!l.phone },
    { key: 'country', label: 'Country', icon: 'map-pin', type: 'enum', options: opts.country, get: l => l.country, known: l => !!l.country },
  ], [opts]);
  const activeFilterCount = advCount(advq);
  const [filterOpen, setFilterOpen] = useStateLL(false);
  const [focusField, setFocusField] = useStateLL(null);
  const openFilter = (fk) => { setFocusField(fk || null); setFilterOpen(true); };
  function resetView() { setQuick([]); setAdvq(advEmpty()); setQuery(''); setPreset('all'); }
  useEffectLL(() => {
    try { localStorage.setItem(LEADS_VIEW_KEY, JSON.stringify({ preset, query, quick, advq, sortKey, sortDir, hidden, colOrder })); } catch (e) {}
  }, [preset, query, quick, advq, sortKey, sortDir, hidden, colOrder]);

  // Possible-duplicate set: active leads sharing exact email or phone (duplicate-detection.md Flow 3).
  const dupIds = useMemoLL(() => {
    const em = {}, ph = {}, set = new Set();
    leads.forEach(l => {
      if (l.is_archived) return;
      const e = (l.email || '').toLowerCase().trim();
      const p = (l.phone || '').replace(/[^\d]/g, '');
      if (e) (em[e] = em[e] || []).push(l.id);
      if (p && p.length > 6) (ph[p] = ph[p] || []).push(l.id);
    });
    Object.values(em).forEach(ids => { if (ids.length > 1) ids.forEach(id => set.add(id)); });
    Object.values(ph).forEach(ids => { if (ids.length > 1) ids.forEach(id => set.add(id)); });
    return set;
  }, [leads]);

  // View-mode tabs are exactly two and mandatory (prd-leads-filters: View Tabs & System Preset Surfacing).
  // Archived is a separate layer (is_archived), surfaced only in its own tab (lead-lifecycle §2.3).
  const tabs = [
    { id: 'all', label: 'All leads', filter: l => !l.is_archived },
    { id: 'archived', label: 'Archived', filter: l => l.is_archived },
  ];
  const curTab = tabs.find(t => t.id === preset) || tabs[0];
  // System presets live in the filter bar as one-click multi-toggle quick filters (not tabs). Role-gated.
  const QUICK = [
    { k: 'problem', label: 'Problem leads', icon: 'flame', match: l => leadFlagMatch('problem', l) },
    { k: 'unassigned', label: 'Unassigned', icon: 'user-x', match: l => leadFlagMatch('unassigned', l), mgrOnly: true },
    { k: 'overdue', label: 'Overdue', icon: 'alarm-clock', match: l => leadFlagMatch('overdue', l) },
    { k: 'nonext', label: 'No next step', icon: 'circle-dashed', match: l => leadFlagMatch('nonext', l) },
    { k: 'new', label: 'New', icon: 'sparkles', match: l => l.isNew },
    { k: 'dupes', label: 'Possible duplicates', icon: 'copy', match: l => dupIds.has(l.id), mgrOnly: true },
  ].filter(q => !q.mgrOnly || isMgrPlus);
  const quickMatch = (k) => { const q = QUICK.find(x => x.k === k); return q ? q.match : (() => true); };

  const filtered = useMemoLL(() => {
    let r = scoped.filter(curTab.filter);
    quick.forEach(k => { r = r.filter(quickMatch(k)); });
    r = applyAdvQuery(r, advq, leadFields);
    if (query && query.trim().length >= 2) {
      const q = query.toLowerCase();
      r = r.filter(l => (l.id + l.name + l.email + l.company + (l.phone || '') + window.MonolitData.publicId(l.id)).toLowerCase().includes(q));
    }
    r = r.slice().sort((a, b) => {
      let va, vb;
      if (sortKey === 'createdAt') { va = a.createdAt; vb = b.createdAt; }
      else if (sortKey === 'score') { va = a.score; vb = b.score; }
      else if (sortKey === 'name')  { va = a.name; vb = b.name; }
      else if (sortKey === 'status') { va = LEAD_STATUS_ORDER[a.status]; vb = LEAD_STATUS_ORDER[b.status]; }
      else if (sortKey === 'nextStep') { va = a.nextStep ? a.nextStep.due : null; vb = b.nextStep ? b.nextStep.due : null; }
      else if (sortKey === 'lastActivity') { va = a.updatedAt; vb = b.updatedAt; }
      // Null / empty values always sort to the bottom for both ASC and DESC (BR-SO-006).
      const aNull = va == null, bNull = vb == null;
      if (aNull && bNull) return 0;
      if (aNull) return 1;
      if (bNull) return -1;
      const cmp = va < vb ? -1 : va > vb ? 1 : 0;
      return sortDir === 'asc' ? cmp : -cmp;
    });
    return r;
  }, [scoped, preset, quick, advq, query, sortKey, sortDir, leadFields]);

  function toggleSort(k) {
    if (sortKey === k) setSortDir(sortDir === 'asc' ? 'desc' : 'asc');
    else { setSortKey(k); setSortDir(k === 'name' ? 'asc' : 'desc'); }
  }

  // ---- Bulk selection & actions (prd-leads-bulk-actions) -------------------
  const isSel = (id) => (sel.mode === 'all_matching' ? !sel.excluded.has(id) : sel.ids.has(id));
  function toggleSel(id) {
    setSel(s => {
      // In all_matching mode unchecking a row enters/extends exclusion mode (BR-BA-004).
      if (s.mode === 'all_matching') { const excluded = new Set(s.excluded); excluded.has(id) ? excluded.delete(id) : excluded.add(id); return { ...s, excluded }; }
      const ids = new Set(s.ids); ids.has(id) ? ids.delete(id) : ids.add(id); return { ...s, ids };
    });
  }
  function toggleAll() {
    // Header checkbox toggles the CURRENT PAGE (BR-BA-002); in all_matching mode it clears the whole selection.
    if (sel.mode === 'all_matching') { clearSel(); return; }
    const ids = pageRows.map(l => l.id);
    const allOn = ids.length > 0 && ids.every(id => sel.ids.has(id));
    setSel(s => { const next = new Set(s.ids); if (allOn) ids.forEach(id => next.delete(id)); else ids.forEach(id => next.add(id)); return { ...s, ids: next }; });
  }
  const selectedLeads = useMemoLL(() => (sel.mode === 'all_matching' ? filtered.filter(l => !sel.excluded.has(l.id)) : filtered.filter(l => sel.ids.has(l.id))), [filtered, sel]);
  const selCount = selectedLeads.length;
  const bulkScopeLabel = sel.mode === 'all_matching'
    ? 'All matching filter · ' + fmtCount(selCount) + (sel.excluded.size ? ' (' + fmtCount(sel.excluded.size) + ' excluded)' : '')
    : fmtCount(selCount) + ' selected';
  const selectedTagOpts = useMemoLL(() => { const m = {}; selectedLeads.forEach(l => (l.tags || []).forEach(t => { m[t] = (m[t] || 0) + 1; })); return Object.keys(m).sort().map(t => ({ value: t, count: m[t] })); }, [selectedLeads]);
  // Existing tags (with counts) — tag catalog for the Add-tags suggestions + filters.
  const tagOpts = opts.tag;
  // Export field order follows the current table order (BR-BA-022); defaults = visible columns.
  const exportOrder = ['id', 'name'].concat(colOrderSafe).filter(k => BULK_EXPORT_FIELDS.some(f => f.k === k));
  const exportFields = exportOrder.map(k => BULK_EXPORT_FIELDS.find(f => f.k === k));
  const exportDefault = ['id', 'name'].concat(colOrderSafe.filter(show)).filter(k => { const f = BULK_EXPORT_FIELDS.find(x => x.k === k); return f && !f.blocked; });

  // Hand the operation to the App-level executor (sync ≤50 / async job — BR-BA-006) and reset selection.
  function startBulk(spec) { setBulkModal(null); clearSel(); onRunBulk && onRunBulk(spec); }
  // Disqualify / Archive with active Lead-linked Deals → blocking modal, explicit decision (BR-BA-015).
  function guardDeals(action, run) {
    const D = window.MonolitData;
    const affected = selectedLeads.map(l => ({ lead: l, deals: (D.deals || []).filter(d => d.leadId === l.id && dealIsActive(d)) })).filter(x => x.deals.length);
    const wonBlocked = selectedLeads.map(l => ({ lead: l, deals: (D.deals || []).filter(d => d.leadId === l.id && d.stage === 'won') })).filter(x => x.deals.length);
    if (!affected.length) { run(); return; }
    setBulkModal({ kind: 'deals-block', action, affected, wonBlocked, onContinue: () => {
      // Explicit user decision: cancel active deals atomically with each lead — never silently.
      affected.forEach(x => x.deals.forEach(d => { d.stage = 'cancelled'; d.cancelReason = action === 'disqualify' ? 'Lead disqualified' : 'Lead archived'; d.closedAt = D.NOW; d.updatedAt = D.NOW; }));
      run();
    } });
  }
  // Won linked deals block the lead until manual Admin resolution (BR-BA-015).
  const wonBlockReason = (l) => ((window.MonolitData.deals || []).some(d => d.leadId === l.id && d.stage === 'won') ? { ok: false, reason: 'Won linked deal — requires Admin resolution', category: 'dependency' } : null);
  function bulkAssign(ownerId) {
    const o = window.MonolitData.owners.find(x => x.id === ownerId);
    startBulk({ label: 'Assign owner → ' + o.name + ' · ' + fmtCount(selCount) + ' leads', items: selectedLeads,
      apply: (l) => (l.is_archived ? { ok: false, reason: 'Archived lead — assignment blocked by policy', category: 'validation' } : { ok: true, patch: { owner: o } }),
      doneLabel: (n) => n + ' assigned to ' + o.name });
  }
  function bulkStatus({ status, reason, text }) {
    const run = () => startBulk({ label: 'Change status → ' + STATUS_CFG[status].label + ' · ' + fmtCount(selCount) + ' leads', items: selectedLeads,
      apply: (l) => {
        if (l.status === status) return { ok: true, patch: {} }; // no-op
        if (l.status === 'converted') return { ok: false, reason: 'Converted is terminal (BR-BA-012)', category: 'validation' };
        if (!(LEAD_TRANSITIONS[l.status] || []).includes(status)) return { ok: false, reason: 'Invalid transition: ' + l.status + ' → ' + status, category: 'validation' };
        if (status === 'disqualified') {
          const w = wonBlockReason(l); if (w) return w;
          // Shared reason applies only to leads that actually transition (BR-BA-013).
          return { ok: true, patch: { status, disqualifiedReason: reason, disqualifiedReasonText: text || '' } };
        }
        return { ok: true, patch: { status } };
      },
      doneLabel: (n) => n + ' → ' + STATUS_CFG[status].label });
    if (status === 'disqualified') guardDeals('disqualify', run); else run();
  }
  function bulkTags({ tagMode, tags }) {
    startBulk({ label: (tagMode === 'add' ? 'Add tags: ' : 'Remove tags: ') + tags.join(', ') + ' · ' + fmtCount(selCount) + ' leads', items: selectedLeads,
      apply: (l) => { const cur = l.tags || []; const next = tagMode === 'add' ? Array.from(new Set(cur.concat(tags))) : cur.filter(tg => !tags.includes(tg)); return next.length === cur.length ? { ok: true, patch: {} } : { ok: true, patch: { tags: next } }; },
      doneLabel: (n) => (tagMode === 'add' ? 'Tags added to ' : 'Tags removed from ') + n + ' leads' });
  }
  function runBulkArchive() {
    const D = window.MonolitData;
    startBulk({ label: 'Archive · ' + fmtCount(selCount) + ' leads', items: selectedLeads,
      apply: (l) => wonBlockReason(l) || { ok: true, patch: { is_archived: true, archived_at: D.NOW, archive_reason: 'manual' } },
      doneLabel: (n) => n + ' archived' });
  }
  function bulkExport({ fields, format, delivery, maskSensitive }) {
    // Snapshot at export start (BR-BA-020): the file is built from the current selection now.
    const file = buildLeadsExportFile(selectedLeads, exportOrder.filter(k => fields.includes(k)), format, maskSensitive);
    startBulk({ action: 'export', label: 'Export ' + format.toUpperCase() + ' · ' + fmtCount(selCount) + ' leads', count: selCount, delivery, file });
  }
  // CSV import: pick a file, parse, hand rows to the App-level batch creator.
  function handleImportClick() {
    const input = document.createElement('input');
    input.type = 'file'; input.accept = '.csv,text/csv';
    input.onchange = () => {
      const file = input.files && input.files[0]; if (!file) return;
      const reader = new FileReader();
      reader.onload = () => { const rows = parseLeadsCSV(String(reader.result || '')); const n = onImport ? onImport(rows) : 0; onToast && onToast(n + ' lead' + (n === 1 ? '' : 's') + ' imported'); };
      reader.readAsText(file);
    };
    input.click();
  }

  // Numbered pagination, rendered OUTSIDE the horizontal-scroll wrap (lead-list-view §4.6). Reset to page 1 on filter change.
  const [page, setPage] = useStateLL(1);
  const [pageSize, setPageSize] = useStateLL(25);
  const pageRows = filtered.slice((page - 1) * pageSize, page * pageSize);
  useEffectLL(() => { setPage(1); }, [preset, query, quick, advq, pageSize]);
  // Filter/search change invalidates the selection descriptor (stale filter_signature → safe reset).
  useEffectLL(() => { clearSel(); }, [preset, query, quick, advq]);

  // Scrollable columns are rendered from `colOrderSafe` so the Columns popover can drag-reorder them.
  function renderHeadCell(k) {
    switch (k) {
      case 'status': return <SortableTh key={k} label="Status" k="status" sortKey={sortKey} sortDir={sortDir} onClick={toggleSort} style={{ width: 150 }} />;
      case 'score': return <SortableTh key={k} label="Score" k="score" sortKey={sortKey} sortDir={sortDir} onClick={toggleSort} style={{ width: 120 }} />;
      case 'nextStep': return <SortableTh key={k} label="Next Step" k="nextStep" sortKey={sortKey} sortDir={sortDir} onClick={toggleSort} style={{ width: 220 }} />;
      case 'lastActivity': return <SortableTh key={k} label="Last Activity Date" k="lastActivity" sortKey={sortKey} sortDir={sortDir} onClick={toggleSort} style={{ width: 130 }} />;
      case 'createdAt': return <SortableTh key={k} label="Created Date" k="createdAt" sortKey={sortKey} sortDir={sortDir} onClick={toggleSort} style={{ width: 120 }} />;
      case 'company': return <th key={k} style={{ width: 160 }}>Company</th>;
      case 'owner': return <th key={k} style={{ width: 170 }}>Owner</th>;
      case 'phone': return <th key={k} style={{ width: 150 }}>Phone</th>;
      case 'email': return <th key={k} style={{ width: 200 }}>Email</th>;
      case 'sourceType': return <th key={k} style={{ width: 120 }}>Source Type</th>;
      case 'sourceName': return <th key={k} style={{ width: 140 }}>Source Name</th>;
      case 'tags': return <th key={k} style={{ width: 160 }}>Tags</th>;
      case 'leadTime': return <th key={k} style={{ width: 120 }}>Lead Current Time</th>;
      case 'city': return <th key={k} style={{ width: 120 }}>City</th>;
      case 'country': return <th key={k} style={{ width: 120 }}>Country</th>;
      default: return null;
    }
  }
  function renderBodyCell(k, l, tz) {
    switch (k) {
      case 'company': return <td key={k}><span style={{ font: 'var(--font-weight-medium) 13px/16px var(--font-sans)' }}>{l.company}</span></td>;
      case 'status': return (
        <td key={k} onClick={(e) => e.stopPropagation()}>
          {(!isViewer && !l.is_archived && l.status !== 'converted' && RB.can(role, 'leads.update')) ? (
            <Dropdown align="left" width={200} trigger={({ toggle }) => <span onClick={toggle}><StatusBadge status={l.status} onClick={() => {}} /></span>}>
              {({ close }) => (
                <React.Fragment>
                  <div className="mn-dd-h">Change status</div>
                  <div className="mn-dd-list">
                    {(LEAD_TRANSITIONS[l.status] || []).filter(() => l.status !== 'disqualified' || isMgrPlus).map(s => (
                      <button key={s} className="mn-dd-item" onClick={() => { close(); if (s === 'disqualified') onDisqualify && onDisqualify(l.id); else { onUpdate && onUpdate(l.id, { status: s }); onToast && onToast('Status → ' + (STATUS_CFG[s] ? STATUS_CFG[s].label : s)); } }}>
                        <StatusBadge status={s} /><span style={{ flex: 1 }} />
                      </button>
                    ))}
                    {!(LEAD_TRANSITIONS[l.status] || []).filter(() => l.status !== 'disqualified' || isMgrPlus).length ? <div style={{ padding: 10, color: 'var(--text-3)', font: 'var(--font-weight-medium) 12px/16px var(--font-sans)' }}>No transitions available.</div> : null}
                  </div>
                </React.Fragment>
              )}
            </Dropdown>
          ) : <StatusBadge status={l.status} />}
        </td>
      );
      case 'owner': return (
        <td key={k} onClick={(e) => e.stopPropagation()}>
          {(!isViewer && !l.is_archived && RB.can(role, 'leads.assign')) ? (
            <Dropdown align="left" width={220} trigger={({ toggle }) => <span onClick={toggle} style={{ cursor: 'pointer' }}><OwnerCellInner lead={l} /></span>}>
              {({ close }) => (
                <React.Fragment>
                  <div className="mn-dd-h">Assign owner</div>
                  <div className="mn-dd-list">
                    {window.MonolitData.owners.filter(o => role !== 'Manager' || RB.TEAM_IDS.includes(o.id)).map(o => (
                      <button key={o.id} className="mn-dd-item" style={{ alignItems: 'center' }} onClick={() => { close(); onUpdate && onUpdate(l.id, { owner: o }); onToast && onToast('Owner → ' + o.name); }}>
                        <Avatar user={o} size={20} /><span className="mn-dd-item-tt">{o.name}</span>{l.owner && l.owner.id === o.id ? <Icon name="check" size="sm" color="primary" /> : null}
                      </button>
                    ))}
                  </div>
                </React.Fragment>
              )}
            </Dropdown>
          ) : <OwnerCellInner lead={l} />}
        </td>
      );
      case 'score': return <td key={k}><ScoreCell value={l.score} /></td>;
      case 'phone': return <td key={k}>{l.phone ? <a href={'tel:' + l.phone.replace(/[^\d+]/g, '')} onClick={(e) => e.stopPropagation()} className="mn-mono" style={{ fontSize: 12, color: 'var(--text-2)', textDecoration: 'none' }}>{l.phone}</a> : <span style={{ color: 'var(--text-3)' }}>—</span>}</td>;
      case 'email': return <td key={k}>{l.email ? <a href={'mailto:' + l.email} onClick={(e) => e.stopPropagation()} style={{ fontSize: 13, color: 'var(--secondary-main)', textDecoration: 'none' }}>{l.email}</a> : <span style={{ color: 'var(--text-3)' }}>—</span>}</td>;
      case 'sourceType': return <td key={k}><span className="mn-chip mn-chip--md mn-chip--tag">{l.source ? l.source.type : '—'}</span></td>;
      case 'sourceName': return <td key={k}><span style={{ font: 'var(--font-weight-medium) 12px/16px var(--font-sans)', color: 'var(--text-2)' }}>{l.source ? l.source.name : '—'}</span></td>;
      case 'tags': return <td key={k}><TagCells tags={l.tags} /></td>;
      case 'leadTime': return <td key={k}>{tz == null ? <span style={{ color: 'var(--text-3)', fontSize: 12 }}>—</span> : <span className="mn-mono" style={{ fontSize: 12, color: 'var(--text-2)' }}>{clientTime(tz)}</span>}</td>;
      case 'nextStep': return (
        <td key={k} onClick={(e) => e.stopPropagation()}>
          {(!isViewer && !l.is_archived && l.status !== 'converted' && RB.can(role, 'leads.update')) ? (
            <Dropdown align="left" width={280} trigger={({ toggle }) => <span onClick={toggle} style={{ cursor: 'pointer' }}><NextStepCell nextStep={l.nextStep} now={window.MonolitData.NOW} /></span>}>
              {({ close }) => <NextStepEditor lead={l} onSave={(ns) => { close(); onUpdate && onUpdate(l.id, { nextStep: ns }); onToast && onToast(ns ? 'Next step updated' : 'Next step cleared'); }} />}
            </Dropdown>
          ) : <NextStepCell nextStep={l.nextStep} now={window.MonolitData.NOW} />}
        </td>
      );
      case 'lastActivity': return <td key={k}><span style={{ fontSize: 12, color: 'var(--text-2)' }}>{l.lastActivity || l.updatedAt ? relTime(new Date(l.lastActivity || l.updatedAt)) : '—'}</span></td>;
      case 'createdAt': return <td key={k}><span className="mn-mono" style={{ fontSize: 12, color: 'var(--text-3)' }}>{new Date(l.createdAt).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })}</span></td>;
      case 'city': return <td key={k}><span style={{ font: 'var(--font-weight-medium) 12px/16px var(--font-sans)', color: 'var(--text-2)' }}>{l.city || '—'}</span></td>;
      case 'country': return <td key={k}><span style={{ font: 'var(--font-weight-medium) 12px/16px var(--font-sans)', color: 'var(--text-2)' }}>{l.country || '—'}</span></td>;
      default: return null;
    }
  }

  return (
    <React.Fragment>
      <div className="mn-page-h">
        <div className="mn-page-title">
          <h1 className="title-1">Leads</h1>
          <span className="mn-page-count">{filtered.length} of {scoped.length}</span>
        </div>
        <div className="mn-page-actions">
          {RB.can(role, 'leads.export') ? <Button variant="ghost" size="md" icon="download" onClick={() => downloadLeadsCSV(filtered, 'leads.csv')}>Export</Button> : null}
          {RB.can(role, 'leads.import') ? <Button variant="ghost" size="md" icon="upload" onClick={handleImportClick}>Import</Button> : null}
          {RB.can(role, 'leads.create') ? <Button variant="primary" size="md" icon="plus" onClick={onCreate}>Add lead</Button> : null}
        </div>
      </div>

      {isViewer ? <div className="mn-robanner"><Icon name="eye" size="sm" />Read-only access — your role is Viewer.</div> : null}

      <div className="mn-presets">
        {tabs.map((p) => (
          <button key={p.id} type="button"
                  className={'mn-preset' + (preset === p.id ? ' mn-preset--active' : '')}
                  onClick={() => { setPreset(p.id); clearSel(); }}>
            <span>{p.label}</span>
            <span className="mn-preset-c">{scoped.filter(p.filter).length}</span>
          </button>
        ))}
      </div>

      <div className="mn-toolbar">
        <div style={{ flex: '0 1 240px', minWidth: 140 }}>
          <Input fullWidth icon="search" placeholder="Search leads…" value={query} onChange={setQuery} />
        </div>
        <Dropdown align="left" width={224} trigger={({ toggle }) => <Button variant={statusSel.length ? 'secondary' : 'ghost'} size="md" icon="list-filter" iconRight="chevron-down" onClick={toggle}>Status{statusSel.length ? ' · ' + statusSel.length : ''}</Button>}>
          {() => (
            <React.Fragment>
              <div className="mn-dd-h">Status{statusSel.length ? <button type="button" className="mn-login-link" style={{ fontSize: 12 }} onClick={() => setAdvq(advRemove(advq, 'status'))}>Clear</button> : null}</div>
              <div className="mn-dd-list" style={{ padding: '4px 0 8px' }}>
                {opts.status.map(o => (
                  <label key={o.value} className="mn-check-row" style={{ padding: '5px 12px' }}>
                    <input type="checkbox" className="mn-checkbox" checked={statusSel.includes(o.value)} onChange={() => toggleStatus(o.value)} />
                    <StatusBadge status={o.value} />
                    <span style={{ flex: 1 }} />
                    <span className="mn-mono mn-muted" style={{ fontSize: 11 }}>{o.count}</span>
                  </label>
                ))}
              </div>
            </React.Fragment>
          )}
        </Dropdown>
        <Dropdown align="left" width={240} trigger={({ toggle }) => <Button variant={quick.length ? 'secondary' : 'ghost'} size="md" icon="zap" iconRight="chevron-down" onClick={toggle}>Quick filters{quick.length ? ' · ' + quick.length : ''}</Button>}>
          {() => (
            <React.Fragment>
              <div className="mn-dd-h">Quick filters{quick.length ? <button type="button" className="mn-login-link" style={{ fontSize: 12 }} onClick={() => setQuick([])}>Clear</button> : null}</div>
              <div className="mn-dd-list" style={{ padding: '4px 0 8px' }}>
                {QUICK.map(q => { const c = scoped.filter(curTab.filter).filter(q.match).length; return (
                  <label key={q.k} className="mn-check-row" style={{ padding: '5px 12px' }}>
                    <input type="checkbox" className="mn-checkbox" checked={quick.includes(q.k)} onChange={() => toggleQuick(q.k)} />
                    <span className="mn-check-lbl" style={{ flex: 1 }}>{q.label}</span>
                    <span className="mn-mono mn-muted" style={{ fontSize: 11 }}>{c}</span>
                  </label>
                ); })}
              </div>
            </React.Fragment>
          )}
        </Dropdown>
        <Button variant={activeFilterCount ? 'secondary' : 'ghost'} size="md" icon="filter" onClick={() => openFilter(null)}>Filters{activeFilterCount ? ' · ' + activeFilterCount : ''}</Button>
        <SortPopover options={SORT_OPTS} sortKey={sortKey} sortDir={sortDir} onChange={(k, d) => { setSortKey(k); setSortDir(d); }} />
        <ColumnsPopover columns={COLS} hidden={hidden} onToggle={toggleCol} onReset={resetCols} order={colOrderSafe} onReorder={setColOrder} />
        {canBulk && selCount > 0 ? (
          <BulkBar count={selCount} mode={sel.mode} excludedCount={sel.excluded.size} matchingCount={filtered.length}
                   locked={!!bulkLock} canAssign={RB.can(role, 'leads.assign')} canArchive={RB.can(role, 'leads.archive')} canExport={RB.can(role, 'leads.export')}
                   onSelectAllMatching={() => setSel({ mode: 'all_matching', ids: new Set(), excluded: new Set() })}
                   onClear={clearSel} onAction={setBulkModal} />
        ) : null}
      </div>

      {canBulk && bulkLock ? <BulkLockBanner /> : null}

      {(quick.length || advActive(advq)) ? (
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, alignItems: 'center', margin: '0 0 12px', padding: '0 24px' }}>
          <span className="mn-eyebrow" style={{ margin: '0 4px 0 0' }}>Active</span>
          {quick.map(k => { const q = QUICK.find(x => x.k === k); return q ? <FilterBarPill key={'q' + k} onClick={() => openFilter(null)} onRemove={() => toggleQuick(k)}><span style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>{q.icon ? <Icon name={q.icon} size="xs" /> : null}{q.label}</span></FilterBarPill> : null; })}
          {advChips(advq, leadFields).map(ch => <FilterBarPill key={'a' + ch.gi + '-' + ch.ci} onClick={() => openFilter(ch.field)} onRemove={() => removeAdvCond(ch.gi, ch.ci)}><span style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>{ch.icon ? <Icon name={ch.icon} size="xs" color="text-3" /> : null}{ch.not ? <span style={{ color: 'var(--error-main)' }}>NOT </span> : null}{ch.label} <span style={{ color: 'var(--text-3)' }}>{ch.op}</span>{ch.val ? ' ' + ch.val : ''}</span></FilterBarPill>)}
          <button type="button" className="mn-login-link" style={{ fontSize: 12, background: 'none', border: 0, cursor: 'pointer', marginLeft: 2 }} onClick={resetView}>Clear all</button>
        </div>
      ) : null}

      {/* Sticky identity block (checkbox + Lead ID + Lead Name) frozen during horizontal scroll — BR-CL-003. */}
      <style>{'.mn-lead-tw{overflow-x:auto}.mn-lead-tw .mn-stick{position:sticky;z-index:2;background:var(--bg-1)}.mn-lead-tw thead .mn-stick{top:0;z-index:4}.mn-lead-tw tr.mn-row--selected .mn-stick{background:var(--secondary-low)}.mn-lead-tw tr.mn-row--clickable:hover .mn-stick{background:var(--bg-2)}'}</style>
      <div className="mn-table-wrap mn-lead-tw">
        <table className="mn-table" style={{ minWidth: 'max-content' }}>
          <thead>
            <tr>
              {canBulk ? (
              <th className="mn-stick" style={{ width: 36, left: 0 }}>
                <input className="mn-checkbox" type="checkbox" aria-label="Select current page"
                       checked={pageRows.length > 0 && pageRows.every(l => isSel(l.id))}
                       ref={(el) => { if (el) el.indeterminate = pageRows.some(l => isSel(l.id)) && !pageRows.every(l => isSel(l.id)); }}
                       onChange={toggleAll} />
              </th>
              ) : null}
              <th className="mn-stick" style={{ width: 110, left: canBulk ? 36 : 0 }}>Lead ID</th>
              <SortableTh label="Lead Name" k="name" sortKey={sortKey} sortDir={sortDir} onClick={toggleSort} className="mn-stick" style={{ width: 200, left: canBulk ? 146 : 110 }} />
              {colOrderSafe.filter(show).map(k => renderHeadCell(k))}
              <th style={{ width: 36 }}></th>
            </tr>
          </thead>
          <tbody>
            {pageRows.map((l) => {
              const overdue = l.status !== 'disqualified' && l.nextStep && new Date(l.nextStep.due) < window.MonolitData.NOW;
              const rowCls = ['mn-row--clickable'];
              if (isSel(l.id)) rowCls.push('mn-row--selected');
              if (overdue) rowCls.push('mn-row--overdue');
              const tz = leadTzOffset(l);
              return (
                <tr key={l.id} className={rowCls.join(' ')} onClick={(e) => {
                  if (e.target.closest('button, input, a, .mn-chip--btn')) return;
                  onOpenLead(l.id);
                }}>
                  {canBulk ? (
                  <td className="mn-stick" style={{ left: 0 }} onClick={(e) => e.stopPropagation()}>
                    <input className="mn-checkbox" type="checkbox" aria-label={'Select ' + l.name} checked={isSel(l.id)} onChange={() => toggleSel(l.id)} />
                  </td>
                  ) : null}
                  <td className="mn-cell-id mn-stick" style={{ left: canBulk ? 36 : 0 }}>{window.MonolitData.publicId(l.id)}</td>
                  <td className="mn-stick" style={{ left: canBulk ? 146 : 110 }}>
                    <div className="mn-cell-name">
                      {window.MonolitData.isProblemLead(l) ? <Icon name="alert-triangle" size="sm" color="error" /> : null}
                      <strong>{l.name}</strong>
                    </div>
                  </td>
                  {colOrderSafe.filter(show).map(k => renderBodyCell(k, l, tz))}
                  <td onClick={(e) => e.stopPropagation()}>
                    <RowMenu items={[
                      { icon: 'external-link', label: 'Open lead', onClick: () => onOpenLead(l.id) },
                      !isViewer && !l.is_archived && RB.can(role, 'leads.assign') ? { icon: 'user-plus', label: l.owner ? 'Reassign owner' : 'Assign owner', onClick: () => onAssign && onAssign(l.id) } : null,
                      !isViewer && !l.is_archived && l.status === 'qualified' && RB.can(role, 'leads.convert') ? { icon: 'check-check', label: 'Convert', onClick: () => onOpenLead(l.id) } : null,
                      !isViewer && RB.can(role, 'leads.archive') ? (l.is_archived
                        ? { icon: 'archive-restore', label: 'Restore', onClick: () => onRestore && onRestore(l.id) }
                        : { icon: 'archive', label: 'Archive', onClick: () => onArchive && onArchive(l.id) }) : null,
                    ]} />
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
        {filtered.length === 0 ? ((activeFilterCount > 0 || quick.length || query || preset !== 'all') ? (
          <div className="mn-empty">
            <Icon name="search-x" size="lg" />
            <div className="mn-empty-msg">No matches. Reset filters or change your search.</div>
            <Button variant="ghost" size="sm" onClick={resetView}>Reset</Button>
          </div>
        ) : (
          <div className="mn-empty">
            <Icon name="users" size="lg" />
            <div className="mn-empty-msg">No leads yet — start by adding one.</div>
            {RB.can(role, 'leads.create') ? <Button variant="primary" size="sm" icon="plus" onClick={onCreate}>Add lead</Button> : null}
          </div>
        )) : null}
      </div>
      {filtered.length > 0 ? <Pagination total={filtered.length} page={page} pageSize={pageSize} onPage={setPage} onPageSize={(n) => { setPageSize(n); setPage(1); }} /> : null}
      {filterOpen ? <LeadFilterDrawer eyebrow="Leads" title="Filters" fields={leadFields} query={advq} focusField={focusField} total={scoped.filter(curTab.filter).length} countFor={(q) => { let r = scoped.filter(curTab.filter); quick.forEach(k => { r = r.filter(quickMatch(k)); }); return applyAdvQuery(r, q, leadFields).length; }} onApply={setAdvq} onClose={() => { setFilterOpen(false); setFocusField(null); }} /> : null}

      {/* Bulk action modals (prd-leads-bulk-actions UX) — focus-managed, keyboard-accessible. */}
      {bulkModal === 'assign' ? <BulkAssignModal scopeLabel={bulkScopeLabel} role={role} onClose={() => setBulkModal(null)} onConfirm={bulkAssign} /> : null}
      {bulkModal === 'status' ? <BulkStatusModal scopeLabel={bulkScopeLabel} onClose={() => setBulkModal(null)} onConfirm={bulkStatus} /> : null}
      {bulkModal === 'tags' ? <BulkTagsModal scopeLabel={bulkScopeLabel} tagCatalog={tagOpts.map(t => t.value)} selectionTags={selectedTagOpts} onClose={() => setBulkModal(null)} onConfirm={bulkTags} /> : null}
      {bulkModal === 'archive' ? <BulkArchiveModal count={selCount} scopeLabel={bulkScopeLabel} onClose={() => setBulkModal(null)} onConfirm={() => guardDeals('archive', runBulkArchive)} /> : null}
      {bulkModal === 'export' ? <BulkExportModal count={selCount} scopeLabel={bulkScopeLabel} fields={exportFields} defaultChecked={exportDefault} role={role} onClose={() => setBulkModal(null)} onConfirm={bulkExport} /> : null}
      {bulkModal && bulkModal.kind === 'deals-block' ? <BulkDealsBlockModal action={bulkModal.action} affected={bulkModal.affected} wonBlocked={bulkModal.wonBlocked} onClose={() => setBulkModal(null)} onContinue={bulkModal.onContinue} /> : null}
    </React.Fragment>
  );
}

// Naive CSV parse for import (handles clean comma-separated files incl. our own export).
function parseLeadsCSV(text) {
  const lines = String(text || '').split(/\r?\n/).filter(x => x.trim().length);
  if (lines.length < 2) return [];
  const headers = lines[0].split(',').map(h => h.trim().toLowerCase());
  const idx = (n) => headers.indexOf(n);
  return lines.slice(1).map(line => {
    const cells = line.split(',');
    const get = (n) => { const i = idx(n); return i >= 0 ? (cells[i] || '').trim().replace(/^"|"$/g, '') : ''; };
    const name = get('name'); const parts = name.split(' ');
    return {
      first: get('first') || parts[0] || 'Imported', last: get('last') || parts.slice(1).join(' '),
      email: get('email'), phone: get('phone'), company: get('company'), country: get('country'),
      status: 'New', sourceName: get('source') || 'import', tags: [],
    };
  });
}

// CSV export of leads (used by toolbar Export and bulk Export). prd-leads-bulk-actions BR-BA-018.
function downloadLeadsCSV(rows, filename) {
  const cols = [
    ['public_id', l => window.MonolitData.publicId(l.id)], ['name', l => l.name],
    ['company', l => l.company || ''], ['email', l => l.email || ''], ['phone', l => l.phone || ''],
    ['status', l => l.status], ['score', l => l.score == null ? '' : l.score],
    ['owner', l => l.owner ? l.owner.name : ''], ['next_step', l => l.nextStep ? l.nextStep.text : ''],
    ['created_at', l => { try { return new Date(l.createdAt).toISOString().slice(0, 10); } catch (e) { return ''; } }],
  ];
  const esc = (v) => { const s = String(v == null ? '' : v); return /[",\n]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s; };
  const csv = [cols.map(c => c[0]).join(',')].concat(rows.map(l => cols.map(c => esc(c[1](l))).join(','))).join('\n');
  const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a'); a.href = url; a.download = filename || 'leads.csv';
  document.body.appendChild(a); a.click(); a.remove();
  setTimeout(() => URL.revokeObjectURL(url), 1000);
}

// Lead Current Time needs a tz; leads carry only country, so approximate (prd-leads-columns: fallback / muted if unknown).
const LEAD_TZ = { 'Ukraine': 2, 'USA': -5, 'Kazakhstan': 5, 'Switzerland': 1, 'Italy': 1, 'UK': 0, 'Japan': 9, 'Poland': 1, 'India': 5.5, 'Russia': 3, 'Germany': 1, 'France': 1, 'Spain': 1, 'Netherlands': 1 };
function leadTzOffset(l) { const v = LEAD_TZ[l && l.country]; return v == null ? null : v; }

// Score cell (prd-leads-columns) — band label + numeric value + proportional bar, coloured by band
// per lead-scoring-bant color mapping: High ≥70 green · Medium 40–69 orange · Low 0–39 red.
function scoreBand(n) {
  if (n == null) return { label: '—', color: 'var(--text-3)' };
  if (n >= 70) return { label: 'High', color: 'var(--success-main)' };
  if (n >= 40) return { label: 'Medium', color: 'var(--warning-main)' };
  return { label: 'Low', color: 'var(--error-main)' };
}
function ScoreCell({ value }) {
  if (value == null) return <span style={{ color: 'var(--text-3)' }}>—</span>;
  const b = scoreBand(value);
  return (
    <div style={{ minWidth: 84 }}>
      <div className="mn-row" style={{ justifyContent: 'space-between', alignItems: 'baseline', gap: 8 }}>
        <span style={{ font: 'var(--font-weight-strong) 13px/16px var(--font-sans)', color: 'var(--text-1)' }}>{b.label}</span>
        <span className="mn-mono" style={{ font: 'var(--font-weight-strong) 13px/16px var(--font-mono)', color: b.color }}>{value}</span>
      </div>
      <span style={{ display: 'block', height: 4, marginTop: 5, borderRadius: 'var(--radi-full)', background: 'var(--bg-3)', overflow: 'hidden' }}>
        <span style={{ display: 'block', height: '100%', width: value + '%', borderRadius: 'var(--radi-full)', background: b.color }} />
      </span>
    </div>
  );
}

// Tags cell: compact chips with +N overflow (prd-leads-columns: Tags column).
function TagCells({ tags }) {
  const t = tags || [];
  if (!t.length) return <span style={{ color: 'var(--text-3)' }}>—</span>;
  return (
    <span style={{ display: 'inline-flex', gap: 4, alignItems: 'center' }}>
      {t.slice(0, 2).map(x => <span key={x} className="mn-chip mn-chip--md mn-chip--tag">{x}</span>)}
      {t.length > 2 ? <span className="mn-mono mn-muted" style={{ fontSize: 11 }}>+{t.length - 2}</span> : null}
    </span>
  );
}

// Owner cell content (avatar+name or No-owner chip) — shared by display and inline-edit trigger.
function OwnerCellInner({ lead }) {
  return lead.owner
    ? <div className="mn-cell-owner"><Avatar user={lead.owner} size={22} /><span>{lead.owner.name}</span></div>
    : <span className="mn-chip mn-chip--noowner"><Icon name="user-x" size="xs" stroke="2" />No owner</span>;
}

// Inline Next Step editor (text + date) for the list row popover.
function NextStepEditor({ lead, onSave }) {
  const ns = lead.nextStep || {};
  const [text, setText] = useStateLL(ns.text || '');
  const [due, setDue] = useStateLL(ns.due ? new Date(ns.due).toISOString().slice(0, 10) : '');
  return (
    <div style={{ padding: 12, width: 256 }}>
      <div className="mn-eyebrow" style={{ marginBottom: 6 }}>Next step</div>
      <Input fullWidth value={text} onChange={setText} placeholder="What's next…" autoFocus />
      <input type="date" value={due} onChange={(e) => setDue(e.target.value)} style={{ width: '100%', marginTop: 8, padding: '8px 10px', border: '1px solid var(--border-1)', borderRadius: 'var(--radi-sm)', font: 'var(--font-weight-medium) 13px/16px var(--font-sans)', color: 'var(--text-1)', background: 'var(--bg-1)', boxSizing: 'border-box' }} />
      <div className="mn-row" style={{ justifyContent: 'space-between', marginTop: 10 }}>
        <Button variant="ghost" size="sm" onClick={() => onSave(null)}>Clear</Button>
        <Button variant="primary" size="sm" icon="check" disabled={!text.trim() || !due} onClick={() => onSave({ text: text.trim(), due: new Date(due) })}>Save</Button>
      </div>
    </div>
  );
}

function SortableTh({ label, k, sortKey, sortDir, onClick, style, className }) {
  const active = sortKey === k;
  return (
    <th style={style} onClick={() => onClick(k)} title={`Sort by ${label}`} className={'mn-th-sortable' + (className ? ' ' + className : '')} >
      <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4, cursor: 'pointer', color: active ? 'var(--text-1)' : undefined }}>
        {label}
        <Icon name={active ? (sortDir === 'asc' ? 'arrow-up' : 'arrow-down') : 'arrow-up-down'} size="xs" />
      </span>
    </th>
  );
}

Object.assign(window, { LeadsList });
