// Clientes — list view with filters, search, table

// ── Helpers para normalizar leads do Supabase como clientes ───────────────────
const CLI_STATUS_LABELS = {
  qualifying: 'Qualificando', engaged: 'Engajado',
  proposal: 'Em proposta',   won: 'Ganho', lost: 'Perdido',
};

// Tentativas de contato antes de arquivar (list_archived_leads → attempts): "8 mensagens em 4 dias · respondeu 2×"
function cliAttemptsText(a) {
  if (!a) return '';
  const p = [];
  if (a.sent > 0) p.push(`${a.sent} mensage${a.sent !== 1 ? 'ns' : 'm'} em ${a.days} dia${a.days !== 1 ? 's' : ''}`);
  else p.push('Nenhuma mensagem do corretor');
  if (a.broadcasts > 0) p.push(`${a.broadcasts} disparo${a.broadcasts !== 1 ? 's' : ''}`);
  p.push(a.replies > 0 ? `respondeu ${a.replies}×` : 'cliente nunca respondeu');
  return p.join(' · ');
}

function deriveCreditStatus(col) {
  if (['em_analise', 'serasa'].includes(col)) return 'analysis';
  if (['aguardando_docs', 'aguardando_agenda'].includes(col)) return 'pending';
  if (['aprovado_final', 'arquivado_aprovado', 'fechado'].includes(col)) return 'approved';
  return 'na';
}

function normalizeLeadAsCliente(l) {
  const hash = [...(l.id || l.name || '')].reduce((a, c) => a + c.charCodeAt(0), 0);
  const palette = AVATAR_PALETTES[hash % AVATAR_PALETTES.length];
  const status = COL_TO_STATUS[l.col] || 'qualifying';
  const ts = l.updated_at || l.created_at;
  const colMeta = pipelineCols(leadPipeline(l)).find(c => c.id === l.col) || loadKanbanCols().find(c => c.id === l.col) || COLUMNS.find(c => c.id === l.col);
  const creditStatus = deriveCreditStatus(l.col);
  return {
    id: l.id,
    name: l.name || '—',
    initials: l.initials || (l.name || '?').split(' ').map(w => w[0]).join('').slice(0, 2).toUpperCase(),
    palette,
    cpf: '—',
    email: l.email || '—',
    phone: l.phone || '—',
    seg: l.seg || '—',
    cat: l.cat || '—',
    status,
    statusLabel: CLI_STATUS_LABELS[status] || status,
    temp: l.temp || 'warm',
    creditStatus,
    creditValue: leadMoney(l),
    nextAction: colMeta ? colMeta.title : (l.col || 'Em atendimento'),
    colColor: colMeta?.color || null,
    nextWhen: ts ? new Date(ts).toLocaleDateString('pt-BR') : '—',
    urgency: l.temp === 'hot' ? 'now' : l.temp === 'warm' ? 'soon' : '',
    score: l.score || 0,
    col: l.col,
    _isLead: true,
    broker: l.broker || '—',
    archiveReason: l.archive_reason || null, // ex.: "Número sem WhatsApp" (disparo em massa)
    ownOnly: !!l.own_only,
    region: l.cat || '—',
    value: leadMoney(l),
  };
}

// Exporta para uso em clientes-app.jsx
window.normalizeLeadAsCliente = normalizeLeadAsCliente;

function buildClientesRows() {
  return [...(window.LEADS || [])]
    .sort((a, b) => new Date(b.updated_at || b.created_at || 0) - new Date(a.updated_at || a.created_at || 0))
    .map(normalizeLeadAsCliente);
}

function computeClienteFilterCounts(rows) {
  return {
    all:      rows.length,
    hot:      rows.filter(c => c.temp === 'hot').length,
    analysis: rows.filter(c => c.credit?.status === 'analysis' || c.creditStatus === 'analysis' || c.col === 'em_analise').length,
    visit:    rows.filter(c => /visita/i.test(c.nextAction || '') || c.col === 'agendado').length,
    approved: rows.filter(c => c.credit?.status === 'approved' || c.creditStatus === 'approved' || c.col === 'aprovado_final').length,
    closed:   rows.filter(c => c.status === 'won' || c.col === 'fechado').length,
  };
}

// Menu flutuante simples (portal: não é cortado pelo painel)
function CliMenu({ anchor, onClose, items }) {
  React.useEffect(() => {
    const close = (e) => { if (e.type !== 'keydown' || e.key === 'Escape') onClose(); };
    const t = setTimeout(() => document.addEventListener('mousedown', close), 0);
    document.addEventListener('keydown', close);
    return () => { clearTimeout(t); document.removeEventListener('mousedown', close); document.removeEventListener('keydown', close); };
  }, []);
  return ReactDOM.createPortal(
    <div className="kb-head-menu" style={{ top: anchor.top, left: anchor.left }} onMouseDown={e => e.stopPropagation()}>
      {items.map(it => (
        <button key={it.label} onClick={() => { onClose(); it.action(); }}>
          <Icon name={it.icon} size={13} /> {it.label}
          {it.active && <Icon name="check" size={13} stroke={2.4} style={{ marginLeft: 'auto', color: 'var(--accent)' }} />}
        </button>
      ))}
    </div>,
    document.body
  );
}
const cliAnchor = (e, w = 220) => { const r = e.currentTarget.getBoundingClientRect(); return { top: r.bottom + 4, left: Math.max(8, r.right - w) }; };

function ClientesHeader({ filter, onFilter, search, onSearch, filters, onExport, archived, archCount, onArchived }) {
  const [menu, setMenu] = React.useState(null);
  return (
    <div className="crm-head">
      <div className="crm-head-top">
        <div>
          <h1 className="crm-title">Clientes</h1>
          <p className="crm-sub">Gerencie clientes e acompanhe toda sua jornada.</p>
        </div>
        <div className="crm-head-actions">
          <button className="ghost-btn lg" onClick={e => setMenu(menu ? null : cliAnchor(e))}><Icon name="bolt" size={13} stroke={2} /> Ações rápidas</button>
          {menu && <CliMenu anchor={menu} onClose={() => setMenu(null)} items={[
            { icon: 'plus',     label: 'Novo lead',                action: () => window.dispatchEvent(new CustomEvent('horos:new-lead')) },
            { icon: 'arrRight', label: 'Exportar clientes (CSV)',  action: onExport },
            { icon: 'chart',    label: 'Abrir funil (CRM)',        action: () => { window.location.href = 'crm.html'; } },
          ]} />}
          <button className="btn-primary" onClick={() => window.dispatchEvent(new CustomEvent('horos:new-lead'))}>
            <Icon name="plus" size={14} stroke={2} /> Novo Lead
          </button>
        </div>
      </div>

      <div className="crm-toolbar">
        <div className="filter-pills">
          {filters.map(f => (
            <button key={f.id}
                    className={`filter-pill ${filter === f.id ? 'active' : ''}`}
                    onClick={() => onFilter(f.id)}>
              {f.dot && <span className="filter-dot" style={{ background: f.dot }} />}
              <span>{f.label}</span>
              <span className="filter-count">{f.count}</span>
            </button>
          ))}
          {onArchived && (
            <button className={`filter-pill cli-arch-pill ${archived ? 'active' : ''}`} onClick={onArchived}
                    title="Leads que saíram do funil, com o motivo">
              <Icon name="archive" size={13} />
              <span>Arquivados</span>
              {archCount != null && <span className="filter-count">{archCount.toLocaleString('pt-BR')}</span>}
            </button>
          )}
        </div>

        <div className="crm-search">
          <Icon name="search" />
          <input value={search} onChange={e => onSearch(e.target.value)}
                 placeholder="Pesquisar nome, CPF, telefone…" />
          <span className="kbd">⌘F</span>
        </div>
      </div>
    </div>
  );
}

function CreditBadge({ status, value }) {
  if (status === 'na') return <span className="cred-na">—</span>;
  if (status === 'pending') return (
    <div className="cred-cell">
      <span className="cred-dot pending" />
      <div className="cred-meta">
        <span className="cred-label">Pendente</span>
        <span className="cred-sub">Aguardando docs</span>
      </div>
    </div>
  );
  if (status === 'analysis') return (
    <div className="cred-cell">
      <span className="cred-dot analysis" />
      <div className="cred-meta">
        <span className="cred-label">Em análise</span>
        <span className="cred-sub">{cliFmt(value)}</span>
      </div>
    </div>
  );
  if (status === 'approved') return (
    <div className="cred-cell">
      <span className="cred-dot approved" />
      <div className="cred-meta">
        <span className="cred-label cred-approved">Aprovado</span>
        <span className="cred-sub">{cliFmt(value)}</span>
      </div>
    </div>
  );
  return null;
}

function TempPill({ t }) {
  const labels = { hot: 'Quente', warm: 'Morno', cold: 'Frio' };
  const ico = { hot: 'flame', warm: 'drop', cold: 'snow' };
  return (
    <span className={`temp-pill temp-${t}`}>
      <Icon name={ico[t]} size={10} stroke={1.9} />
      {labels[t]}
    </span>
  );
}

// ── Filtros avançados (etapa, região, temperatura, corretor, origem) ─────────
const CLI_TEMPS = [
  { value: 'hot',  label: 'Quente' },
  { value: 'warm', label: 'Morno' },
  { value: 'cold', label: 'Frio' },
];
const CLI_ORIGINS = { campanha: 'Campanha', lista: 'Lista', conhecido: 'Conhecido', indicacao: 'Indicação', site: 'Site / Landing page', feirao: 'Feirão / Eventos', acao_rua: 'Ação de rua', outros: 'Outros', meta_ads: 'Meta Ads', whatsapp: 'WhatsApp', formulario: 'Formulário' };
const CLI_FX_EMPTY = { cols: [], regions: [], temps: [], brokers: [], origins: [] };
const cliFxCount = fx => Object.values(fx).reduce((n, a) => n + a.length, 0);

// Aplica os filtros avançados numa query de leads
function cliApplyFx(q, fx) {
  if (fx.cols.length)    q = q.in('col', fx.cols);
  if (fx.regions.length) q = q.in('cat', fx.regions);
  if (fx.temps.length)   q = q.in('temp', fx.temps);
  if (fx.brokers.length) q = q.in('broker', fx.brokers);
  if (fx.origins.length) q = q.in('data->>origemLead', fx.origins);
  return q;
}

let _cliFxOptsCache = null;
async function cliLoadFxOpts(force) {
  if (_cliFxOptsCache && !force) return _cliFxOptsCache;
  const { data, error } = await window.HOROS_SB.rpc('lead_filter_options');
  if (!error && data) _cliFxOptsCache = data;
  return _cliFxOptsCache || { regions: [], brokers: [], origins: [], cols: {} };
}

function CliFxSection({ title, options, selected, onToggle, searchable }) {
  const [q, setQ] = React.useState('');
  const norm = s => String(s || '').normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase();
  const list = searchable && q ? options.filter(o => norm(o.label).includes(norm(q))) : options;
  if (!options.length) return null;
  return (
    <div className="cli-fx-sec">
      <div className="cli-fx-sec-title">
        {title}
        {selected.length > 0 && <span className="cli-fx-sec-n">{selected.length}</span>}
      </div>
      {searchable && options.length > 6 && (
        <input className="cli-fx-search" value={q} onChange={e => setQ(e.target.value)} placeholder={`Buscar ${title.toLowerCase()}…`} />
      )}
      <div className="cli-fx-opts">
        {list.map(o => {
          const on = selected.includes(o.value);
          return (
            <button key={o.value} className={`cli-fx-opt${on ? ' on' : ''}`} onClick={() => onToggle(o.value)}>
              <span className="cli-fx-box">{on && <Icon name="check" size={10} stroke={3} />}</span>
              {o.color && <span className="cli-fx-dot" style={{ background: o.color }} />}
              <span className="cli-fx-lbl">{o.label}</span>
              {o.count != null && <span className="cli-fx-n">{o.count}</span>}
            </button>
          );
        })}
        {!list.length && <span className="cli-fx-none">Nada encontrado</span>}
      </div>
    </div>
  );
}

function CliFxPanel({ anchor, fx, onChange, onClose }) {
  const solo = window.useHorosSolo ? window.useHorosSolo() : false;
  const cols = useKanbanCols();
  const [opts, setOpts] = React.useState(_cliFxOptsCache);
  React.useEffect(() => { cliLoadFxOpts(true).then(setOpts); }, []);
  React.useEffect(() => {
    const close = (e) => { if (e.type !== 'keydown' || e.key === 'Escape') onClose(); };
    const t = setTimeout(() => document.addEventListener('mousedown', close), 0);
    document.addEventListener('keydown', close);
    return () => { clearTimeout(t); document.removeEventListener('mousedown', close); document.removeEventListener('keydown', close); };
  }, []);
  const toggle = (key) => (v) => onChange({ ...fx, [key]: fx[key].includes(v) ? fx[key].filter(x => x !== v) : [...fx[key], v] });
  const colCounts = opts?.cols || {};
  return ReactDOM.createPortal(
    <div className="cli-fx-panel" style={{ top: anchor.top, left: anchor.left }} onMouseDown={e => e.stopPropagation()}>
      <div className="cli-fx-head">
        <span>Filtros</span>
        {cliFxCount(fx) > 0 && <button className="cli-fx-clear" onClick={() => onChange(CLI_FX_EMPTY)}>Limpar tudo</button>}
      </div>
      <div className="cli-fx-body">
        <CliFxSection title="Etapa do atendimento" selected={fx.cols} onToggle={toggle('cols')}
          options={cols.map(c => ({ value: c.id, label: c.title, color: c.color, count: opts ? (colCounts[c.id] || 0) : null }))} />
        <CliFxSection title="Temperatura" selected={fx.temps} onToggle={toggle('temps')}
          options={CLI_TEMPS.map(t => ({ ...t, color: { hot: '#ff5a4e', warm: '#f5b544', cold: '#59c8ff' }[t.value] }))} />
        <CliFxSection title="Região" searchable selected={fx.regions} onToggle={toggle('regions')}
          options={(opts?.regions || []).map(r => ({ value: r.value, label: r.value, count: r.count }))} />
        {!solo && <CliFxSection title={segTerm('Agent', 'Corretor')} searchable selected={fx.brokers} onToggle={toggle('brokers')}
          options={(opts?.brokers || []).map(r => ({ value: r.value, label: r.value, count: r.count }))} />}
        <CliFxSection title="Origem" selected={fx.origins} onToggle={toggle('origins')}
          options={(opts?.origins || []).map(r => ({ value: r.value, label: CLI_ORIGINS[r.value] || r.value, count: r.count }))} />
        {!opts && <SkelList rows={4} avatar={0} lines={1} pad="8px 14px" />}
      </div>
    </div>,
    document.body
  );
}

// Chips dos filtros ativos (clique no × remove)
function CliFxChips({ fx, onChange }) {
  const cols = useKanbanCols();
  const chips = [
    ...fx.cols.map(v => ({ k: 'cols', v, label: cols.find(c => c.id === v)?.title || v })),
    ...fx.temps.map(v => ({ k: 'temps', v, label: CLI_TEMPS.find(t => t.value === v)?.label || v })),
    ...fx.regions.map(v => ({ k: 'regions', v, label: v })),
    ...fx.brokers.map(v => ({ k: 'brokers', v, label: v })),
    ...fx.origins.map(v => ({ k: 'origins', v, label: CLI_ORIGINS[v] || v })),
  ];
  if (!chips.length) return null;
  return (
    <div className="cli-fx-chips">
      {chips.map(c => (
        <span key={c.k + c.v} className="cli-fx-chip">
          {c.label}
          <button title="Remover filtro" onClick={() => onChange({ ...fx, [c.k]: fx[c.k].filter(x => x !== c.v) })}>
            <Icon name="close" size={10} stroke={2.4} />
          </button>
        </span>
      ))}
      <button className="cli-fx-chip-clear" onClick={() => onChange(CLI_FX_EMPTY)}>Limpar filtros</button>
    </div>
  );
}

function ClientesTable({ rows, onSelect, total, fx, onFx, onExport, exporting, archived, onToggleArchived, archivedCount, onRestore, restoring, canRestore }) {
  const [fxAnchor, setFxAnchor] = React.useState(null);
  const nFx = cliFxCount(fx);
  return (
    <div className="panel cli-table-panel">
      <div className="panel-head">
        <h3 className="panel-title">
          {archived ? 'Arquivados' : 'Clientes'}
          <span className="count">{total ?? rows.length}</span>
        </h3>
        <div className="panel-actions">
          {!archived && (
            <button className={`ghost-btn${nFx ? ' kb-select-on' : ''}`} onClick={e => setFxAnchor(fxAnchor ? null : cliAnchor(e, 320))}>
              <Icon name="filter" size={13} /> Filtrar{nFx ? ` · ${nFx}` : ''}
            </button>
          )}
          {fxAnchor && <CliFxPanel anchor={fxAnchor} fx={fx} onChange={onFx} onClose={() => setFxAnchor(null)} />}
          {archived && onToggleArchived && <button className="ghost-btn" onClick={onToggleArchived} title="Voltar para os clientes ativos">
            <Icon name="arrLeft" size={13} /> Voltar aos ativos
          </button>}
          {!archived && (
            <button className="ghost-btn" onClick={onExport} disabled={exporting}>
              {exporting ? 'Exportando…' : <>Exportar <Icon name="arrRight" size={13} /></>}
            </button>
          )}
        </div>
      </div>
      {!archived && <CliFxChips fx={fx} onChange={onFx} />}

      <div className="cli-table-wrap">
        <table className="table cli-table">
          <thead>
            <tr>
              <th style={{ paddingLeft: 18 }}>Cliente</th>
              <th>Contato</th>
              <th>Categoria</th>
              <th>Etapa</th>
              <th>Temp.</th>
              <th>Crédito (CCA)</th>
              <th>{archived ? 'Arquivado em' : 'Atualizado'}</th>
              <th>Score IA</th>
              <th style={{ width: archived && canRestore ? 120 : 60 }}></th>
            </tr>
          </thead>
          <tbody>
            {rows.map((c) => (
              <tr key={c.id} className={`cli-row${archived ? ' cli-row-archived' : ''}${archived && !canRestore ? ' cli-row-static' : ''}`}
                  onClick={() => { if (!archived || canRestore) onSelect(c); }}>
                <td style={{ paddingLeft: 18 }}>
                  <div className="cell-name">
                    <div className="avatar" style={{ background: `linear-gradient(135deg, ${c.palette[0]}, ${c.palette[1]})`, width: 32, height: 32 }}>
                      {c.initials}
                    </div>
                    <div className="meta-stack">
                      <span className="name">{c.name}</span>
                      <span className="email">{c.cpf}</span>
                    </div>
                  </div>
                </td>
                <td>
                  <div className="cli-contact">
                    <span className="phone">{fmtPhone(c.phone)}</span>
                    <span className="cli-email">{c.email}</span>
                  </div>
                </td>
                <td>
                  <span className="tag">{c.seg}{c.cat ? ` · ${c.cat}` : ''}</span>
                </td>
                <td>
                  <span className="cli-stage"><span className="cli-stage-dot" style={{ background: c.colColor || 'var(--fg-4)' }} />{c.nextAction}</span>
                </td>
                <td><TempPill t={c.temp} /></td>
                <td><CreditBadge status={c.credit ? c.credit.status : c.creditStatus} value={c.credit ? c.credit.approved : c.creditValue} /></td>
                <td>
                  <div className="next-action">
                    <span className="action-name">{c.nextWhen}</span>
                    {archived && c.archiveReason
                      ? <span className="action-when" title="Motivo do arquivamento">{c.archiveReason}</span>
                      : c.broker && c.broker !== '—' && <span className="action-when">{c.broker}</span>}
                    {archived && c.archiveAttempts && (
                      <span className="action-when" title={`Tentativas de contato antes de arquivar${c.archiveHolder ? ` · lead com ${c.archiveHolder}` : ''}`}
                        style={{ color: 'var(--fg-3)' }}>{c.archiveAttempts}</span>
                    )}
                  </div>
                </td>
                <td>
                  <div className="score-cell">
                    <ScoreRing score={c.score} />
                    <span className="score-num">{c.score}</span>
                  </div>
                </td>
                <td>
                  {archived ? (canRestore && (c.archiveRecoverable === false
                    ? <span className="action-when" style={{ color: 'var(--fg-4)' }} title="Motivo definitivo: não volta ao funil">Definitivo</span>
                    : <button className="btn-primary sm cli-restore-btn" disabled={restoring === c.id}
                            onClick={(e) => { e.stopPropagation(); onRestore(c); }}>
                      <Icon name="undo" size={12} stroke={2} /> {restoring === c.id ? 'Recuperando…' : 'Recuperar'}
                    </button>)
                  ) : (
                    <div className="row-actions">
                      <button className="icon-btn" style={{ width: 28, height: 28 }} title="Abrir ficha em tela cheia"
                              onClick={(e) => { e.stopPropagation(); window.location.href = `client.html?id=${c.id}`; }}>
                        <Icon name="expand" size={13} />
                      </button>
                    </div>
                  )}
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

// Arquivados em blocos por motivo — "12 arquivados por Sem interesse". Clique filtra a lista.
function ArchiveReasonBlocks({ groups, active, onPick }) {
  if (!groups) return <div className="arq-blocks" aria-busy="true">{[0, 1, 2, 3].map(i => <div key={i} className="arq-block skel"><Skel h={12} w="60%" /><Skel h={22} w="30%" style={{ marginTop: 10 }} /></div>)}</div>;
  if (!groups.length) return null;
  const label = window.archiveReasonLabel || (x => x);
  const all = groups.reduce((a, g) => a + g.total, 0);
  const fmtD = (t) => t ? new Date(t).toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit', year: '2-digit' }) : '';
  return (
    <div className="arq-blocks" role="tablist" aria-label="Arquivados por motivo">
      <button type="button" role="tab" aria-selected={!active} className={`arq-block${!active ? ' on' : ''}`} onClick={() => onPick(null)}>
        <span className="arq-block-lbl">Todos os motivos</span>
        <span className="arq-block-num">{all.toLocaleString('pt-BR')}</span>
        <span className="arq-block-sub">{groups.length} {groups.length === 1 ? 'motivo' : 'motivos'}</span>
      </button>
      {groups.map(g => (
        <button key={g.reason} type="button" role="tab" aria-selected={active === g.reason}
          className={`arq-block${active === g.reason ? ' on' : ''}${g.reason === '_none' ? ' muted' : ''}`}
          onClick={() => onPick(active === g.reason ? null : g.reason)}>
          <span className="arq-block-lbl">{label(g.reason)}</span>
          <span className="arq-block-num">{g.total.toLocaleString('pt-BR')}</span>
          <span className="arq-block-sub">{g.total === 1 ? 'arquivado' : 'arquivados'}{g.last_at ? ` · último ${fmtD(g.last_at)}` : ''}</span>
          <span className="arq-block-bar"><i style={{ width: `${Math.max(4, Math.round(g.total / all * 100))}%` }} /></span>
        </button>
      ))}
    </div>
  );
}

const CLI_PAGE_SIZE = 100;
const CLI_COLS = 'id,name,initials,seg,cat,phone,email,value,score,temp,col,broker,archived,created_at,updated_at';

// Filtros avançados lembrados entre aberturas (só no navegador)
const cliFxLoad = () => {
  try { const v = JSON.parse(localStorage.getItem('horos-cli-fx') || 'null'); return v ? { ...CLI_FX_EMPTY, ...v } : CLI_FX_EMPTY; }
  catch { return CLI_FX_EMPTY; }
};

function ClientesListPage({ onSelect }) {
  const [filter,      setFilter]      = React.useState('all');
  const [search,      setSearch]      = React.useState('');
  const [fx,          setFxState]     = React.useState(cliFxLoad);
  const [archived,    setArchived]    = React.useState(false);
  // Arquivados só para gerente (equipe) e super — list_archived_leads devolve allowed:false para os demais
  const [archAllowed, setArchAllowed] = React.useState(false);
  const [canRestore,  setCanRestore]  = React.useState(false);   // recuperar: só gerente e super
  const [archCount,   setArchCount]   = React.useState(null);
  const [restoring,   setRestoring]   = React.useState(null);
  const [archReason,  setArchReason]  = React.useState(null);   // bloco de motivo escolhido (null = todos)
  const [archGroups,  setArchGroups]  = React.useState(null);   // [{ reason, total, last_at }]
  const { canEdit: arqEdit } = window.useArchiveReasons();
  const [arqMgr, setArqMgr] = React.useState(false);
  const [notice,      setNotice]      = React.useState(null); // { kind: 'ok'|'err', text }
  const [exporting,   setExporting]   = React.useState(false);
  const [dSearch,     setDSearch]     = React.useState('');
  const [rows,        setRows]        = React.useState([]);
  const [total,       setTotal]       = React.useState(0);
  const [counts,      setCounts]      = React.useState({});
  const [loading,     setLoading]     = React.useState(true);
  const [loadingMore, setLoadingMore] = React.useState(false);
  const debRef = React.useRef(null);
  const noticeRef = React.useRef(null);
  const sb = window.HOROS_SB;
  const fxKey = JSON.stringify(fx);

  const setFx = (v) => {
    setFxState(v);
    try { localStorage.setItem('horos-cli-fx', JSON.stringify(v)); } catch {}
  };

  // Debounce search: só dispara query 400ms após parar de digitar
  React.useEffect(() => {
    clearTimeout(debRef.current);
    debRef.current = setTimeout(() => setDSearch(search), 400);
    return () => clearTimeout(debRef.current);
  }, [search]);

  // Busca por nome, telefone (com ou sem máscara), e-mail ou região
  const applySearch = (q, s) => {
    s = s.trim().replace(/[,()]/g, ' ');
    if (!s) return q;
    const dig = s.replace(/\D/g, '');
    const parts = [`name.ilike.%${s}%`, `phone.ilike.%${s}%`, `email.ilike.%${s}%`, `cat.ilike.%${s}%`];
    if (dig.length >= 4 && dig !== s) parts.push(`phone.ilike.%${dig}%`);
    return q.or(parts.join(','));
  };

  // Monta query Supabase com filtro + busca + filtros avançados aplicados no banco
  const buildQ = (f, s) => {
    let q = sb.from('leads')
      .select(CLI_COLS, { count: 'exact' })
      .eq('archived', false)
      .order('updated_at', { ascending: false, nullsFirst: false });
    if (f === 'hot')           q = q.eq('temp', 'hot');
    else if (f === 'analysis') q = q.in('col', ['em_analise', 'serasa']);
    else if (f === 'visit')    q = q.eq('col', 'agendado');
    else if (f === 'approved') q = q.in('col', ['aprovado_final', 'arquivado_aprovado', 'fechado']);
    else if (f === 'closed')   q = q.eq('col', 'fechado');
    return cliApplyFx(applySearch(q, s), fx);
  };

  const loadArchived = async (offset) => {
    const { data, error } = await sb.rpc('list_archived_leads', { p_search: dSearch, p_limit: CLI_PAGE_SIZE, p_offset: offset, p_reason: archReason });
    if (error || !data) return null;
    await window.horosLoadArchiveReasons?.();
    const label = window.archiveReasonLabel || (x => x);
    return { rows: (data.rows || []).map(r => ({
      ...normalizeLeadAsCliente({ ...r, updated_at: r.archived_at || r.updated_at }),
      archiveRecoverable: window.archiveReasonRecoverable ? window.archiveReasonRecoverable(r.reason) : true,
      archiveReason: [label(r.reason), r.reason_note, r.archived_by_name && `por ${r.archived_by_name}`].filter(Boolean).join(' · '),
      archiveAttempts: cliAttemptsText(r.attempts),
      archiveHolder: r.holder_name || null,
    })), total: data.total || 0 };
  };

  // Exporta TODOS os clientes do filtro atual (não só a página carregada)
  const exportAll = async () => {
    if (exporting) return;
    setExporting(true);
    try {
      let all = [];
      for (let page = 0; page < 30; page++) {
        const { data, error } = await buildQ(filter, dSearch).range(page * 1000, page * 1000 + 999);
        if (error || !data) break;
        all = all.concat(data);
        if (data.length < 1000) break;
      }
      window.exportLeadsCSV(all, `clientes-${new Date().toISOString().slice(0, 10)}.csv`);
    } finally { setExporting(false); }
  };

  // Carrega primeira página ao mudar filtro, busca ou modo (ativos/arquivados)
  React.useEffect(() => {
    let cancelled = false;
    (async () => {
      setLoading(true);
      setRows([]);
      if (archived) {
        const r = await loadArchived(0);
        if (cancelled) return;
        if (r) { setRows(r.rows); setTotal(r.total); if (!dSearch.trim() && !archReason) setArchCount(r.total); }
      } else {
        const { data, count, error } = await buildQ(filter, dSearch).range(0, CLI_PAGE_SIZE - 1);
        if (cancelled) return;
        if (!error && data) {
          setRows(data.map(normalizeLeadAsCliente));
          setTotal(count || 0);
        }
      }
      setLoading(false);
    })();
    return () => { cancelled = true; };
  }, [filter, dSearch, fxKey, archived, archReason]);

  // Blocos por motivo (Arquivados)
  const loadArchGroups = React.useCallback(() => {
    sb.rpc('archived_reason_groups').then(({ data }) => setArchGroups(Array.isArray(data) ? data : []));
  }, []);
  React.useEffect(() => { if (archived) loadArchGroups(); else setArchReason(null); }, [archived]);

  // Quantos arquivados (para o botão "Arquivados")
  React.useEffect(() => {
    sb.rpc('list_archived_leads', { p_search: '', p_limit: 1, p_offset: 0 })
      .then(({ data }) => { if (data) { setArchCount(data.total || 0); setArchAllowed(data.allowed !== false); setCanRestore(!!data.can_restore); } });
  }, []);

  // Contagens para badges dos filtros (queries leves, só count) — respeitam busca e filtros avançados
  React.useEffect(() => {
    (async () => {
      const base = () => cliApplyFx(applySearch(sb.from('leads').select('*', { count: 'exact', head: true }).eq('archived', false), dSearch), fx);
      const [all, hot, analysis, visit, approved, closed] = await Promise.all([
        base().then(r => r.count || 0),
        base().eq('temp', 'hot').then(r => r.count || 0),
        base().in('col', ['em_analise', 'serasa']).then(r => r.count || 0),
        base().eq('col', 'agendado').then(r => r.count || 0),
        base().in('col', ['aprovado_final', 'arquivado_aprovado', 'fechado']).then(r => r.count || 0),
        base().eq('col', 'fechado').then(r => r.count || 0),
      ]);
      setCounts({ all, hot, analysis, visit, approved, closed });
    })();
  }, [dSearch, fxKey]);

  const loadMore = async () => {
    setLoadingMore(true);
    if (archived) {
      const r = await loadArchived(rows.length);
      if (r) setRows(prev => [...prev, ...r.rows]);
    } else {
      const { data, error } = await buildQ(filter, dSearch).range(rows.length, rows.length + CLI_PAGE_SIZE - 1);
      if (!error && data) setRows(prev => [...prev, ...data.map(normalizeLeadAsCliente)]);
    }
    setLoadingMore(false);
  };

  const flash = (kind, text) => {
    setNotice({ kind, text });
    clearTimeout(noticeRef.current);
    noticeRef.current = setTimeout(() => setNotice(null), 6000);
  };

  // Recupera um contato arquivado (volta para a etapa em que estava)
  const restore = async (c) => {
    if (restoring) return;
    setRestoring(c.id);
    const { data, error } = await sb.rpc('restore_lead', { p_id: c.id });
    setRestoring(null);
    if (error || !data?.ok) {
      const e = data?.error;
      flash('err', e === 'duplicate'
        ? `Não dá para recuperar: já existe um lead ativo com esse telefone (${data.name}).`
        : e === 'forbidden' ? 'Você não tem permissão para recuperar este contato.'
        : e === 'not_recoverable' ? 'Este contato foi arquivado com um motivo definitivo e não volta ao funil.'
        : 'Não foi possível recuperar. Tente de novo.');
      return;
    }
    setRows(prev => prev.filter(r => r.id !== c.id));
    setTotal(t => Math.max(0, t - 1));
    setArchCount(n => (n == null ? n : Math.max(0, n - 1)));
    loadArchGroups();
    setCounts(k => ({ ...k, all: (k.all || 0) + 1 }));
    // Lead que estava numa etapa "Arquivado…" volta para a etapa de onde veio (data.col)
    const backTo = data.col ? kanbanColTitle(data.col) : c.nextAction;
    flash('ok', `${c.name} foi recuperado e voltou para "${backTo}".`);
    // Avisa os Kanbans/listas abertos
    const { data: lead } = await sb.from('leads').select(CLI_COLS).eq('id', c.id).maybeSingle();
    if (lead) {
      if (Array.isArray(window.LEADS) && !window.LEADS.some(l => l.id === lead.id)) window.LEADS.unshift(lead);
      window.dispatchEvent(new CustomEvent('horos:lead-created', { detail: { ...lead, _restored: true } }));
    }
  };

  // Insere/atualiza na lista local sem recarregar tudo
  React.useEffect(() => {
    const onCreate = (e) => {
      if (!e.detail || e.detail._restored) return;
      const row = normalizeLeadAsCliente(e.detail);
      setRows(prev => [row, ...prev]);
      setTotal(t => t + 1);
      setCounts(c => ({ ...c, all: (c.all || 0) + 1 }));
    };
    const onUpdate = (e) => {
      if (!e.detail) return;
      setRows(prev => prev.map(r => r.id === e.detail.id ? normalizeLeadAsCliente({ ...r, ...e.detail }) : r));
    };
    window.addEventListener('horos:lead-created', onCreate);
    window.addEventListener('horos:lead-updated', onUpdate);
    const onArchived = (e) => { const ids = new Set(e.detail?.ids || []); if (ids.size) { setRows(prev => { const n = prev.filter(r => !ids.has(r.id)); setTotal(t => Math.max(0, t - (prev.length - n.length))); return n; }); if (Array.isArray(window.LEADS)) window.LEADS = window.LEADS.filter(l => !ids.has(l.id)); } };
    window.addEventListener('horos:lead-archived', onArchived);
    return () => {
      window.removeEventListener('horos:lead-created', onCreate);
      window.removeEventListener('horos:lead-updated', onUpdate);
      window.removeEventListener('horos:lead-archived', onArchived);
    };
  }, []);

  const filters = React.useMemo(() => CLIENTES_FILTERS.map(f => ({ ...f, count: counts[f.id] ?? 0 })), [counts]);
  const hasMore = rows.length < total;
  const anyFilter = dSearch.trim() || filter !== 'all' || cliFxCount(fx) > 0;
  const linkBtn = { background:'none', border:'none', color:'var(--accent)', cursor:'pointer', fontSize:13, textDecoration:'underline', padding:0 };

  return (
    <>
      <ClientesHeader filter={archived ? null : filter} onFilter={f => { setArchived(false); setFilter(f); }} search={search} onSearch={setSearch} filters={filters} onExport={exportAll}
        archived={archived} archCount={archCount} onArchived={archAllowed ? () => setArchived(a => !a) : null} />

      {notice && (
        <div className={`cli-notice ${notice.kind}`} role="status">
          <Icon name={notice.kind === 'ok' ? 'check' : 'alert'} size={14} stroke={2} />
          <span>{notice.text}</span>
          <button onClick={() => setNotice(null)} title="Fechar"><Icon name="close" size={12} /></button>
        </div>
      )}

      {loading ? (
        <div className="panel" style={{ padding:0 }} aria-label={archived ? 'Carregando arquivados' : 'Carregando clientes'}>
          <SkelTable rows={10} />
        </div>
      ) : (
        <>
          {archived && arqEdit && <button type="button" className="arq-mgr-link" onClick={() => setArqMgr(true)}>Editar motivos de arquivamento</button>}
          {arqMgr && <ArchiveReasonsManager onClose={() => { setArqMgr(false); loadArchGroups(); }} />}
          {archived && <ArchiveReasonBlocks groups={archGroups} active={archReason} onPick={setArchReason} />}
          {archived && !canRestore && <p className="cli-arch-note">Estes são os leads que eram seus e saíram do funil. Para trazer um de volta, peça ao seu gerente — ele pode recuperar.</p>}
          <ClientesTable rows={rows} onSelect={onSelect} total={total} fx={fx} onFx={setFx}
            onExport={exportAll} exporting={exporting}
            archived={archived} archivedCount={archCount} onToggleArchived={archAllowed ? () => setArchived(a => !a) : null}
            onRestore={restore} restoring={restoring} canRestore={canRestore} />
          {rows.length === 0 && (
            <div className="panel" style={{ padding:'40px 24px', textAlign:'center', color:'var(--fg-4)', fontSize:13, marginTop:12 }}>
              {archived
                ? (dSearch.trim() ? 'Nenhum arquivado encontrado com essa busca.' : 'Nenhum contato arquivado.')
                : anyFilter
                ? <>Nenhum cliente encontrado com esses filtros.{' '}
                    {cliFxCount(fx) > 0 && <button onClick={() => setFx(CLI_FX_EMPTY)} style={linkBtn}>Limpar filtros</button>}
                  </>
                : <>Nenhum lead cadastrado ainda.{' '}
                    <button onClick={() => window.dispatchEvent(new CustomEvent('horos:new-lead'))} style={linkBtn}>
                      Criar o primeiro lead
                    </button>
                  </>
              }
            </div>
          )}
          {hasMore && (
            <div style={{ padding:'16px 24px', textAlign:'center' }}>
              <button onClick={loadMore} disabled={loadingMore} className="ghost-btn" style={{ minWidth:200 }}>
                {loadingMore
                  ? 'Carregando…'
                  : `Carregar mais ${Math.min(CLI_PAGE_SIZE, total - rows.length)} de ${total - rows.length} restantes`}
              </button>
            </div>
          )}
        </>
      )}
    </>
  );
}

// Ficha do cliente na metade da tela — mesmo painel global de todas as telas (HorosLeadSheet em data.jsx)
function ClienteSheetDrawer({ leadId, onClose }) {
  return ReactDOM.createPortal(<HorosLeadSheet leadId={leadId} onClose={onClose} />, document.body);
}

window.ClientesListPage = ClientesListPage;
window.ClienteSheetDrawer = ClienteSheetDrawer;
