// Customer 360 — slide-over drawer: Resumo retrátil no topo + abas Mensagens, Crédito, Visitas, Timeline IA

function ScoreRingBig({ score, max = 100, size = 56 }) {
  const stroke = 4;
  const r = (size - stroke) / 2;
  const c = size / 2;
  const circ = 2 * Math.PI * r;
  const offset = circ * (1 - score / max);
  const color = score >= 85 ? '#34d399' : score >= 65 ? '#7EC8FF' : score >= 45 ? '#f5b544' : '#94a3b8';
  return (
    <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} className="score-ring-big">
      <circle cx={c} cy={c} r={r} fill="none" stroke="rgba(148,163,184,0.18)" strokeWidth={stroke} />
      <circle cx={c} cy={c} r={r} fill="none" stroke={color} strokeWidth={stroke} strokeLinecap="round"
              strokeDasharray={circ} strokeDashoffset={offset}
              transform={`rotate(-90 ${c} ${c})`} />
      <text x={c} y={c + 4} textAnchor="middle" fontSize="14" fontWeight="600" fill="currentColor">{score}</text>
    </svg>
  );
}

// Resumo — tudo o que foi preenchido na ficha do cliente (leads.data), lido do banco ao abrir
const _C360_OPT = {
  estadoCivil: { solteiro: 'Solteiro(a)', casado: 'Casado(a)', divorciado: 'Divorciado(a)', viuvo: 'Viúvo(a)', uniao_estavel: 'União estável' },
  sexo: { feminino: 'Feminino', masculino: 'Masculino', nao_informado: 'Não informado' },
  origemLead: { 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',
    google_ads: 'Google Ads', tiktok_ads: 'TikTok Ads', formulario: 'Formulário do site', api: 'API do Horos', zapier: 'Zapier', make: 'Make', whatsapp: 'WhatsApp',
    importacao: 'Importação de planilha', zap: 'ZAP Imóveis', vivareal: 'VivaReal', imovelweb: 'Imovelweb', chavesnamao: 'Chaves na Mão', imagem_ia: 'Imagem IA', manual: 'Cadastro manual' },
  statusCredito: { aprovado: 'Aprovado', pendente: 'Pendente', reprovado: 'Reprovado' },
};
const _c360Val = (d, k) => {
  const v = d?.[k];
  if (v == null || v === '' || v === false || typeof v === 'object') return '';
  if (_C360_OPT[k]) return _C360_OPT[k][v] || String(v);
  if (/^(renda|outrasRendas|valor|entrada|parcela)/.test(k)) return /^r\$/i.test(String(v)) ? String(v) : 'R$ ' + v;
  if (/^(data|validade)/.test(k)) { const m = String(v).match(/^(\d{4})-(\d{2})-(\d{2})/); return m ? `${m[3]}/${m[2]}/${m[1]}` : String(v); }
  if (v === true) return 'Sim';
  return String(v);
};
const _c360Join = (...a) => a.filter(Boolean).join(' · ');

function _c360Sections(d, lead) {
  const ec = d.enderecoComercial && typeof d.enderecoComercial === 'object' ? d.enderecoComercial : null;
  const di = d.documentoIdentidade && typeof d.documentoIdentidade === 'object' ? d.documentoIdentidade : null;
  const ac = d.autorizacaoCredito;
  const row = (label, k) => ({ label, value: _c360Val(d, k) });
  return [
    { title: 'Dados pessoais', rows: [
      row('CPF', 'cpf'), row('RG', 'rg'), row('Nascimento', 'dataNascimento'), row('Estado civil', 'estadoCivil'),
      row('Sexo', 'sexo'), row('Dependentes', 'dependentes'), row('Nacionalidade', 'nacionalidade'), row('Origem', 'origemLead'),
      { label: 'Documento', value: di ? _c360Join(di.tipo, di.numero) : '' },
      { label: 'Órgão emissor / UF', value: di ? [di.orgao, di.uf].filter(Boolean).join(' / ') : '' },
      { label: 'Data de emissão', value: di && /^\d{4}-\d{2}-\d{2}$/.test(di.dataEmissao || '') ? di.dataEmissao.split('-').reverse().join('/') : '' },
    ] },
    { title: 'Contato e endereço', rows: [
      { label: 'Telefone 2', value: d.telefone2 ? fmtPhone(d.telefone2) : '' }, row('E-mail 2', 'email2'),
      { label: 'Endereço', wide: true, value: _c360Join(d.rua && [d.rua, d.numero].filter(Boolean).join(', '), d.complemento, d.bairro, [d.cidade, d.estado].filter(Boolean).join(' / '), d.cep) },
      { label: 'Endereço comercial', wide: true, value: ec ? (ec.nenhum ? 'Não possui' : ec.mesmo ? 'Mesmo do residencial' : _c360Join((ec.endereco || ec.rua) && [ec.endereco || ec.rua, ec.numero].filter(Boolean).join(', '), ec.bairro, [ec.cidade, ec.estado].filter(Boolean).join(' / '), ec.cep)) : '' },
    ] },
    { title: 'Trabalho e renda', rows: [
      row('Profissão', 'profissao'), row('Cargo', 'cargo'), row('Empresa', 'empresa'), row('Tempo na empresa', 'tempoEmpresa'),
      row('Renda individual', 'rendaIndividual'), row('Renda familiar', 'rendaFamiliar'), row('Outras rendas', 'outrasRendas'),
      { label: 'Obs. financeiras', value: _c360Val(d, 'obsFinanceiras'), wide: true },
    ] },
    { title: 'Interesse', rows: [
      { label: 'Categoria', value: _c360Val(d, 'categoriaInteresse') || lead.cat || '' },
      { label: 'Região', value: _c360Val(d, 'regiaoDesejada') || lead.region || '' },
      row('Bairros', 'bairrosDesejados'), row('Faixa de valor', 'faixaValor'),
      row('Valor pretendido', 'valorPretendido'), row('Entrada disponível', 'entradaDisponivel'),
      row('Urgência', 'urgencia'), { label: 'Primeira compra', value: d.primeiraCompra ? 'Sim' : '' },
      { label: 'Observações', value: _c360Val(d, 'obsInteresses'), wide: true },
    ] },
    { title: 'Crédito', rows: [
      { label: 'Autorização de consulta', value: ac?.status === 'assinada' ? 'Assinada' + (ac.assinadaEm ? ' em ' + new Date(ac.assinadaEm).toLocaleDateString('pt-BR') : '') : '' },
      row('Status', 'statusCredito'), row('Valor aprovado', 'valorAprovado'), row('Banco', 'banco'),
      row('Prazo (meses)', 'prazo'), row('Parcela máxima', 'parcelaMax'), row('Renda avaliada', 'rendaAvaliada'),
      row('Data da análise', 'dataAnalise'), row('Validade', 'validadeAnalise'),
      { label: 'Observações', value: _c360Val(d, 'obsCredito'), wide: true },
    ] },
  ].map(s => {
    const narrow = s.rows.filter(r => r.value && !r.wide), wide = s.rows.filter(r => r.value && r.wide);
    return { ...s, rows: [...narrow, ...(narrow.length % 2 ? [{ label: '', value: '', filler: true }] : []), ...wide] };
  }).filter(s => s.rows.some(r => r.value));
}

const TabResumo = ({ lead }) => {
  const solo = window.useHorosSolo ? window.useHorosSolo() : false;
  // Ficha completa (data/docs) — o lead do Kanban vem "enxuto", sem esses campos
  const [full, setFull] = React.useState(null);
  const load = React.useCallback(async () => {
    const sb = window.HOROS_SB;
    if (!sb || !lead?.id) return;
    const { data } = await sb.from('leads').select('data, docs, broker').eq('id', lead.id).maybeSingle();
    if (data) setFull(data);
  }, [lead?.id]);
  React.useEffect(() => {
    setFull(null);
    load();
    const onUpd = (e) => { if (!e.detail?.id || e.detail.id === lead?.id || e.detail.leadId === lead?.id) load(); };
    const onVis = () => { if (document.visibilityState === 'visible') load(); };
    window.addEventListener('horos:lead-updated', onUpd);
    window.addEventListener('horos:credit-auth-changed', onUpd);
    document.addEventListener('visibilitychange', onVis);
    return () => {
      window.removeEventListener('horos:lead-updated', onUpd);
      window.removeEventListener('horos:credit-auth-changed', onUpd);
      document.removeEventListener('visibilitychange', onVis);
    };
  }, [load, lead?.id]);

  const d = full?.data && typeof full.data === 'object' ? full.data : {};
  const rows = [
    { label: 'Valor estimado',  value: isArchivedLead({ ...lead, col: full?.col || lead.col }) ? 'Arquivado' : lead.value ? 'R$ ' + (lead.value >= 1000000 ? (lead.value/1000000).toFixed(2).replace('.', ',') + 'M' : Math.round(lead.value/1000) + 'k') : '—' },
    { label: 'Segmento',        value: lead.seg || '—' },
    ...(solo ? [] : [{ label: segTerm('Agent', 'Corretor'), value: full?.broker || lead.broker || 'Sem atribuição', highlight: true }]),
  ];
  const docs = full?.docs && typeof full.docs === 'object' ? Object.entries(full.docs).filter(([k, v]) => !/^p\d+_/.test(k) && v?.status) : [];
  rows.push({ label: 'Documentos', value: !full ? '…' : docs.length ? `${docs.filter(([, v]) => v.status === 'approved').length} de ${docs.length} aprovados` : 'Nenhum enviado' });
  const sections = full ? _c360Sections(d, lead) : [];
  const parts = Array.isArray(d.participantes) ? d.participantes.filter(p => p?.nome) : [];
  return (
    <div className="drawer-pad">
      <div className="info-grid">
        {rows.map(r => (
          <div className="info-row" key={r.label}>
            <span className="info-label">{r.label}</span>
            <span className={`info-value ${r.highlight ? 'highlight' : ''}`}>{r.value}</span>
          </div>
        ))}
      </div>

      {full && !sections.length && !parts.length && (
        <div className="c360-sum-empty">
          Nada preenchido na ficha ainda. <a href={`client.html?id=${lead.id}`} onClick={e => { e.preventDefault(); window.horosOpenLeadSheet(lead.id); }}>Preencher ficha</a>
        </div>
      )}
      {sections.map(s => (
        <div className="c360-sum-sec" key={s.title}>
          <div className="c360-sum-title">{s.title}</div>
          <div className="info-grid">
            {s.rows.map((r, i) => (
              <div className={`info-row${r.wide ? ' wide' : ''}`} key={r.label || 'f' + i}>
                {!r.filler && <span className="info-label">{r.label}</span>}
                {!r.filler && <span className="info-value">{r.value}</span>}
              </div>
            ))}
          </div>
        </div>
      ))}
      {parts.length > 0 && (
        <div className="c360-sum-sec">
          <div className="c360-sum-title">Participantes da compra</div>
          <div className="info-grid">
            {parts.map((p, i) => (
              <div className="info-row wide" key={i}>
                <span className="info-label">{p.vinculo ? String(p.vinculo).replace(/_/g, ' ') : 'Participante'}</span>
                <span className="info-value">{_c360Join(p.nome, p.cpf, p.telefone && fmtPhone(p.telefone))}</span>
              </div>
            ))}
          </div>
        </div>
      )}

      {lead.proxAcao && (
        <div className="next-action-card">
          <div className="next-action-head">
            <Icon name="bolt" size={13} stroke={2} />
            Próxima ação
          </div>
          <div className="next-action-body">
            <span className="next-action-title">{lead.proxAcao}</span>
            <span className={`next-action-when ${lead.proxAcaoUrgency === 'now' ? 'urgent' : lead.proxAcaoUrgency === 'soon' ? 'soon' : ''}`}>
              {lead.proxAcaoWhen}
            </span>
          </div>
          <div className="next-action-cta">
            <button className="btn-primary sm">Executar agora</button>
            <button className="ghost-btn">Reagendar</button>
          </div>
        </div>
      )}

      {lead.observacoes && (
        <div className="obs-card">
          <div className="obs-label">Observações</div>
          <p className="obs-text">{lead.observacoes}</p>
        </div>
      )}
    </div>
  );
};

const _C360_MEDIA = {
  image: 'Foto', imageMessage: 'Foto', sticker: 'Figurinha', stickerMessage: 'Figurinha',
  audio: 'Áudio', audioMessage: 'Áudio', ptt: 'Áudio', video: 'Vídeo', videoMessage: 'Vídeo',
  document: 'Documento', documentMessage: 'Documento', doc: 'Documento',
};
const _C360_LEGACY = { '[Imagem]': 'image', '[Image]': 'image', '[Sticker]': 'sticker', '[Áudio]': 'audio', '[Audio]': 'audio', '[Vídeo]': 'video', '[Video]': 'video' };

// ── Respostas rápidas (mesma tabela `quick_replies` e bucket da tela de Mensagens) ──
let _c360QrCache = null;
async function _c360QrLoad(force) {
  if (_c360QrCache && !force) return _c360QrCache;
  const { data, error } = await window.HOROS_SB.from('quick_replies').select('*').order('created_at');
  if (error) return _c360QrCache || [];
  _c360QrCache = data || [];
  return _c360QrCache;
}
const _c360QrMedia = (r) => (Array.isArray(r?.media) ? r.media : []);
const _c360MediaType = (mime) => {
  const m = String(mime || '');
  return m.startsWith('image/') ? 'image' : m.startsWith('video/') ? 'video' : m.startsWith('audio/') ? 'audio' : 'document';
};
const _c360QrBlobs = new Map();
async function _c360QrFiles(reply) {
  const out = [];
  for (const m of _c360QrMedia(reply)) {
    let blob = _c360QrBlobs.get(m.path);
    if (!blob) {
      const { data, error } = await window.HOROS_SB.storage.from('quick-replies').download(m.path);
      if (error || !data) throw new Error(m.fileName || 'arquivo');
      blob = data;
      _c360QrBlobs.set(m.path, blob);
    }
    const f = new File([blob], m.fileName || 'arquivo', { type: m.mimetype || blob.type || 'application/octet-stream' });
    f.__qrRef = { bucket: 'quick-replies', path: m.path };   // envio usa o arquivo já guardado
    out.push(f);
  }
  return out;
}
const _c360QrFilter = (list, q) => {
  q = String(q || '').trim().toLowerCase().replace(/^\//, '');
  return (list || []).filter(r => !q || r.shortcut?.toLowerCase().includes(q) || (r.message || '').toLowerCase().includes(q));
};

function _c360MapMsg(m) {
  let type = m.type || 'text';
  if (type === 'text' && _C360_LEGACY[m.body]) type = _C360_LEGACY[m.body];
  const ts = m.sent_at || m.created_at;
  return {
    id: m.id,
    waId: m.wa_message_id || null,
    from: m.direction === 'out' ? 'corretor' : 'cliente',
    type,
    text: _C360_LEGACY[m.body] ? '' : (m.body || ''),
    ts,
    time: ts ? new Date(ts).toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }) : '',
    day: ts ? new Date(ts).toDateString() : '',
  };
}

function _c360DayLabel(ts) {
  const d = new Date(ts), now = new Date();
  const diff = Math.round((new Date(now.toDateString()) - new Date(d.toDateString())) / 86400000);
  if (diff === 0) return 'Hoje';
  if (diff === 1) return 'Ontem';
  if (diff < 7) return d.toLocaleDateString('pt-BR', { weekday: 'long' });
  return d.toLocaleDateString('pt-BR', { day: '2-digit', month: 'short', year: 'numeric' });
}

// Mídia do WhatsApp: carrega via /api/whatsapp-media (foto/áudio automático, vídeo/documento no clique)
function C360Media({ msg }) {
  const [state, setState] = React.useState({ url: null, loading: false, err: false });
  const kind = /image|sticker/i.test(msg.type) ? 'image' : /audio|ptt/i.test(msg.type) ? 'audio' : /video/i.test(msg.type) ? 'video' : 'document';
  const label = _C360_MEDIA[msg.type] || 'Mídia';
  const load = async () => {
    if (!msg.waId || state.loading) return;
    setState(s => ({ ...s, loading: true, err: false }));
    try {
      const tok = window.horosToken ? await window.horosToken() : window.HOROS_SESSION?.access_token;
      const r = await fetch(`/api/whatsapp-media?msgId=${encodeURIComponent(msg.waId)}`, { headers: { Authorization: `Bearer ${tok}` } });
      const j = await r.json();
      if (!r.ok || (!j.base64 && !j.url)) throw new Error();
      const url = j.url || `data:${j.mimetype};base64,${j.base64}`;
      if (kind === 'document') {
        const a = document.createElement('a'); a.href = url; a.download = msg.text || 'documento'; a.click();
      }
      setState({ url, loading: false, err: false });
    } catch { setState(s => ({ ...s, loading: false, err: true })); }
  };
  React.useEffect(() => { if (kind === 'image' || kind === 'audio') load(); }, [msg.waId]);

  if (state.url && kind === 'image') return <img className="c360-media-img" src={state.url} alt="" onClick={() => { const w = window.open(); if (!w) return; const img = w.document.createElement('img'); img.src = state.url; w.document.body.appendChild(img); }} />;
  if (state.url && kind === 'audio') return <audio className="c360-media-audio" controls src={state.url} />;
  if (state.url && kind === 'video') return <video className="c360-media-img" controls src={state.url} />;
  return (
    <button className="c360-media-btn" onClick={load} disabled={!msg.waId || state.loading}>
      <Icon name={kind === 'document' ? 'file' : kind === 'audio' ? 'mic' : 'image'} size={13} stroke={1.8} />
      {state.loading ? 'Carregando…' : state.err ? `${label} indisponível` : label + (kind === 'document' || kind === 'video' ? ' — abrir' : '')}
    </button>
  );
}

const TabMensagens = ({ lead, autoFocus }) => {
  const [text, setText]       = React.useState('');
  const [msgs, setMsgs]       = React.useState([]);
  const [convId, setConvId]   = React.useState(null);
  const [sending, setSending] = React.useState(false);
  const [loading, setLoading] = React.useState(true);
  const threadRef = React.useRef(null);
  const inputRef  = React.useRef(null);

  // Campo de mensagem sempre pronto para digitar (igual à tela de Mensagens):
  // foco ao abrir e ao terminar de carregar, depois de cliques no chat, ao voltar
  // para a aba, e qualquer tecla digitada fora de outro campo vai para ele.
  const focusInput = React.useCallback(() => {
    const el = inputRef.current;
    if (!el || el.disabled) return;
    const ae = document.activeElement;
    if (ae && ae !== el && (ae.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(ae.tagName))) return;
    if (window.getSelection && String(window.getSelection()).length) return;
    el.focus();
  }, []);

  React.useEffect(() => {
    if (!autoFocus) return;
    const t = setTimeout(focusInput, 120);
    return () => clearTimeout(t);
  }, [autoFocus, lead.id, loading, focusInput]);

  React.useEffect(() => {
    if (!autoFocus) return;
    const onKey = (e) => {
      if (e.defaultPrevented || (e.ctrlKey && e.key.toLowerCase() !== 'v') || e.metaKey || e.altKey) return;
      const ae = document.activeElement;
      if (ae && (ae.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(ae.tagName))) return;
      if (e.key.length === 1 || (e.ctrlKey && e.key.toLowerCase() === 'v')) focusInput();
    };
    const onUp = (e) => {
      const t = e.target;
      if (!t.closest || !t.closest('.chat-wrap')) return;
      if (t.closest('button, a, input, textarea, select, audio, video, img')) return;
      setTimeout(focusInput, 0);
    };
    const onVisible = () => { if (document.visibilityState === 'visible') setTimeout(focusInput, 50); };
    document.addEventListener('keydown', onKey, true);
    document.addEventListener('mouseup', onUp);
    document.addEventListener('visibilitychange', onVisible);
    window.addEventListener('focus', onVisible);
    return () => {
      document.removeEventListener('keydown', onKey, true);
      document.removeEventListener('mouseup', onUp);
      document.removeEventListener('visibilitychange', onVisible);
      window.removeEventListener('focus', onVisible);
    };
  }, [autoFocus, focusInput]);

  const rawPhone = (lead.phone || '').replace(/\D/g, '').replace(/^55(\d{10,11})$/, '$1');
  const [syncing, setSyncing] = React.useState(false);
  const [convIds, setConvIds] = React.useState([]);
  const [histProgress, setHistProgress] = React.useState(null); // { page, pages, total } durante a importação
  const aliveRef = React.useRef(true);
  React.useEffect(() => () => { aliveRef.current = false; }, []);

  const getToken = async () => (window.horosToken ? await window.horosToken() : window.HOROS_SESSION?.access_token);

  // Junta sem perder nada: mensagens já na tela nunca somem por causa de um recarregamento
  const mergeMsgs = (prev, incoming) => {
    const out = new Map();
    const waSeen = new Map();
    for (const m of [...prev.filter(x => !String(x.id).startsWith('tmp-')), ...incoming]) {
      if (m.waId && waSeen.has(m.waId)) { out.set(waSeen.get(m.waId), m); continue; }
      out.set(m.id, m);
      if (m.waId) waSeen.set(m.waId, m.id);
    }
    const pend = prev.filter(x => String(x.id).startsWith('tmp-') &&
      !incoming.some(m => m.from === 'corretor' && m.text === x.text && (x.type || 'text') === m.type
        && Math.abs(new Date(m.ts) - new Date(x.ts)) < 120000));
    return [...out.values()].sort((a, b) => new Date(a.ts || 0) - new Date(b.ts || 0)).concat(pend);
  };

  // Carrega TODAS as mensagens de todas as conversas do contato (PostgREST: máx. 1000 por consulta)
  const loadAll = React.useCallback(async (ids) => {
    const sb = window.HOROS_SB;
    const PAGE = 1000;
    let all = [];
    for (let page = 0; page < 50; page++) {
      const { data, error } = await sb.from('messages')
        .select('id, wa_message_id, direction, type, body, sent_at')
        .in('conversation_id', ids)
        .order('sent_at', { ascending: true, nullsFirst: true })
        .range(page * PAGE, (page + 1) * PAGE - 1);
      if (error || !data) break;
      all = all.concat(data);
      if (data.length < PAGE) break;
    }
    return all.map(_c360MapMsg);
  }, []);

  // Conversas do contato: pelo número (com/sem 55, com/sem 9º dígito) + a conversa "@lid" vinculada
  const findConvs = React.useCallback(async () => {
    const sb = window.HOROS_SB;
    const vars = new Set([rawPhone]);
    if (rawPhone.length === 11 && rawPhone[2] === '9') vars.add(rawPhone.slice(0, 2) + rawPhone.slice(3));
    if (rawPhone.length === 10) vars.add(rawPhone.slice(0, 2) + '9' + rawPhone.slice(2));
    const filters = [];
    vars.forEach(v => {
      filters.push(`contact_phone.eq.${v}`, `contact_phone.eq.55${v}`, `wa_jid.eq.55${v}@s.whatsapp.net`, `wa_jid.eq.${v}@s.whatsapp.net`);
    });
    const { data: convs } = await sb.from('conversations')
      .select('id, wa_jid, wa_lid, last_message_at')
      .or(filters.join(','))
      .order('last_message_at', { ascending: false, nullsFirst: false });
    let list = convs || [];
    const lids = [...new Set(list.map(c => c.wa_lid).filter(Boolean))];
    if (lids.length) {
      const { data: ghosts } = await sb.from('conversations').select('id, wa_jid, wa_lid, last_message_at').in('wa_jid', lids);
      list = list.concat((ghosts || []).filter(g => !list.some(c => c.id === g.id)));
    }
    // Conversa para enviar: a do número com mensagem mais recente
    const primary = list.find(c => !String(c.wa_jid || '').endsWith('@lid')) || list[0] || null;
    return { ids: list.map(c => c.id), primary: primary?.id || null };
  }, [rawPhone]);

  React.useEffect(() => {
    if (!rawPhone) { setLoading(false); return; }
    let cancelled = false;
    (async () => {
      const sb = window.HOROS_SB;
      let { ids, primary } = await findConvs();
      if (cancelled) return;

      let first = ids.length ? await loadAll(ids) : [];
      if (cancelled) return;

      // Sem mensagens pelo número: o WhatsApp pode guardar o histórico numa conversa
      // "@lid" (contato com privacidade, sem número) — procura pelo nome do contato
      const nm = String(lead.name || '').trim();
      if (first.length === 0 && nm.length >= 3 && !/^(lead|contato horos)\b/i.test(nm)) {
        const { data: ghosts } = await sb.from('conversations').select('id')
          .like('wa_jid', '%@lid').ilike('contact_name', nm).limit(3);
        // Só um contato com esse nome, e só para MOSTRAR: o envio continua pelo número do lead
        // (mesmo nome pode ser outra pessoa — nunca vira a conversa principal)
        const extra = (ghosts || []).length === 1 ? ghosts.map(g => g.id).filter(id => !ids.includes(id)) : [];
        if (extra.length) {
          ids = [...ids, ...extra];
          first = await loadAll(ids);
          if (cancelled) return;
        }
      }
      setConvId(primary);
      setConvIds(ids);
      if (!ids.length) { setLoading(false); return; }

      setMsgs(prev => mergeMsgs(prev, first));
      setLoading(false);

      // Puxa do WhatsApp o que ainda não está no banco: recentes e depois todo o histórico
      if (!primary) return;
      setSyncing(true);
      try {
        let changed = false;
        const tok = await getToken();
        const r = await fetch(`/api/whatsapp-sync?convId=${encodeURIComponent(primary)}`, { headers: { Authorization: `Bearer ${tok}` } });
        const j = r.ok ? await r.json().catch(() => ({})) : {};
        if (j.messages) changed = true;
        // Histórico COMPLETO do WhatsApp, página por página (número + endereço interno @lid),
        // até a primeira mensagem — mostra 100% da conversa
        let page = 1;
        for (let round = 0; round < 40 && page && !cancelled && aliveRef.current; round++) {
          const tk = await getToken();
          const rh = await fetch(`/api/whatsapp-sync?convId=${encodeURIComponent(primary)}&history=1&page=${page}`, { headers: { Authorization: `Bearer ${tk}` } });
          const jh = rh.ok ? await rh.json().catch(() => ({})) : {};
          if (!rh.ok || !jh.history) break;
          if (jh.inserted) changed = true;
          setHistProgress(jh.pages ? { page: Math.min(jh.nextPage || jh.pages, jh.pages), pages: jh.pages, total: jh.total } : null);
          // mostra as mensagens antigas conforme chegam
          if (jh.inserted && !cancelled && aliveRef.current) {
            const part = await loadAll(ids);
            if (!cancelled && aliveRef.current) setMsgs(prev => mergeMsgs(prev, part));
          }
          page = jh.nextPage;
        }
        setHistProgress(null);
        if (changed && !cancelled && aliveRef.current) {
          // O sync pode ter unido conversas (número ↔ @lid) — procura de novo
          const again = await findConvs();
          if (again.ids.length) { ids = again.ids; setConvIds(ids); if (again.primary) setConvId(again.primary); }
          const full = await loadAll(ids);
          if (!cancelled && aliveRef.current) setMsgs(prev => mergeMsgs(prev, full));
        }
      } catch {}
      if (!cancelled && aliveRef.current) setSyncing(false);
    })();
    return () => { cancelled = true; };
  }, [rawPhone, loadAll, findConvs]);

  // Mensagens novas (recebidas e enviadas) em tempo real
  React.useEffect(() => {
    if (!convIds.length || !window.HOROS_SB) return;
    const onRow = (p) => { if (p.new?.id) setMsgs(prev => mergeMsgs(prev, [_c360MapMsg(p.new)])); };
    let rtOk = false, tick = 0;
    const ch = window.HOROS_SB.channel('c360-' + convIds.join('-').slice(0, 80))
      .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'messages', filter: `conversation_id=in.(${convIds.join(',')})` }, onRow)
      .subscribe(status => { rtOk = status === 'SUBSCRIBED'; });
    // Rede de segurança: com o Realtime fora, confere a cada 8 s; com ele ativo, só a cada ~1 min
    // (antes eram 8 s sempre — pesava no banco). Aba escondida não consulta.
    const poll = setInterval(async () => {
      tick++;
      if (document.hidden || (rtOk && tick % 8 !== 0)) return;
      const { data } = await window.HOROS_SB.from('messages')
        .select('id, wa_message_id, direction, type, body, sent_at')
        .in('conversation_id', convIds)
        .order('sent_at', { ascending: false, nullsFirst: false })
        .limit(20);
      if (data?.length && aliveRef.current) setMsgs(prev => mergeMsgs(prev, data.map(_c360MapMsg)));
    }, 8000);
    return () => { clearInterval(poll); window.HOROS_SB.removeChannel(ch); };
  }, [convIds.join(',')]);

  React.useEffect(() => {
    if (threadRef.current) threadRef.current.scrollTop = threadRef.current.scrollHeight;
  }, [msgs.length]);

  // Conversa do contato para enviar (cria se ainda não existir)
  const convIdRef = React.useRef(null);
  convIdRef.current = convId || convIdRef.current;
  const ensureConv = async () => {
    let cid = convIdRef.current;

    // Cria conversa se não existir
    if (!cid) {
      const sb = window.HOROS_SB;
      const userId = window.horosActUid?.() || window.HOROS_USER?.id;
      const jid = (rawPhone.length <= 11 ? '55' + rawPhone : rawPhone) + '@s.whatsapp.net';
      const { data: newConv } = await sb.from('conversations')
        .insert({ wa_jid: jid, contact_name: lead.name, contact_phone: rawPhone, user_id: userId })
        .select('id')
        .single();
      cid = newConv?.id;
      if (cid) {
        convIdRef.current = cid;
        setConvId(cid);
        setConvIds(prev => prev.includes(cid) ? prev : [...prev, cid]);
        // Upsert contato na Evolution API (fire-and-forget)
        const tk = await getToken();
        fetch('/api/whatsapp-instance', {
          method: 'PUT',
          headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${tk}` },
          body: JSON.stringify({ number: rawPhone, contactName: lead.name }),
        }).catch(() => {});
      }
    }

    if (!cid) throw new Error('sem conversa');
    return cid;
  };

  // Envia texto ({ text }) ou mídia ({ file, mediaType, caption }) e marca a bolha provisória
  const sendItem = async (item, tmpId) => {
    try {
      const cid = await ensureConv();
      const body = item.file
        ? { conversationId: cid, mediaType: item.mediaType, ...(await window.horosWaMediaBody(item.file)),
            mimetype: item.file.type || 'application/octet-stream', fileName: item.file.name, caption: item.caption || '' }
        : { conversationId: cid, text: item.text };
      const tok = await getToken();
      const res = await fetch('/api/whatsapp-send', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${tok}` },
        body: JSON.stringify(body),
      });
      if (!res.ok) {
        const j = await res.json().catch(() => ({}));
        throw new Error(j.error || 'Falha ao enviar');
      }
      setMsgs(prev => prev.map(m => m.id === tmpId ? { ...m, failed: false, error: null } : m));
    } catch (e) {
      // Não some: fica marcada como falha, com opção de reenviar
      setMsgs(prev => prev.map(m => m.id === tmpId ? { ...m, failed: true, error: e.message } : m));
    }
  };

  const tmpSeqRef = React.useRef(0);
  const addTmp = (item) => {
    const now = new Date();
    const type = item.file ? ({ document: 'doc' }[item.mediaType] || item.mediaType) : 'text';
    const tmp = {
      id: 'tmp-' + Date.now() + '-' + (++tmpSeqRef.current),
      from: 'corretor', type, item,
      day: now.toDateString(), ts: now.toISOString(),
      text: item.file ? (item.caption || '') : item.text,
      time: now.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }),
    };
    setMsgs(prev => [...prev, tmp]);
    return tmp.id;
  };

  // ── Respostas rápidas: botão raio, "/" no campo; com anexos fica pendente até enviar ──
  const [qrList, setQrList]       = React.useState(null);
  const [showQr, setShowQr]       = React.useState(false);
  const [qrSearch, setQrSearch]   = React.useState('');
  const [qrActive, setQrActive]   = React.useState(0);
  const [qrPending, setQrPending] = React.useState(null); // { reply, files }
  const [qrBusy, setQrBusy]       = React.useState(false);
  const [qrErr, setQrErr]         = React.useState('');
  const qrPopRef = React.useRef(null);
  const slashMode = text.startsWith('/') && !text.includes(' ');
  const qrOpen = showQr || slashMode;
  const qrShown = _c360QrFilter(qrList, showQr ? qrSearch : text).slice(0, 50);

  React.useEffect(() => { if (qrOpen && qrList === null) _c360QrLoad().then(setQrList).catch(() => setQrList([])); }, [qrOpen]);
  React.useEffect(() => { setQrActive(0); }, [qrSearch, text, showQr]);
  React.useEffect(() => {
    if (!showQr) return;
    const h = (e) => { if (qrPopRef.current && !qrPopRef.current.contains(e.target) && !e.target.closest?.('.c360-qr-btn')) setShowQr(false); };
    document.addEventListener('mousedown', h);
    return () => document.removeEventListener('mousedown', h);
  }, [showQr]);

  const applyQuickReply = async (r) => {
    setShowQr(false); setQrSearch(''); setQrErr('');
    setText(r.message || '');
    setQrPending(null);
    if (_c360QrMedia(r).length) {
      setQrBusy(true);
      try { setQrPending({ reply: r, files: await _c360QrFiles(r) }); }
      catch (e) { setQrErr(`Não foi possível carregar o anexo "${e.message}" da resposta /${r.shortcut}.`); }
      setQrBusy(false);
    }
    setTimeout(() => inputRef.current?.focus(), 0);
  };

  const handleSend = async () => {
    const trimmed = text.trim();
    const files = qrPending?.files || [];
    if ((!trimmed && !files.length) || sending || qrBusy) return;

    setText('');
    setQrPending(null);
    setSending(true);

    // Texto vai como legenda do 1º anexo; áudio não tem legenda no WhatsApp → texto segue depois
    const items = files.map(f => ({ file: f, mediaType: _c360MediaType(f.type), caption: '' }));
    const capIdx = items.findIndex(it => it.mediaType !== 'audio');
    if (trimmed && capIdx === 0) items[0].caption = trimmed;
    if (trimmed && capIdx !== 0) items.push({ text: trimmed });
    for (const it of items) {
      const tmpId = addTmp(it);
      await sendItem(it, tmpId);
    }
    setSending(false);
    setTimeout(() => inputRef.current?.focus(), 0);
  };

  // ── Anexar arquivos ("+") ──
  const fileRef = React.useRef(null);
  const sendFiles = async (fileList) => {
    const files = Array.from(fileList || []).filter(f => f.size > 0);
    if (!files.length) return;
    const tooBig = files.find(f => f.size > 50 * 1024 * 1024);
    if (tooBig) { setQrErr(`"${tooBig.name}" passa de 50 MB.`); return; }
    setSending(true);
    const trimmed = text.trim();
    setText('');
    const items = files.map((f, i) => {
      const mt = _c360MediaType(f.type);
      return { file: f, mediaType: mt, caption: i === 0 && mt !== 'audio' ? trimmed : '' };
    });
    if (trimmed && !items[0].caption) items.push({ text: trimmed });
    for (const it of items) { const id = addTmp(it); await sendItem(it, id); }
    setSending(false);
    setTimeout(() => inputRef.current?.focus(), 0);
  };

  // ── Gravar áudio (clique inicia, clique de novo envia; × cancela) ──
  const [rec, setRec] = React.useState(null); // { secs }
  const recRef = React.useRef(null);
  const startRec = async () => {
    if (rec || !navigator.mediaDevices?.getUserMedia) return;
    try {
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
      const mimeType = MediaRecorder.isTypeSupported('audio/webm;codecs=opus') ? 'audio/webm;codecs=opus'
        : MediaRecorder.isTypeSupported('audio/ogg;codecs=opus') ? 'audio/ogg;codecs=opus' : 'audio/webm';
      const mr = new MediaRecorder(stream, { mimeType });
      const chunks = [];
      const state = { mr, stream, chunks, mimeType, cancel: false, t0: Date.now(), timer: null };
      mr.ondataavailable = e => { if (e.data?.size) chunks.push(e.data); };
      mr.onstop = async () => {
        clearInterval(state.timer);
        stream.getTracks().forEach(t => t.stop());
        recRef.current = null;
        setRec(null);
        if (state.cancel || !chunks.length) return;
        const ext = mimeType.includes('ogg') ? 'ogg' : 'webm';
        const file = new File([new Blob(chunks, { type: mimeType })], `audio.${ext}`, { type: mimeType });
        const it = { file, mediaType: 'audio', caption: '' };
        const id = addTmp(it);
        await sendItem(it, id);
      };
      state.timer = setInterval(() => setRec({ secs: Math.floor((Date.now() - state.t0) / 1000) }), 500);
      recRef.current = state;
      mr.start();
      setRec({ secs: 0 });
    } catch {
      setQrErr('Não foi possível acessar o microfone. Libere a permissão no navegador.');
    }
  };
  const stopRec = (cancel) => {
    const st = recRef.current;
    if (!st) return;
    st.cancel = !!cancel;
    if (st.mr.state !== 'inactive') st.mr.stop();
  };
  React.useEffect(() => () => stopRec(true), []);

  const retrySend = async (m) => {
    setMsgs(prev => prev.map(x => x.id === m.id ? { ...x, failed: false, error: null } : x));
    await sendItem(m.item || { text: m.text }, m.id);
  };

  // Abre a conversa completa na tela de Mensagens
  const openInMessages = () => {
    const id = convId || convIds[0];
    if (!id) return;
    try { sessionStorage.setItem('horos:open-conv', id); } catch {}
    window.location.href = 'messages.html';
  };

  const handleKey = e => {
    if (slashMode && qrShown.length) {
      if (e.key === 'ArrowDown') { e.preventDefault(); setQrActive(v => Math.min(v + 1, qrShown.length - 1)); return; }
      if (e.key === 'ArrowUp')   { e.preventDefault(); setQrActive(v => Math.max(v - 1, 0)); return; }
      if (e.key === 'Tab' || (e.key === 'Enter' && !e.shiftKey)) { e.preventDefault(); applyQuickReply(qrShown[qrActive] || qrShown[0]); return; }
    }
    if (e.key === 'Escape' && (slashMode || qrPending)) { e.preventDefault(); if (slashMode) setText(''); else setQrPending(null); return; }
    if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend(); }
  };
  const firstName = lead.name.split(' ')[0];

  return (
    <div className="chat-wrap">
      {(convId || convIds.length > 0) && (
        <div className="c360-chat-bar">
          <span>{histProgress
            ? `Importando histórico do WhatsApp… ${Math.round(histProgress.page / histProgress.pages * 100)}%`
            : syncing ? 'Buscando mensagens no WhatsApp…'
            : `${msgs.length} ${msgs.length === 1 ? 'mensagem' : 'mensagens'} · conversa completa`}</span>
          <button onClick={openInMessages} title="Abrir esta conversa na tela de Mensagens">
            Abrir em Mensagens
            <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round"><path d="M7 17 17 7M9 7h8v8" /></svg>
          </button>
        </div>
      )}
      <div className="chat-thread" ref={threadRef}>
        {loading
          ? <div role="status" aria-label="Carregando conversa" style={{ display:'flex', flexDirection:'column', gap:10, padding:'16px 4px' }}>
              {[['46%',36,0],['30%',36,1],['58%',54,0],['26%',36,1],['40%',36,0],['34%',36,1]].map(([w,h,me],i) => <Skel key={i} w={w} h={h} r={8} style={{ alignSelf: me ? 'flex-end' : 'flex-start' }} />)}
            </div>
          : msgs.length === 0
            ? <div className="chat-empty">{syncing ? 'Buscando mensagens no WhatsApp…'
                : !rawPhone ? 'Este lead não tem telefone cadastrado.'
                : (convId || convIds.length) ? 'Nenhuma mensagem salva desta conversa ainda. Envie a primeira abaixo.'
                : `Nenhuma conversa de WhatsApp com ${fmtPhone(rawPhone)} ainda. Envie a primeira mensagem abaixo.`}</div>
            : msgs.map((m, i) => (
                <React.Fragment key={m.id}>
                  {m.day && (i === 0 || msgs[i - 1].day !== m.day) && <div className="chat-day">{_c360DayLabel(m.ts)}</div>}
                  <div className={`chat-msg from-${m.from}`}>
                    <div className="chat-bubble">
                      {_C360_MEDIA[m.type] && <C360Media msg={m} />}
                      {m.text && <span style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>{m.text}</span>}
                      <span className="chat-time">{m.time}</span>
                    </div>
                    {m.failed && (
                      <button className="c360-failed" onClick={() => retrySend(m)} title={m.error || ''}>
                        <Icon name="alert" size={11} stroke={2} /> {/24 h/.test(m.error || '') ? 'Fora das 24 h do WhatsApp oficial — envie um modelo em Mensagens' : 'Não enviada — tentar de novo'}
                      </button>
                    )}
                  </div>
                </React.Fragment>
              ))
        }
      </div>
      {(qrPending || qrBusy || qrErr) && (
        <div className={`c360-qr-pending${qrErr ? ' err' : ''}`}>
          <Icon name={qrErr ? 'alert' : 'bolt'} size={12} stroke={2} />
          <span className="c360-qr-pending-txt">
            {qrErr || (qrBusy ? 'Carregando anexos da resposta rápida…'
              : `${qrPending.files.length} ${qrPending.files.length === 1 ? 'anexo' : 'anexos'} de /${qrPending.reply.shortcut}: ${qrPending.files.map(f => f.name).join(', ')}`)}
          </span>
          {!qrBusy && <button onClick={() => { setQrPending(null); setQrErr(''); }} title="Remover anexos">×</button>}
        </div>
      )}
      <div className="chat-input c360-chat-input">
        {qrOpen && (
          <div className="c360-qr-pop" ref={qrPopRef}>
            {showQr && (
              <input className="c360-qr-search" autoFocus placeholder="Buscar resposta rápida…" value={qrSearch}
                onChange={e => setQrSearch(e.target.value)}
                onKeyDown={e => {
                  if (e.key === 'Escape') { setShowQr(false); inputRef.current?.focus(); }
                  if (e.key === 'ArrowDown') { e.preventDefault(); setQrActive(v => Math.min(v + 1, qrShown.length - 1)); }
                  if (e.key === 'ArrowUp')   { e.preventDefault(); setQrActive(v => Math.max(v - 1, 0)); }
                  if (e.key === 'Enter' && qrShown.length) { e.preventDefault(); applyQuickReply(qrShown[qrActive] || qrShown[0]); }
                }} />
            )}
            <div className="c360-qr-list">
              {qrList === null ? <SkelList rows={3} avatar={0} pad="8px 12px" />
                : qrShown.length === 0 ? <div className="c360-qr-empty">{qrList.length ? 'Nenhuma resposta encontrada' : 'Nenhuma resposta rápida — crie em Mensagens'}</div>
                : qrShown.map((r, i) => {
                    const nMedia = _c360QrMedia(r).length;
                    return (
                      <button key={r.id} className={`c360-qr-item${i === qrActive ? ' active' : ''}`}
                        onMouseEnter={() => setQrActive(i)} onMouseDown={e => e.preventDefault()}
                        onClick={() => applyQuickReply(r)}>
                        <span className="c360-qr-short">/{r.shortcut}</span>
                        {r.message && <span className="c360-qr-msg">{r.message}</span>}
                        {nMedia > 0 && <span className="c360-qr-media"><Icon name="paperclip" size={10} stroke={2} />{nMedia}</span>}
                      </button>
                    );
                  })}
            </div>
            <a className="c360-qr-manage" href="messages.html">Gerenciar respostas rápidas</a>
          </div>
        )}
        <input ref={fileRef} type="file" multiple hidden
          accept="image/*,video/*,audio/*,application/pdf,.doc,.docx,.xls,.xlsx,.txt,.zip"
          onChange={e => { const fl = e.target.files; sendFiles(fl); e.target.value = ''; }} />
        <button className="chat-attach" title="Anexar fotos, vídeos ou documentos" disabled={!rawPhone || sending || !!rec}
          onClick={() => fileRef.current?.click()}><Icon name="plus" size={14} stroke={2} /></button>
        <button className={`chat-action c360-qr-btn${showQr ? ' on' : ''}`} title="Respostas rápidas (ou digite /)"
          disabled={!rawPhone} onClick={() => { setShowQr(v => !v); setQrSearch(''); }}>
          <Icon name="bolt" size={14} stroke={2} />
        </button>
        {rec ? (
          <div className="c360-rec">
            <span className="c360-rec-dot" />
            Gravando {Math.floor(rec.secs / 60)}:{String(rec.secs % 60).padStart(2, '0')}
            <button className="c360-rec-cancel" onClick={() => stopRec(true)} title="Cancelar gravação">Cancelar</button>
          </div>
        ) : (
          <input
            ref={inputRef}
            placeholder={rawPhone ? `Mensagem para ${firstName}… (/ para respostas rápidas)` : 'Lead sem telefone cadastrado'}
            value={text}
            onChange={e => setText(e.target.value)}
            onKeyDown={handleKey}
            onPaste={e => { const fl = e.clipboardData?.files; if (fl && fl.length) { e.preventDefault(); sendFiles(fl); } }}
            disabled={!rawPhone}
          />
        )}
        <button className={`chat-action${rec ? ' c360-rec-on' : ''}`} title={rec ? 'Parar e enviar áudio' : 'Gravar áudio'}
          disabled={!rawPhone || (sending && !rec)} onClick={() => (rec ? stopRec(false) : startRec())}>
          {rec ? <Icon name="send" size={14} stroke={2} /> : (
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round">
              <rect x="9" y="3" width="6" height="12" rx="3" />
              <path d="M5 11a7 7 0 0 0 14 0M12 18v3M9 21h6" />
            </svg>
          )}
        </button>
        {!rec && <button className="chat-action send" title="Enviar"
          onClick={handleSend}
          disabled={(!text.trim() && !qrPending) || sending || qrBusy || !rawPhone}
          style={{ opacity: ((!text.trim() && !qrPending) || sending || qrBusy || !rawPhone) ? 0.4 : 1 }}>
          <Icon name="send" size={14} stroke={2} />
        </button>}
      </div>
    </div>
  );
};

const DocsList = ({ docs }) => (
  <div className="docs-card">
    <div className="docs-head">
      <span className="obs-label">Documentação</span>
      <span className="docs-progress">
        {(docs || []).filter(d => d.ok).length}/{(docs || []).length}
      </span>
    </div>
    <ul className="docs-list">
      {(docs || []).map(d => (
        <li key={d.label} className={d.ok ? 'ok' : 'pending'}>
          <span className={`docs-dot ${d.ok ? 'ok' : ''}`}>
            {d.ok ? (
              <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><path d="m4 12 5 5 11-11" /></svg>
            ) : (
              <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"><circle cx="12" cy="12" r="9" /></svg>
            )}
          </span>
          {d.label}
          {!d.ok && <span className="docs-pending-tag">pendente</span>}
        </li>
      ))}
      {(!docs || docs.length === 0) && (
        <li className="docs-empty">Nenhum documento ainda. Solicite ao cliente.</li>
      )}
    </ul>
  </div>
);

const TabCredito = ({ lead }) => {
  const hasDocs = lead.creditDocs && lead.creditDocs.length > 0;

  // Autorização de consulta (link para o cliente assinar) — só para leads reais do banco
  const authPanel = lead.id && !/^C\d{2}$/.test(String(lead.id)) && window.CreditAuthPanel
    ? <div style={{ marginBottom: 14 }}><window.CreditAuthPanel lead={lead} compact /></div> : null;

  if (!lead.creditScore) {
    return (
      <div className="drawer-pad">
        {authPanel}
        <div style={{ textAlign:'center', padding:'40px 20px', color:'var(--fg-muted)' }}>
          <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" style={{ opacity:.4, marginBottom:12 }}>
            <circle cx="12" cy="12" r="10"/><path d="M12 8v4M12 16h.01"/>
          </svg>
          <div style={{ fontSize:14, fontWeight:600, marginBottom:6 }}>Análise não realizada</div>
          <div style={{ fontSize:12, lineHeight:1.6 }}>O resultado da análise aparece aqui.<br/>Antes, envie ao cliente o link de autorização acima.</div>
        </div>
        {hasDocs && <DocsList docs={lead.creditDocs} />}
      </div>
    );
  }

  const score = lead.creditScore;
  const tier = lead.creditTier ?? '—';
  const tierColor = score >= 900 ? '#34d399' : score >= 800 ? '#7EC8FF' : score >= 700 ? '#f5b544' : '#94a3b8';
  return (
    <div className="drawer-pad">
      {authPanel}
      <div className="credit-card">
        <div className="credit-score-wrap">
          <ScoreRingBig score={score} max={1000} size={84} />
          <div className="credit-meta">
            <span className="credit-tier" style={{ color: tierColor, borderColor: tierColor + '55' }}>{tier}</span>
            <span className="credit-label">Score de crédito</span>
            <span className="credit-fonte">Fonte: Serasa · SCR BCB</span>
          </div>
        </div>
        <div className="credit-breakdown">
          <div>
            <span className="info-label">Faixa de renda</span>
            <span className="info-value">{lead.creditFaixa ?? '—'}</span>
          </div>
          <div>
            <span className="info-label">Comprometimento</span>
            <span className="info-value">{lead.creditCompromisso ?? '—'}</span>
          </div>
        </div>
      </div>
      <DocsList docs={lead.creditDocs} />
    </div>
  );
};

// Visitas = eventos da Agenda (tabela `events`, cat 'visit') com o telefone do lead
const _c360PhoneVars = (raw) => {
  const d = String(raw || '').replace(/\D/g, '').replace(/^55(\d{10,11})$/, '$1');
  if (d.length < 10) return [];
  const v = new Set([d, '55' + d]);
  if (d.length === 11 && d[2] === '9') { const s = d.slice(0, 2) + d.slice(3); v.add(s); v.add('55' + s); }
  if (d.length === 10) { const s = d.slice(0, 2) + '9' + d.slice(2); v.add(s); v.add('55' + s); }
  return [...v];
};

const _c360VisitStatusLabel = { agendada: 'Agendada', realizada: 'Realizada', cancelada: 'Cancelada' };

const TabVisitas = ({ lead }) => {
  const phones = _c360PhoneVars(lead.phone);
  const [events, setEvents] = React.useState(null);
  const [form, setForm]     = React.useState(null); // { date, start, end, location, note }
  const [saving, setSaving] = React.useState(false);
  const [err, setErr]       = React.useState('');

  const load = React.useCallback(async () => {
    if (!phones.length || !window.HOROS_SB) { setEvents([]); return; }
    const { data } = await window.HOROS_SB.from('events').select('*')
      .in('phone', phones).eq('cat', 'visit').order('start_at', { ascending: false }).limit(50);
    setEvents(data || []);
  }, [phones.join(',')]);
  React.useEffect(() => { load(); }, [load]);

  const openForm = () => {
    const d = new Date(); d.setDate(d.getDate() + 1);
    setErr('');
    setForm({ date: d.toISOString().slice(0, 10), start: '10:00', end: '11:00',
      location: lead.cat && lead.cat !== '—' ? lead.cat : '', note: '' });
  };

  const save = async () => {
    if (!form.date) { setErr('Informe a data.'); return; }
    if (form.start >= form.end) { setErr('O fim deve ser depois do início.'); return; }
    setSaving(true); setErr('');
    const { data, error } = await window.HOROS_SB.from('events').insert({
      title:    `Visita — ${lead.name}`,
      cat:      'visit',
      client:   lead.name,
      phone:    String(lead.phone || '').replace(/\D/g, '') || null,
      sub:      form.note.trim() || null,
      location: form.location.trim() || null,
      start_at: new Date(`${form.date}T${form.start}:00`).toISOString(),
      end_at:   new Date(`${form.date}T${form.end}:00`).toISOString(),
      user_id:  window.HOROS_USER?.id,
    }).select().single();
    setSaving(false);
    if (error) { setErr('Não foi possível salvar: ' + error.message); return; }
    setEvents(prev => [data, ...(prev || [])]);
    setForm(null);
  };

  const openInAgenda = (ev) => {
    try { sessionStorage.setItem('horos:highlight-event', ev.id); } catch {}
    window.location.href = 'calendar.html';
  };

  // Leads de demonstração ainda trazem visitas prontas (lead.visits)
  const rows = [
    ...(events || []).map(ev => {
      const st = new Date(ev.start_at);
      return {
        id: ev.id, ev,
        status: new Date(ev.end_at).getTime() < Date.now() ? 'realizada' : 'agendada',
        date: st.toLocaleDateString('pt-BR', { day: '2-digit', month: 'short' }),
        time: st.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }),
        agent: ev.location || '', note: ev.sub || '',
      };
    }),
    ...(lead.visits || []).map((v, i) => ({ id: 'demo' + i, ...v })),
  ];

  return (
    <div className="drawer-pad">
      {events === null
        ? <SkelList rows={2} avatar={36} pad="10px 4px" />
        : rows.length === 0 && !form && <div className="empty-card">{segTerm('meet1', 'visita') === 'visita' ? 'Nenhuma visita registrada. Agende a primeira.' : `Nada agendado ainda. Agende ${segTerm('meet1', 'reunião')} pela Agenda.`}</div>}
      <div className="visit-list">
        {rows.map(v => (
          <div key={v.id} className={`visit-item visit-${v.status}`}
            style={v.ev ? { cursor: 'pointer' } : undefined}
            title={v.ev ? 'Abrir na Agenda' : undefined}
            onClick={v.ev ? () => openInAgenda(v.ev) : undefined}>
            <div className="visit-date">
              <span className="visit-day">{v.date}</span>
              <span className="visit-time">{v.time}</span>
            </div>
            <div className="visit-body">
              <div className="visit-top">
                <span className={`visit-status visit-status-${v.status}`}>{_c360VisitStatusLabel[v.status] || v.status}</span>
                {v.agent && <span className="visit-agent">{v.agent}</span>}
              </div>
              {v.note && <p className="visit-note">{v.note}</p>}
            </div>
          </div>
        ))}
      </div>

      {form ? (
        <div className="c360-visit-form">
          <div className="c360-visit-row">
            <label>Data<input type="date" value={form.date} onChange={e => setForm(f => ({ ...f, date: e.target.value }))} /></label>
            <label>Início<input type="time" value={form.start} onChange={e => setForm(f => ({ ...f, start: e.target.value }))} /></label>
            <label>Fim<input type="time" value={form.end} onChange={e => setForm(f => ({ ...f, end: e.target.value }))} /></label>
          </div>
          <label>{segTerm('product') === 'imóvel' ? 'Local / imóvel' : 'Local'}<input value={form.location} placeholder={segTerm('product') === 'imóvel' ? 'Endereço ou empreendimento' : 'Endereço ou link da reunião'}
            onChange={e => setForm(f => ({ ...f, location: e.target.value }))} /></label>
          <label>Observação<input value={form.note} placeholder="Opcional"
            onChange={e => setForm(f => ({ ...f, note: e.target.value }))} /></label>
          {err && <div className="c360-visit-err">{err}</div>}
          <div className="c360-visit-actions">
            <button className="ghost-btn" onClick={() => setForm(null)} disabled={saving}>Cancelar</button>
            <button className="btn-primary sm" onClick={save} disabled={saving}>{saving ? 'Salvando…' : 'Agendar visita'}</button>
          </div>
        </div>
      ) : (
        <button className="btn-primary sm wide" onClick={openForm} disabled={!phones.length}
          title={phones.length ? `Agendar ${segTerm('meet1', 'visita')} na Agenda` : 'Lead sem telefone cadastrado'}>
          <Icon name="plus" size={13} stroke={2} /> Agendar {segTerm('meet1', 'visita')}
        </button>
      )}
    </div>
  );
};

const kindIcon = { ai: 'sparkles', client: 'message', broker: 'user', system: 'bolt' };
const kindLabel = { ai: 'IA', client: 'Cliente', broker: segTerm('Agent', 'Corretor'), system: 'Sistema' };

const TabTimeline = ({ lead }) => {
  const ev = lead.timeline || [];
  return (
    <div className="drawer-pad">
      {window.LeadCallsList && <LeadCallsList leadId={lead.id} />}
      {window.LeadEmailsList && <LeadEmailsList leadId={lead.id} />}
      {ev.length === 0 && (
        <div className="empty-card">Sem eventos registrados ainda.</div>
      )}
      <div className="tl-list">
        {ev.map((e, i) => (
          <div key={i} className={`tl-item tl-kind-${e.kind}`}>
            <div className="tl-rail">
              <div className="tl-dot"><Icon name={kindIcon[e.kind] || 'bolt'} size={10} stroke={2} /></div>
              {i < ev.length - 1 && <div className="tl-line" />}
            </div>
            <div className="tl-body">
              <div className="tl-row1">
                <span className="tl-time">{e.time}</span>
                <span className={`tl-kind-tag tl-kind-${e.kind}`}>{kindLabel[e.kind] || 'Evento'}</span>
              </div>
              <p className="tl-label">{e.label}</p>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
};

// Etapa do lead no funil — trocar aqui move o card no Kanban (move_lead) e
// avisa as telas abertas pelo evento horos:lead-updated
function C360Stage({ lead, onArchived }) {
  // Vários funis: etapas do funil do lead; com funis extras aparece o seletor de funil
  const pipes = usePipelines();
  const [pid, setPid] = React.useState(() => leadPipeline(lead));
  React.useEffect(() => { setPid(leadPipeline(lead)); }, [lead.id, lead.pipeline_id, pipes]);
  const cols = useKanbanCols(pid);
  const pipeList = [{ id: 'main', name: pipes.mainName }, ...pipes.pipelines];
  const [col, setCol]   = React.useState(lead.col);
  const [open, setOpen] = React.useState(false);
  const [busy, setBusy] = React.useState(false);
  const [err, setErr]   = React.useState('');
  const boxRef = React.useRef(null);
  React.useEffect(() => { setCol(lead.col); setErr(''); }, [lead.id, lead.col]);
  React.useEffect(() => {
    if (!open) return;
    const onDown = (e) => { if (boxRef.current && !boxRef.current.contains(e.target)) setOpen(false); };
    const onKey = (e) => { if (e.key === 'Escape') { e.stopPropagation(); setOpen(false); } };
    document.addEventListener('mousedown', onDown);
    window.addEventListener('keydown', onKey, true);
    return () => { document.removeEventListener('mousedown', onDown); window.removeEventListener('keydown', onKey, true); };
  }, [open]);

  const cur = cols.find(c => c.id === col);
  const movePipe = async (to) => {
    setOpen(false);
    if (to === pid || busy) return;
    setBusy(true); setErr('');
    try {
      await horosMoveLeadsPipeline([lead.id], to, 'inbox');
      setPid(to); setCol('inbox');
    } catch (e) { setErr('Não foi possível mudar o funil. ' + (e.message || '')); }
    setBusy(false);
  };
  const move = async (to) => {
    setOpen(false);
    if (to === col || busy) return;
    const prev = col;
    setCol(to); setBusy(true); setErr('');
    try {
      const { data, error } = await window.HOROS_SB.rpc('move_lead', { p_id: lead.id, p_col: to });
      if (error || data === false) throw new Error();
      const now = new Date().toISOString();
      window.dispatchEvent(new CustomEvent('horos:lead-updated', {
        detail: { id: lead.id, col: to, updated_at: now, progress: COL_PROGRESS[to] ?? lead.progress },
      }));
      if (horosIsWonCol(to, pid)) horosAskSale(lead.id);
      if (window.horosIsApprovedCol?.(to, pid)) window.horosAskApproval?.(lead.id);
    } catch {
      setCol(prev);
      setErr('Não foi possível mudar a etapa. Tente de novo.');
    }
    setBusy(false);
  };

  return (
    <div className="c360-stage" ref={boxRef}>
      <span className="hero-stat-label">Etapa no funil</span>
      {pipes.pipelines.length > 0 && !lead._prevHolder && (!lead.assigned_to || lead.assigned_to === (window.horosActUid ? window.horosActUid() : lead.assigned_to)) && (
        <button className="c360-stage-btn c360-pipe-btn" onClick={() => setOpen(v => v === 'pipe' ? false : 'pipe')} disabled={busy}
          aria-expanded={open === 'pipe'} title="Mudar o funil do lead (entra na Entrada)">
          <Icon name="layers" size={12} />
          <span className="c360-stage-name">{(pipeList.find(p => p.id === pid) || {}).name}</span>
          <svg width="10" height="10" viewBox="0 0 10 10" fill="currentColor"><path d="M5 7.5 1 2.5h8z" /></svg>
        </button>
      )}
      <button className="c360-stage-btn" onClick={() => setOpen(v => v === true ? false : true)} disabled={busy} aria-expanded={open === true}>
        <span className="c360-stage-dot" style={{ background: cur?.color || 'var(--fg-4)' }} />
        <span className="c360-stage-name">{cur?.title || col || 'Sem etapa'}</span>
        {busy ? <span className="c360-stage-hint">Salvando…</span>
          : <svg width="10" height="10" viewBox="0 0 10 10" fill="currentColor" style={{ transform: open === true ? 'rotate(180deg)' : 'none', transition: 'transform 140ms ease' }}><path d="M5 7.5 1 2.5h8z" /></svg>}
      </button>
      {!lead.archived && (
        <button type="button" className="c360-archive-btn" title="Tirar o lead do funil (pede o motivo)"
          onClick={async () => { const ok = await window.horosArchiveLeads([lead.id], { names: [lead.name] }); if (ok.length && onArchived) onArchived(); }}>
          <Icon name="archive" size={15} />
          Arquivar lead
        </button>
      )}
      {err && <span className="c360-stage-err">{err}</span>}
      {open === 'pipe' && (
        <div className="c360-stage-menu" role="listbox" aria-label="Funil">
          {pipeList.map(p => (
            <button key={p.id} role="option" aria-selected={p.id === pid}
              className={`c360-stage-opt${p.id === pid ? ' on' : ''}`} onClick={() => movePipe(p.id)}>
              <Icon name="layers" size={12} />
              <span className="c360-stage-name">{p.name}</span>
              {p.id === pid && <Icon name="check" size={12} stroke={2.2} />}
            </button>
          ))}
        </div>
      )}
      {open === true && (
        <div className="c360-stage-menu" role="listbox">
          {cols.map(c => (
            <button key={c.id} role="option" aria-selected={c.id === col}
              className={`c360-stage-opt${c.id === col ? ' on' : ''}`} onClick={() => move(c.id)}>
              <span className="c360-stage-dot" style={{ background: c.color || 'var(--fg-4)' }} />
              <span className="c360-stage-name">{c.title}</span>
              {c.id === col && <Icon name="check" size={12} stroke={2.2} />}
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

// Abas por ramo (2026-10-13): Crédito só onde o ramo financia; "Visitas" vira Reuniões, Avaliações, Test drives…
const C360_CREDIT_SEGS = new Set(['imobiliario', 'credito', 'consorcio', 'veiculos']);
function c360Tabs() {
  const seg = window.horosSeg ? window.horosSeg() : { id: 'imobiliario' };
  return [
    { id: 'messages', label: 'Mensagens' },
    ...((window.horosSegHas ? window.horosSegHas('credit', seg.id) : C360_CREDIT_SEGS.has(seg.id)) ? [{ id: 'credit', label: 'Crédito' }] : []),
    { id: 'visits',   label: segTerm('Meet', 'Visitas') },
    { id: 'timeline', label: 'Timeline IA' },
  ];
}

function Customer360({ lead, onClose }) {
  const originLead = (window.useLeadOrigin || ((l) => l))(lead);
  const [tab, setTab] = React.useState('messages');
  React.useEffect(() => { setTab('messages'); }, [lead?.id]);
  // Resumo aberto/fechado — lembrado entre aberturas
  const [summaryOpen, setSummaryOpen] = React.useState(() => {
    try { return localStorage.getItem('horos-360-summary') !== '0'; } catch { return true; }
  });
  const toggleSummary = () => setSummaryOpen(v => {
    try { localStorage.setItem('horos-360-summary', v ? '0' : '1'); } catch {}
    return !v;
  });
  React.useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);

  if (!lead) return null;
  const tm = { hot: 'Quente', warm: 'Morno', cold: 'Frio' }[lead.temp];

  return (
    <>
      <div className="drawer-backdrop" onClick={onClose} />
      <aside className="drawer" role="dialog" aria-label="Horos 360°">
        <div className="drawer-head">
          <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between' }}>
            <div className="drawer-id">
              <span className="drawer-id-label">HOROS 360°</span>
              <span className="drawer-id-meta">ID {lead.id}</span>
            </div>
            <div style={{ display:'flex', flexDirection:'column', alignItems:'flex-end', gap:6 }}>
              <button className="drawer-close" onClick={onClose} title="Fechar (Esc)" style={{ position:'static' }}>
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="m6 6 12 12M18 6 6 18" /></svg>
              </button>
              <a href={`client.html?id=${lead.id}`} onClick={e => { e.preventDefault(); window.horosOpenLeadSheet(lead.id); }} title="Abrir a ficha do cliente"
                style={{ display:'flex', alignItems:'center', gap:5, padding:'5px 10px', borderRadius:7, background:'var(--accent)', color:'#fff', fontSize:12, fontWeight:600, textDecoration:'none', lineHeight:1, whiteSpace:'nowrap' }}>
                <Icon name="sheet" size={12} />
                Abrir ficha
              </a>
            </div>
          </div>

          <div className="drawer-hero">
            <div className="lead-avatar xl" style={{ background: `linear-gradient(135deg, ${lead.palette[0]}, ${lead.palette[1]})` }}>
              {lead.initials}
              <span className="hero-status" />
            </div>
            <div className="drawer-hero-text">
              <h3 className="drawer-hero-name">{lead.name}</h3>
              <div className="drawer-hero-meta">
                <span>{lead.cat} · {lead.seg}</span>
                <LeadOriginBadge lead={originLead || lead} />
              </div>
              {window.LeadHandoffNote && <div className="drawer-hero-handoff"><LeadHandoffNote lead={originLead || lead} /></div>}
              <div className="drawer-hero-contact">
                <span><Icon name="phone" size={11} stroke={1.8} /> {fmtPhone(lead.phone)}</span>
                {window.LeadCallButton && <LeadCallButton lead={lead} />}
                {window.LeadEmailButton && <LeadEmailButton lead={lead} />}
                {lead.email && <span><Icon name="msgs" size={11} stroke={1.8} /> {lead.email}</span>}
              </div>
            </div>
          </div>

          <div className="drawer-hero-stats">
            <div className="hero-stat">
              <span className="hero-stat-label">Temperatura</span>
              <span className={`hero-stat-value temp-${lead.temp}`}>
                <span className={`temp-dot temp-${lead.temp}`} /> {tm}
              </span>
            </div>
            <div className="hero-stat">
              <span className="hero-stat-label">Score IA</span>
              <span className="hero-stat-value"><b>{lead.score}</b>/100</span>
            </div>
            <div className="hero-stat">
              <span className="hero-stat-label">Engajamento</span>
              <div className="hero-progress">
                <div className="hero-progress-fill" style={{ width: `${lead.progress}%` }} />
              </div>
            </div>
            <div className="hero-stat">
              <span className="hero-stat-label">Última interação</span>
              <span className="hero-stat-value muted">{lead.last}</span>
            </div>
          </div>

          <C360Stage lead={lead} onArchived={onClose} />

          <div className="c360-summary-head">
            <button className="c360-summary-toggle" onClick={toggleSummary} aria-expanded={summaryOpen}>
              Resumo
              <svg width="10" height="10" viewBox="0 0 10 10" fill="currentColor" style={{ transform: summaryOpen ? 'none' : 'rotate(180deg)', transition: 'transform 160ms ease' }}>
                <path d="M5 2 9 7.5H1z" />
              </svg>
            </button>
          </div>
          {summaryOpen && (
            <div className="c360-summary">
              <TabResumo lead={lead} />
              {window.LeadPropertiesList && <LeadPropertiesList leadId={lead.id} />}
            </div>
          )}

          <div className="drawer-tabs">
            {c360Tabs().map(t => (
              <button key={t.id}
                      className={`drawer-tab ${tab === t.id ? 'active' : ''}`}
                      onClick={() => setTab(t.id)}>
                {t.label}
              </button>
            ))}
          </div>
        </div>

        <div className={`drawer-body${tab === 'messages' ? ' drawer-body--chat' : ''}`}>
          {tab === 'messages' && <TabMensagens lead={lead} autoFocus />}
          {tab === 'credit'   && <TabCredito  lead={lead} />}
          {tab === 'visits'   && <TabVisitas  lead={lead} />}
          {tab === 'timeline' && <TabTimeline lead={lead} />}
        </div>
      </aside>
    </>
  );
}

window.Customer360 = Customer360;
