/* screen_marketing_crm.jsx
   Marketing · CRM — consulta real Gestor (sync FM → PostgreSQL local).
   Expõe window.MktCRMScreen.
*/

// ── Chips de stage (cores do design system) ────────────────────────────────
// Stages reais do Gestor GestorDeveloper (auditados 2026-08-02)
// Ordem funil: INTERESSE → LEAD → EVENTO → DEMO → AG DECISAO → AG FINANCEIRO → APROVADO → WON/LOST
const _CRM_STAGES = [
  { id: 'INTERESSE',              color: '#94a3b8', match: /interesse/i },
  { id: 'LEAD',                   color: '#64748b', match: /^lead$/i },
  { id: 'EVENTO',                 color: '#0ea5e9', match: /evento/i },
  { id: 'DEMO',                   color: '#8b5cf6', match: /demo/i },
  { id: 'AG DECISAO',             color: '#f59e0b', match: /decis|ag\s*decisao/i },
  { id: 'AG FINANCEIRO',          color: '#d97706', match: /financ/i },
  { id: 'APROVADO FINANCIAMENTO', color: '#22c55e', match: /aprovado/i },
  { id: 'WON',                    color: '#15803d', match: /won/i },
  { id: 'LOST',                   color: '#ef4444', match: /lost/i },
];

function stageColor(name) {
  if (!name) return '#64748b';
  const s = _CRM_STAGES.find(x => x.match.test(name));
  return s ? s.color : '#64748b';
}

function StageChip({ name, small }) {
  const color = stageColor(name);
  return (
    <span style={{
      fontSize: small ? 9 : 10, fontFamily: 'var(--font-mono)', fontWeight: 700,
      padding: small ? '1px 5px' : '2px 7px', borderRadius: 4, letterSpacing: '0.04em',
      background: `color-mix(in oklch, ${color} 14%, transparent)`,
      color, border: `1px solid ${color}30`, whiteSpace: 'nowrap',
    }}>{name}</span>
  );
}

// ── API helpers ────────────────────────────────────────────────────────────
const BASE = '/api/marketing/crm';

const CRMAPI = {
  meta:     () => fetch(`${BASE}/meta`).then(r => r.json()),
  stats:    (params) => {
    const qs = new URLSearchParams();
    Object.entries(params || {}).forEach(([k, v]) => {
      if (Array.isArray(v)) v.forEach(vi => qs.append(k, vi));
      else if (v !== undefined && v !== '' && v !== null) qs.set(k, v);
    });
    const query = qs.toString();
    return fetch(`${BASE}/stats${query ? `?${query}` : ''}`).then(r => r.json());
  },
  sources:  () => fetch(`${BASE}/sources`).then(r => r.json()),
  syncStatus: () => fetch(`${BASE}/sync-status`).then(r => r.json()),

  entidades: (params) => {
    const qs = new URLSearchParams();
    Object.entries(params || {}).forEach(([k, v]) => {
      if (Array.isArray(v)) v.forEach(vi => qs.append(k, vi));
      else if (v !== undefined && v !== '' && v !== null) qs.set(k, v);
    });
    return fetch(`${BASE}/entidades?${qs}`).then(r => r.json());
  },

  contactos: (params) => {
    const qs = new URLSearchParams();
    Object.entries(params || {}).forEach(([k, v]) => {
      if (Array.isArray(v)) v.forEach(vi => qs.append(k, vi));
      else if (v !== undefined && v !== '' && v !== null) qs.set(k, v);
    });
    return fetch(`${BASE}/contactos?${qs}`).then(r => r.json());
  },

  ent360: (id, tenant) => fetch(`${BASE}/entidades/${id}/360${tenant ? `?tenant=${encodeURIComponent(tenant)}` : ''}`).then(r => r.json()),

  // ── Profile PA1/PA2/PA7 — KPIs + Apreciação + 360 + Timeline por pilar ─
  entKpis:      (id, tenant) => fetch(`${BASE}/entidades/${id}/kpis${tenant ? `?tenant=${encodeURIComponent(tenant)}` : ''}`).then(r => r.json()),
  entApreciacao:(id, tenant) => fetch(`${BASE}/entidades/${id}/apreciacao${tenant ? `?tenant=${encodeURIComponent(tenant)}` : ''}`).then(r => r.json()),
  ct360:        (id) => fetch(`${BASE}/contactos/${id}/360`).then(r => r.json()),
  ctKpis:       (id) => fetch(`${BASE}/contactos/${id}/kpis`).then(r => r.json()),
  ctApreciacao: (id) => fetch(`${BASE}/contactos/${id}/apreciacao`).then(r => r.json()),
  ctTimeline:   (id, params) => {
    const qs = new URLSearchParams(params || {});
    return fetch(`${BASE}/contactos/${id}/timeline?${qs}`).then(r => r.json());
  },
  op360:        (id) => fetch(`${BASE}/oportunidades/${id}/360`).then(r => r.json()),
  opKpis:       (id) => fetch(`${BASE}/oportunidades/${id}/kpis`).then(r => r.json()),
  opApreciacao: (id) => fetch(`${BASE}/oportunidades/${id}/apreciacao`).then(r => r.json()),
  opTimeline:   (id, params) => {
    const qs = new URLSearchParams(params || {});
    return fetch(`${BASE}/oportunidades/${id}/timeline?${qs}`).then(r => r.json());
  },

  // ── PA10 · signals + activity feed + engagement score ──
  entSignalsBatch: (ids) => fetch(`${BASE}/entidades/signals-batch`, {
    method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ids }),
  }).then(r => r.json()),
  ctSignalsBatch: (ids) => fetch(`${BASE}/contactos/signals-batch`, {
    method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ids }),
  }).then(r => r.json()),
  entSignals:    (id) => fetch(`${BASE}/entidades/${id}/signals`).then(r => r.json()),
  entActivity:   (id, params) => fetch(`${BASE}/entidades/${id}/activity-feed?${new URLSearchParams(params || {})}`).then(r => r.json()),
  entScore:      (id) => fetch(`${BASE}/entidades/${id}/engagement-score`).then(r => r.json()),
  ctSignals:     (id) => fetch(`${BASE}/contactos/${id}/signals`).then(r => r.json()),
  ctActivity:    (id, params) => fetch(`${BASE}/contactos/${id}/activity-feed?${new URLSearchParams(params || {})}`).then(r => r.json()),
  ctScore:       (id) => fetch(`${BASE}/contactos/${id}/engagement-score`).then(r => r.json()),
  opSignals:     (id) => fetch(`${BASE}/oportunidades/${id}/signals`).then(r => r.json()),
  opActivity:    (id, params) => fetch(`${BASE}/oportunidades/${id}/activity-feed?${new URLSearchParams(params || {})}`).then(r => r.json()),

  audiencias: (estado = 'activas') => fetch(`${BASE}/audiences?estado=${encodeURIComponent(estado)}`).then(r => r.json()),
  audienciasAccao: (ids, acao) => fetch(`${BASE}/audiences/bulk`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ids, acao }) }).then(r => r.json()),
  audiencia: (id) => fetch(`${BASE}/audiences/${id}`).then(r => r.json()),
  audienciaContactos: (id, params) => fetch(`${BASE}/audiences/${id}/contactos?${new URLSearchParams(params || {})}`).then(r => r.json()),
  criarAudiencia: (body) => fetch(`${BASE}/audiences`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }).then(r => r.json()),
  atualizarAudiencia: (id, body) => fetch(`${BASE}/audiences/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }).then(r => r.json()),
  eliminarAudiencia: (id) => fetch(`${BASE}/audiences/${id}`, { method: 'DELETE' }).then(r => r.json()),
  pushAudiencia: (id, body) => fetch(`${BASE}/audiences/${id}/push`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }).then(r => r.json()),
  pushesAudiencia: (id) => fetch(`${BASE}/audiences/${id}/pushes`).then(r => r.json()),

  preview: (definicao, created_by) => fetch(`${BASE}/segments/preview`, {
    method: 'POST', headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ definicao, created_by }),
  }).then(r => r.json()),

  aiSegment: (texto, created_by, canal) => fetch(`${BASE}/segments/ai`, {
    method: 'POST', headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ texto, created_by, canal }),
  }).then(r => r.json()),

  campanhasActivas: () => fetch('/api/marketing/campanhas?per_page=50').then(r => r.json()),

  sync: (signal) => fetch(`${BASE}/sync`, { method: 'POST', signal }),

  heroState: () => fetch(`${BASE}/hero-state`).then(r => r.json()),
  syncLogs:  () => fetch(`${BASE}/sync-logs`).then(r => r.json()),

  // ── Preview mock endpoints (Fase A) ────────────────────
  previewMeta:     () => fetch(`${BASE}/preview/meta`).then(r => r.json()),
  previewEngagement: () => fetch(`${BASE}/preview/engagement`).then(r => r.json()),
  previewEmail:    () => fetch(`${BASE}/preview/email-events`).then(r => r.json()),
  previewAds:      () => fetch(`${BASE}/preview/ads-events`).then(r => r.json()),
  previewWeb:      () => fetch(`${BASE}/preview/web-events`).then(r => r.json()),
  previewPrimavera:() => fetch(`${BASE}/preview/primavera`).then(r => r.json()),
  previewSat:      () => fetch(`${BASE}/preview/sat`).then(r => r.json()),
  previewColab:    () => fetch(`${BASE}/preview/colaboradores`).then(r => r.json()),

  // ─── Tags ──────────────────────────────────────────────
  tags:        () => fetch(`${BASE}/tags`).then(r => r.json()),
  criarTag:    (body) => fetch(`${BASE}/tags`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }).then(r => r.json()),
  eliminarTag: (id) => fetch(`${BASE}/tags/${id}`, { method: 'DELETE' }).then(r => r.json()),
  entTags:     (id) => fetch(`${BASE}/entidades/${id}/tags`).then(r => r.json()),
  addEntTag:   (id, tagId) => fetch(`${BASE}/entidades/${id}/tags/${tagId}`, { method: 'POST' }).then(r => r.json()),
  delEntTag:   (id, tagId) => fetch(`${BASE}/entidades/${id}/tags/${tagId}`, { method: 'DELETE' }).then(r => r.json()),
  ctTags:      (id) => fetch(`${BASE}/contactos/${id}/tags`).then(r => r.json()),
  addCtTag:    (id, tagId) => fetch(`${BASE}/contactos/${id}/tags/${tagId}`, { method: 'POST' }).then(r => r.json()),
  delCtTag:    (id, tagId) => fetch(`${BASE}/contactos/${id}/tags/${tagId}`, { method: 'DELETE' }).then(r => r.json()),

  // ─── Segmentos ─────────────────────────────────────────
  segmentos:   () => fetch(`${BASE}/segmentos`).then(r => r.json()),
  entSegs:     (id) => fetch(`${BASE}/entidades/${id}/segmentos`).then(r => r.json()),
  setEntSeg:   (id, segId, valorId, setBy) => fetch(`${BASE}/entidades/${id}/segmentos/${segId}`, {
    method: 'POST', headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ valor_id: valorId, set_by: setBy }),
  }).then(r => r.json()),
  delEntSeg:   (id, segId) => fetch(`${BASE}/entidades/${id}/segmentos/${segId}`, { method: 'DELETE' }).then(r => r.json()),

  // ─── Notas ─────────────────────────────────────────────
  entNotas:    (id) => fetch(`${BASE}/entidades/${id}/notas`).then(r => r.json()),
  criarNota:   (id, body) => fetch(`${BASE}/entidades/${id}/notas`, {
    method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
  }).then(r => r.json()),
  eliminarNota: (id) => fetch(`${BASE}/notas/${id}`, { method: 'DELETE' }).then(r => r.json()),

  // ─── Overrides + Consentimentos ────────────────────────
  ctOverride:      (id) => fetch(`${BASE}/contactos/${id}/override`).then(r => r.json()),
  setCtOverride:   (id, body) => fetch(`${BASE}/contactos/${id}/override`, {
    method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
  }).then(r => r.json()),
  ctConsent:       (id) => fetch(`${BASE}/contactos/${id}/consentimentos`).then(r => r.json()),
  setCtConsent:    (id, body) => fetch(`${BASE}/contactos/${id}/consentimentos`, {
    method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
  }).then(r => r.json()),

  // ─── Timeline ──────────────────────────────────────────
  timeline: (id, params) => {
    const qs = new URLSearchParams();
    Object.entries(params || {}).forEach(([k, v]) => {
      if (Array.isArray(v)) v.forEach(vi => qs.append(k, vi));
      else if (v !== undefined && v !== '' && v !== null) qs.set(k, v);
    });
    return fetch(`${BASE}/entidades/${id}/timeline?${qs}`).then(r => r.json());
  },
  rebuildTimeline: () => fetch(`${BASE}/timeline/rebuild`, { method: 'POST' }).then(r => r.json()),

  // ─── Listas ────────────────────────────────────────────
  listas:      () => fetch(`${BASE}/listas`).then(r => r.json()).then(d => d.rows ? d : { rows: d, total_oportunidades: 0 }),
  lista:       (id) => fetch(`${BASE}/listas/${id}`).then(r => r.json()),
  criarLista:  (body) => fetch(`${BASE}/listas`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }).then(r => r.json()),
  eliminarLista: (id) => fetch(`${BASE}/listas/${id}`, { method: 'DELETE' }).then(r => r.json()),
  addItemLista: (listaId, body) => fetch(`${BASE}/listas/${listaId}/items`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }).then(r => r.json()),
  delItemLista: (listaId, itemId) => fetch(`${BASE}/listas/${listaId}/items/${itemId}`, { method: 'DELETE' }).then(r => r.json()),
  importCSV: (listaId, formData) => fetch(`${BASE}/listas/${listaId}/import-csv`, { method: 'POST', body: formData }).then(r => r.json()),

  // ─── Contactos externos ────────────────────────────────
  extContactos: (matching_status) => fetch(`${BASE}/contactos-externos${matching_status ? `?matching_status=${matching_status}` : ''}`).then(r => r.json()),
  matchExt:    (id, body) => fetch(`${BASE}/contactos-externos/${id}/match`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }).then(r => r.json()),
  externo360:  (id) => fetch(`${BASE}/externos/${id}`).then(r => r.json()),
  externoNota: (id, body) => fetch(`${BASE}/externos/${id}/nota`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }).then(r => r.json()),
  ignorarExt:  (id) => fetch(`${BASE}/contactos-externos/${id}/ignorar`, { method: 'POST' }).then(r => r.json()),

  // ─── Bulk ──────────────────────────────────────────────
  bulkTag:   (body) => fetch(`${BASE}/bulk/tag`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }).then(r => r.json()),
  bulkLista: (body) => fetch(`${BASE}/bulk/adicionar-lista`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }).then(r => r.json()),

  // ─── Export CSV (#47) ──────────────────────────────────
  exportEntidades: (params) => {
    const qs = new URLSearchParams();
    Object.entries(params || {}).forEach(([k, v]) => Array.isArray(v) ? v.forEach(vi => qs.append(k, vi)) : (v !== undefined && v !== '' && v !== null && qs.set(k, v)));
    window.open(`${BASE}/entidades/export?${qs}`);
  },
  exportContactos: (params) => {
    const qs = new URLSearchParams();
    Object.entries(params || {}).forEach(([k, v]) => Array.isArray(v) ? v.forEach(vi => qs.append(k, vi)) : (v !== undefined && v !== '' && v !== null && qs.set(k, v)));
    window.open(`${BASE}/contactos/export?${qs}`);
  },
};

// ── Helpers de UI ──────────────────────────────────────────────────────────
function humanTimeAgo(ts) {
  if (!ts) return 'nunca';
  const diff = Date.now() - new Date(ts).getTime();
  const mins = Math.floor(diff / 60000);
  if (mins < 60) return `${mins}min`;
  const hours = Math.floor(mins / 60);
  if (hours < 24) return `${hours}h`;
  const days = Math.floor(hours / 24);
  return `${days}d`;
}

// KPI Card ao estilo Briefings — label mono 9.5px + número 22px display + barra base 3px
// Props: { label, value, accent, fill, sub, onClick }
function CRMKPICard({ label, value, accent = 'var(--ai-500)', fill = 0, sub, onClick, active }) {
  return (
    <div onClick={onClick}
      style={{
        flex: '1 1 0', background: 'var(--bg-elev, #ffffff)',
        border: `1px solid ${active ? accent : 'var(--border, #dde3ef)'}`,
        borderRadius: 8, padding: '12px 16px 0',
        display: 'flex', flexDirection: 'column', gap: 4, overflow: 'hidden',
        cursor: onClick ? 'pointer' : 'default',
        transition: 'border-color 150ms ease, background 150ms ease',
      }}>
      <div style={{ fontSize: 9.5, fontWeight: 600, letterSpacing: '0.10em', textTransform: 'uppercase', color: active ? accent : 'var(--text-muted, #4a5e7a)', fontFamily: 'var(--font-mono, monospace)', whiteSpace: 'nowrap', transition: 'color 150ms ease' }}>
        {label}
      </div>
      <div style={{ fontSize: 22, fontWeight: 700, fontFamily: 'var(--font-display, Montserrat, sans-serif)', color: accent, lineHeight: 1, paddingBottom: sub ? 4 : 10 }}>
        {value}
      </div>
      {sub && <div style={{ fontSize: 11, color: 'var(--text-muted)', paddingBottom: 8 }}>{sub}</div>}
      <div style={{ height: 3, background: 'var(--bg-sunken, #eef1f6)', overflow: 'hidden', marginLeft: -16, marginRight: -16 }}>
        <div style={{ height: '100%', width: `${Math.min(100, Math.max(0, fill * 100))}%`, background: accent, transition: 'width 400ms ease' }} />
      </div>
    </div>
  );
}

// Strip com 4 KPIs (padrão Briefings: flex 1fr cada, gap 12)
function KPIStrip({ cards }) {
  return (
    <div style={{ display: 'flex', gap: 12 }}>
      {cards.map((c, i) => <CRMKPICard key={i} {...c} />)}
    </div>
  );
}

// Filter pill — label uppercase mono + valor inline (padrão Briefings BriefingDropdown)
function FilterPill({ label, children }) {
  return (
    <div style={{
      display: 'inline-flex', alignItems: 'center', gap: 8,
      padding: '5px 10px 5px 12px', borderRadius: 6,
      border: '1px solid var(--border)', background: 'var(--bg-elev, #ffffff)',
    }}>
      <span style={{ fontSize: 9, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '0.08em', textTransform: 'uppercase' }}>{label}</span>
      {children}
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════════════════
// SyncModal — modal proper com progresso por fase da sync FM → PostgreSQL
// ═══════════════════════════════════════════════════════════════════════════
const SYNC_FASES = [
  { id: 'entidades',     label: 'Entidades',           layout: 'FMRS_API_ENTIDADES',     target: 24000 },
  { id: 'contactos',     label: 'Contactos',           layout: 'FMRS_API_CONTACTOS',     target: 26000 },
  { id: 'oportunidades', label: 'Oportunidades',       layout: 'FMRS_API_OPORTUNIDADES', target: 22000 },
  { id: 'sources',       label: 'Sources (origens)',   layout: 'FMRS_API_SOURCES',       target: null  },
  { id: 'notas',         label: 'Notas Gestor',        layout: 'FMRS_API_NOTAS',         target: null  },
  { id: 'profile',       label: 'Perfil empresas',     layout: 'FMRS_API_ENTIDADES_PROFILE', target: null },
];

// Estado inicial por fase — usado tanto no mount como para reset
function makeInitialPhases() {
  const p = {};
  for (const f of SYNC_FASES) p[f.id] = { progresso: 0, ok: false, msg: null, startedAt: null, finishedAt: null };
  return p;
}

// Actualiza estado persistente das fases a partir de um evento SSE (imutável)
function reducePhases(prev, ev) {
  const next = { ...prev };
  for (const id of Object.keys(prev)) next[id] = { ...prev[id] };
  if (ev.fase === 'done') {
    if (ev.entidades)     { next.entidades.ok = true;     next.entidades.progresso = ev.entidades;         next.entidades.finishedAt = Date.now(); }
    if (ev.contactos)     { next.contactos.ok = true;     next.contactos.progresso = ev.contactos;         next.contactos.finishedAt = Date.now(); }
    if (ev.oportunidades) { next.oportunidades.ok = true; next.oportunidades.progresso = ev.oportunidades; next.oportunidades.finishedAt = Date.now(); }
    if (ev.notas != null) { next.notas.ok = true;    next.notas.progresso = ev.notas;    next.notas.finishedAt = Date.now(); }
    next.sources.ok  = true; next.sources.finishedAt  = Date.now();
    next.profile.ok  = true; next.profile.finishedAt  = Date.now();
    return next;
  }
  if (ev.fase === 'erro' || !next[ev.fase]) return next;
  const p = next[ev.fase];
  if (!p.startedAt) p.startedAt = Date.now();
  if (ev.progresso != null) p.progresso = ev.progresso;
  if (ev.total != null)     p.progresso = ev.total;
  if (ev.msg) p.msg = ev.msg;
  if (ev.ok) { p.ok = true; p.finishedAt = Date.now(); }
  return next;
}

function fmtDuration(ms) {
  if (!ms || ms < 0) return '—';
  const s = Math.floor(ms / 1000);
  if (s < 60) return `${s}s`;
  const m = Math.floor(s / 60);
  const rs = s % 60;
  return `${m}m${rs.toString().padStart(2,'0')}s`;
}

function fmtLastSync(iso) {
  if (!iso) return 'nunca';
  const d = new Date(iso);
  const diffMs = Date.now() - d.getTime();
  const min = Math.floor(diffMs / 60000);
  if (min < 1) return 'agora';
  if (min < 60) return `há ${min}min`;
  const h = Math.floor(min / 60);
  if (h < 24) return `há ${h}h`;
  return d.toLocaleString('pt-PT', { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' });
}

function SyncModal({ phases, syncing, syncStartedAt, erroEv, isDone, syncStatus, onClose, onCancel, onRetry }) {
  const [minimized, setMinimized] = React.useState(false);
  const [now, setNow] = React.useState(Date.now());

  // Tick para actualizar elapsed time e ETR
  React.useEffect(() => {
    if (!syncing) return;
    const t = setInterval(() => setNow(Date.now()), 1000);
    return () => clearInterval(t);
  }, [syncing]);

  const isErro = !!erroEv;

  const overallProgress = React.useMemo(() => {
    let total = 0;
    const targets = { entidades: 24000, contactos: 26000, oportunidades: 22000 };
    for (const id of ['entidades', 'contactos', 'oportunidades']) {
      const p = phases[id];
      if (p.ok) total += 25;
      else if (p.progresso) total += Math.min(25, (p.progresso / targets[id]) * 25);
    }
    if (phases.sources.ok) total += 25;
    if (isDone) total = 100;
    return Math.min(100, Math.round(total));
  }, [phases, isDone]);

  // Tempo decorrido + ETR
  const elapsedMs = syncStartedAt ? now - syncStartedAt : 0;
  const etrMs = overallProgress > 5 && !isDone && !isErro && syncing
    ? (elapsedMs / overallProgress) * (100 - overallProgress)
    : null;

  // Última sync por layout (do endpoint /sync-status)
  const layoutLogs = React.useMemo(() => {
    const map = {};
    for (const l of (syncStatus?.logs || [])) map[l.layout] = l;
    return map;
  }, [syncStatus]);

  // Minimized: barra flutuante bottom-right
  if (minimized) {
    return (
      <div style={{
        position: 'fixed', bottom: 24, right: 24, zIndex: 400,
        padding: '12px 16px', borderRadius: 8,
        background: 'var(--bg-elev, #fff)', border: '1px solid var(--border)',
        boxShadow: '0 8px 24px rgba(17,41,84,0.15)',
        display: 'flex', alignItems: 'center', gap: 12, minWidth: 280,
        cursor: 'pointer',
      }} onClick={() => setMinimized(false)}>
        <div style={{ flex: 1 }}>
          <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text)' }}>
            {isErro ? 'Sync com erro' : isDone ? 'Sync concluída' : `A sincronizar... ${overallProgress}%`}
          </div>
          <div style={{ height: 3, background: 'var(--bg-sunken)', borderRadius: 99, marginTop: 4, overflow: 'hidden' }}>
            <div style={{ width: `${overallProgress}%`, height: '100%', background: isErro ? 'var(--danger)' : isDone ? 'var(--success)' : 'var(--ai-500)', transition: 'width 400ms ease' }} />
          </div>
        </div>
        <span style={{ fontSize: 14, color: 'var(--text-muted)' }}>▴</span>
      </div>
    );
  }

  return (
    <>
      {/* Backdrop */}
      <div onClick={() => syncing && setMinimized(true)} style={{
        position: 'fixed', inset: 0, background: 'rgba(17,41,84,0.35)',
        zIndex: 399, animation: 'crm-sync-fade 200ms ease-out',
      }}>
        <style>{`@keyframes crm-sync-fade { from { opacity: 0; } to { opacity: 1; } }`}</style>
      </div>

      {/* Modal */}
      <div role="dialog" aria-label="Sincronização Gestor"
        style={{
          position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)',
          width: 560, maxWidth: '90vw', maxHeight: '85vh',
          background: 'var(--bg-elev, #fff)', borderRadius: 12,
          boxShadow: '0 24px 48px rgba(17,41,84,0.24), 0 8px 16px rgba(17,41,84,0.12)',
          zIndex: 400, display: 'flex', flexDirection: 'column', overflow: 'hidden',
          animation: 'crm-sync-in 250ms cubic-bezier(0.16, 1, 0.3, 1)',
        }}>
        <style>{`@keyframes crm-sync-in { from { opacity: 0; transform: translate(-50%, -48%) scale(0.98); } to { opacity: 1; transform: translate(-50%, -50%) scale(1); } }`}</style>

        {/* Header (sem acções — só título + progresso) */}
        <div style={{ padding: '20px 24px 16px', borderBottom: '1px solid var(--border)' }}>
          <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '0.08em', textTransform: 'uppercase', marginBottom: 4 }}>
            {isErro ? (erroEv.aborted ? 'Sync · Cancelada' : 'Sync · Erro') : isDone ? 'Sync · Concluída' : 'Sync · Em curso'}
          </div>
          <h2 style={{ margin: 0, fontSize: 18, fontWeight: 700, fontFamily: 'var(--font-display)', color: 'var(--text)', letterSpacing: '-0.01em' }}>
            Sincronização com o Gestor
          </h2>

          {/* Overall progress bar */}
          <div style={{ marginTop: 14 }}>
            <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 6 }}>
              <span style={{ fontSize: 12, color: 'var(--text-muted)' }}>Progresso total</span>
              <span style={{ fontSize: 20, fontWeight: 700, color: isErro ? 'var(--danger)' : isDone ? 'var(--success)' : 'var(--ai-500)', fontFamily: 'var(--font-display)' }}>
                {overallProgress}%
              </span>
            </div>
            <div style={{ height: 6, background: 'var(--bg-sunken)', borderRadius: 99, overflow: 'hidden' }}>
              <div style={{
                width: `${overallProgress}%`, height: '100%',
                background: isErro ? 'var(--danger)' : isDone ? 'var(--success)' : 'var(--ai-500)',
                transition: 'width 400ms ease, background 200ms ease',
              }} />
            </div>

            {/* Elapsed + ETR (só métricas, sem acções) */}
            <div style={{ display: 'flex', gap: 14, marginTop: 10, fontSize: 11, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)', flexWrap: 'wrap' }}>
              <span>Decorrido: <strong style={{ color: 'var(--text)' }}>{fmtDuration(elapsedMs)}</strong></span>
              {etrMs != null && (
                <span>Restam: <strong style={{ color: 'var(--text)' }}>~{fmtDuration(etrMs)}</strong></span>
              )}
              {isDone && <span style={{ color: 'var(--success)' }}>✓ Total {fmtDuration(elapsedMs)}</span>}
            </div>
          </div>
        </div>

        {/* Fases */}
        <div className="scrollbar" style={{ flex: 1, overflowY: 'auto', padding: '16px 24px' }}>
          {SYNC_FASES.map(f => {
            const s = phases[f.id];
            const done = s?.ok;
            const active = syncing && !done && !isErro && (s?.progresso > 0 || s?.msg);
            const errored = isErro && !done;
            const phaseElapsed = s?.startedAt ? (s.finishedAt || now) - s.startedAt : 0;
            const lastForLayout = layoutLogs[f.layout];

            return (
              <div key={f.id} style={{
                display: 'flex', alignItems: 'center', gap: 12, padding: '10px 0',
                borderBottom: '1px solid var(--border-light, rgba(0,0,0,0.05))',
              }}>
                {/* Status icon */}
                <div style={{
                  width: 22, height: 22, borderRadius: 99, flexShrink: 0,
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                  background: done ? 'var(--success)' : active ? 'var(--ai-500)' : errored ? 'var(--danger)' : 'var(--bg-sunken)',
                  color: done || active || errored ? '#fff' : 'var(--text-dim)',
                  fontSize: 11, fontWeight: 700,
                }}>
                  {done ? '✓' : errored ? '!' : active ? (
                    <span style={{ display: 'inline-block', width: 8, height: 8, borderRadius: 99, background: '#fff', animation: 'crm-pulse 1s ease-in-out infinite' }}>
                      <style>{`@keyframes crm-pulse { 0%,100% { opacity: 0.4; } 50% { opacity: 1; } }`}</style>
                    </span>
                  ) : '·'}
                </div>

                {/* Label + status */}
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ display: 'flex', alignItems: 'baseline', gap: 8, flexWrap: 'wrap' }}>
                    <span style={{ fontSize: 13, fontWeight: 600, color: 'var(--text)' }}>{f.label}</span>
                    {(active || done) && phaseElapsed > 500 && (
                      <span style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>{fmtDuration(phaseElapsed)}</span>
                    )}
                  </div>
                  <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 2, fontFamily: 'var(--font-mono)' }}>
                    {done && s?.progresso != null && s.progresso > 0 ? `${s.progresso.toLocaleString('pt-PT')} registos sincronizados` :
                     done ? 'Concluído' :
                     active && s?.progresso ? `${s.progresso.toLocaleString('pt-PT')} registos${f.target ? ` de ~${f.target.toLocaleString('pt-PT')}` : ''}...` :
                     active ? 'A processar...' :
                     errored ? 'Interrompido' :
                     s?.msg || (lastForLayout ? `Última: ${fmtLastSync(lastForLayout.finished_at)}` : 'A aguardar')}
                  </div>
                </div>

                {/* Progress bar da fase */}
                {f.target && (
                  <div style={{ width: 100, flexShrink: 0 }}>
                    <div style={{ height: 4, background: 'var(--bg-sunken)', borderRadius: 99, overflow: 'hidden' }}>
                      <div style={{
                        width: done ? '100%' : s?.progresso ? `${Math.min(100, (s.progresso / f.target) * 100)}%` : '0%',
                        height: '100%',
                        background: done ? 'var(--success)' : active ? 'var(--ai-500)' : 'transparent',
                        transition: 'width 500ms ease',
                      }} />
                    </div>
                  </div>
                )}
              </div>
            );
          })}

          {/* Done summary */}
          {isDone && (
            <div style={{ marginTop: 20, padding: '14px 16px', borderRadius: 8, background: 'color-mix(in oklch, var(--success) 8%, transparent)', border: '1px solid color-mix(in oklch, var(--success) 30%, transparent)' }}>
              <div style={{ fontSize: 12, fontWeight: 700, color: 'var(--success, #16a34a)', marginBottom: 6, fontFamily: 'var(--font-mono)', letterSpacing: '0.05em', textTransform: 'uppercase' }}>
                ✓ Sincronização concluída em {fmtDuration(elapsedMs)}
              </div>
              <div style={{ fontSize: 12, color: 'var(--text)', lineHeight: 1.6 }}>
                <strong>{(phases.entidades.progresso || 0).toLocaleString('pt-PT')}</strong> entidades ·{' '}
                <strong>{(phases.contactos.progresso || 0).toLocaleString('pt-PT')}</strong> contactos ·{' '}
                <strong>{(phases.oportunidades.progresso || 0).toLocaleString('pt-PT')}</strong> oportunidades
              </div>
            </div>
          )}

          {/* Erro */}
          {isErro && (
            <div style={{ marginTop: 20, padding: '14px 16px', borderRadius: 8, background: 'color-mix(in oklch, var(--danger) 8%, transparent)', border: '1px solid color-mix(in oklch, var(--danger) 30%, transparent)' }}>
              <div style={{ fontSize: 12, fontWeight: 700, color: 'var(--danger)', marginBottom: 6, fontFamily: 'var(--font-mono)', letterSpacing: '0.05em', textTransform: 'uppercase' }}>
                {erroEv.aborted ? 'Cancelado' : 'Erro'}
              </div>
              <div style={{ fontSize: 12, color: 'var(--text)', lineHeight: 1.6, fontFamily: 'var(--font-mono)' }}>
                {erroEv.msg}
              </div>
            </div>
          )}
        </div>

        {/* Footer — acções sempre por baixo do resultado */}
        <div style={{
          padding: '14px 24px',
          borderTop: '1px solid var(--border)',
          background: 'var(--bg-sunken, #f8fafc)',
          display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, flexWrap: 'wrap',
        }}>
          {/* Info à esquerda */}
          <div style={{ fontSize: 11, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>
            {syncing && !isErro && !isDone ? 'Podes minimizar e continuar a trabalhar' :
             isDone ? 'Dados actualizados no CRM' :
             isErro && erroEv.aborted ? 'Nenhum registo foi apagado' :
             isErro ? 'A sync pode ser retomada' : ''}
          </div>

          {/* Botões à direita */}
          <div style={{ display: 'flex', gap: 8, marginLeft: 'auto' }}>
            {/* Sync em curso */}
            {syncing && !isDone && !isErro && (
              <>
                <button
                  onClick={() => setMinimized(true)}
                  style={{ background: 'none', border: '1px solid var(--border)', borderRadius: 6, padding: '7px 14px', fontSize: 12, fontWeight: 500, cursor: 'pointer', color: 'var(--text-muted)' }}>
                  Minimizar
                </button>
                {onCancel && (
                  <button
                    onClick={onCancel}
                    style={{ background: 'none', border: '1px solid var(--danger, #dc2626)', borderRadius: 6, padding: '7px 14px', fontSize: 12, fontWeight: 600, cursor: 'pointer', color: 'var(--danger, #dc2626)' }}>
                    Cancelar
                  </button>
                )}
              </>
            )}

            {/* Erro (inclui cancelamento) */}
            {isErro && (
              <>
                <button
                  onClick={onClose}
                  style={{ background: 'none', border: '1px solid var(--border)', borderRadius: 6, padding: '7px 14px', fontSize: 12, fontWeight: 500, cursor: 'pointer', color: 'var(--text-muted)' }}>
                  Fechar
                </button>
                {onRetry && (
                  <button
                    onClick={onRetry}
                    style={{ background: 'var(--ai-500)', border: 'none', borderRadius: 6, padding: '7px 16px', fontSize: 12, fontWeight: 600, cursor: 'pointer', color: '#fff' }}>
                    Tentar novamente
                  </button>
                )}
              </>
            )}

            {/* Concluída */}
            {isDone && (
              <button
                onClick={onClose}
                autoFocus
                style={{ background: 'var(--ai-500)', border: 'none', borderRadius: 6, padding: '7px 20px', fontSize: 12, fontWeight: 600, cursor: 'pointer', color: '#fff' }}>
                OK
              </button>
            )}
          </div>
        </div>
      </div>
    </>
  );
}

// ═══════════════════════════════════════════════════════════════════════════
// PROFILE — componentes partilhados HubSpot/Apollo (PA3)
// ═══════════════════════════════════════════════════════════════════════════
// Layout canónico 3 colunas usado por EntidadeProfile · ContactoProfile · OportunidadeProfile.
// - Left rail (ProfileHeader): identidade + KPIs sticky
// - Centre (ProfileActivity): timeline + tabs
// - Right rail (DigiAIPanel): resumo + alertas + próximas acções

// Helper — formata número k€ com fallback
function fmtK(n) {
  const v = Number(n) || 0;
  if (v === 0) return '—';
  if (v >= 1000) return `${(v/1000).toFixed(1)}M€`;
  return `${v.toLocaleString('pt-PT', { maximumFractionDigits: 1 })}k€`;
}

// Helper — dias → label humano
function fmtDias(d) {
  if (d == null) return '—';
  if (d === 0) return 'hoje';
  if (d === 1) return 'ontem';
  if (d < 7)   return `há ${d}d`;
  if (d < 30)  return `há ${Math.floor(d/7)}sem`;
  if (d < 365) return `há ${Math.floor(d/30)}m`;
  return `há ${Math.floor(d/365)}a`;
}

// Helper — consent estado → cor (usado em QpContacto + ContactoCard)
function consentDot(estado) {
  if (estado === 'opt_in') return 'var(--success, #22c55e)';
  if (estado === 'opt_out') return 'var(--danger, #ef4444)';
  return 'var(--border, #cbd5e1)';
}

// Helper — opt_rgpd → cor + label (campo FM directo no contacto)
// Semântica Walter: '1'=bloqueado sistema (bounce), '2'=bloqueado cliente (RGPD legal)
// ''/NULL/'0'=OK enviar. Ambos '1' e '2' são hard-block para envio.
function rgpdDotColor(val) {
  if (val === '1') return '#f97316';  // laranja — bloqueio técnico
  if (val === '2') return '#ef4444';  // vermelho — bloqueio legal RGPD
  return '#22c55e';  // verde — OK enviar
}
function rgpdLabel(val) {
  if (val === '1') return 'bloqueado (sistema)';
  if (val === '2') return 'bloqueado (cliente)';
  return 'ok';
}
function rgpdShortLabel(val) {
  if (val === '1') return 'RGPD SISTEMA';
  if (val === '2') return 'RGPD CLIENTE';
  return null;
}

// Helper — nome composto a partir de nome_proprio + apelido (fallback para nome)
function nomeComposto(c) {
  if (c.nome_proprio || c.apelido) {
    return [c.nome_proprio, c.apelido].filter(Boolean).join(' ');
  }
  return c.nome || '—';
}

// ═══════════════════════════════════════════════════════════════════════════
// PA11 · Componentes visuais densos (HubSpot/Apollo-grade)
// ═══════════════════════════════════════════════════════════════════════════

// Hash de string → cor HSL determinística (mesmo nome sempre mesma cor)
function stringToHslColor(str, sat = 55, light = 55) {
  if (!str) return `hsl(220, ${sat}%, ${light}%)`;
  let h = 0;
  for (let i = 0; i < str.length; i++) h = str.charCodeAt(i) + ((h << 5) - h);
  return `hsl(${h % 360}, ${sat}%, ${light}%)`;
}

function initialsOf(name) {
  if (!name) return '?';
  const parts = String(name).trim().split(/\s+/).filter(Boolean);
  if (parts.length === 0) return '?';
  if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
  return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
}

// ── Avatar — círculo com iniciais + cor derivada
function Avatar({ name, size = 44, color }) {
  const bg = color || stringToHslColor(name || '', 55, 55);
  const fontSize = Math.round(size * 0.36);
  return (
    <div style={{
      width: size, height: size, borderRadius: 99, background: bg,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      color: '#fff', fontWeight: 700, fontSize, fontFamily: 'var(--font-display)',
      flexShrink: 0, letterSpacing: '-0.02em',
      boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.15)',
    }}>{initialsOf(name)}</div>
  );
}

// ── LifecyclePill — pill colorida com estado
function LifecyclePill({ lifecycle, size = 'md' }) {
  if (!lifecycle) return null;
  const sizeMap = {
    sm: { p: '3px 8px',  fs: 10, ls: '0.06em' },
    md: { p: '5px 12px', fs: 11, ls: '0.05em' },
    lg: { p: '7px 14px', fs: 12, ls: '0.04em' },
  }[size];
  return (
    <span style={{
      display: 'inline-flex', alignItems: 'center', gap: 6,
      padding: sizeMap.p, borderRadius: 99,
      background: lifecycle.tint || `${lifecycle.color}14`,
      color: lifecycle.color,
      border: `1px solid ${lifecycle.color}30`,
      fontSize: sizeMap.fs, fontWeight: 700, fontFamily: 'var(--font-mono)',
      letterSpacing: sizeMap.ls, textTransform: 'uppercase',
    }}>
      <span style={{ width: 6, height: 6, borderRadius: 99, background: lifecycle.color }} />
      {lifecycle.label}
    </span>
  );
}

// ── SignalDots — row de dots coloridos com tooltip
const SIGNAL_STATE_COLORS = {
  ready:    { color: '#15803d', label: 'Pronto' },
  possible: { color: '#0891b2', label: 'Possível' },
  active:   { color: '#0284c7', label: 'Activo' },
  hot:      { color: '#dc2626', label: 'Alto' },
  high:     { color: '#7c3aed', label: 'Alto' },
  advanced: { color: '#0284c7', label: 'Avançado' },
  early:    { color: '#64748b', label: 'Inicial' },
  above:    { color: '#7c3aed', label: 'Acima' },
  below:    { color: '#94a3b8', label: 'Abaixo' },
  match:    { color: '#0284c7', label: 'Alinhado' },
  fresh:    { color: '#15803d', label: 'Fresco' },
  aging:    { color: '#d97706', label: 'A envelhecer' },
  stale:    { color: '#dc2626', label: 'Parado' },
  medium:   { color: '#d97706', label: 'Médio' },
  low:      { color: '#94a3b8', label: 'Baixo' },
  recent:   { color: '#15803d', label: 'Recente' },
  warm:     { color: '#15803d', label: 'Quente' },
  cold:     { color: '#64748b', label: 'Frio' },
  long:     { color: '#dc2626', label: 'Longo' },
  none:     { color: '#cbd5e1', label: 'Nenhum' },
  missing:  { color: '#94a3b8', label: 'Em falta' },
  blocked:  { color: '#dc2626', label: 'Bloqueado' },
  won:      { color: '#15803d', label: 'Ganha' },
  lost:     { color: '#dc2626', label: 'Perdida' },
  closed:   { color: '#94a3b8', label: 'Fechada' },
  unknown:  { color: '#cbd5e1', label: 'Sem info' },
  risk_high:{ color: '#dc2626', label: 'Alto' },
};

function SignalDots({ signals, size = 'md' }) {
  if (!signals?.length) return null;
  const dotSize = size === 'sm' ? 6 : 8;
  return (
    <div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
      {signals.map(s => {
        const meta = SIGNAL_STATE_COLORS[s.estado] || SIGNAL_STATE_COLORS.unknown;
        return (
          <div key={s.id}
            title={`${s.label}: ${meta.label}`}
            style={{
              display: 'flex', alignItems: 'center', gap: 4,
              padding: size === 'sm' ? '2px 6px' : '3px 8px',
              borderRadius: 99, background: `${meta.color}12`, border: `1px solid ${meta.color}30`,
              cursor: 'help',
            }}>
            <span style={{ width: dotSize, height: dotSize, borderRadius: 99, background: meta.color }} />
            <span style={{ fontSize: size === 'sm' ? 9 : 10, fontFamily: 'var(--font-mono)', fontWeight: 600, color: meta.color, textTransform: 'uppercase', letterSpacing: '0.03em' }}>
              {s.label}
            </span>
          </div>
        );
      })}
    </div>
  );
}

// ── SignalDotsCompact — só dots (para tabela), tooltip com label + estado
function SignalDotsCompact({ signals }) {
  if (!signals?.length) return null;
  return (
    <div style={{ display: 'flex', gap: 3 }}>
      {signals.map(s => {
        const meta = SIGNAL_STATE_COLORS[s.estado] || SIGNAL_STATE_COLORS.unknown;
        return (
          <span key={s.id} title={`${s.label}: ${meta.label}`}
            style={{ width: 7, height: 7, borderRadius: 99, background: meta.color, cursor: 'help', flexShrink: 0 }} />
        );
      })}
    </div>
  );
}

// ── EngagementScoreBadge — número + cor por bucket + progress bar
function EngagementScoreBadge({ score, compact }) {
  if (score == null) return null;
  const total = Number(score.total || 0);
  const color = total >= 70 ? '#15803d' : total >= 40 ? '#0284c7' : total >= 20 ? '#d97706' : '#94a3b8';
  const label = total >= 70 ? 'Alto' : total >= 40 ? 'Médio' : total >= 20 ? 'Baixo' : 'Frio';

  if (compact) {
    return (
      <div style={{
        display: 'inline-flex', alignItems: 'baseline', gap: 4,
        padding: '2px 8px', borderRadius: 4, background: `${color}12`, border: `1px solid ${color}30`,
      }}>
        <span style={{ fontSize: 12, fontWeight: 700, color, fontFamily: 'var(--font-mono)' }}>{total}</span>
        <span style={{ fontSize: 9, color, opacity: 0.7, fontFamily: 'var(--font-mono)', textTransform: 'uppercase' }}>/100</span>
      </div>
    );
  }

  return (
    <div style={{ padding: '12px 14px', borderRadius: 8, background: `${color}0a`, border: `1px solid ${color}25` }}>
      <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 6 }}>
        <div style={{ fontSize: 9, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>Engagement</div>
        <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', fontWeight: 700, color, textTransform: 'uppercase' }}>{label}</div>
      </div>
      <div style={{ display: 'flex', alignItems: 'baseline', gap: 4, marginBottom: 6 }}>
        <span style={{ fontSize: 22, fontWeight: 700, color, fontFamily: 'var(--font-display)', letterSpacing: '-0.02em' }}>{total}</span>
        <span style={{ fontSize: 11, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>/ 100</span>
      </div>
      <div style={{ height: 4, background: 'var(--bg-sunken)', borderRadius: 99, overflow: 'hidden' }}>
        <div style={{ width: `${Math.min(100, total)}%`, height: '100%', background: color, transition: 'width 400ms ease' }} />
      </div>
      {score.breakdown && (
        <div style={{ marginTop: 10, display: 'flex', flexDirection: 'column', gap: 4 }}>
          {score.breakdown.map((b, i) => {
            const pct = b.max > 0 ? (b.score / b.max) * 100 : 0;
            return (
              <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 10 }}>
                <span style={{ flex: 1, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>{b.label}</span>
                <span style={{ color: 'var(--text)', fontWeight: 600, fontFamily: 'var(--font-mono)', minWidth: 32, textAlign: 'right' }}>{b.score}/{b.max}</span>
                <span style={{ color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', minWidth: 60, textAlign: 'right' }}>{b.detail}</span>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

// ── QuickActionBar — icons sticky no topo (Email · Call · WA · Note · Task · List)
function QuickActionBar({ actions, sticky }) {
  const style = {
    display: 'flex', gap: 4, padding: '10px 20px',
    borderBottom: '1px solid var(--border)', background: 'var(--bg-elev)',
    ...(sticky ? { position: 'sticky', top: 0, zIndex: 5 } : {}),
  };
  const btn = {
    display: 'flex', alignItems: 'center', gap: 6, padding: '7px 12px',
    background: 'transparent', border: '1px solid var(--border)', borderRadius: 6,
    fontSize: 12, fontWeight: 600, color: 'var(--text-muted)', cursor: 'pointer',
    fontFamily: 'var(--font-display)', transition: 'all 0.15s',
  };
  return (
    <div style={style}>
      {actions.map((a, i) => (
        <button key={i}
          onClick={a.onClick}
          disabled={a.disabled}
          title={a.tooltip || a.label}
          style={{
            ...btn,
            opacity: a.disabled ? 0.4 : 1,
            cursor: a.disabled ? 'not-allowed' : 'pointer',
            color: a.primary ? 'var(--ai-500)' : 'var(--text-muted)',
            borderColor: a.primary ? 'var(--ai-500)' : 'var(--border)',
          }}
          onMouseEnter={ev => { if (!a.disabled) { ev.currentTarget.style.background = 'var(--bg-sunken)'; ev.currentTarget.style.color = 'var(--text)'; } }}
          onMouseLeave={ev => { ev.currentTarget.style.background = 'transparent'; ev.currentTarget.style.color = a.primary ? 'var(--ai-500)' : 'var(--text-muted)'; }}>
          <span style={{ fontSize: 14 }}>{a.icon}</span>
          <span>{a.label}</span>
        </button>
      ))}
    </div>
  );
}

// ── ActivityFeedRow — timeline unificada (agrega OPs, notas, consent, interacções)
const ACTIVITY_ICON = {
  op_open:            { icon: '▲', color: '#0284c7' },
  op_won:             { icon: '✓', color: '#15803d' },
  op_lost:            { icon: '✕', color: '#dc2626' },
  op_created:         { icon: '▲', color: '#0284c7' },
  consent_change:     { icon: '⚿', color: '#7c3aed' },
  nota:               { icon: '✎', color: '#d97706' },
  interaction_wa:     { icon: '💬', color: '#0891b2' },
  interaction_email:  { icon: '✉', color: '#0284c7' },
  interaction_nota_marketing: { icon: '✎', color: '#d97706' },
};

function ActivityFeed({ events, loading, emptyCTA }) {
  if (loading) return <div style={{ padding: 40, textAlign: 'center', color: 'var(--text-muted)', fontSize: 12 }}>A carregar actividade...</div>;
  if (!events?.length) {
    return (
      <div style={{ padding: '32px 20px', textAlign: 'center', background: 'var(--bg-sunken)', borderRadius: 8, border: '1px dashed var(--border)' }}>
        <div style={{ fontSize: 13, color: 'var(--text-muted)', marginBottom: emptyCTA ? 12 : 0 }}>Sem actividade registada.</div>
        {emptyCTA && <button onClick={emptyCTA.onClick} style={{ background:'none', border:'1px solid var(--border)', borderRadius:6, padding:'5px 14px', fontSize:12, color:'var(--ai-500)', cursor:'pointer', fontWeight:600 }}>{emptyCTA.label}</button>}
      </div>
    );
  }
  return (
    <div style={{ position: 'relative' }}>
      {/* vertical line */}
      <div style={{ position: 'absolute', left: 11, top: 8, bottom: 8, width: 1, background: 'var(--border)' }} />
      {events.map((ev, i) => {
        const meta = ACTIVITY_ICON[ev.kind] || { icon: '·', color: '#64748b' };
        const dt = new Date(ev.ts);
        return (
          <div key={i} style={{ display: 'flex', gap: 12, paddingBottom: 14, position: 'relative' }}>
            <div style={{
              width: 22, height: 22, borderRadius: 99, background: `${meta.color}18`,
              border: `1px solid ${meta.color}40`, color: meta.color,
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              fontSize: 11, fontWeight: 700, flexShrink: 0, zIndex: 1,
              position: 'relative', background: 'var(--bg-elev)',
            }}>
              <span style={{ color: meta.color }}>{meta.icon}</span>
            </div>
            <div style={{ flex: 1, minWidth: 0, paddingTop: 1 }}>
              <div style={{ display: 'flex', alignItems: 'baseline', gap: 8, flexWrap: 'wrap' }}>
                <span style={{ fontSize: 12, fontWeight: 600, color: 'var(--text)' }}>{ev.title}</span>
                <span style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', marginLeft: 'auto' }}>
                  {dt.toLocaleDateString('pt-PT', { day: '2-digit', month: 'short', year: 'numeric' })}
                </span>
              </div>
              {ev.description && (
                <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 2, lineHeight: 1.45 }}>
                  {ev.description}
                </div>
              )}
            </div>
          </div>
        );
      })}
    </div>
  );
}

// ── PropertyGroup — secção colapsável para agrupar campos
function PropertyGroup({ title, count, defaultOpen = true, children }) {
  const [open, setOpen] = React.useState(defaultOpen);
  return (
    <div>
      <button onClick={() => setOpen(o => !o)}
        style={{
          width: '100%', textAlign: 'left', background: 'transparent', border: 'none', cursor: 'pointer',
          padding: '4px 0', display: 'flex', alignItems: 'center', gap: 6, marginBottom: 6,
        }}>
        <span style={{ fontSize: 10, color: 'var(--text-dim)', transition: 'transform 0.15s', display: 'inline-block', transform: open ? 'rotate(90deg)' : 'rotate(0deg)' }}>▸</span>
        <span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '0.08em', textTransform: 'uppercase', fontWeight: 700 }}>{title}</span>
        {count != null && (
          <span style={{ fontSize: 9, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', background: 'var(--bg-sunken)', padding: '1px 5px', borderRadius: 3 }}>{count}</span>
        )}
      </button>
      {open && children}
    </div>
  );
}

// ── PropRow — key/value denso para dentro de PropertyGroup
function PropRow({ label, value, mono }) {
  return (
    <div style={{ display: 'grid', gridTemplateColumns: '110px 1fr', gap: 8, padding: '4px 0', fontSize: 12 }}>
      <div style={{ color: 'var(--text-dim)' }}>{label}</div>
      <div style={{ color: 'var(--text)', fontFamily: mono ? 'var(--font-mono)' : 'inherit', wordBreak: 'break-word' }}>{value ?? <span style={{ color: 'var(--text-dim)' }}>—</span>}</div>
    </div>
  );
}

// ── ProfileHeader — left rail sticky com identidade + KPIs
// props: type ('entidade'|'contacto'|'oportunidade'), data, kpis, tags, segs, onBack
function ProfileHeader({ type, data, kpis, signals, score, tags, segs, onBack, subtitle }) {
  const title = type === 'contacto'
    ? nomeComposto(data || {})
    : (data?.nome || data?.produto_name || '—');
  const typeLabel = { entidade: 'Entidade', contacto: 'Contacto', oportunidade: 'Oportunidade' }[type];

  // Facts compactos (2 cols, sem cards enormes)
  const facts = React.useMemo(() => {
    if (!kpis) return [];
    if (type === 'entidade') return [
      { label: 'OPs abertas',    value: kpis.ops_abertas ?? 0, accent: kpis.ops_abertas > 0 ? 'ai' : null },
      { label: 'Pipeline',       value: fmtK(kpis.pipeline_valor_k), accent: kpis.pipeline_valor_k > 100 ? 'ai' : null },
      { label: 'WON 12m',        value: `${kpis.ops_won_12m || 0} · ${fmtK(kpis.valor_won_12m_k)}` },
      { label: 'Última OP',      value: fmtDias(kpis.dias_desde_ultima_op) },
      { label: 'Marca top',      value: kpis.marca_dominante || '—' },
      { label: 'Contactos',      value: `${kpis.contactos_com_tel || 0}/${kpis.contactos_total || 0} tel` },
    ];
    if (type === 'contacto') return [
      { label: 'OPs entidade',   value: kpis.ops_abertas_entidade ?? 0, accent: 'ai' },
      { label: 'Pipeline emp.',  value: fmtK(kpis.pipeline_valor_k_entidade) },
      { label: 'Interacções',    value: kpis.interacoes_total ?? 0 },
      { label: 'Última interacção', value: fmtDias(kpis.dias_desde_ultima_interacao) },
    ];
    if (type === 'oportunidade') return [
      { label: 'Valor',          value: fmtK(data?.produto_valor_k), accent: 'ai' },
      { label: 'Dias em curso',  value: fmtDias(kpis.dias_desde_inicio) },
      { label: 'Média equipa',   value: fmtK(kpis.media_valor_equipa_k) },
      { label: 'Última interacção', value: fmtDias(kpis.dias_desde_ultima_interacao) },
      { label: 'Histórico emp.', value: `${kpis.entidade_ops_won || 0}W / ${kpis.entidade_ops_lost || 0}L` },
      { label: 'OPs abertas',    value: kpis.entidade_ops_abertas ?? 0 },
    ];
    return [];
  }, [type, data, kpis]);

  return (
    <div style={{ padding: '16px 20px 20px', display: 'flex', flexDirection: 'column', gap: 14 }}>
      {onBack && (
        <button onClick={onBack}
          style={{ alignSelf: 'flex-start', background: 'none', border: 'none', cursor: 'pointer',
            fontSize: 11, color: 'var(--text-muted)', padding: '2px 4px', fontFamily: 'var(--font-mono)' }}>
          ← Voltar
        </button>
      )}

      {/* Breadcrumb */}
      <div style={{ fontSize: 9, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '0.08em', textTransform: 'uppercase' }}>
        CRM · {typeLabel}
      </div>

      {/* Hero: Avatar + Nome + Lifecycle */}
      <div style={{ display: 'flex', gap: 12, alignItems: 'flex-start' }}>
        <Avatar name={title} size={48} />
        <div style={{ flex: 1, minWidth: 0 }}>
          <h1 style={{ margin: 0, fontSize: 18, fontWeight: 700, fontFamily: 'var(--font-display)', color: 'var(--text)', letterSpacing: '-0.015em', lineHeight: 1.25, wordBreak: 'break-word' }}>
            {title}
          </h1>
          {signals?.lifecycle && (
            <div style={{ marginTop: 6 }}>
              <LifecyclePill lifecycle={signals.lifecycle} size="md" />
            </div>
          )}
        </div>
      </div>

      {/* Subtitle */}
      {subtitle && subtitle.length > 0 && (
        <div style={{ fontSize: 11, color: 'var(--text-muted)', display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: -6 }}>
          {subtitle}
        </div>
      )}

      {/* Signal dots row */}
      {signals?.signals?.length > 0 && (
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
          <SignalDots signals={signals.signals} size="sm" />
        </div>
      )}

      {/* Engagement score card (só entidade + contacto) */}
      {score && type !== 'oportunidade' && <EngagementScoreBadge score={score} />}

      {/* Chips: tags + segmentos */}
      {((tags?.length || 0) + (segs?.length || 0)) > 0 && (
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 3 }}>
          {(segs || []).map(s => (
            <span key={s.segmento_id || s.id} style={{
              fontSize: 9, fontFamily: 'var(--font-mono)', fontWeight: 600,
              padding: '2px 7px', borderRadius: 3,
              background: `${s.cor || '#3859D0'}14`, color: s.cor || '#3859D0',
              letterSpacing: '0.03em', textTransform: 'uppercase',
            }}>{s.valor_nome || s.nome}</span>
          ))}
          {(tags || []).map(t => (
            <span key={t.id} style={{
              fontSize: 9, fontFamily: 'var(--font-mono)', fontWeight: 500,
              padding: '2px 7px', borderRadius: 3,
              background: 'var(--bg-sunken)', color: t.cor || 'var(--text-muted)',
              border: `1px solid ${t.cor || 'var(--border)'}30`,
            }}>#{t.nome}</span>
          ))}
        </div>
      )}

      {/* Facts — grid compacto 2 cols, tighter type */}
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6 }}>
        {facts.map((k, i) => (
          <div key={i} style={{ padding: '7px 10px', borderRadius: 6, background: 'var(--bg-sunken)', border: '1px solid var(--border)' }}>
            <div style={{ fontSize: 9, textTransform: 'uppercase', color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', letterSpacing: '0.04em', marginBottom: 2 }}>
              {k.label}
            </div>
            <div style={{
              fontSize: 13, fontWeight: 700, fontFamily: 'var(--font-display)',
              color: k.accent === 'ai' ? 'var(--ai-500)' : 'var(--text)',
              lineHeight: 1.2,
            }}>{k.value}</div>
          </div>
        ))}
      </div>
    </div>
  );
}

// ── DigiAIPanel — right rail com next-best-action em destaque + alertas + score breakdown
function DigiAIPanel({ apreciacao, loading }) {
  if (loading) return <div style={{ padding: 24, color: 'var(--text-muted)', fontSize: 12 }}>A analisar contexto...</div>;
  if (!apreciacao) return null;

  const alertaColor = (nivel) => nivel === 'error' ? '#dc2626' : nivel === 'warn' ? '#d97706' : '#0284c7';
  const alertaBg    = (nivel) => `${alertaColor(nivel)}0f`;
  const topAction = apreciacao.proximas_accoes?.[0];
  const restActions = (apreciacao.proximas_accoes || []).slice(1);

  return (
    <div style={{ padding: '16px 20px 24px', display: 'flex', flexDirection: 'column', gap: 14 }}>
      {/* Header Digi AI compacto */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
        <div style={{
          width: 22, height: 22, borderRadius: 6, background: 'linear-gradient(135deg, #3859D0, #7B61FF)',
          display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', fontSize: 10, fontWeight: 700,
        }}>AI</div>
        <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '0.08em', textTransform: 'uppercase' }}>Digi AI · Apreciação</div>
      </div>

      {/* Resumo em prosa denso */}
      <div style={{ fontSize: 12, color: 'var(--text)', lineHeight: 1.55 }}>
        {apreciacao.resumo}
      </div>

      {/* NEXT BEST ACTION — grande e prominent */}
      {topAction && (
        <div style={{
          padding: '12px 14px', borderRadius: 8,
          background: 'linear-gradient(135deg, color-mix(in oklch, var(--ai-500) 8%, var(--bg-sunken)), var(--bg-sunken))',
          border: '1px solid var(--ai-500)',
        }}>
          <div style={{ fontSize: 9, fontFamily: 'var(--font-mono)', color: 'var(--ai-500)', letterSpacing: '0.08em', textTransform: 'uppercase', marginBottom: 4, fontWeight: 700 }}>
            → Próxima acção
          </div>
          <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--text)', lineHeight: 1.4 }}>
            {topAction.texto}
          </div>
        </div>
      )}

      {/* Alertas compactos */}
      {apreciacao.alertas?.length > 0 && (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
          <div style={{ fontSize: 9, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '0.06em', textTransform: 'uppercase', fontWeight: 700 }}>Alertas</div>
          {apreciacao.alertas.map((a, i) => (
            <div key={i} style={{
              padding: '8px 10px', borderRadius: 6,
              background: alertaBg(a.nivel), borderLeft: `3px solid ${alertaColor(a.nivel)}`,
              display: 'flex', gap: 8, alignItems: 'flex-start',
            }}>
              <span style={{ color: alertaColor(a.nivel), fontFamily: 'var(--font-mono)', fontSize: 12, fontWeight: 700, lineHeight: 1.2 }}>!</span>
              <span style={{ fontSize: 11.5, color: 'var(--text)', lineHeight: 1.45 }}>{a.texto}</span>
            </div>
          ))}
        </div>
      )}

      {/* Outras acções (não a top) */}
      {restActions.length > 0 && (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
          <div style={{ fontSize: 9, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '0.06em', textTransform: 'uppercase', fontWeight: 700 }}>Também considera</div>
          {restActions.map((p, i) => (
            <div key={i} style={{ padding: '7px 10px', fontSize: 11.5, color: 'var(--text-muted)', lineHeight: 1.45, display: 'flex', gap: 6 }}>
              <span style={{ color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', minWidth: 12 }}>·</span>
              <span>{p.texto}</span>
            </div>
          ))}
        </div>
      )}

      {(!apreciacao.alertas?.length && !topAction) && (
        <div style={{ fontSize: 11.5, color: 'var(--text-muted)', fontStyle: 'italic', padding: '12px 0' }}>
          Nada a assinalar neste momento.
        </div>
      )}
    </div>
  );
}

// ── ProfileActivity — centro com QuickActionBar sticky + tabs + conteúdo
// props: tabs [{ id, label, count?, render }], defaultTab, storageKey?, quickActions?
function ProfileActivity({ tabs, defaultTab, storageKey, quickActions }) {
  const [tab, setTab] = React.useState(() => {
    if (storageKey) {
      try { return sessionStorage.getItem(storageKey) || defaultTab || tabs[0]?.id; } catch {}
    }
    return defaultTab || tabs[0]?.id;
  });

  React.useEffect(() => {
    if (storageKey) { try { sessionStorage.setItem(storageKey, tab); } catch {} }
  }, [tab, storageKey]);

  const active = tabs.find(t => t.id === tab) || tabs[0];

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
      {/* Quick action bar sticky top (HubSpot-style) */}
      {quickActions?.length > 0 && <QuickActionBar actions={quickActions} sticky />}

      {/* Tabs underline canónicas */}
      <div style={{
        display: 'flex', gap: 0, padding: '0 20px', borderBottom: '1px solid var(--border)',
        background: 'var(--bg-elev)', flexShrink: 0, overflowX: 'auto',
      }}>
        {tabs.map(t => {
          const isActive = t.id === tab;
          return (
            <button key={t.id} data-crm-tab={t.id} onClick={() => setTab(t.id)}
              style={{
                padding: '12px 14px', border: 'none', background: 'transparent',
                cursor: 'pointer', fontSize: 12, fontWeight: 600, fontFamily: 'var(--font-display)',
                color: isActive ? 'var(--ai-500)' : 'var(--text-muted)',
                borderBottom: isActive ? '2px solid var(--ai-500)' : '2px solid transparent',
                marginBottom: -1, transition: 'color 0.15s',
                display: 'flex', alignItems: 'center', gap: 5, flexShrink: 0, whiteSpace: 'nowrap',
              }}>
              {t.label}
              {t.count != null && (
                <span style={{
                  fontSize: 10, padding: '1px 6px', borderRadius: 99, fontFamily: 'var(--font-mono)',
                  background: isActive ? 'var(--ai-500)' : 'var(--bg-sunken)',
                  color:      isActive ? '#fff' : 'var(--text-muted)',
                }}>{t.count}</span>
              )}
            </button>
          );
        })}
      </div>

      {/* Corpo do tab activo */}
      <div className="scrollbar" style={{ flex: 1, overflowY: 'auto', padding: '20px' }}>
        {active?.render()}
      </div>
    </div>
  );
}

// ── ProfileLayout — orquestra as 3 colunas (usado por EntidadeProfile/ContactoProfile/OpProfile)
function ProfileLayout({ left, centre, right }) {
  return (
    <div style={{
      height: '100%',
      display: 'grid',
      gridTemplateColumns: '320px 1fr 340px',
      gap: 0,
      overflow: 'hidden',
      background: 'var(--bg)',
    }}>
      <aside className="scrollbar" style={{
        borderRight: '1px solid var(--border)',
        overflowY: 'auto', background: 'var(--bg-elev)',
      }}>{left}</aside>
      <main style={{ overflow: 'hidden', minWidth: 0 }}>{centre}</main>
      <aside className="scrollbar" style={{
        borderLeft: '1px solid var(--border)',
        overflowY: 'auto', background: 'var(--bg-elev)',
      }}>{right}</aside>
    </div>
  );
}

// ── Componente principal ───────────────────────────────────────────────────
const MktCRMScreen = ({ userName, surface = 'crm' }) => {
  const hour = new Date().getHours();
  const saudacao = hour < 12 ? 'Bom dia' : hour < 19 ? 'Boa tarde' : 'Boa noite';
  // Prioridade: prop userName (do wrapper) → currentUser → sessionStorage → sem nome
  const rawName = React.useMemo(() => {
    if (userName) return userName;
    if (window.currentUser?.nome_apresentar) return window.currentUser.nome_apresentar;
    if (window.currentUser?.nome) return window.currentUser.nome;
    try {
      const stored = sessionStorage.getItem('digi_user_name');
      if (stored) return stored;
    } catch {}
    return '';
  }, [userName]);
  const firstName = rawName ? rawName.split(' ')[0] : '';

  // Guardar em sessionStorage para próximos loads
  React.useEffect(() => {
    if (rawName) {
      try { sessionStorage.setItem('digi_user_name', rawName); } catch {}
    }
  }, [rawName]);

  const [tab, setTab] = React.useState(surface === 'audiencias' ? 'audiencias' : 'entidades');
  const [leadsDireccao, setLeadsDireccao] = React.useState('inbound');
  const [leadsMarca, setLeadsMarca] = React.useState('todas');
  const [leadsLib, setLeadsLib] = React.useState([]);
  const leadsIndice = React.useMemo(() => {
    const rows = (leadsLib || []).filter(l => leadsMarca === 'todas' || listaMarca(l)?.slug === leadsMarca);
    const campanha = rows.filter(l => l.tipo === 'campanha' && !l.organica
      && !(String(l.canal || '').toUpperCase() === 'FORM' && !(parseInt(l.n_items || 0, 10) > 0)));
    const organicas = rows.filter(l => l.organica);
    const inbound = campanha.reduce((s, l) => s + (parseInt(l.n_inbound || 0, 10) || 0), 0);
    const outbound = campanha.reduce((s, l) => s + (parseInt(l.n_outbound || 0, 10) || 0), 0);
    const nOrg = organicas.reduce((s, l) => s + (parseInt(l.n_organicas || l.n_items || 0, 10) || 0), 0);
    const total = inbound + outbound + nOrg;
    return { inbound, outbound, organicas: nOrg, total };
  }, [leadsLib, leadsMarca]);
  React.useEffect(() => {
    if (surface === 'audiencias') setTab('audiencias');
  }, [surface]);
  // CRM é transversal — sem filtro por marca (dados vêm todos do Gestor).
  const [meta, setMeta] = React.useState(null);
  window.useMktZoneLoading(meta == null);
  const [stats, setStats] = React.useState(null);
  const [syncStatus, setSyncStatus] = React.useState(null);
  const [syncing, setSyncing] = React.useState(false);
  const [syncPhases, setSyncPhases] = React.useState(() => makeInitialPhases());
  const [syncDoneEv, setSyncDoneEv] = React.useState(null);   // {entidades, contactos, oportunidades}
  const [syncErroEv, setSyncErroEv] = React.useState(null);   // {msg, aborted?}
  const [syncStartedAt, setSyncStartedAt] = React.useState(null);
  const [syncDone, setSyncDone] = React.useState(false);      // badge "✓ Sincronizado" no botão
  const syncAborter = React.useRef(null);
  const [viewingEntity, setViewingEntity]   = React.useState(null);
  const [viewingTenant, setViewingTenant]   = React.useState(null);
  const [viewingContact, setViewingContact] = React.useState(null);
  const [viewingOp, setViewingOp]           = React.useState(null);
  // QuickPreview drawer state — { type: 'entidade'|'contacto'|'oportunidade', fmId }
  const [preview, setPreview] = React.useState(null);
  // Modal AudienceBuilder — self-contained com todos os filtros
  const [audienceModal, setAudienceModal] = React.useState(null);  // null | { initialDef, audienceId? }
  // Refresh ping para TabAudiencias após criar
  const [audRefresh, setAudRefresh] = React.useState(0);
  const [audLib, setAudLib] = React.useState([]);
  const [audMarca, setAudMarca] = React.useState('todas');
  // Contexto de campanha (Workflow — vem de Campanhas com ?campanha=xxx&intent=segmentar)
  const [campaignContext, setCampaignContext] = React.useState(null);  // { id, nome, marca, canal, intent }

  // Ler hash para saber se estamos a ver perfil (entidade OU contacto OU op)
  // + contexto de campanha (vem de Campanhas com ?campanha=xxx&intent=segmentar)
  React.useEffect(() => {
    const readHash = () => {
      const h = window.location.hash;
      const e = h.match(/[?&]entidade=([^&]+)/);
      const c = h.match(/[?&]contacto=([^&]+)/);
      const o = h.match(/[?&]op=([^&]+)/);
      const t = h.match(/[?&]tenant=([^&]+)/);
      const camp   = h.match(/[?&]campanha=([^&]+)/);
      const intent = h.match(/[?&]intent=([^&]+)/);
      const canal  = h.match(/[?&]canal=([^&]+)/);
      setViewingEntity(e ? decodeURIComponent(e[1]) : null);
      setViewingTenant(e && t ? decodeURIComponent(t[1]) : null);
      setViewingContact(c ? decodeURIComponent(c[1]) : null);
      setViewingOp(o ? decodeURIComponent(o[1]) : null);
      if (camp) {
        const campId = decodeURIComponent(camp[1]);
        setCampaignContext(prev => {
          if (prev?.id === campId) return prev;
          return { id: campId, intent: intent ? decodeURIComponent(intent[1]) : null, canal: canal ? decodeURIComponent(canal[1]) : null, nome: null, marca: null };
        });
      } else {
        setCampaignContext(null);
      }
    };
    readHash();
    window.addEventListener('hashchange', readHash);
    return () => window.removeEventListener('hashchange', readHash);
  }, []);

  // Buscar dados da campanha quando o contexto muda + auto-abrir modal
  React.useEffect(() => {
    if (!campaignContext?.id || campaignContext.nome) return;
    fetch(`/api/marketing/campanhas/${campaignContext.id}`)
      .then(r => r.json())
      .then(camp => {
        setCampaignContext(prev => prev ? {
          ...prev,
          nome: camp.titulo || camp.commercial_name || camp.nome || 'Campanha',
          marca: camp.brand_name || camp.brand_slug || null,
          approved: !!camp.estrategia_approved_at,
          channels: camp.channels || [],
        } : prev);
        // Se intent=segmentar, abre modal automaticamente
        if (campaignContext.intent === 'segmentar' && !audienceModal) {
          const initialDef = {};
          if (camp.brand_slug) {
            // Mapear brand slug para equipa_name mais comum
            const brandToEquipa = { mimaki: 'MIMAKI PT', decal: 'DECAL PT', biond: null };
            const eq = brandToEquipa[camp.brand_slug];
            if (eq) initialDef.equipa = [eq];
          }
          // Canal WA → sugere consent_wa opt_in
          if (campaignContext.canal === 'wa' || camp.channels?.includes('whatsapp')) {
            initialDef.consent_wa = 'sem_opt_out';
          } else if (campaignContext.canal === 'email' || camp.channels?.includes('email')) {
            initialDef.consent_email = 'sem_opt_out';
          }
          setAudienceModal({ initialDef, campaignContext: { id: campaignContext.id, nome: camp.titulo || camp.commercial_name, canal: campaignContext.canal } });
        }
      })
      .catch(() => {});
  }, [campaignContext, audienceModal]);

  // Helper genérico — abre profile e limpa os outros (os 3 são mutuamente exclusivos)
  const setHashParam = (key, value, tenant) => {
    let h = window.location.hash;
    h = h.replace(/[?&]entidade=[^&]*/, '').replace(/[?&]contacto=[^&]*/, '').replace(/[?&]op=[^&]*/, '').replace(/[?&]tenant=[^&]*/, '');
    if (value) {
      h = h + (h.includes('?') ? '&' : (h ? '?' : '#')) + key + '=' + encodeURIComponent(value);
      if (key === 'entidade' && tenant) h += '&tenant=' + encodeURIComponent(tenant);
    }
    window.location.hash = h;
  };
  const openProfile  = (fmId, tenant) => setHashParam('entidade', fmId, tenant);
  const openContacto = (fmId) => setHashParam('contacto', fmId);
  const openOp       = (fmId) => setHashParam('op',       fmId);
  const closeProfile = () => setHashParam(null, null);
  const openPreview  = (type, fmId) => setPreview({ type, fmId });
  const closePreview = () => setPreview(null);
  const previewOpenFull = () => {
    if (!preview) return;
    const { type, fmId } = preview;
    setPreview(null);
    if (type === 'entidade')     openProfile(fmId);
    else if (type === 'contacto') openContacto(fmId);
    else if (type === 'oportunidade') openOp(fmId);
  };
  const previewOpenRelated = (type, fmId) => setPreview({ type, fmId });

  // Hero state + sync logs
  const [heroState, setHeroState] = React.useState(null);
  const [syncLogs,  setSyncLogs]  = React.useState(null);
  const loadHeroState = React.useCallback(() => {
    CRMAPI.heroState().then(setHeroState).catch(() => {});
    CRMAPI.syncLogs().then(setSyncLogs).catch(() => {});
  }, []);

  // Carregar meta + stats + hero ao montar
  React.useEffect(() => {
    CRMAPI.meta().then(setMeta).catch(() => setMeta({ stages: [], equipas: [], paises: [], contagens: {} }));
    CRMAPI.stats().then(setStats).catch(() => {});
    CRMAPI.syncStatus().then(setSyncStatus).catch(() => {});
    loadHeroState();
    const t = setInterval(loadHeroState, 60_000);
    return () => clearInterval(t);
  }, [loadHeroState]);

  // ── Sync SSE ──────────────────────────────────────────────────────────────
  // Estado por fase persistente (não depende de log truncado) — evita bug 75% stuck
  const handleSync = React.useCallback(async () => {
    // Reset estado
    setSyncing(true);
    setSyncPhases(makeInitialPhases());
    setSyncDoneEv(null);
    setSyncErroEv(null);
    setSyncDone(false);
    setSyncStartedAt(Date.now());

    const aborter = new AbortController();
    syncAborter.current = aborter;

    try {
      const resp = await CRMAPI.sync(aborter.signal);
      const reader = resp.body.getReader();
      const dec = new TextDecoder();
      let buf = '';
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        buf += dec.decode(value, { stream: true });
        const lines = buf.split('\n');
        buf = lines.pop();
        for (const line of lines) {
          if (!line.startsWith('data:')) continue;
          try {
            const ev = JSON.parse(line.slice(5).trim());
            if (ev.fase === 'done') {
              setSyncPhases(p => reducePhases(p, ev));
              setSyncDoneEv(ev);
              setSyncDone(true);
              CRMAPI.meta().then(setMeta).catch(() => {});
              CRMAPI.stats().then(setStats).catch(() => {});
              CRMAPI.syncStatus().then(setSyncStatus).catch(() => {});
              loadHeroState();
              setTimeout(() => setSyncDone(false), 3000);
            } else if (ev.fase === 'erro') {
              setSyncErroEv({ msg: ev.msg || 'Erro desconhecido' });
            } else {
              setSyncPhases(p => reducePhases(p, ev));
            }
          } catch (_) {}
        }
      }
    } catch (e) {
      if (e.name === 'AbortError') {
        setSyncErroEv({ msg: 'Sincronização cancelada pelo utilizador.', aborted: true });
      } else {
        setSyncErroEv({ msg: e.message || 'Falha na ligação' });
      }
    } finally {
      syncAborter.current = null;
      setSyncing(false);
    }
  }, [loadHeroState]);

  const handleCancelSync = React.useCallback(() => {
    if (syncAborter.current) syncAborter.current.abort();
  }, []);

  const handleCloseSyncModal = React.useCallback(() => {
    setSyncDoneEv(null);
    setSyncErroEv(null);
    setSyncPhases(makeInitialPhases());
  }, []);

  const lastSync = syncLogs?.last_success?.finished_at
    || syncStatus?.logs?.find(l => l.layout === 'FMRS_API_OPORTUNIDADES')?.finished_at;
  const lastSyncAgo = React.useMemo(() => {
    if (!lastSync) return null;
    const h = Math.floor((Date.now() - new Date(lastSync).getTime()) / 3600000);
    const d = Math.floor(h / 24);
    return h < 1 ? 'agora' : h < 24 ? `há ${h}h` : `há ${d}d`;
  }, [lastSync]);
  const lastSyncLabel = lastSync
    ? new Date(lastSync).toLocaleString('pt-PT', { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' })
    : 'Nunca sincronizado';
  const lastSyncOk = syncLogs?.last_success != null;

  const sectionLabel = { fontSize: 10, fontFamily: 'var(--font-mono)', letterSpacing: '0.08em', color: 'var(--text-dim)', marginBottom: 8 };

  // Sub-tab funcional (pill compacta, padrão Farben)
  // Brand tabs vêm do wrapper (MktBrandPills global)
  const subTabBtn = (id, label) => (
    <button
      key={id}
      onClick={() => setTab(id)}
      style={{
        padding: '5px 12px', margin: 0, borderRadius: 6, border: '1px solid var(--border)', cursor: 'pointer',
        background: tab === id ? 'var(--ai-500, #3859D0)' : 'var(--bg-elev, #ffffff)',
        color: tab === id ? '#fff' : 'var(--text-muted)',
        fontSize: 12, fontWeight: 600, fontFamily: 'var(--font-display)', transition: 'all 0.15s',
      }}
    >{label}</button>
  );

  // Contadores para hero copy e KPI
  const totEnt = stats?.totais?.entidades || 0;
  const totCt  = stats?.totais?.contactos || 0;
  const totOps = stats?.totais?.ops_activas || 0;
  const pctTel = stats?.pct_com_telefone || 0;
  // Hero copy dinâmico determinístico (Fase A · FA5)
  // Ordena regras por prioridade descendente, escolhe até 2 frases relevantes
  const heroCopy = React.useMemo(() => {
    // Estado ainda a carregar
    if (!stats) return { titulo: `${saudacao}${firstName ? `, ${firstName}` : ''}.`, sub: 'A carregar dados do CRM...' };

    // CRM vazio — mensagem forte de onboarding
    if (totEnt === 0) {
      return {
        titulo: `${saudacao}${firstName ? `, ${firstName}` : ''}. CRM ainda vazio.`,
        sub: 'Clica em Sincronizar Gestor para carregar 24k entidades, 26k contactos e 22k oportunidades.',
      };
    }

    const hs = heroState || {};

    // Hero contextual por tab
    const syncInfo = lastSyncAgo ? `sync ${lastSyncAgo}` : 'nunca sincronizado';
    const pctOptIn = hs.pct_opt_in_email != null ? `${hs.pct_opt_in_email}% opt-in email` : null;

    if (tab === 'contactos') {
      return {
        titulo: `${saudacao}${firstName ? `, ${firstName}` : ''}. ${totCt.toLocaleString('pt-PT')} contactos no CRM.`,
        sub: [`${pctTel}% com telefone`, pctOptIn, `${syncInfo}`].filter(Boolean).join(' · '),
      };
    }
    if (tab === 'audiencias') {
      const na = hs.audiencias_activas || 0;
      return {
        titulo: `${saudacao}${firstName ? `, ${firstName}` : ''}. ${na} audiência${na !== 1 ? 's' : ''} guardada${na !== 1 ? 's' : ''}.`,
        sub: na > 0
          ? `Usa Audiências para segmentar e enviar campanhas WA ou email.`
          : `Cria a tua primeira audiência com filtros do Gestor.`,
      };
    }
    if (tab === 'listas') {
      return {
        titulo: `${saudacao}${firstName ? `, ${firstName}` : ''}. Leads das campanhas.`,
        sub: 'Listas de leads capturados em campanhas activas — qualificação manual e CRM.',
      };
    }
    if (tab === 'wa-templates') {
      return {
        titulo: `${saudacao}${firstName ? `, ${firstName}` : ''}. Templates WhatsApp.`,
        sub: 'Mensagens aprovadas pela Meta para envio em campanhas WA.',
      };
    }
    if (tab === 'gestao') {
      const lastS = syncLogs?.last_success;
      const sessCount = syncLogs?.sessions?.length || 0;
      return {
        titulo: `${saudacao}${firstName ? `, ${firstName}` : ''}. Definições do CRM.`,
        sub: lastS
          ? `Última sync com sucesso: ${new Date(lastS.finished_at).toLocaleString('pt-PT', { day:'2-digit', month:'short', hour:'2-digit', minute:'2-digit' })} · ${sessCount} sessões registadas`
          : 'Sem sync registada ainda.',
      };
    }

    // BD Gestor / Entidades — lógica existente
    const partes = [];

    // Regra 1: sync desactualizada > 48h
    if (hs.horas_desde_sync != null && hs.horas_desde_sync > 48) {
      partes.push({ prio: 100, texto: `Sync desactualizada há ${hs.horas_desde_sync}h — considera sincronizar` });
    }

    // Regra 2: muitos externos pendentes
    if (hs.externos_pendentes >= 10) {
      partes.push({ prio: 90, texto: `${hs.externos_pendentes} contactos externos aguardam matching` });
    } else if (hs.externos_pendentes > 0) {
      partes.push({ prio: 40, texto: `${hs.externos_pendentes} contacto${hs.externos_pendentes > 1 ? 's' : ''} externo${hs.externos_pendentes > 1 ? 's' : ''} por resolver` });
    }

    // Regra 3: audiências activas
    if (hs.audiencias_activas > 0) {
      partes.push({ prio: 70, texto: `${hs.audiencias_activas} audiência${hs.audiencias_activas > 1 ? 's' : ''} guardada${hs.audiencias_activas > 1 ? 's' : ''}` });
    }

    // Regra 4: campanhas WA activas
    if (hs.campanhas_wa_30d > 0) {
      partes.push({ prio: 60, texto: `${hs.campanhas_wa_30d} campanha${hs.campanhas_wa_30d > 1 ? 's' : ''} WA nos últimos 30d` });
    }

    // Regra 5: contactados na última semana
    if (hs.contactados_7d > 0) {
      partes.push({ prio: 50, texto: `${hs.contactados_7d.toLocaleString('pt-PT')} interacções WA registadas esta semana` });
    }

    // Regra 6: segmentação incompleta (>80% entidades sem segmento)
    if (hs.entidades_totais > 100 && hs.segmentos_atribuidos < hs.entidades_totais * 0.2) {
      partes.push({ prio: 35, texto: `só ${hs.segmentos_atribuidos} entidades com segmento atribuído — usa Gestão para categorizar` });
    }

    // Título principal: entidades + sync info
    const syncLabel = hs.horas_desde_sync != null
      ? (hs.horas_desde_sync < 1 ? 'agora' : hs.horas_desde_sync < 24 ? `há ${hs.horas_desde_sync}h` : `há ${Math.floor(hs.horas_desde_sync/24)}d`)
      : 'nunca';

    // Escolher a frase de maior prioridade para o título
    partes.sort((a, b) => b.prio - a.prio);
    const topFrase = partes[0]?.texto;
    const secondFrase = partes[1]?.texto;

    if (topFrase) {
      // Frase forte no título
      return {
        titulo: `${saudacao}${firstName ? `, ${firstName}` : ''}. ${topFrase[0].toUpperCase() + topFrase.slice(1)}.`,
        sub: [
          `${totEnt.toLocaleString('pt-PT')} entidades · ${totCt.toLocaleString('pt-PT')} contactos · ${pctTel}% com telefone · sync ${syncLabel}`,
          secondFrase ? `Também: ${secondFrase}.` : null,
        ].filter(Boolean).join(' '),
      };
    }

    // Nenhuma regra activa — fallback com KPIs
    return {
      titulo: `${saudacao}${firstName ? `, ${firstName}` : ''}. ${totEnt.toLocaleString('pt-PT')} entidades no CRM.`,
      sub: `${totCt.toLocaleString('pt-PT')} contactos · ${totOps.toLocaleString('pt-PT')} OPs activas · ${pctTel}% com telefone · sync ${syncLabel}.`,
    };
  }, [stats, firstName, saudacao, lastSync, lastSyncAgo, tab, heroState, syncLogs, totEnt, totCt, totOps, pctTel]);

  // Se estamos a ver perfil, substitui tudo
  if (viewingOp) {
    return <OportunidadeProfile fmId={viewingOp} onBack={closeProfile} onOpenEntity={openProfile} onOpenContacto={openContacto} userEmail={window.currentUser?.email} userName={firstName} />;
  }
  if (viewingContact) {
    return <ContactoProfile fmId={viewingContact} onBack={closeProfile} onOpenEntity={openProfile} onOpenOp={openOp} userEmail={window.currentUser?.email} userName={firstName} />;
  }
  if (viewingEntity) {
    return <EntidadeProfile fmId={viewingEntity} tenant={viewingTenant} onBack={closeProfile} onOpenContacto={openContacto} onOpenOp={openOp} userEmail={window.currentUser?.email} userName={firstName} />;
  }

  return (
    <div data-theme={surface === 'audiencias' || surface === 'leads' ? 'light' : undefined} style={{
      height: '100%', display: 'flex', flexDirection: 'column', overflow: 'hidden',
      ...(surface === 'audiencias' || surface === 'leads' ? {
        background: '#f8fafc',
        '--bg': '#f8fafc', '--bg-elev': '#ffffff', '--bg-hover': '#f1f5f9', '--bg-sunken': '#e8edf5',
        '--border': '#dde3ef', '--text': '#112954', '--text-muted': '#4a5e7a', '--text-dim': '#6b7fa3', '--ai-500': '#3859D0',
      } : {}),
    }}>

      {/* ─── HEADER FIXO (padrão Briefings) ─────────────────────────────── */}
      <div style={{ background: 'var(--bg-elev)', borderBottom: '1px solid var(--border)', padding: '20px 40px 0', flexShrink: 0 }}>

        {/* Banner contexto de campanha (Workflow — Briefings→Campanhas→CRM→Activação) */}
        {campaignContext && (
          <div style={{
            marginBottom: 12, padding: '10px 14px', borderRadius: 8,
            background: 'linear-gradient(135deg, color-mix(in oklch, var(--ai-500) 8%, var(--bg-sunken)), var(--bg-sunken))',
            border: '1px solid var(--ai-500)',
            display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap',
          }}>
            <span style={{ fontSize: 9, fontFamily: 'var(--font-mono)', color: 'var(--ai-500)', letterSpacing: '0.08em', textTransform: 'uppercase', fontWeight: 700 }}>
              Segmentar para campanha
            </span>
            <span style={{ fontSize: 13, fontWeight: 600, color: 'var(--text)' }}>
              {campaignContext.nome || 'A carregar...'}
            </span>
            {campaignContext.marca && (
              <span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', padding: '2px 8px', borderRadius: 3, background: 'var(--ai-500)', color: '#fff', textTransform: 'uppercase' }}>
                {campaignContext.marca}
              </span>
            )}
            {campaignContext.canal && (
              <span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', padding: '2px 8px', borderRadius: 3, background: 'var(--bg-elev)', color: 'var(--text-muted)', border: '1px solid var(--border)', textTransform: 'uppercase' }}>
                canal {campaignContext.canal}
              </span>
            )}
            <div style={{ marginLeft: 'auto', display: 'flex', gap: 8 }}>
              {!audienceModal && (
                <button onClick={() => setAudienceModal({ initialDef: {}, campaignContext: { id: campaignContext.id, nome: campaignContext.nome, canal: campaignContext.canal } })}
                  className="btn-ai" style={{ fontSize: 11, padding: '5px 12px' }}>
                  ✂ Segmentar audiência
                </button>
              )}
              <button onClick={() => { window.location.hash = `#screen=marketing&sub=campanhas&id=${campaignContext.id}`; }}
                style={{ background: 'none', border: '1px solid var(--border)', borderRadius: 6, padding: '5px 12px', fontSize: 11, color: 'var(--text-muted)', cursor: 'pointer' }}>
                ← Voltar à campanha
              </button>
            </div>
          </div>
        )}

        {/* Breadcrumb */}
        <div style={{ fontSize: 11, fontWeight: 600, letterSpacing: '0.07em', color: 'var(--text-muted)', textTransform: 'uppercase', marginBottom: 6, fontFamily: 'var(--font-mono)' }}>
          {surface === 'audiencias' ? 'MARKETING · AUDIÊNCIAS' : surface === 'leads' ? 'MARKETING · LEADS' : 'MARKETING · CRM'}
        </div>

        {/* Título. Audiências segue o header de Prospeção: H1 sem subtítulo. */}
        {surface === 'audiencias' ? (
          <h1 style={{ margin: 0, fontSize: 21, fontWeight: 700, color: 'var(--text)', fontFamily: 'var(--font-display)', letterSpacing: '-0.01em', lineHeight: 1.2 }}>Audiências</h1>
        ) : (
        <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16 }}>
          <div>
            <h1 style={{ margin: 0, fontSize: 21, fontWeight: 700, color: 'var(--text)', fontFamily: 'var(--font-display)', letterSpacing: '-0.01em', lineHeight: 1.2 }}>
              {surface === 'leads' ? 'Leads Campanhas' : 'CRM'}
            </h1>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 6, flexWrap: 'wrap' }}>
              {surface === 'leads' ? (
                <>
                  <span style={{ fontSize: 11, color: 'var(--text-muted)', fontFamily: 'var(--font-body)' }}>
                    {leadsIndice.total.toLocaleString('pt-PT')} lead{leadsIndice.total !== 1 ? 's' : ''} · {leadsMarca === 'todas' ? 'todas as marcas' : (AUD_BRANDS.find(b => b.slug === leadsMarca)?.name || leadsMarca)}
                  </span>
                  {leadsIndice.total > 0 && (
                    <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                      <div style={{ width: 60, height: 4, borderRadius: 99, background: 'var(--border)', overflow: 'hidden' }}>
                        <div style={{ width: `${Math.round(leadsIndice.inbound / leadsIndice.total * 100)}%`, height: '100%', borderRadius: 99, background: 'var(--ai-500, #3859D0)', transition: 'width 0.4s ease' }} />
                      </div>
                      <span style={{ fontSize: 11, color: 'var(--text-muted)', fontFamily: 'var(--font-body)' }}>{Math.round(leadsIndice.inbound / leadsIndice.total * 100)}% inbound</span>
                    </div>
                  )}
                </>
              ) : (
                <>
                  <span style={{ fontSize: 11, color: 'var(--text-muted)', fontFamily: 'var(--font-body)' }}>
                    {totEnt > 0
                      ? `${totEnt.toLocaleString('pt-PT')} entidade${totEnt !== 1 ? 's' : ''} · ${totCt.toLocaleString('pt-PT')} contactos`
                      : 'Sem dados sincronizados'}
                  </span>
                  {stats && totEnt > 0 && (
                    <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                      <div style={{ width: 60, height: 4, borderRadius: 99, background: 'var(--border)', overflow: 'hidden' }}>
                        <div style={{ width: `${pctTel}%`, height: '100%', borderRadius: 99, background: 'var(--ai-500, #3859D0)', transition: 'width 0.4s ease' }} />
                      </div>
                      <span style={{ fontSize: 11, color: 'var(--text-muted)', fontFamily: 'var(--font-body)' }}>{pctTel}% com telefone</span>
                    </div>
                  )}
                </>
              )}
            </div>
          </div>
          <div style={{ display: 'flex', gap: 8, alignItems: 'center', flexShrink: 0, marginTop: 2 }}>
            {/* CTA contextual — 1 acção principal por tab */}
            {(surface === 'crm' && (tab === 'entidades' || tab === 'contactos')) && (
              <button className="btn btn-ai"
                style={{ height: 32, padding: '0 14px', fontSize: 12, display: 'flex', alignItems: 'center', gap: 6,
                  ...(syncDone ? { background: '#15803d', borderColor: '#15803d' } : {}) }}
                onClick={handleSync} disabled={syncing}
                title={`Última sync: ${lastSyncLabel}`}>
                <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0, ...(syncing ? { animation: 'spin 1s linear infinite' } : {}) }}><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>
                <span>
                  {syncing ? 'A sincronizar...' : syncDone ? '✓ Sincronizado' : 'Sincronizar Gestor'}
                  {!syncing && !syncDone && lastSyncAgo && (
                    <span style={{ fontSize: 10, opacity: .75, marginLeft: 5, fontFamily: 'var(--font-mono)' }}>· {lastSyncAgo}</span>
                  )}
                </span>
              </button>
            )}
            {surface === 'crm' && tab === 'wa-templates' && (
              <button className="btn btn-ai"
                style={{ height: 30, padding: '0 14px', fontSize: 12 }}
                onClick={() => {/* TODO: abrir modal novo template */}}
                title="Criar novo template WhatsApp">+ Novo Template WA</button>
            )}
          </div>
        </div>
        )}

        {/* Sync modal (proper) — aparece quando activo */}
        {(syncing || syncDoneEv || syncErroEv) && (
          <SyncModal
            phases={syncPhases}
            syncing={syncing}
            syncStartedAt={syncStartedAt}
            isDone={!!syncDoneEv}
            erroEv={syncErroEv}
            syncStatus={syncStatus}
            onClose={handleCloseSyncModal}
            onCancel={handleCancelSync}
            onRetry={handleSync}
          />
        )}

        {/* Separador com margem negativa (padrão Briefings) */}
        {(surface === 'crm' || surface === 'audiencias' || surface === 'leads') && <div style={{ height: 1, background: 'var(--border)', margin: '16px -40px 0' }} />}
        {surface === 'audiencias' && (
          <div style={{ display: 'flex', gap: 0, overflowX: 'auto' }}>
            {[{ slug: 'todas', name: 'Todas' }, ...AUD_BRAND_TABS].map(b => {
              const active = audMarca === b.slug;
              return (
                <button key={b.slug} onClick={() => setAudMarca(b.slug)}
                  style={{
                    background: 'none', border: 'none', outline: 'none',
                    borderBottom: `2px solid ${active ? 'var(--ai-500)' : 'transparent'}`,
                    color: active ? 'var(--text)' : 'var(--text-muted)',
                    fontSize: 13, fontWeight: active ? 600 : 500,
                    fontFamily: 'var(--font-display)', letterSpacing: '.01em',
                    padding: '10px 18px 12px', cursor: 'pointer', whiteSpace: 'nowrap',
                    transition: 'color .15s, border-color .15s',
                  }}>{b.name}</button>
              );
            })}
          </div>
        )}

        {/* Sub-tabs: BD Gestor / Audiências / Leads Campanhas / Templates WA / Definições
            BD Gestor tem toggle interno Entidades | Contactos.
            Audiências e Leads Campanhas são menus próprios — esta barra só fica no CRM completo. */}
        {surface === 'crm' && <div style={{ display: 'flex', gap: 0, overflowX: 'auto', alignItems: 'center' }}>
          {[
            { primary: 'consultar',    label: 'BD Gestor',        match: t => t === 'entidades' || t === 'contactos', default: 'entidades' },
            { primary: 'audiencias',   label: 'Audiências',       match: t => t === 'audiencias',    default: 'audiencias' },
            { primary: 'listas',       label: 'Leads Campanhas',  match: t => t === 'listas',        default: 'listas' },
            { primary: 'wa-templates', label: 'Templates WA',     match: t => t === 'wa-templates',  default: 'wa-templates' },
            { primary: 'gestao',       label: 'Definições',       match: t => t === 'gestao',        default: 'gestao' },
          ].map(prim => {
            const active = prim.match(tab);
            return (
              <button key={prim.primary} onClick={() => setTab(prim.default)}
                style={{
                  background: 'none', border: 'none', outline: 'none',
                  borderBottom: `2px solid ${active ? 'var(--ai-500)' : 'transparent'}`,
                  color: active ? 'var(--text)' : 'var(--text-muted)',
                  fontSize: 13, fontWeight: active ? 600 : 500,
                  fontFamily: 'var(--font-display)', letterSpacing: '.01em',
                  padding: '10px 18px 12px', cursor: 'pointer', whiteSpace: 'nowrap',
                  transition: 'color .15s, border-color .15s',
                }}>
                {prim.label}
              </button>
            );
          })}

          {/* Toggle Entidades|Contactos — só quando BD Gestor está activo */}
          {(tab === 'entidades' || tab === 'contactos') && (
            <div style={{ marginLeft: 'auto', display: 'flex', gap: 4, alignItems: 'center', padding: '0 4px' }}>
              <span style={{ fontSize: 9, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '0.06em', textTransform: 'uppercase', marginRight: 6 }}>ver:</span>
              {[
                { id: 'entidades', label: 'Entidades',  count: totEnt },
                { id: 'contactos', label: 'Contactos',  count: totCt },
              ].map(v => {
                const isActive = tab === v.id;
                return (
                  <button key={v.id} onClick={() => setTab(v.id)}
                    style={{
                      background: isActive ? 'var(--ai-500)' : 'var(--bg-sunken)',
                      color: isActive ? '#fff' : 'var(--text-muted)',
                      border: isActive ? 'none' : '1px solid var(--border)',
                      borderRadius: 6, padding: '5px 10px', fontSize: 11, fontWeight: 600,
                      cursor: 'pointer', fontFamily: 'var(--font-display)',
                      display: 'flex', alignItems: 'center', gap: 5,
                    }}>
                    {v.label}
                    <span style={{ fontSize: 9, fontFamily: 'var(--font-mono)', opacity: 0.7 }}>{v.count.toLocaleString('pt-PT')}</span>
                  </button>
                );
              })}
            </div>
          )}
        </div>}
        {surface === 'leads' && (
          <div style={{ display: 'flex', gap: 0, overflowX: 'auto' }}>
            {[{ slug: 'todas', name: 'Todas' }, ...AUD_BRAND_TABS].map(b => {
              const active = leadsMarca === b.slug;
              return (
                <button key={b.slug} onClick={() => setLeadsMarca(b.slug)}
                  style={{
                    background: 'none', border: 'none', outline: 'none',
                    borderBottom: `2px solid ${active ? 'var(--ai-500)' : 'transparent'}`,
                    color: active ? 'var(--text)' : 'var(--text-muted)',
                    fontSize: 13, fontWeight: active ? 600 : 500,
                    fontFamily: 'var(--font-display)', letterSpacing: '.01em',
                    padding: '10px 18px 12px', cursor: 'pointer', whiteSpace: 'nowrap',
                    transition: 'color .15s, border-color .15s',
                  }}>{b.name}</button>
              );
            })}
          </div>
        )}
      </div>

      {/* ─── SCROLLABLE BODY ────────────────────────────────────────────── */}
      <div className="scrollbar" style={{ flex: 1, minHeight: 0, overflowY: 'auto', overflowX: 'clip', padding: '28px 40px 0', background: 'var(--bg, #f8fafc)', display: 'flex', flexDirection: 'column', gap: 18 }}>

        {/* Tab content */}
        {surface === 'leads' && (
          <TabListas onLibrary={setLeadsLib} marca={leadsMarca} direccao={leadsDireccao} onDireccao={setLeadsDireccao} />
        )}
        {surface !== 'leads' && tab === 'entidades' && <TabEntidades meta={meta} stats={stats} firstName={firstName} onOpenProfile={openProfile} onOpenPreview={openPreview} />}
        {surface !== 'leads' && tab === 'contactos' && <TabContactos meta={meta} stats={stats} firstName={firstName} onOpenProfile={openProfile} onOpenContacto={openContacto} onOpenPreview={openPreview} onCreateAudience={(preFill) => setAudienceModal({ initialDef: preFill || {} })} />}
        {surface !== 'leads' && tab === 'audiencias' && <TabAudiencias meta={meta} firstName={firstName} refreshPing={audRefresh}
          marcaFiltro={audMarca} onMarca={setAudMarca}
          onLibrary={setAudLib}
          onCreateNew={() => setAudienceModal({ initialDef: {} })}
          onEdit={(aud) => setAudienceModal({
            audienceId: aud.id,
            initialDef: typeof aud.definicao === 'string' ? JSON.parse(aud.definicao) : (aud.definicao || {}),
            initialName: aud.nome || '',
            initialDescricao: aud.descricao || '',
          })}
          onDuplicate={(aud) => setAudienceModal({
            initialDef: typeof aud.definicao === 'string' ? JSON.parse(aud.definicao) : (aud.definicao || {}),
            initialName: `${aud.nome} (cópia)`,
            initialDescricao: aud.descricao || '',
          })} />}
        {surface !== 'leads' && tab === 'listas' && <TabListas />}
        {surface !== 'leads' && tab === 'wa-templates' && <TabWaTemplates />}
        {surface !== 'leads' && tab === 'gestao' && <TabGestao />}

        <div style={{ height: 40 }} />
      </div>

      {/* ─── FOOTER STATS (padrão Briefings) ─────────────────────────────── */}
      {surface !== 'audiencias' && surface !== 'leads' && (
      <div style={{
        padding: '10px 40px', flexShrink: 0, borderTop: '1px solid var(--border)', background: 'var(--bg-elev)',
        fontSize: 11, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)',
        display: 'flex', gap: 20, flexWrap: 'wrap',
      }}>
        <span><strong style={{ color: 'var(--text-muted)' }}>DIGI CRM</strong></span>
        <span>Última sync: <strong style={{ color: 'var(--text-muted)' }}>{lastSync ? humanTimeAgo(lastSync) : 'nunca'}</strong></span>
        <span>Entidades: <strong style={{ color: 'var(--text-muted)' }}>{totEnt.toLocaleString('pt-PT')}</strong></span>
        <span>Contactos: <strong style={{ color: 'var(--text-muted)' }}>{totCt.toLocaleString('pt-PT')}</strong></span>
        <span>OPs activas: <strong style={{ color: 'var(--text-muted)' }}>{totOps.toLocaleString('pt-PT')}</strong></span>
      </div>
      )}

      {/* QuickPreview drawer (PA6/PA9) — HubSpot/Apollo peek */}
      {preview && (
        <QuickPreview
          type={preview.type}
          fmId={preview.fmId}
          onClose={closePreview}
          onOpenFull={previewOpenFull}
          onOpenRelated={previewOpenRelated}
        />
      )}

      {/* AudienceBuilderModal — criar audiência com todos os filtros (fix bug crítico) */}
      {audienceModal && (
        <AudienceBuilderModal
          meta={meta}
          initialDef={audienceModal.initialDef}
          initialName={audienceModal.initialName}
          campaignContext={audienceModal.campaignContext}
          audienceId={audienceModal.audienceId}
          initialDescricao={audienceModal.initialDescricao}
          userEmail={window.currentUser?.email}
          onSaved={(aud) => {
            setAudRefresh(v => v + 1);
            if (!aud) return;
            const slugs = audienceMarcas(aud).map(b => b.slug);
            setAudMarca(atual => (atual === 'todas' || slugs.includes(atual) ? atual : (slugs[0] || 'todas')));
          }}
          onClose={() => setAudienceModal(null)}
        />
      )}

      {/* Digi AI Hub — widget flutuante bottom-right (Sprint 1 mock) */}
      <MktDigiCrmWidget
        tab={tab}
        openProfile={null}
        campaignContext={campaignContext}
        userName={firstName}
      />
    </div>
  );
};

// ── Dropdown multi-select CRM (nome único para evitar conflito scope Babel) ─
function CRMDropdown({ label, options, selected, onChange }) {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);

  React.useEffect(() => {
    const fn = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', fn);
    return () => document.removeEventListener('mousedown', fn);
  }, []);

  const toggle = (v) => {
    const s = new Set(selected);
    s.has(v) ? s.delete(v) : s.add(v);
    onChange([...s]);
  };

  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <button
        onClick={() => setOpen(o => !o)}
        style={{
          display: 'flex', alignItems: 'center', gap: 6,
          padding: '5px 10px', borderRadius: 6, border: '1px solid var(--border)',
          background: selected.length > 0 ? 'color-mix(in oklch, var(--ai-500) 10%, transparent)' : 'var(--bg-card)',
          color: selected.length > 0 ? 'var(--ai-500)' : 'var(--text-muted)',
          fontSize: 12, cursor: 'pointer', whiteSpace: 'nowrap',
        }}
      >
        {label}{selected.length > 0 ? ` (${selected.length})` : ''}
        <span style={{ fontSize: 8, opacity: 0.6 }}>▼</span>
      </button>
      {open && (
        <div style={{
          position: 'absolute', top: '100%', left: 0, marginTop: 4, zIndex: 99,
          background: 'var(--bg-card)', border: '1px solid var(--border)', borderRadius: 8,
          boxShadow: '0 8px 24px rgba(0,0,0,0.15)', minWidth: 180, maxHeight: 260, overflowY: 'auto',
        }}>
          {options.map(opt => (
            <div
              key={opt}
              onClick={() => toggle(opt)}
              style={{
                display: 'flex', alignItems: 'center', gap: 8, padding: '7px 12px', cursor: 'pointer',
                background: selected.includes(opt) ? 'color-mix(in oklch, var(--ai-500) 8%, transparent)' : 'transparent',
                fontSize: 12, color: 'var(--text)',
              }}
            >
              <div style={{
                width: 14, height: 14, borderRadius: 3, border: '1px solid var(--border)', flexShrink: 0,
                background: selected.includes(opt) ? 'var(--ai-500)' : 'transparent',
                display: 'flex', alignItems: 'center', justifyContent: 'center',
              }}>
                {selected.includes(opt) && <span style={{ color: '#fff', fontSize: 9, fontWeight: 700 }}>✓</span>}
              </div>
              {opt}
            </div>
          ))}
          {options.length === 0 && <div style={{ padding: '10px 12px', color: 'var(--text-dim)', fontSize: 12 }}>Sem dados (sincronize primeiro)</div>}
        </div>
      )}
    </div>
  );
}

// ── Drawer 360° ────────────────────────────────────────────────────────────
// ── Tab Entidades ──────────────────────────────────────────────────────────
// ═══════════════════════════════════════════════════════════════════════════
// AdvancedFiltersPanel — Fase A · filtros reais + preview (mock)
// Grid 3 colunas: Marketing interno · Consentimentos+Preview external · Preview engagement
// ═══════════════════════════════════════════════════════════════════════════
// ═══════════════════════════════════════════════════════════════════════════
// FilterPopover · popover custom com search + opções + contagens (canonical 2026)
// ═══════════════════════════════════════════════════════════════════════════
function CRMFilterPopover({ anchor, onClose, title, options, selected, onChange, multi, previewMode, aguarda, allowFallback, freeText, freeTextPlaceholder }) {
  const [search, setSearch] = React.useState('');
  const [textVal, setTextVal] = React.useState(freeText && typeof selected === 'string' ? selected : '');
  const [pos, setPos] = React.useState(() => {
    const r = anchor?.getBoundingClientRect();
    return r ? { top: r.bottom + 6, left: r.left, width: r.width } : { top: 0, left: 0, width: 260 };
  });
  const ref = React.useRef(null);
  const inputRef = React.useRef(null);

  React.useEffect(() => {
    setTimeout(() => inputRef.current?.focus(), 50);
    const reposition = () => {
      const r = anchor?.getBoundingClientRect();
      if (!r) return;
      const spaceBelow = window.innerHeight - r.bottom;
      const openUp = spaceBelow < 320 && r.top > 320;
      setPos({
        top: openUp ? r.top - 6 - 320 : r.bottom + 6,
        left: Math.min(r.left, window.innerWidth - 340),
        width: Math.max(r.width, 260),
      });
    };
    reposition();
    const onDown = (e) => { if (ref.current && !ref.current.contains(e.target)) onClose(); };
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    document.addEventListener('mousedown', onDown);
    document.addEventListener('keydown', onKey);
    window.addEventListener('resize', reposition);
    window.addEventListener('scroll', reposition, true);
    return () => {
      document.removeEventListener('mousedown', onDown);
      document.removeEventListener('keydown', onKey);
      window.removeEventListener('resize', reposition);
      window.removeEventListener('scroll', reposition, true);
    };
  }, [onClose, anchor]);

  const top = pos.top;
  const left = pos.left;

  const filtered = (options || []).filter(o => !search || (o.label || '').toLowerCase().includes(search.toLowerCase()));

  const applyFreeText = () => {
    onChange(textVal.trim() || undefined);
    onClose();
  };

  if (freeText) {
    return ReactDOM.createPortal(
      <div ref={ref} role="dialog" aria-label={title}
        style={{
          position: 'fixed', top, left, minWidth: pos.width, maxWidth: 400,
          background: 'var(--bg-card, #fff)', border: '1px solid var(--border)', borderRadius: 10,
          boxShadow: '0 8px 24px rgba(17,41,84,0.12), 0 2px 6px rgba(17,41,84,0.06)',
          zIndex: 10000, overflow: 'hidden', padding: 12,
        }}>
        <div style={{ fontSize: 11, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '0.06em', textTransform: 'uppercase', marginBottom: 8 }}>{title}</div>
        <input ref={inputRef} value={textVal} onChange={e => setTextVal(e.target.value)}
          onKeyDown={e => e.key === 'Enter' && applyFreeText()}
          placeholder={freeTextPlaceholder || 'Escrever...'}
          style={{ width: '100%', padding: '7px 10px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg-sunken)', color: 'var(--text)', fontSize: 12, marginBottom: 8 }} />
        <div style={{ display: 'flex', gap: 6, justifyContent: 'flex-end' }}>
          <button onClick={onClose} style={{ background: 'none', border: '1px solid var(--border)', borderRadius: 5, padding: '5px 10px', fontSize: 11, cursor: 'pointer', color: 'var(--text-muted)' }}>Cancelar</button>
          <button onClick={applyFreeText} className="btn-ai" style={{ fontSize: 11, padding: '5px 12px' }}>Aplicar</button>
        </div>
      </div>,
      document.body
    );
  }

  const isSel = (v) => Array.isArray(selected) ? selected.includes(v) : selected === v;
  const toggle = (v) => {
    if (multi) {
      const s = new Set(Array.isArray(selected) ? selected : []);
      s.has(v) ? s.delete(v) : s.add(v);
      onChange([...s]);
    } else {
      onChange(selected === v ? undefined : v);
      onClose();
    }
  };

  return ReactDOM.createPortal(
    <div ref={ref} role="dialog" aria-label={title}
      style={{
        position: 'fixed', top, left, minWidth: pos.width, maxWidth: 400,
        background: 'var(--bg-card, #fff)', border: '1px solid var(--border)', borderRadius: 10,
        boxShadow: '0 8px 24px rgba(17,41,84,0.12), 0 2px 6px rgba(17,41,84,0.06)',
        zIndex: 10000, overflow: 'hidden',
        animation: 'crm-pop-in 150ms ease-out',
      }}>
      <style>{`@keyframes crm-pop-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }`}</style>

      {/* Header — title + preview banner se aplicável */}
      <div style={{ padding: '10px 12px 8px', borderBottom: '1px solid var(--border)' }}>
        <div style={{ fontSize: 11, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '0.06em', textTransform: 'uppercase', marginBottom: 6 }}>
          {title}
        </div>
        <input ref={inputRef} value={search} onChange={e => setSearch(e.target.value)}
          placeholder="Pesquisar..." aria-label="Pesquisar opções"
          style={{ width: '100%', padding: '5px 10px', borderRadius: 5, border: '1px solid var(--border)', background: 'var(--bg-sunken)', color: 'var(--text)', fontSize: 12 }} />
      </div>

      {previewMode && (
        <div style={{ padding: '8px 12px', fontSize: 11, background: 'color-mix(in oklch, #7B61FF 6%, transparent)', color: 'var(--text-muted)', borderBottom: '1px solid var(--border)', lineHeight: 1.4 }}>
          <strong style={{ color: '#5B43C5' }}>Preview</strong> — dados mock. Fica real com <em>{aguarda}</em>.
        </div>
      )}

      {/* Options */}
      <div className="scrollbar" role="listbox" aria-multiselectable={multi ? 'true' : 'false'}
        style={{ maxHeight: 300, overflowY: 'auto', padding: 4 }}>
        {filtered.length === 0 && (
          <div style={{ padding: 20, textAlign: 'center', color: 'var(--text-dim)', fontSize: 12 }}>Sem opções</div>
        )}
        {filtered.map(o => {
          const sel = isSel(o.value);
          return (
            <button
              key={o.value ?? 'empty'}
              role="option" aria-selected={sel}
              onClick={() => toggle(o.value)}
              disabled={previewMode && !allowFallback}
              style={{
                display: 'flex', alignItems: 'center', gap: 8, width: '100%',
                padding: '7px 10px', borderRadius: 5, cursor: previewMode && !allowFallback ? 'help' : 'pointer',
                background: sel ? 'color-mix(in oklch, var(--ai-500) 8%, transparent)' : 'transparent',
                border: 'none', outline: 'none', textAlign: 'left',
                fontSize: 12.5, color: 'var(--text)',
                opacity: previewMode ? 0.85 : 1,
                transition: 'background 120ms ease',
              }}
              onMouseEnter={e => { if (!sel) e.currentTarget.style.background = 'var(--bg-sunken)'; }}
              onMouseLeave={e => { if (!sel) e.currentTarget.style.background = 'transparent'; }}
            >
              {multi && (
                <span aria-hidden="true" style={{
                  width: 14, height: 14, borderRadius: 3, border: '1px solid var(--border)', flexShrink: 0,
                  background: sel ? 'var(--ai-500)' : 'transparent',
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                }}>{sel && <span style={{ color: '#fff', fontSize: 9, fontWeight: 700 }}>✓</span>}</span>
              )}
              <span style={{ flex: 1, minWidth: 0, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{o.label}</span>
              {o.count != null && (
                <span style={{ fontSize: 11, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', flexShrink: 0 }}>
                  {o.count.toLocaleString('pt-PT')}{previewMode ? '*' : ''}
                </span>
              )}
            </button>
          );
        })}
      </div>

      {/* Footer — clear / apply se multi */}
      {multi && (
        <div style={{ padding: 8, borderTop: '1px solid var(--border)', display: 'flex', justifyContent: 'space-between', gap: 6 }}>
          <button onClick={() => onChange([])}
            style={{ background: 'none', border: 'none', color: 'var(--text-muted)', fontSize: 11, cursor: 'pointer' }}>
            Limpar
          </button>
          <button onClick={onClose}
            style={{ background: 'var(--ai-500)', border: 'none', color: '#fff', fontSize: 11, fontWeight: 600, padding: '4px 12px', borderRadius: 4, cursor: 'pointer' }}>
            Aplicar
          </button>
        </div>
      )}
    </div>,
    document.body
  );
}

// ═══════════════════════════════════════════════════════════════════════════
// FilterChip · trigger visual dum filtro (pill compacto)
// ═══════════════════════════════════════════════════════════════════════════
function CRMFilterChip({ label, hint, value, count, isPreview, aguarda, active, onClick, onClear }) {
  const previewColor = '#7B61FF';
  const previewBg = 'color-mix(in oklch, #7B61FF 8%, transparent)';
  const activeBg = 'color-mix(in oklch, var(--ai-500) 10%, transparent)';

  return (
    <button
      onClick={onClick}
      title={hint ? `${label} · ${hint}` : label}
      aria-label={`Filtro ${label}${value ? `: ${value}` : ''}${isPreview ? ' — preview' : ''}`}
      style={{
        display: 'flex', alignItems: 'stretch', gap: 0,
        width: '100%', minHeight: 52, padding: '8px 12px', borderRadius: 8,
        border: `1px solid ${active ? 'var(--ai-500)' : isPreview ? previewColor + '40' : 'var(--border)'}`,
        background: active ? activeBg : isPreview ? previewBg : 'var(--bg-elev, #fff)',
        color: active ? 'var(--ai-500)' : isPreview ? previewColor : 'var(--text)',
        cursor: 'pointer',
        transition: 'all 150ms ease',
        outline: 'none',
        textAlign: 'left',
        position: 'relative',
      }}
      onFocus={e => e.currentTarget.style.boxShadow = '0 0 0 2px color-mix(in oklch, var(--ai-500) 25%, transparent)'}
      onBlur={e => e.currentTarget.style.boxShadow = 'none'}
    >
      <div style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 2 }}>
        <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', letterSpacing: '0.03em', textTransform: 'uppercase', color: isPreview ? previewColor : 'var(--text-dim)', fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
          {label}
        </div>
        <div style={{ fontSize: 13, fontWeight: active ? 700 : 500, color: active ? 'var(--ai-500)' : value ? 'var(--text)' : 'var(--text-dim)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
          {value || 'Todos'}
        </div>
      </div>
      {count != null && (
        <span style={{ alignSelf: 'center', fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', marginLeft: 6 }}>·{count}</span>
      )}
      {isPreview && (
        <span style={{ alignSelf: 'flex-start', fontSize: 9, fontFamily: 'var(--font-mono)', fontWeight: 700, padding: '1px 5px', borderRadius: 3, background: previewColor + '20', color: previewColor, textTransform: 'uppercase', letterSpacing: '0.05em', marginLeft: 6 }}>
          preview
        </span>
      )}
      {active && onClear && (
        <span
          role="button" aria-label="Limpar filtro"
          onClick={e => { e.stopPropagation(); onClear(); }}
          style={{ position: 'absolute', top: 4, right: 6, cursor: 'pointer', color: 'var(--ai-500)', fontSize: 16, lineHeight: 1, padding: '2px 4px' }}
        >×</span>
      )}
    </button>
  );
}

// ═══════════════════════════════════════════════════════════════════════════
// AdvancedFiltersPanel · canonical 2026 · chip-based, agrupado por intenção
// ═══════════════════════════════════════════════════════════════════════════
function AdvancedFiltersPanel({
  adv, setAdv, filteredCount, onSaveAudience, onClearAll,
  // Filtros do Gestor (Nível 1) — passados de TabEntidades para consolidar tudo num só painel
  meta, pais, setPais, equipa, setEquipa, stages, setStages,
  comOp, setComOp, wonSemOp, setWonSemOp, lostJanela, setLostJanela,
}) {
  const [tags, setTags] = React.useState([]);
  const [segs, setSegs] = React.useState([]);
  const [listas, setListas] = React.useState([]);
  const [sourcesList, setSourcesList] = React.useState([]);
  const [previewEng, setPreviewEng] = React.useState(null);
  const [previewEmail, setPreviewEmail] = React.useState(null);
  const [previewAds, setPreviewAds] = React.useState(null);
  const [previewWeb, setPreviewWeb] = React.useState(null);
  const [previewPrim, setPreviewPrim] = React.useState(null);
  const [previewSat, setPreviewSat] = React.useState(null);
  const [previewColab, setPreviewColab] = React.useState(null);
  const [openChip, setOpenChip] = React.useState(null);  // { key, anchor }

  // Accordion — só 1 grupo aberto de cada vez (persistido)
  const [openGroup, setOpenGroup] = React.useState(() => {
    try { return localStorage.getItem('crm_adv_open_group') || 'entidade'; }
    catch { return 'entidade'; }
  });
  const toggleGroup = (id) => setOpenGroup(cur => {
    const next = cur === id ? null : id;
    try { localStorage.setItem('crm_adv_open_group', next || ''); } catch {}
    return next;
  });
  const isCollapsed = (id) => openGroup !== id;

  React.useEffect(() => {
    CRMAPI.tags().then(setTags).catch(() => {});
    CRMAPI.segmentos().then(setSegs).catch(() => {});
    CRMAPI.listas().then(d => setListas(Array.isArray(d) ? d : (d.rows || []))).catch(() => {});
    CRMAPI.sources().then(setSourcesList).catch(() => {});
    CRMAPI.previewEngagement().then(setPreviewEng).catch(() => {});
    CRMAPI.previewEmail().then(setPreviewEmail).catch(() => {});
    CRMAPI.previewAds().then(setPreviewAds).catch(() => {});
    CRMAPI.previewWeb().then(setPreviewWeb).catch(() => {});
    CRMAPI.previewPrimavera().then(setPreviewPrim).catch(() => {});
    CRMAPI.previewSat().then(setPreviewSat).catch(() => {});
    CRMAPI.previewColab().then(setPreviewColab).catch(() => {});
  }, []);

  const updateAdv = (patch) => setAdv(a => ({ ...a, ...patch }));
  const updatePreview = (key, val) => setAdv(a => ({ ...a, preview: { ...(a.preview || {}), [key]: val || undefined } }));
  const clearField = (field) => setAdv(a => { const n = { ...a }; delete n[field]; return n; });
  const clearPreview = (field) => setAdv(a => { const p = { ...(a.preview || {}) }; delete p[field]; return { ...a, preview: p }; });

  // ── Logic AND/OR per group + between groups ────────────────────────────────
  const logic = adv._logic || {};
  const logicOf = (g) => logic[g] || 'AND';
  const setLogic = (g, val) => setAdv(a => ({ ...a, _logic: { ...(a._logic || {}), [g]: val } }));

  const segByChave = (chave) => segs.find(s => s.chave === chave);
  const findValueLabel = (chave, valorId) => {
    const s = segByChave(chave); if (!s || !valorId) return null;
    return s.valores?.find(v => v.id === valorId)?.valor;
  };
  const valorForList = (options, val) => options?.find(o => o.value === val)?.label;

  const chipRef = React.useRef({});
  const openPop = (key) => setOpenChip({ key, anchor: chipRef.current[key] });

  // Badges de origem dos dados — Gestor (azul) | Qualificação (roxo) | Marketing (verde)
  const ORIGEM = {
    gestor:       { label: 'Gestor',       color: '#1d4ed8', bg: 'rgba(29,78,216,.08)' },
    qualificacao: { label: 'Qualificação', color: '#7c3aed', bg: 'rgba(124,58,237,.08)' },
    marketing:    { label: 'Marketing',    color: '#059669', bg: 'rgba(5,150,105,.08)'  },
  };

  // Helpers para chips reutilizados
  const chipTemNif = {
    key: 'tem_nif', label: 'NIF', preview: false,
    value: adv.tem_nif === 'true' ? 'Com NIF' : adv.tem_nif === 'false' ? 'Sem NIF' : null,
    active: adv.tem_nif !== undefined, selectedValue: adv.tem_nif,
    options: [{ value: 'true', label: 'Com NIF' }, { value: 'false', label: 'Sem NIF' }],
    onSelect: v => updateAdv({ tem_nif: v || undefined }),
    onClear: () => clearField('tem_nif'),
  };

  // Definição dos grupos organizados por LAYOUT FM
  const groupDefs = [
    // ═══ FMRS_API_ENTIDADES ═══════════════════════════════════════════════════
    {
      id: 'entidade',
      title: 'FMRS_API_ENTIDADES',
      subtitle: 'Entidade',
      origem: 'gestor',
      hint: 'Nome, país, cidade, CP, NIF, website, morada',
      chips: [
        {
          key: 'tenant', label: 'Tenant', hint: 'Equipa que gere', preview: false, multi: true,
          value: adv.tenant?.length ? (adv.tenant.length === 1 ? adv.tenant[0] : `${adv.tenant.length} tenants`) : null,
          active: (adv.tenant?.length || 0) > 0, selectedValue: adv.tenant || [],
          options: (meta?.tenants || []).map(t => ({
            value: t.tenant,
            label: `${t.tenant} — ${parseInt(t.entidades || 0).toLocaleString('pt-PT')} entidades`,
            count: parseInt(t.entidades || 0),
          })),
          onSelect: v => updateAdv({ tenant: v }),
          onClear: () => clearField('tenant'),
        },
        {
          key: 'pais', label: 'País', hint: 'Address_Pais', preview: false, multi: true,
          value: pais?.length ? (pais.length === 1 ? pais[0] : `${pais.length} países`) : null,
          active: pais?.length > 0, selectedValue: pais || [],
          options: (meta?.paises || []).map(p => ({ value: p, label: p })),
          onSelect: v => setPais(v),
          onClear: () => setPais([]),
        },
        {
          key: 'cidade', label: 'Cidade', hint: 'Address_City', preview: false, multi: true,
          value: adv.cidade?.length ? (adv.cidade.length === 1 ? adv.cidade[0] : `${adv.cidade.length} cidades`) : null,
          active: adv.cidade?.length > 0, selectedValue: adv.cidade || [],
          options: (meta?.cidades || []).map(c => ({ value: c.val, label: c.val, count: c.n })),
          onSelect: v => updateAdv({ cidade: v }),
          onClear: () => clearField('cidade'),
        },
        {
          key: 'zip_prefix', label: 'Código postal', hint: 'Address_Zip', preview: false,
          value: adv.zip_prefix ? `Começa com ${adv.zip_prefix}` : null,
          active: !!adv.zip_prefix, selectedValue: adv.zip_prefix, freeText: true,
          onSelect: v => updateAdv({ zip_prefix: v || undefined }),
          onClear: () => clearField('zip_prefix'),
        },
        chipTemNif,
        {
          key: 'tem_website', label: 'Website', preview: false,
          value: adv.tem_website === 'true' ? 'Com website' : adv.tem_website === 'false' ? 'Sem website' : null,
          active: adv.tem_website !== undefined, selectedValue: adv.tem_website,
          options: [{ value: 'true', label: 'Com website' }, { value: 'false', label: 'Sem website' }],
          onSelect: v => updateAdv({ tem_website: v || undefined }),
          onClear: () => clearField('tem_website'),
        },
        {
          key: 'tem_morada', label: 'Morada', hint: 'Address1', preview: false,
          value: adv.tem_morada === 'true' ? 'Com morada' : adv.tem_morada === 'false' ? 'Sem morada' : null,
          active: adv.tem_morada !== undefined, selectedValue: adv.tem_morada,
          options: [{ value: 'true', label: 'Com morada' }, { value: 'false', label: 'Sem morada' }],
          onSelect: v => updateAdv({ tem_morada: v || undefined }),
          onClear: () => clearField('tem_morada'),
        },
      ],
    },
    // ═══ FMRS_API_ENTIDADES_PROFILE ═══════════════════════════════════════════
    {
      id: 'profile',
      title: 'FMRS_API_ENTIDADES_PROFILE',
      subtitle: 'Perfil Gestor',
      origem: 'qualificacao',
      hint: 'SEG1/SEG2/SEG3 (Tipo Indústria · Impressão Digital · Dimensão)',
      chips: [
        {
          key: 'profile_seg1', label: 'Tipo indústria', hint: 'SEG1', preview: false, multi: true,
          value: adv.profile_seg1?.length
            ? (adv.profile_seg1.length === 1 ? adv.profile_seg1[0] : `${adv.profile_seg1.length} tipos`)
            : null,
          active: (adv.profile_seg1?.length || 0) > 0,
          selectedValue: adv.profile_seg1 || [],
          options: (() => {
            const seen = new Set();
            const opts = [];
            (meta?.profile_segs || []).forEach(seg => {
              (seg.valores || []).filter(v => v.atributo === '1').forEach(v => {
                if (v.valor && !seen.has(v.valor)) { seen.add(v.valor); opts.push({ value: v.valor, label: v.valor }); }
              });
            });
            return opts;
          })(),
          onSelect: v => updateAdv({ profile_seg1: v }),
          onClear: () => clearField('profile_seg1'),
        },
        {
          key: 'profile_seg2', label: 'Impressão digital', hint: 'SEG2', preview: false,
          value: adv.profile_seg2 || null,
          active: !!adv.profile_seg2,
          selectedValue: adv.profile_seg2,
          options: (() => {
            const seg1 = (meta?.profile_segs || []).find(s => s.id_segmento === '1');
            return (seg1?.valores || []).filter(v => v.atributo === '2').map(v => ({ value: v.valor, label: v.valor }));
          })(),
          onSelect: v => updateAdv({ profile_seg2: v || undefined }),
          onClear: () => clearField('profile_seg2'),
        },
        {
          key: 'profile_seg3', label: 'Dimensão', hint: 'SEG3', preview: false, multi: true,
          value: adv.profile_seg3?.length ? (adv.profile_seg3.length === 1 ? adv.profile_seg3[0] : `${adv.profile_seg3.length} dimensões`) : null,
          active: (adv.profile_seg3?.length || 0) > 0,
          selectedValue: adv.profile_seg3 || [],
          options: (() => {
            const seen = new Set();
            const opts = [];
            (meta?.profile_segs || []).forEach(seg => {
              (seg.valores || []).filter(v => v.atributo === '3').forEach(v => {
                if (v.valor && !seen.has(v.valor)) { seen.add(v.valor); opts.push({ value: v.valor, label: v.valor }); }
              });
            });
            return opts;
          })(),
          onSelect: v => updateAdv({ profile_seg3: v }),
          onClear: () => clearField('profile_seg3'),
        },
      ],
    },
    // ═══ FMRS_API_CONTACTOS ═══════════════════════════════════════════════════
    {
      id: 'contacto',
      title: 'FMRS_API_CONTACTOS',
      subtitle: 'Contactos',
      origem: 'gestor',
      hint: 'Cargo, RGPD, busca por nome/email/telefone',
      chips: [
        {
          key: 'cargo_contacto', label: 'Cargo', preview: false, multi: true,
          value: adv.cargo_contacto?.length ? (adv.cargo_contacto.length === 1 ? adv.cargo_contacto[0] : `${adv.cargo_contacto.length} cargos`) : null,
          active: (adv.cargo_contacto?.length || 0) > 0, selectedValue: adv.cargo_contacto || [],
          options: (meta?.cargos || []).map(c => ({ value: c, label: c })),
          onSelect: v => updateAdv({ cargo_contacto: v }),
          onClear: () => clearField('cargo_contacto'),
        },
        {
          key: 'opt_rgpd', label: 'Opt_Rgpd', preview: false,
          value: adv.opt_rgpd === 'ok' ? 'OK enviar' : adv.opt_rgpd === 'bloqueado' ? 'Bloqueado (1+2)' : adv.opt_rgpd === 'bloqueado_sistema' ? 'Bloqueado sistema (=1)' : adv.opt_rgpd === 'bloqueado_cliente' ? 'Bloqueado cliente (=2)' : null,
          active: !!adv.opt_rgpd, selectedValue: adv.opt_rgpd,
          options: [
            { value: 'ok', label: 'OK enviar (vazio/0/NULL)' },
            { value: 'bloqueado', label: 'Bloqueado (sistema + cliente)' },
            { value: 'bloqueado_sistema', label: 'Bloqueado sistema (=1, bounce)' },
            { value: 'bloqueado_cliente', label: 'Bloqueado cliente (=2, RGPD legal)' },
          ],
          onSelect: v => updateAdv({ opt_rgpd: v || undefined }),
          onClear: () => clearField('opt_rgpd'),
        },
        {
          key: 'busca_contacto', label: 'Busca contacto', hint: 'Nome/Email/Telefone', preview: false,
          value: adv.busca_contacto || null,
          active: !!adv.busca_contacto, selectedValue: adv.busca_contacto, freeText: true,
          onSelect: v => updateAdv({ busca_contacto: v || undefined }),
          onClear: () => clearField('busca_contacto'),
        },
        {
          key: 'tem_email', label: 'Tem email', preview: false,
          value: adv.tem_email === 'true' ? 'Sim' : adv.tem_email === 'false' ? 'Não' : null,
          active: !!adv.tem_email, selectedValue: adv.tem_email,
          options: [
            { value: 'true',  label: 'Sim — com email válido' },
            { value: 'false', label: 'Não — sem email' },
          ],
          onSelect: v => updateAdv({ tem_email: v || undefined }),
          onClear: () => clearField('tem_email'),
        },
        {
          key: 'tem_telefone', label: 'Tem telefone', preview: false,
          value: adv.tem_telefone === 'true' ? 'Sim' : adv.tem_telefone === 'false' ? 'Não' : null,
          active: !!adv.tem_telefone, selectedValue: adv.tem_telefone,
          options: [
            { value: 'true',  label: 'Sim — com telefone' },
            { value: 'false', label: 'Não — sem telefone' },
          ],
          onSelect: v => updateAdv({ tem_telefone: v || undefined }),
          onClear: () => clearField('tem_telefone'),
        },
      ],
    },
    // ═══ FMRS_API_OPORTUNIDADES ═══════════════════════════════════════════════
    {
      id: 'oportunidade',
      title: 'FMRS_API_OPORTUNIDADES',
      subtitle: 'Oportunidades',
      origem: 'gestor',
      hint: 'Equipa, Stage, Status, Produto, Vendedor, Origem, valores e datas',
      chips: [
        {
          key: 'equipa', label: 'Equipa', hint: 'Equipa_Name', preview: false, multi: true,
          value: equipa?.length ? (equipa.length === 1 ? equipa[0] : `${equipa.length} equipas`) : null,
          active: equipa?.length > 0, selectedValue: equipa || [],
          options: (meta?.equipas || []).map(e => ({ value: e, label: e })),
          onSelect: v => setEquipa(v),
          onClear: () => setEquipa([]),
        },
        {
          key: 'stages', label: 'Stage', hint: 'Stage_Name', preview: false, multi: true,
          value: stages?.length ? (stages.length === 1 ? stages[0] : `${stages.length} stages`) : null,
          active: stages?.length > 0, selectedValue: stages || [],
          options: (meta?.stages || []).map(s => ({ value: s, label: s })),
          onSelect: v => setStages(v),
          onClear: () => setStages([]),
        },
        {
          key: 'com_op_activa', label: 'Com OP activa', hint: 'Status = OPEN', preview: false, toggle: true,
          value: comOp ? 'Sim' : null, active: !!comOp,
          onToggle: () => setComOp(v => !v),
          onClear: () => setComOp(false),
        },
        {
          key: 'won_sem_op', label: 'WON sem OP nova', preview: false, toggle: true,
          value: wonSemOp ? 'Sim' : null, active: !!wonSemOp,
          onToggle: () => setWonSemOp(v => !v),
          onClear: () => setWonSemOp(false),
        },
        {
          key: 'lost_janela', label: 'LOST 6-24m', preview: false, toggle: true,
          value: lostJanela ? 'Sim' : null, active: !!lostJanela,
          onToggle: () => setLostJanela(v => !v),
          onClear: () => setLostJanela(false),
        },
        {
          key: 'origem', label: 'Origem', hint: 'ID_Source', preview: false, multi: true,
          value: adv.sources?.length
            ? (adv.sources.length === 1
                ? (sourcesList.find(s => s.fm_id === adv.sources[0])?.source_name || adv.sources[0])
                : `${adv.sources.length} origens`)
            : null,
          active: adv.sources?.length > 0, selectedValue: adv.sources || [],
          options: sourcesList.map(s => ({
            value: s.fm_id,
            label: `${s.source_name}${s.grupo ? ` · ${s.grupo}` : ''}`,
            count: parseInt(s.n_ops || 0),
          })),
          onSelect: v => updateAdv({ sources: v }),
          onClear: () => clearField('sources'),
        },
        {
          key: 'produto', label: 'Produto', hint: 'Produto_Name', preview: false, multi: true,
          value: adv.produto?.length ? (adv.produto.length === 1 ? adv.produto[0] : `${adv.produto.length} produtos`) : null,
          active: adv.produto?.length > 0, selectedValue: adv.produto || [],
          options: (meta?.produtos || []).map(pr => ({ value: pr.val, label: pr.val, count: pr.n })),
          onSelect: v => updateAdv({ produto: v }),
          onClear: () => clearField('produto'),
        },
        {
          key: 'id_vendedor', label: 'Vendedor', hint: 'ID_Vendedor', preview: false, multi: true,
          value: adv.id_vendedor?.length ? (adv.id_vendedor.length === 1 ? adv.id_vendedor[0] : `${adv.id_vendedor.length} vendedores`) : null,
          active: adv.id_vendedor?.length > 0, selectedValue: adv.id_vendedor || [],
          options: (meta?.vendedores || []).map(v => ({ value: v.val, label: v.val, count: v.n })),
          onSelect: v => updateAdv({ id_vendedor: v }),
          onClear: () => clearField('id_vendedor'),
        },
        {
          key: 'id_gestor_op', label: 'Comercial atribuído', hint: 'ID_Gestor', preview: false, multi: true,
          value: adv.id_gestor_op?.length ? (adv.id_gestor_op.length === 1 ? adv.id_gestor_op[0] : `${adv.id_gestor_op.length} comerciais`) : null,
          active: adv.id_gestor_op?.length > 0, selectedValue: adv.id_gestor_op || [],
          options: (meta?.gestores_op || []).map(v => ({ value: v.val, label: v.val, count: v.n })),
          onSelect: v => updateAdv({ id_gestor_op: v }),
          onClear: () => clearField('id_gestor_op'),
        },
        {
          key: 'op_criada_janela_dias', label: 'OP criada há', hint: 'StartDate', preview: false,
          value: adv.op_criada_janela_dias ? `≤ ${adv.op_criada_janela_dias} dias` : null,
          active: !!adv.op_criada_janela_dias, selectedValue: adv.op_criada_janela_dias,
          options: [
            { value: 7, label: '7 dias' }, { value: 14, label: '14 dias' },
            { value: 30, label: '30 dias' }, { value: 90, label: '90 dias' }, { value: 180, label: '180 dias' },
          ],
          onSelect: v => updateAdv({ op_criada_janela_dias: v || undefined }),
          onClear: () => clearField('op_criada_janela_dias'),
        },
        {
          key: 'op_status', label: 'Status', hint: 'Status', preview: false, multi: true,
          value: adv.op_status?.length ? (adv.op_status.length === 1 ? adv.op_status[0] : `${adv.op_status.length} status`) : null,
          active: (adv.op_status?.length || 0) > 0, selectedValue: adv.op_status || [],
          options: [
            { value: 'OPEN',    label: 'OPEN — em curso' },
            { value: 'WON',     label: 'WON — ganha' },
            { value: 'LOST',    label: 'LOST — perdida' },
            { value: 'PENDING', label: 'PENDING — pendente' },
          ],
          onSelect: v => updateAdv({ op_status: v }),
          onClear: () => clearField('op_status'),
        },
        {
          key: 'valor_op_min', label: 'Valor OP min (€K)', hint: 'Produto_Valor_K ≥', preview: false, freeText: true,
          value: adv.valor_op_min ? `≥ €${adv.valor_op_min}K` : null,
          active: adv.valor_op_min != null && adv.valor_op_min !== '', selectedValue: adv.valor_op_min,
          freeTextPlaceholder: 'ex: 10 (= 10.000€)',
          onSelect: v => updateAdv({ valor_op_min: v || undefined }),
          onClear: () => clearField('valor_op_min'),
        },
        {
          key: 'valor_op_max', label: 'Valor OP max (€K)', hint: 'Produto_Valor_K ≤', preview: false, freeText: true,
          value: adv.valor_op_max ? `≤ €${adv.valor_op_max}K` : null,
          active: adv.valor_op_max != null && adv.valor_op_max !== '', selectedValue: adv.valor_op_max,
          freeTextPlaceholder: 'ex: 100 (= 100.000€)',
          onSelect: v => updateAdv({ valor_op_max: v || undefined }),
          onClear: () => clearField('valor_op_max'),
        },
        {
          key: 'op_fechada_janela_dias', label: 'OP fechada há', hint: 'CloseDate', preview: false,
          value: adv.op_fechada_janela_dias ? `≤ ${adv.op_fechada_janela_dias} dias` : null,
          active: !!adv.op_fechada_janela_dias, selectedValue: adv.op_fechada_janela_dias,
          options: [
            { value: 30,  label: '30 dias' },
            { value: 60,  label: '60 dias' },
            { value: 90,  label: '90 dias' },
            { value: 180, label: '180 dias' },
            { value: 365, label: '365 dias (1 ano)' },
          ],
          onSelect: v => updateAdv({ op_fechada_janela_dias: v || undefined }),
          onClear: () => clearField('op_fechada_janela_dias'),
        },
      ],
    },
    // ═══ Marketing interno (portal, não FM) ═══════════════════════════════════
    {
      id: 'marketing',
      title: 'Marketing (interno)',
      origem: 'marketing',
      hint: 'Tags, listas, notas MKT, interacções',
      chips: [
        {
          key: 'tags', label: 'Tags', preview: false, multi: true,
          value: (adv.tags?.length ? `${adv.tags.length} seleccionada${adv.tags.length > 1 ? 's' : ''}` : null),
          active: adv.tags?.length > 0, selectedValue: adv.tags || [],
          options: tags.map(t => ({ value: t.id, label: t.nome })),
          onSelect: v => updateAdv({ tags: v }),
          onClear: () => clearField('tags'),
        },
        {
          key: 'em_lista', label: 'Em lista', preview: false, multi: true,
          value: (adv.em_lista?.length ? `${adv.em_lista.length} lista${adv.em_lista.length > 1 ? 's' : ''}` : null),
          active: adv.em_lista?.length > 0, selectedValue: adv.em_lista || [],
          options: listas.map(l => ({ value: l.id, label: `${l.nome} (${l.n_items || 0})` })),
          onSelect: v => updateAdv({ em_lista: v }),
          onClear: () => clearField('em_lista'),
        },
        {
          key: 'contactado', label: 'Contactado há', preview: false,
          value: adv.contactado_janela_dias ? `≤ ${adv.contactado_janela_dias} dias` : null,
          active: !!adv.contactado_janela_dias, selectedValue: adv.contactado_janela_dias,
          options: [
            { value: 7, label: '7 dias' }, { value: 14, label: '14 dias' },
            { value: 30, label: '30 dias' }, { value: 90, label: '90 dias' },
          ],
          onSelect: v => updateAdv({ contactado_janela_dias: v || undefined }),
          onClear: () => clearField('contactado_janela_dias'),
        },
        {
          key: 'nunca_contactado', label: 'Nunca contactado', preview: false, toggle: true,
          value: adv.nunca_contactado ? 'Sim' : null,
          active: !!adv.nunca_contactado,
          onToggle: () => updateAdv({ nunca_contactado: !adv.nunca_contactado || undefined }),
          onClear: () => clearField('nunca_contactado'),
        },
        {
          key: 'notas_mkt', label: 'Notas MKT', preview: false,
          value: adv.tem_notas_mkt === true ? 'Com notas' : adv.tem_notas_mkt === false ? 'Sem notas' : null,
          active: adv.tem_notas_mkt !== undefined,
          selectedValue: adv.tem_notas_mkt === true ? 'true' : adv.tem_notas_mkt === false ? 'false' : undefined,
          options: [
            { value: 'true', label: 'Com notas Marketing' },
            { value: 'false', label: 'Sem notas Marketing' },
          ],
          onSelect: v => updateAdv({ tem_notas_mkt: v === 'true' ? true : v === 'false' ? false : undefined }),
          onClear: () => clearField('tem_notas_mkt'),
        },
      ],
    },
    // ═══ Consentimentos (crm_consentimentos, portal) ══════════════════════════
    {
      id: 'consent',
      title: 'Consentimentos (portal)',
      origem: 'marketing',
      hint: 'Opt-in/out por canal — sobrepõe FM (crm_consentimentos)',
      chips: [{ canal: 'wa', label: 'WA' }, { canal: 'email', label: 'Email' }, { canal: 'tel', label: 'Tel' }].map(c => ({
        key: `consent_${c.canal}`, label: `Consent ${c.label}`, preview: false,
        value: adv[`consent_${c.canal}`] ? adv[`consent_${c.canal}`].replace('_', ' ') : null,
        active: !!adv[`consent_${c.canal}`], selectedValue: adv[`consent_${c.canal}`],
        options: [
          { value: 'opt_in', label: 'Opt-in explícito' },
          { value: 'sem_opt_out', label: 'Exclui só o contacto com opt-out' },
          { value: 'opt_out', label: 'Opt-out' },
          { value: 'desconhecido', label: 'Desconhecido' },
        ],
        onSelect: v => updateAdv({ [`consent_${c.canal}`]: v || undefined }),
        onClear: () => clearField(`consent_${c.canal}`),
      })),
    },
  ];

  // Contar filtros activos totais
  const totalActive = groupDefs.reduce((n, g) => n + g.chips.filter(c => c.active).length, 0);

  const groupHeaderStyle = { display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', padding: '4px 0', userSelect: 'none' };

  // Toggle E/OU visual
  const LogicToggle = ({ value, onChange, size = 'sm' }) => {
    const sm = size === 'sm';
    const btnStyle = (active) => ({
      padding: sm ? '2px 8px' : '3px 10px',
      fontSize: sm ? 10 : 11,
      fontWeight: 700,
      fontFamily: 'var(--font-mono)',
      cursor: 'pointer',
      border: 'none',
      background: active ? 'var(--ai-500)' : 'transparent',
      color: active ? '#fff' : 'var(--text-muted)',
      transition: 'all 120ms',
    });
    return (
      <div onClick={(e) => e.stopPropagation()}
        style={{ display: 'inline-flex', border: '1px solid var(--border)', borderRadius: 4, overflow: 'hidden', background: 'var(--bg-sunken)' }}>
        <button onClick={() => onChange('AND')} style={btnStyle(value === 'AND' || !value)} title="Todos os filtros deste grupo devem cumprir-se">E</button>
        <button onClick={() => onChange('OR')} style={btnStyle(value === 'OR')} title="Pelo menos um filtro deste grupo deve cumprir-se">OU</button>
      </div>
    );
  };

  return (
    <div style={{
      padding: 20, marginBottom: 16, borderRadius: 12,
      background: 'var(--bg-elev, #fff)', border: '1px solid var(--border)',
      boxShadow: '0 1px 3px rgba(17,41,84,0.04)',
    }}>
      {/* Toggle "Entre grupos" global */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 16, paddingBottom: 12, borderBottom: '1px dashed var(--border)' }}>
        <span style={{ fontSize: 11, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '.05em', textTransform: 'uppercase', fontWeight: 700 }}>Entre grupos:</span>
        <LogicToggle value={logicOf('groups')} onChange={(v) => setLogic('groups', v)} size="md" />
        <span style={{ fontSize: 11, color: 'var(--text-muted)', fontStyle: 'italic' }}>
          {logicOf('groups') === 'OR'
            ? 'Entidade match SE cumprir qualquer grupo abaixo'
            : 'Entidade match SE cumprir todos os grupos abaixo (default)'}
        </span>
      </div>

      {/* Grupos */}
      <div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
        {groupDefs.map(g => {
          const collapsed = isCollapsed(g.id);
          const activeInGroup = g.chips.filter(c => c.active).length;
          return (
            <div key={g.id}>
              <div style={{ ...groupHeaderStyle, justifyContent: 'space-between' }}>
                <div onClick={() => toggleGroup(g.id)} style={{ display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', flex: 1 }}
                  role="button" aria-expanded={!collapsed} tabIndex={0}
                  onKeyDown={e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggleGroup(g.id); }}}>
                  <span style={{ fontSize: 14, color: 'var(--text-dim)', transition: 'transform 150ms ease', transform: collapsed ? 'rotate(-90deg)' : 'rotate(0deg)', display: 'inline-block' }}>▾</span>
                  <span style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)', letterSpacing: '-0.01em' }}>{g.subtitle || g.title}</span>
                  {g.subtitle && <span style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }} title={g.title}>({g.title.replace('FMRS_API_','')})</span>}
                  {/* Badge de origem */}
                  {g.origem && (() => {
                    const o = ORIGEM[g.origem];
                    return (
                      <span style={{ fontSize: 9, fontFamily: 'var(--font-mono)', fontWeight: 700, padding: '2px 6px', borderRadius: 3, background: o.bg, color: o.color, letterSpacing: '.04em', textTransform: 'uppercase' }}>{o.label}</span>
                    );
                  })()}
                  {g.origemExtra && (() => {
                    const o = ORIGEM[g.origemExtra];
                    return (
                      <span style={{ fontSize: 9, fontFamily: 'var(--font-mono)', fontWeight: 700, padding: '2px 6px', borderRadius: 3, background: o.bg, color: o.color, letterSpacing: '.04em', textTransform: 'uppercase' }}>{o.label}</span>
                    );
                  })()}
                  {activeInGroup > 0 && (
                    <span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', fontWeight: 700, padding: '1px 6px', borderRadius: 3, background: 'var(--ai-500)', color: '#fff' }}>{activeInGroup}</span>
                  )}
                  {collapsed && <span style={{ fontSize: 11, color: 'var(--text-dim)', marginLeft: 4, fontStyle: 'italic', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{g.hint}</span>}
                </div>
                {/* Toggle E/OU do grupo — só se >1 filtro activo */}
                {activeInGroup > 1 && (
                  <LogicToggle value={logicOf(g.id)} onChange={(v) => setLogic(g.id, v)} />
                )}
              </div>

              {!collapsed && (
                <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, minmax(0, 1fr))', gap: 8, paddingLeft: 22, paddingTop: 10, animation: 'crm-fade-in 200ms ease-out' }}>
                  <style>{`@keyframes crm-fade-in { from { opacity: 0; transform: translateY(-2px); } to { opacity: 1; transform: translateY(0); } }`}</style>
                  {g.chips.map(chip => {
                    if (chip.toggle) {
                      return (
                        <CRMFilterChip
                          key={chip.key} label={chip.label} hint={chip.hint} value={chip.value} active={chip.active}
                          onClick={chip.onToggle}
                          onClear={chip.active ? chip.onClear : undefined}
                        />
                      );
                    }
                    return (
                      <div key={chip.key} ref={el => chipRef.current[chip.key] = el}>
                        <CRMFilterChip
                          label={chip.label} hint={chip.hint} value={chip.value} active={chip.active}
                          isPreview={chip.preview} aguarda={chip.aguarda}
                          onClick={() => openPop(chip.key)}
                          onClear={chip.active ? chip.onClear : undefined}
                        />
                        {openChip?.key === chip.key && (
                          <CRMFilterPopover
                            anchor={openChip.anchor}
                            onClose={() => setOpenChip(null)}
                            title={chip.label}
                            options={chip.options}
                            selected={chip.selectedValue}
                            onChange={chip.onSelect}
                            multi={chip.multi}
                            previewMode={chip.preview}
                            aguarda={chip.aguarda}
                            allowFallback={true}
                            freeText={chip.freeText}
                            freeTextPlaceholder={chip.freeTextPlaceholder}
                          />
                        )}
                      </div>
                    );
                  })}
                </div>
              )}
            </div>
          );
        })}
      </div>

      {/* Bottom action bar sticky */}
      {totalActive > 0 && (
        <div style={{
          marginTop: 20, paddingTop: 14, borderTop: '1px solid var(--border)',
          display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap',
        }}>
          <div style={{ display: 'flex', alignItems: 'baseline', gap: 12 }}>
            <span style={{ fontSize: 13, fontWeight: 600, color: 'var(--text)' }}>
              {totalActive} filtro{totalActive !== 1 ? 's' : ''} activo{totalActive !== 1 ? 's' : ''}
            </span>
            {filteredCount != null && (
              <span style={{ fontSize: 12, fontFamily: 'var(--font-mono)', color: 'var(--text-muted)' }}>
                {filteredCount.toLocaleString('pt-PT')} resultados
              </span>
            )}
          </div>
          <div style={{ display: 'flex', gap: 8 }}>
            <button
              onClick={onClearAll}
              style={{ background: 'none', border: '1px solid var(--border)', borderRadius: 6, padding: '6px 12px', fontSize: 12, cursor: 'pointer', color: 'var(--text-muted)' }}>
              Limpar tudo
            </button>
            {onSaveAudience && (
              <button
                onClick={onSaveAudience}
                className="btn-ai"
                style={{ fontSize: 12, padding: '6px 14px' }}>
                Guardar como audiência
              </button>
            )}
          </div>
        </div>
      )}
    </div>
  );
}

function TabEntidades({ meta, stats: statsGlobal, onOpenProfile, onOpenPreview }) {
  const [q, setQ] = React.useState('');
  const [tenant, setTenant] = React.useState([]);
  const [apenasClientes, setApenasClientes] = React.useState(false);
  const [pais, setPais] = React.useState([]);
  const [equipa, setEquipa] = React.useState([]);
  const [stages, setStages] = React.useState([]);
  const [comOp, setComOp] = React.useState(false);
  const [wonSemOp, setWonSemOp] = React.useState(false);
  const [lostJanela, setLostJanela] = React.useState(false);
  const [page, setPage] = React.useState(1);
  const [data, setData] = React.useState(null);
  const [loading, setLoading] = React.useState(false);
  const [selected, setSelected] = React.useState(new Set());
  const [rowSignals, setRowSignals] = React.useState({});
  const [tenantStats, setTenantStats] = React.useState(null);
  // stats: usa scoped por tenant/apenasClientes quando há filtro, senão o global
  const stats = (tenant.length > 0 || apenasClientes) ? (tenantStats || statsGlobal) : statsGlobal;

  // Recarrega stats sempre que tenant ou apenasClientes muda
  React.useEffect(() => {
    if (tenant.length === 0 && !apenasClientes) { setTenantStats(null); return; }
    const params = {};
    if (tenant.length) params.tenant = tenant;
    if (apenasClientes) params.apenas_clientes = 'true';
    CRMAPI.stats(params).then(setTenantStats).catch(() => setTenantStats(null));
  }, [tenant, apenasClientes]);
  // Fase A — filtros avançados
  // Painel aberto por default na primeira visita (força utilizador a ver a nova UX)
  const [showAdv, setShowAdv] = React.useState(() => {
    try {
      const v = localStorage.getItem('crm_adv_open');
      if (v === null) return true;  // primeira vez
      return v === '1';
    } catch { return true; }
  });
  const [adv, setAdv] = React.useState({});  // { tags:[], tags_mode, sector, dimensao, maturidade_digital, consent_wa, consent_email, consent_tel, em_lista:[], tem_notas_mkt, contactado_janela_dias, nunca_contactado, profile_seg1:[], cargo:[], sources:[], preview: {} }
  const debouncedQ = useDebounce(q, 300);
  // #43 — Bulk bar: dropdown state + available tags/listas
  const [bulkTagsOpen, setBulkTagsOpen] = React.useState(false);
  const [bulkListasOpen, setBulkListasOpen] = React.useState(false);
  const [availTags, setAvailTags] = React.useState([]);
  const [availListas, setAvailListas] = React.useState([]);
  const [bulkToast, setBulkToast] = React.useState(null);
  React.useEffect(() => {
    CRMAPI.tags().then(setAvailTags).catch(() => {});
    CRMAPI.listas().then(d => setAvailListas(d.rows || [])).catch(() => {});
  }, []);
  const showBulkToast = (msg) => { setBulkToast(msg); setTimeout(() => setBulkToast(null), 3000); };
  const handleBulkTag = async (tagId) => {
    setBulkTagsOpen(false);
    await CRMAPI.bulkTag({ tag_id: tagId, entidade_fm_ids: [...selected] }).catch(() => {});
    showBulkToast('Tag aplicada');
  };
  const handleBulkLista = async (listaId) => {
    setBulkListasOpen(false);
    await CRMAPI.bulkLista({ lista_id: listaId, entidade_fm_ids: [...selected] }).catch(() => {});
    showBulkToast('Adicionadas a lista');
  };

  // #45 — Restaurar filtros do sessionStorage ao montar
  React.useEffect(() => {
    try {
      const saved = JSON.parse(sessionStorage.getItem('crm_filters_entidades') || '{}');
      if (saved.q)         setQ(saved.q);
      if (saved.tenant?.length) setTenant(saved.tenant);
      if (saved.apenasClientes) setApenasClientes(true);
      if (saved.pais?.length)   setPais(saved.pais);
      if (saved.equipa?.length) setEquipa(saved.equipa);
      if (saved.stages?.length) setStages(saved.stages);
      if (saved.comOp)     setComOp(true);
      if (saved.wonSemOp)  setWonSemOp(true);
      if (saved.lostJanela) setLostJanela(true);
      if (saved.adv && Object.keys(saved.adv).length) setAdv(saved.adv);
    } catch {}
  }, []);

  // #45 — Persistir filtros ao alterar
  React.useEffect(() => {
    try {
      sessionStorage.setItem('crm_filters_entidades', JSON.stringify({ q, tenant, apenasClientes, pais, equipa, stages, comOp, wonSemOp, lostJanela, adv }));
    } catch {}
  }, [q, tenant, apenasClientes, pais, equipa, stages, comOp, wonSemOp, lostJanela, adv]);

  const load = React.useCallback(() => {
    setLoading(true);
    const params = { page, pageSize: 50 };
    if (debouncedQ) params.q = debouncedQ;
    if (tenant.length) params.tenant = tenant;
    if (apenasClientes) params.apenas_clientes = 'true';
    if (pais.length) params.pais = pais;
    if (equipa.length) params.equipa = equipa;
    if (stages.length) params.stages = stages;
    if (comOp) params.com_op_activa = true;
    if (wonSemOp) params.won_sem_op_nova = true;
    if (lostJanela) { params.lost_min = 6; params.lost_max = 24; }
    // Fase A — reais
    if (adv.tags?.length)    params.tags = adv.tags;
    if (adv.tags_mode)       params.tags_mode = adv.tags_mode;
    if (adv.sector)          params.sector = adv.sector;
    if (adv.dimensao)        params.dimensao = adv.dimensao;
    if (adv.maturidade_digital) params.maturidade_digital = adv.maturidade_digital;
    if (adv.consent_wa)      params.consent_wa = adv.consent_wa;
    if (adv.consent_email)   params.consent_email = adv.consent_email;
    if (adv.consent_tel)     params.consent_tel = adv.consent_tel;
    if (adv.em_lista?.length) params.em_lista = adv.em_lista;
    if (adv.tem_notas_mkt !== undefined) params.tem_notas_mkt = adv.tem_notas_mkt;
    if (adv.contactado_janela_dias) params.contactado_janela_dias = adv.contactado_janela_dias;
    if (adv.nunca_contactado) params.nunca_contactado = true;
    if (adv.sources?.length)          params.sources = adv.sources;
    if (adv.profile_seg1?.length)     params.profile_seg1 = adv.profile_seg1;
    if (adv.profile_seg2)             params.profile_seg2 = [adv.profile_seg2];
    if (adv.cargo_contacto?.length)   params.cargo_contacto = adv.cargo_contacto;
    if (adv.opt_rgpd)                 params.opt_rgpd = adv.opt_rgpd;
    if (adv.produto?.length)          params.produto = adv.produto;
    if (adv.id_vendedor?.length)      params.id_vendedor = adv.id_vendedor;
    if (adv.id_gestor_op?.length)     params.id_gestor_op = adv.id_gestor_op;
    if (adv.op_criada_janela_dias)    params.op_criada_janela_dias = adv.op_criada_janela_dias;
    if (adv.cidade?.length)           params.cidade = adv.cidade;
    if (adv.zip_prefix)               params.zip_prefix = adv.zip_prefix;
    if (adv.tem_website !== undefined) params.tem_website = adv.tem_website;
    if (adv.tem_nif !== undefined)     params.tem_nif = adv.tem_nif;
    if (adv.tem_morada !== undefined)  params.tem_morada = adv.tem_morada;
    if (adv.busca_contacto)           params.busca_contacto = adv.busca_contacto;
    if (adv._logic && Object.keys(adv._logic).length > 0) params._logic = JSON.stringify(adv._logic);
    CRMAPI.entidades(params)
      .then(d => { setData(d); setLoading(false); })
      .catch(() => setLoading(false));
  }, [debouncedQ, tenant, apenasClientes, pais, equipa, stages, comOp, wonSemOp, lostJanela, page, adv]);

  React.useEffect(() => { setPage(1); setSelected(new Set()); }, [debouncedQ, tenant, apenasClientes, pais, equipa, stages, comOp, wonSemOp, lostJanela, adv]);
  React.useEffect(() => { load(); }, [load]);

  React.useEffect(() => {
    try { localStorage.setItem('crm_adv_open', showAdv ? '1' : '0'); } catch {}
  }, [showAdv]);

  // Keyboard shortcuts — Escape fecha painel, Cmd+Backspace limpa
  React.useEffect(() => {
    const onKey = (e) => {
      if (!showAdv) return;
      if (e.key === 'Escape' && !document.querySelector('[role="dialog"]')) setShowAdv(false);
      if ((e.metaKey || e.ctrlKey) && e.key === 'Backspace') { e.preventDefault(); setAdv({}); }
    };
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, [showAdv]);

  // PA13 — carrega signals batch para as linhas visíveis
  React.useEffect(() => {
    const ids = (data?.rows || []).map(r => r.fm_id).filter(Boolean);
    if (ids.length === 0) return;
    CRMAPI.entSignalsBatch(ids).then(setRowSignals).catch(() => {});
  }, [data]);

  const advCount = React.useMemo(() => {
    let n = 0;
    if (adv.tags?.length) n++;
    if (adv.sector) n++;
    if (adv.dimensao) n++;
    if (adv.maturidade_digital) n++;
    if (adv.consent_wa) n++;
    if (adv.consent_email) n++;
    if (adv.consent_tel) n++;
    if (adv.em_lista?.length) n++;
    if (adv.tem_notas_mkt !== undefined) n++;
    if (adv.contactado_janela_dias) n++;
    if (adv.nunca_contactado) n++;
    if (adv.profile_seg1?.length) n++;
    if (adv.cargo?.length) n++;
    if (adv.preview) n += Object.keys(adv.preview).filter(k => adv.preview[k]).length;
    return n;
  }, [adv]);

  const hasPreviewActive = adv.preview && Object.values(adv.preview).some(Boolean);

  const hasFilter = q || pais.length || equipa.length || stages.length || comOp || wonSemOp || lostJanela || apenasClientes || advCount > 0;
  const clearFilters = () => { setQ(''); setTenant([]); setApenasClientes(false); setPais([]); setEquipa([]); setStages([]); setComOp(false); setWonSemOp(false); setLostJanela(false); setAdv({}); };

  const totalStats = stats?.totais || {};
  const entTot = totalStats.entidades || 0;
  const filteredCount = data?.total || 0;

  // KPI cards contextuais para Entidades (padrão Briefings: accent + fill 0-1)
  const kpiCards = [
    { label: 'ENTIDADES', value: filteredCount.toLocaleString('pt-PT'), sub: hasFilter ? `de ${entTot.toLocaleString('pt-PT')} totais` : 'todas', accent: 'var(--ai-500, #3859D0)', fill: entTot > 0 ? (filteredCount / entTot) : 0 },
    { label: 'COM OP ACTIVA', value: (totalStats.ops_activas || 0).toLocaleString('pt-PT'), sub: 'oportunidades em curso', accent: 'var(--success, #22c55e)', fill: entTot > 0 ? ((totalStats.ops_activas || 0) / entTot) : 0, onClick: () => setComOp(v => !v), active: comOp },
    { label: 'COM TELEFONE', value: `${stats?.pct_com_telefone || 0}%`, sub: 'contactável por WA', accent: '#0ea5e9', fill: (stats?.pct_com_telefone || 0) / 100 },
    { label: 'SELECCIONADAS', value: selected.size, sub: selected.size > 0 ? 'para bulk action' : 'nenhuma', accent: 'var(--warning, #d97706)', fill: filteredCount > 0 ? (selected.size / filteredCount) : 0, active: selected.size > 0 },
  ];

  const thS = { fontSize: 9.5, fontWeight: 700, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', padding: '10px 12px', letterSpacing: '0.08em', textTransform: 'uppercase', textAlign: 'left', borderBottom: '1px solid var(--border)' };
  const tdS = { fontSize: 12.5, color: 'var(--text)', padding: '12px', borderBottom: '1px solid var(--border-light, rgba(0,0,0,0.05))' };

  const toggleAll = (checked) => {
    if (checked) setSelected(new Set((data?.rows || []).map(r => r.fm_id)));
    else setSelected(new Set());
  };
  const toggleRow = (id) => {
    setSelected(s => { const ns = new Set(s); ns.has(id) ? ns.delete(id) : ns.add(id); return ns; });
  };
  const allSelected = data?.rows?.length > 0 && data.rows.every(r => selected.has(r.fm_id));

  return (
    <div>
      {meta?.tenants?.length > 1 && (
        <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap', marginBottom: 12 }}>
          {[{ code: null, label: 'Todos' }, ...(meta.tenants)].map(t => {
            const code = t.tenant || t.code || null;
            const isActive = code === null ? tenant.length === 0 : tenant.includes(code);
            const count = code === null
              ? meta.contagens?.entidades
              : t.entidades;
            const colors = { PT: '#3B82F6', ES: '#F97316', NS: '#22C55E', TAS: '#94A3B8' };
            const color = colors[code] || 'var(--accent)';
            return (
              <button key={code || 'all'}
                onClick={() => {
                  if (code === null) setTenant([]);
                  else setTenant(prev => prev.includes(code) ? prev.filter(x => x !== code) : [...prev, code]);
                }}
                style={{
                  padding: '3px 10px',
                  borderRadius: 4,
                  border: '1px solid var(--border)',
                  background: isActive ? 'var(--bg-sunken)' : 'var(--bg-surface)',
                  color: isActive ? 'var(--text)' : 'var(--text-muted)',
                  fontSize: 11,
                  fontWeight: isActive ? 600 : 500,
                  fontFamily: 'var(--font-mono)',
                  letterSpacing: '0.03em',
                  cursor: 'pointer',
                  borderBottom: isActive ? `2px solid ${color}` : '1px solid var(--border)',
                  transition: 'all 0.15s',
                }}>
                {code || 'Todos'}{count !== undefined ? ` · ${Number(count).toLocaleString('pt-PT')}` : ''}
              </button>
            );
          })}
          <div style={{ width: 1, background: 'var(--border)', margin: '0 4px' }} />
          <button
            onClick={() => setApenasClientes(v => !v)}
            title="Apenas entidades com pelo menos 1 oportunidade ganha (WON)"
            style={{
              padding: '3px 10px',
              borderRadius: 4,
              border: '1px solid var(--border)',
              background: apenasClientes ? 'color-mix(in oklch, #22c55e 12%, var(--bg-surface))' : 'var(--bg-surface)',
              color: apenasClientes ? '#15803d' : 'var(--text-muted)',
              fontSize: 11,
              fontWeight: apenasClientes ? 600 : 500,
              fontFamily: 'var(--font-mono)',
              letterSpacing: '0.03em',
              cursor: 'pointer',
              borderBottom: apenasClientes ? '2px solid #22c55e' : '1px solid var(--border)',
              transition: 'all 0.15s',
            }}>
            {apenasClientes ? '✓' : ''} SÓ CLIENTES · {(() => {
              if (tenant.length === 0) return (meta?.tenants || []).reduce((s,t) => s + Number(t.clientes_activos||0), 0).toLocaleString('pt-PT');
              return tenant.map(code => {
                const t = (meta?.tenants || []).find(x => x.tenant === code);
                return t ? Number(t.clientes_activos||0) : 0;
              }).reduce((a,b) => a+b, 0).toLocaleString('pt-PT');
            })()}
          </button>
        </div>
      )}

      {/* ─── FILTER BAR minimal (pesquisa + botão filtros + contador) ─── */}
      <div style={{ display: 'flex', gap: 10, marginBottom: 16, alignItems: 'center' }}>
        <div style={{
          display: 'inline-flex', alignItems: 'center', gap: 6, flex: '0 0 auto',
          padding: '7px 12px', borderRadius: 8,
          border: '1px solid var(--border)', background: 'var(--bg-elev, #fff)',
          minWidth: 220,
        }}>
          <span style={{ fontSize: 12, color: 'var(--text-dim)' }} aria-hidden="true">⌕</span>
          <input
            value={q} onChange={e => setQ(e.target.value)}
            placeholder="Pesquisar por nome ou NIF..."
            aria-label="Pesquisar entidades"
            style={{ border: 'none', outline: 'none', background: 'transparent', fontSize: 12.5, color: 'var(--text)', flex: 1, minWidth: 0 }}
          />
        </div>

        <button
          onClick={() => setShowAdv(v => !v)}
          aria-expanded={showAdv}
          style={{
            padding: '7px 14px', borderRadius: 8, border: '1px solid var(--border)', cursor: 'pointer',
            fontSize: 12.5, fontFamily: 'var(--font-display)', fontWeight: 600,
            background: showAdv ? 'var(--ai-500)' : hasFilter ? 'color-mix(in oklch, var(--ai-500) 10%, transparent)' : 'var(--bg-elev, #fff)',
            color: showAdv ? '#fff' : hasFilter ? 'var(--ai-500)' : 'var(--text)',
            borderColor: showAdv ? 'var(--ai-500)' : hasFilter ? 'transparent' : 'var(--border)',
            display: 'inline-flex', alignItems: 'center', gap: 6,
            transition: 'all 150ms ease',
          }}>
          <span aria-hidden="true">⚙</span>
          Filtros
          {(advCount > 0 || (pais.length + equipa.length + stages.length + (comOp?1:0) + (wonSemOp?1:0) + (lostJanela?1:0)) > 0) && (
            <span style={{
              fontSize: 10, fontFamily: 'var(--font-mono)', padding: '1px 6px', borderRadius: 3,
              background: showAdv ? 'rgba(255,255,255,0.25)' : 'var(--ai-500)',
              color: '#fff', fontWeight: 700,
            }}>
              {advCount + pais.length + equipa.length + stages.length + (comOp?1:0) + (wonSemOp?1:0) + (lostJanela?1:0)}
            </span>
          )}
        </button>

        <span style={{ fontSize: 12, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>
          {loading ? 'a actualizar...' : `${filteredCount.toLocaleString('pt-PT')} resultados`}
        </span>

        {hasFilter && (
          <button
            onClick={clearFilters}
            style={{ padding: '5px 10px', borderRadius: 6, border: 'none', cursor: 'pointer', fontSize: 11.5, color: 'var(--text-muted)', background: 'transparent', marginLeft: 'auto' }}
            aria-label="Limpar todos os filtros">
            Limpar tudo
          </button>
        )}
      </div>

      {/* ─── ADVANCED FILTERS PANEL (Fase A) ──────────────────────────── */}
      {showAdv && (
        <AdvancedFiltersPanel
          adv={adv} setAdv={setAdv}
          filteredCount={filteredCount}
          onClearAll={clearFilters}
          meta={meta}
          pais={pais} setPais={setPais}
          equipa={equipa} setEquipa={setEquipa}
          stages={stages} setStages={setStages}
          comOp={comOp} setComOp={setComOp}
          wonSemOp={wonSemOp} setWonSemOp={setWonSemOp}
          lostJanela={lostJanela} setLostJanela={setLostJanela}
        />
      )}

      {/* ─── PREVIEW BANNER (quando filtros mock activos) ─────────────── */}
      {hasPreviewActive && (
        <div style={{ padding: '10px 14px', background: 'color-mix(in oklch, var(--warning) 10%, transparent)', border: '1px solid color-mix(in oklch, var(--warning) 30%, transparent)', borderRadius: 8, fontSize: 12, marginBottom: 12, color: 'var(--warning)' }}>
          <strong>Filtros preview activos</strong> — resultados são simulados. Serão reais quando as integrações estiverem ligadas (Touchpoint Tracker, Brevo, Meta, Primavera, SAT).
        </div>
      )}

      {/* ─── KPI STRIP ──────────────────────────────────────────────────── */}
      <KPIStrip cards={kpiCards} />

      {meta?.tenants?.length > 1 && (
        <div style={{ display: 'flex', gap: 8, marginTop: 4, marginBottom: 4, flexWrap: 'wrap' }}>
          {meta.tenants.map(t => (
            <span key={t.tenant} style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)' }}>
              {t.tenant} {Number(t.entidades).toLocaleString('pt-PT')}
            </span>
          ))}
        </div>
      )}

      {/* ─── ERRO ────────────────────────────────────────────────────────── */}
      {data?.error && (
        <div style={{ padding: '10px 14px', background: 'color-mix(in oklch, var(--danger) 10%, transparent)', border: '1px solid color-mix(in oklch, var(--danger) 30%, transparent)', borderRadius: 8, fontSize: 12, color: 'var(--danger)', marginBottom: 10 }}>
          Erro: {data.error}
        </div>
      )}

      {/* ─── BULK BAR (#43) ─────────────────────────────────────────────── */}
      {bulkToast && (
        <div style={{ position: 'fixed', bottom: 24, left: '50%', transform: 'translateX(-50%)', background: '#1e293b', color: '#fff', padding: '9px 18px', borderRadius: 8, fontSize: 12, fontWeight: 600, zIndex: 999, pointerEvents: 'none' }}>
          {bulkToast}
        </div>
      )}
      {selected.size > 0 && (
        <div style={{
          position: 'sticky', bottom: 16, zIndex: 80,
          margin: '0 0 12px',
          padding: '10px 16px', background: 'var(--navy, #112954)', color: '#fff',
          borderRadius: 10, display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap',
          boxShadow: '0 4px 24px rgba(17,41,84,0.28)',
        }}>
          <span style={{ fontSize: 12, fontWeight: 700, fontFamily: 'var(--font-mono)', whiteSpace: 'nowrap' }}>{selected.size} seleccionadas</span>

          {/* Adicionar a lista */}
          <div style={{ position: 'relative' }}>
            <button className="btn-xs" onClick={() => { setBulkListasOpen(v => !v); setBulkTagsOpen(false); }}
              style={{ background: 'rgba(255,255,255,0.12)', border: '1px solid rgba(255,255,255,0.2)', color: '#fff', borderRadius: 6, padding: '4px 10px', fontSize: 11, fontWeight: 600, cursor: 'pointer' }}>
              + Lista
            </button>
            {bulkListasOpen && (
              <>
                <div onClick={() => setBulkListasOpen(false)} style={{ position: 'fixed', inset: 0, zIndex: 90 }} />
                <div style={{ position: 'absolute', bottom: 'calc(100% + 6px)', left: 0, minWidth: 200, background: 'var(--bg-elev, #fff)', border: '1px solid var(--border)', borderRadius: 8, boxShadow: '0 8px 24px rgba(0,0,0,0.15)', zIndex: 91, overflow: 'hidden' }}>
                  {availListas.length === 0 && <div style={{ padding: '10px 14px', fontSize: 12, color: 'var(--text-muted)' }}>Sem listas criadas</div>}
                  {availListas.map(l => (
                    <button key={l.id} onClick={() => handleBulkLista(l.id)}
                      style={{ width: '100%', padding: '9px 14px', border: 'none', borderBottom: '1px solid var(--border-light, rgba(0,0,0,0.05))', background: 'transparent', cursor: 'pointer', textAlign: 'left', fontSize: 12, color: 'var(--text)' }}
                      onMouseEnter={ev => ev.currentTarget.style.background = 'var(--bg-sunken)'}
                      onMouseLeave={ev => ev.currentTarget.style.background = 'transparent'}>
                      {l.nome}
                    </button>
                  ))}
                </div>
              </>
            )}
          </div>

          {/* Tag */}
          <div style={{ position: 'relative' }}>
            <button className="btn-xs" onClick={() => { setBulkTagsOpen(v => !v); setBulkListasOpen(false); }}
              style={{ background: 'rgba(255,255,255,0.12)', border: '1px solid rgba(255,255,255,0.2)', color: '#fff', borderRadius: 6, padding: '4px 10px', fontSize: 11, fontWeight: 600, cursor: 'pointer' }}>
              Tag
            </button>
            {bulkTagsOpen && (
              <>
                <div onClick={() => setBulkTagsOpen(false)} style={{ position: 'fixed', inset: 0, zIndex: 90 }} />
                <div style={{ position: 'absolute', bottom: 'calc(100% + 6px)', left: 0, minWidth: 180, background: 'var(--bg-elev, #fff)', border: '1px solid var(--border)', borderRadius: 8, boxShadow: '0 8px 24px rgba(0,0,0,0.15)', zIndex: 91, overflow: 'hidden' }}>
                  {availTags.length === 0 && <div style={{ padding: '10px 14px', fontSize: 12, color: 'var(--text-muted)' }}>Sem tags criadas</div>}
                  {availTags.map(t => (
                    <button key={t.id} onClick={() => handleBulkTag(t.id)}
                      style={{ width: '100%', padding: '9px 14px', border: 'none', borderBottom: '1px solid var(--border-light, rgba(0,0,0,0.05))', background: 'transparent', cursor: 'pointer', textAlign: 'left', fontSize: 12, color: 'var(--text)', display: 'flex', alignItems: 'center', gap: 8 }}
                      onMouseEnter={ev => ev.currentTarget.style.background = 'var(--bg-sunken)'}
                      onMouseLeave={ev => ev.currentTarget.style.background = 'transparent'}>
                      {t.cor && <span style={{ width: 8, height: 8, borderRadius: 99, background: t.cor, flexShrink: 0 }} />}
                      {t.nome}
                    </button>
                  ))}
                </div>
              </>
            )}
          </div>

          <button className="btn-xs" onClick={() => showBulkToast('Disponivel na tab Activacao')}
            style={{ background: 'rgba(255,255,255,0.12)', border: '1px solid rgba(255,255,255,0.2)', color: '#fff', borderRadius: 6, padding: '4px 10px', fontSize: 11, fontWeight: 600, cursor: 'pointer' }}>
            Enviar WA
          </button>
          <button className="btn-xs" onClick={() => showBulkToast('Em desenvolvimento')}
            style={{ background: 'rgba(255,255,255,0.12)', border: '1px solid rgba(255,255,255,0.2)', color: '#fff', borderRadius: 6, padding: '4px 10px', fontSize: 11, fontWeight: 600, cursor: 'pointer' }}>
            Consentimento
          </button>
          <button className="btn-xs" onClick={() => {
            const p = {};
            if (debouncedQ) p.q = debouncedQ;
            if (pais.length) p.pais = pais;
            if (equipa.length) p.equipa = equipa;
            if (stages.length) p.stages = stages;
            if (comOp) p.com_op_activa = true;
            if (wonSemOp) p.won_sem_op_nova = true;
            CRMAPI.exportEntidades(p);
          }}
            style={{ background: 'rgba(255,255,255,0.12)', border: '1px solid rgba(255,255,255,0.2)', color: '#fff', borderRadius: 6, padding: '4px 10px', fontSize: 11, fontWeight: 600, cursor: 'pointer' }}>
            Export CSV
          </button>
          <button onClick={() => setSelected(new Set())}
            style={{ marginLeft: 'auto', background: 'none', border: '1px solid rgba(255,255,255,0.3)', borderRadius: 6, cursor: 'pointer', color: 'rgba(255,255,255,0.7)', fontSize: 11, padding: '4px 10px' }}>
            Limpar
          </button>
        </div>
      )}

      {/* ─── TABELA MINIMALISTA ─────────────────────────────────────────── */}
      <div>
        <table style={{ width: '100%', borderCollapse: 'collapse' }}>
          <thead>
            <tr>
              <th style={{ ...thS, width: 32 }}>
                <input type="checkbox" checked={allSelected} onChange={e => toggleAll(e.target.checked)}
                  style={{ cursor: 'pointer', accentColor: 'var(--ai-500)' }} />
              </th>
              <th style={thS}>Nome comercial</th>
              <th style={thS}>Estado</th>
              <th style={thS}>Sinais</th>
              <th style={thS}>Marca</th>
              <th style={thS}>Cidade</th>
              <th style={thS}>País</th>
              <th style={thS}>Stages activos</th>
              <th style={{ ...thS, textAlign: 'right' }}>Contactos</th>
              <th style={{ ...thS, textAlign: 'right' }}>Valor open</th>
            </tr>
          </thead>
          <tbody>
            {!data && !loading && (
              <tr><td colSpan={10} style={{ ...tdS, textAlign: 'center', color: 'var(--text-dim)', padding: 40 }}>Sincronize o Gestor para ver dados</td></tr>
            )}
            {loading && !data && (
              <tr><td colSpan={10} style={{ ...tdS, textAlign: 'center', color: 'var(--text-dim)', padding: 40 }}>A carregar...</td></tr>
            )}
            {data?.rows?.length === 0 && !loading && (
              <tr><td colSpan={10} style={{ ...tdS, textAlign: 'center', color: 'var(--text-dim)', padding: 40 }}>Sem resultados para estes filtros</td></tr>
            )}
            {data?.rows?.map((e, i) => {
              const stagesArr = Array.isArray(e.stages_activos) ? e.stages_activos : [];
              const isSel = selected.has(e.fm_id);
              return (
                <tr
                  key={e.fm_id || i}
                  style={{ cursor: 'pointer', background: isSel ? 'color-mix(in oklch, var(--ai-500) 4%, transparent)' : 'transparent' }}
                  onMouseEnter={ev => { if (!isSel) ev.currentTarget.style.background = 'var(--bg-sunken)'; }}
                  onMouseLeave={ev => { if (!isSel) ev.currentTarget.style.background = 'transparent'; }}
                >
                  <td style={{ ...tdS, padding: '12px' }} onClick={ev => ev.stopPropagation()}>
                    <input type="checkbox" checked={isSel} onChange={() => toggleRow(e.fm_id)} style={{ cursor: 'pointer', accentColor: 'var(--ai-500)' }} />
                  </td>
                  <td style={{ ...tdS, fontWeight: 600, maxWidth: 260, overflow: 'hidden' }} onClick={() => onOpenPreview && onOpenPreview('entidade', e.fm_id)}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                      <Avatar name={e.nome} size={26} />
                      <div style={{ minWidth: 0, flex: 1 }}>
                        <div style={{ display: 'flex', alignItems: 'center', gap: 0 }}>
                          <div style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', color: 'var(--text)' }}>{e.nome || '—'}</div>
                          {e.tenant && e.tenant !== 'PT' && (
                            <span style={{
                              fontSize: 9, fontWeight: 700, padding: '1px 5px', borderRadius: 3,
                              background: ({ ES:'#FED7AA', NS:'#BBF7D0', TAS:'#E2E8F0' }[e.tenant] || '#E2E8F0'),
                              color: ({ ES:'#C2410C', NS:'#15803D', TAS:'#475569' }[e.tenant] || '#475569'),
                              fontFamily: 'var(--font-mono)', letterSpacing: '0.05em', marginLeft: 6, flexShrink: 0,
                            }}>{e.tenant}</span>
                          )}
                        </div>
                        {e.nif && <div style={{ fontSize: 10, fontWeight: 400, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>NIF {e.nif}</div>}
                      </div>
                    </div>
                  </td>
                  {/* Estado — Lifecycle */}
                  <td style={{ ...tdS }} onClick={() => onOpenPreview && onOpenPreview('entidade', e.fm_id)}>
                    {rowSignals[e.fm_id]?.lifecycle
                      ? <LifecyclePill lifecycle={rowSignals[e.fm_id].lifecycle} size="sm" />
                      : <span style={{ color: 'var(--text-dim)', fontSize: 11 }}>—</span>}
                  </td>
                  {/* Sinais compactos */}
                  <td style={{ ...tdS }} onClick={() => onOpenPreview && onOpenPreview('entidade', e.fm_id)}>
                    <SignalDotsCompact signals={rowSignals[e.fm_id]?.signals || []} />
                  </td>
                  <td style={{ ...tdS }} onClick={() => onOpenPreview && onOpenPreview('entidade', e.fm_id)}>
                    <MarcaBadge equipas={stagesArr.length > 0 ? [] : []} />
                    <MarcaChipsFromStages fmId={e.fm_id} />
                  </td>
                  <td style={{ ...tdS, color: 'var(--text-muted)' }} onClick={() => onOpenPreview && onOpenPreview('entidade', e.fm_id)}>{e.cidade || '—'}</td>
                  <td style={{ ...tdS, color: 'var(--text-muted)' }} onClick={() => onOpenPreview && onOpenPreview('entidade', e.fm_id)}>{e.pais || '—'}</td>
                  <td style={{ ...tdS }} onClick={() => onOpenPreview && onOpenPreview('entidade', e.fm_id)}>
                    <div style={{ display: 'flex', flexWrap: 'wrap', gap: 3 }}>
                      {stagesArr.slice(0, 3).map((s, si) => <StageChip key={si} name={s} small />)}
                      {stagesArr.length > 3 && <span style={{ fontSize: 10, color: 'var(--text-dim)' }}>+{stagesArr.length - 3}</span>}
                      {stagesArr.length === 0 && <span style={{ color: 'var(--text-dim)', fontSize: 12 }}>—</span>}
                    </div>
                  </td>
                  <td style={{ ...tdS, textAlign: 'right', color: 'var(--text-muted)', fontFamily: 'var(--font-mono)', fontSize: 11 }} onClick={() => onOpenPreview && onOpenPreview('entidade', e.fm_id)}>{e.n_contactos || 0}</td>
                  <td style={{ ...tdS, textAlign: 'right', fontFamily: 'var(--font-mono)', fontSize: 11.5, color: e.valor_open ? 'var(--text)' : 'var(--text-dim)' }} onClick={() => onOpenPreview && onOpenPreview('entidade', e.fm_id)}>
                    {e.valor_open ? `${parseFloat(e.valor_open).toFixed(1)}k€` : '—'}
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>

      {/* Paginação */}
      {data && data.total > 50 && (
        <div style={{ display: 'flex', gap: 8, justifyContent: 'center', marginTop: 20 }}>
          <button className="btn btn-xs" onClick={() => setPage(p => Math.max(1, p-1))} disabled={page <= 1}>Anterior</button>
          <span style={{ fontSize: 12, color: 'var(--text-muted)', alignSelf: 'center' }}>Pág. {page} / {Math.ceil(data.total/50)}</span>
          <button className="btn btn-xs" onClick={() => setPage(p => p+1)} disabled={page >= Math.ceil(data.total/50)}>Seguinte</button>
        </div>
      )}

    </div>
  );
}

// Placeholder helpers para marca (por ora invisíveis; equipa vem do endpoint entidades futuro)
function MarcaBadge() { return null; }
function MarcaChipsFromStages() { return null; }

// ── Tab Contactos ─────────────────────────────────────────────────────────
function TabContactos({ meta, stats: statsGlobal, onOpenProfile, onOpenContacto, onOpenPreview, openSegPing, onCreateAudience }) {
  const [q, setQ] = React.useState('');
  const [tenant, setTenant] = React.useState([]);
  const [apenasClientes, setApenasClientes] = React.useState(false);
  const [pais, setPais] = React.useState([]);
  const [equipa, setEquipa] = React.useState([]);
  const [stages, setStages] = React.useState([]);
  const [temTel, setTemTel] = React.useState('');
  const [temEmail, setTemEmail] = React.useState('');
  const [comOp, setComOp] = React.useState(false);
  const [wonSemOp, setWonSemOp] = React.useState(false);
  const [lostJanela, setLostJanela] = React.useState(false);
  const [emailMarketing, setEmailMarketing] = React.useState(false);
  const [soRgpdBlocked, setSoRgpdBlocked] = React.useState(false);
  const [adv, setAdv] = React.useState({});
  const [showAdv, setShowAdv] = React.useState(false);
  const [page, setPage] = React.useState(1);
  const [data, setData] = React.useState(null);
  const [loading, setLoading] = React.useState(false);
  const [segPainel, setSegPainel] = React.useState(false);
  const [selected, setSelected] = React.useState(new Set());
  const [rowSignals, setRowSignals] = React.useState({});
  const [tenantStats, setTenantStats] = React.useState(null);
  const stats = (tenant.length > 0 || apenasClientes) ? (tenantStats || statsGlobal) : statsGlobal;
  React.useEffect(() => {
    if (tenant.length === 0 && !apenasClientes) { setTenantStats(null); return; }
    const params = {};
    if (tenant.length) params.tenant = tenant;
    if (apenasClientes) params.apenas_clientes = 'true';
    CRMAPI.stats(params).then(setTenantStats).catch(() => setTenantStats(null));
  }, [tenant, apenasClientes]);
  const debouncedQ = useDebounce(q, 300);

  // #43 — Bulk bar state for TabContactos
  const [bulkTagsOpen, setBulkTagsOpen] = React.useState(false);
  const [bulkListasOpen, setBulkListasOpen] = React.useState(false);
  const [availTags, setAvailTags] = React.useState([]);
  const [availListas, setAvailListas] = React.useState([]);
  const [bulkToast, setBulkToast] = React.useState(null);
  React.useEffect(() => {
    CRMAPI.tags().then(setAvailTags).catch(() => {});
    CRMAPI.listas().then(d => setAvailListas(d.rows || [])).catch(() => {});
  }, []);
  const showBulkToast = (msg) => { setBulkToast(msg); setTimeout(() => setBulkToast(null), 3000); };
  const handleBulkTag = async (tagId) => {
    setBulkTagsOpen(false);
    await CRMAPI.bulkTag({ tag_id: tagId, contacto_fm_ids: [...selected] }).catch(() => {});
    showBulkToast('Tag aplicada');
  };
  const handleBulkLista = async (listaId) => {
    setBulkListasOpen(false);
    await CRMAPI.bulkLista({ lista_id: listaId, contacto_fm_ids: [...selected] }).catch(() => {});
    showBulkToast('Adicionados a lista');
  };

  // #45 — Restaurar filtros do sessionStorage ao montar
  React.useEffect(() => {
    try {
      const saved = JSON.parse(sessionStorage.getItem('crm_filters_contactos') || '{}');
      if (saved.q)         setQ(saved.q);
      if (saved.tenant?.length) setTenant(saved.tenant);
      if (saved.apenasClientes) setApenasClientes(true);
      if (saved.pais?.length)   setPais(saved.pais);
      if (saved.equipa?.length) setEquipa(saved.equipa);
      if (saved.stages?.length) setStages(saved.stages);
      if (saved.temTel)    setTemTel(saved.temTel);
      if (saved.temEmail)  setTemEmail(saved.temEmail);
      if (saved.comOp)     setComOp(true);
      if (saved.wonSemOp)  setWonSemOp(true);
      if (saved.lostJanela) setLostJanela(true);
      if (saved.emailMarketing) setEmailMarketing(true);
      if (saved.soRgpdBlocked)  setSoRgpdBlocked(true);
      if (saved.adv && Object.keys(saved.adv).length) setAdv(saved.adv);
    } catch {}
  }, []);

  // #45 — Persistir filtros ao alterar
  React.useEffect(() => {
    try {
      sessionStorage.setItem('crm_filters_contactos', JSON.stringify({ q, tenant, apenasClientes, pais, equipa, stages, temTel, temEmail, comOp, wonSemOp, lostJanela, emailMarketing, soRgpdBlocked, adv }));
    } catch {}
  }, [q, tenant, apenasClientes, pais, equipa, stages, temTel, temEmail, comOp, wonSemOp, lostJanela, emailMarketing, soRgpdBlocked, adv]);

  const clearFilters = () => { setTenant([]); setApenasClientes(false); setPais([]); setEquipa([]); setStages([]); setTemTel(''); setTemEmail(''); setComOp(false); setWonSemOp(false); setLostJanela(false); setEmailMarketing(false); setSoRgpdBlocked(false); setAdv({}); };

  // PA13 — batch signals para contactos visíveis
  React.useEffect(() => {
    const ids = (data?.rows || []).map(r => r.fm_id).filter(Boolean);
    if (ids.length === 0) return;
    CRMAPI.ctSignalsBatch(ids).then(setRowSignals).catch(() => {});
  }, [data]);

  // Auto-abre painel Segmentar quando "+ Nova Audiência" é clicado no header
  React.useEffect(() => {
    if (openSegPing) {
      setSegPainel(true);
      // scroll até ao painel após render
      setTimeout(() => {
        document.querySelector('[data-crm-seg-painel]')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
      }, 100);
    }
  }, [openSegPing]);

  const filtros = React.useMemo(() => {
    const p = { q: debouncedQ, pais, stages, equipa };
    if (tenant.length) p.tenant = tenant;
    if (apenasClientes) p.apenas_clientes = 'true';
    if (temTel)   p.tem_telefone = temTel;
    if (temEmail) p.tem_email = temEmail;
    if (comOp) p.com_op_activa = true;
    if (wonSemOp) p.won_sem_op_nova = true;
    if (lostJanela) { p.lost_min = 6; p.lost_max = 24; }
    if (emailMarketing) p.email_marketing = 'true';
    if (soRgpdBlocked) p.so_rgpd_blocked = 'true';
    if (adv.tags?.length)    p.tags = adv.tags;
    if (adv.tags_mode)       p.tags_mode = adv.tags_mode;
    if (adv.consent_wa)      p.consent_wa = adv.consent_wa;
    if (adv.consent_email)   p.consent_email = adv.consent_email;
    if (adv.consent_tel)     p.consent_tel = adv.consent_tel;
    if (adv.em_lista?.length) p.em_lista = adv.em_lista;
    if (adv.tem_notas_mkt !== undefined) p.tem_notas_mkt = adv.tem_notas_mkt;
    if (adv.contactado_janela_dias) p.contactado_janela_dias = adv.contactado_janela_dias;
    if (adv.nunca_contactado) p.nunca_contactado = true;
    if (adv.sources?.length)        p.sources = adv.sources;
    if (adv.profile_seg1?.length)   p.profile_seg1 = adv.profile_seg1;
    if (adv.profile_seg2)           p.profile_seg2 = [adv.profile_seg2];
    if (adv.cargo_contacto?.length) p.cargo_contacto = adv.cargo_contacto;
    return p;
  }, [debouncedQ, tenant, apenasClientes, pais, equipa, stages, temTel, temEmail, comOp, wonSemOp, lostJanela, emailMarketing, soRgpdBlocked, adv]);

  React.useEffect(() => { setPage(1); setSelected(new Set()); }, [filtros]);
  React.useEffect(() => {
    setLoading(true);
    CRMAPI.contactos({ ...filtros, page, pageSize: 50 })
      .then(d => { setData(d); setLoading(false); })
      .catch(() => setLoading(false));
  }, [filtros, page]);

  const totCt = stats?.totais?.contactos || 0;
  const filteredCount = data?.total || 0;
  const hasFilter = q || pais.length || equipa.length || stages.length || temTel || apenasClientes;

  const kpiCards = [
    { label: 'CONTACTOS', value: filteredCount.toLocaleString('pt-PT'), sub: hasFilter ? `de ${totCt.toLocaleString('pt-PT')} totais` : 'todos', accent: 'var(--ai-500, #3859D0)', fill: totCt > 0 ? (filteredCount / totCt) : 0 },
    { label: 'COM TELEFONE', value: `${stats?.pct_com_telefone || 0}%`, sub: 'contactáveis por WA', accent: 'var(--success, #22c55e)', fill: (stats?.pct_com_telefone || 0) / 100 },
    { label: 'COM EMAIL', value: '—', sub: 'em análise', accent: '#0ea5e9', fill: 0 },
    { label: 'SELECCIONADOS', value: selected.size, sub: selected.size > 0 ? 'para bulk action' : 'nenhum', accent: 'var(--warning, #d97706)', fill: filteredCount > 0 ? (selected.size / filteredCount) : 0, active: selected.size > 0 },
  ];

  const thS = { fontSize: 9.5, fontWeight: 700, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', padding: '10px 12px', letterSpacing: '0.08em', textTransform: 'uppercase', textAlign: 'left', borderBottom: '1px solid var(--border)' };
  const tdS = { fontSize: 12.5, color: 'var(--text)', padding: '12px', borderBottom: '1px solid var(--border-light, rgba(0,0,0,0.05))' };

  const toggleAll = (checked) => setSelected(checked ? new Set((data?.rows || []).map(r => r.fm_id)) : new Set());
  const toggleRow = (id) => setSelected(s => { const ns = new Set(s); ns.has(id) ? ns.delete(id) : ns.add(id); return ns; });
  const allSelected = data?.rows?.length > 0 && data.rows.every(r => selected.has(r.fm_id));

  return (
    <div>
      {meta?.tenants?.length > 1 && (
        <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap', marginBottom: 12 }}>
          {[{ code: null, label: 'Todos' }, ...(meta.tenants)].map(t => {
            const code = t.tenant || t.code || null;
            const isActive = code === null ? tenant.length === 0 : tenant.includes(code);
            const count = code === null ? meta.contagens?.contactos : t.contactos;
            const colors = { PT: '#3B82F6', ES: '#F97316', NS: '#22C55E', TAS: '#94A3B8' };
            const color = colors[code] || 'var(--accent)';
            return (
              <button key={code || 'all'}
                onClick={() => {
                  if (code === null) setTenant([]);
                  else setTenant(prev => prev.includes(code) ? prev.filter(x => x !== code) : [...prev, code]);
                }}
                style={{
                  padding: '3px 10px',
                  borderRadius: 4,
                  border: '1px solid var(--border)',
                  background: isActive ? 'var(--bg-sunken)' : 'var(--bg-surface)',
                  color: isActive ? 'var(--text)' : 'var(--text-muted)',
                  fontSize: 11,
                  fontWeight: isActive ? 600 : 500,
                  fontFamily: 'var(--font-mono)',
                  letterSpacing: '0.03em',
                  cursor: 'pointer',
                  borderBottom: isActive ? `2px solid ${color}` : '1px solid var(--border)',
                  transition: 'all 0.15s',
                }}>
                {code || 'Todos'}{count !== undefined ? ` · ${Number(count).toLocaleString('pt-PT')}` : ''}
              </button>
            );
          })}
          <div style={{ width: 1, background: 'var(--border)', margin: '0 4px' }} />
          <button
            onClick={() => setApenasClientes(v => !v)}
            title="Apenas entidades com pelo menos 1 oportunidade ganha (WON)"
            style={{
              padding: '3px 10px',
              borderRadius: 4,
              border: '1px solid var(--border)',
              background: apenasClientes ? 'color-mix(in oklch, #22c55e 12%, var(--bg-surface))' : 'var(--bg-surface)',
              color: apenasClientes ? '#15803d' : 'var(--text-muted)',
              fontSize: 11,
              fontWeight: apenasClientes ? 600 : 500,
              fontFamily: 'var(--font-mono)',
              letterSpacing: '0.03em',
              cursor: 'pointer',
              borderBottom: apenasClientes ? '2px solid #22c55e' : '1px solid var(--border)',
              transition: 'all 0.15s',
            }}>
            {apenasClientes ? '✓' : ''} SÓ CLIENTES · {(() => {
              if (tenant.length === 0) return (meta?.tenants || []).reduce((s,t) => s + Number(t.clientes_activos||0), 0).toLocaleString('pt-PT');
              return tenant.map(code => {
                const t = (meta?.tenants || []).find(x => x.tenant === code);
                return t ? Number(t.clientes_activos||0) : 0;
              }).reduce((a,b) => a+b, 0).toLocaleString('pt-PT');
            })()}
          </button>
          <button
            onClick={() => setEmailMarketing(v => !v)}
            title="Só contactos elegíveis para email marketing (email válido + sem opt-out RGPD)"
            style={{
              padding: '3px 10px', borderRadius: 4,
              border: '1px solid var(--border)',
              background: emailMarketing ? 'color-mix(in oklch, #059669 12%, var(--bg-surface))' : 'var(--bg-surface)',
              color: emailMarketing ? '#065F46' : 'var(--text-muted)',
              fontSize: 11,
              fontWeight: emailMarketing ? 600 : 500,
              fontFamily: 'var(--font-mono)',
              letterSpacing: '0.03em',
              cursor: 'pointer',
              borderBottom: emailMarketing ? '2px solid #059669' : '1px solid var(--border)',
              transition: 'all 0.15s',
            }}>
            EMAIL MARKETING · {(() => {
              if (tenant.length === 0) return (meta?.tenants || []).reduce((s,t) => s + Number(t.email_marketing_ok||0), 0).toLocaleString('pt-PT');
              return tenant.map(code => {
                const t = (meta?.tenants || []).find(x => x.tenant === code);
                return t ? Number(t.email_marketing_ok||0) : 0;
              }).reduce((a,b) => a+b, 0).toLocaleString('pt-PT');
            })()}
          </button>
          <button
            onClick={() => setSoRgpdBlocked(v => !v)}
            title="Mostrar SÓ contactos em opt-out RGPD (para revisão)"
            style={{
              padding: '3px 10px', borderRadius: 4,
              border: '1px solid var(--border)',
              background: soRgpdBlocked ? 'color-mix(in oklch, #DC2626 12%, var(--bg-surface))' : 'var(--bg-surface)',
              color: soRgpdBlocked ? '#B91C1C' : 'var(--text-muted)',
              fontSize: 11, fontWeight: soRgpdBlocked ? 600 : 500,
              fontFamily: 'var(--font-mono)', letterSpacing: '0.03em',
              cursor: 'pointer',
              borderBottom: soRgpdBlocked ? '2px solid #DC2626' : '1px solid var(--border)',
              transition: 'all 0.15s',
            }}>
            RGPD BLOCKED · {(() => {
              if (tenant.length === 0) return (meta?.tenants || []).reduce((s,t) => s + Number(t.rgpd_blocked||0), 0).toLocaleString('pt-PT');
              return tenant.map(code => {
                const t = (meta?.tenants || []).find(x => x.tenant === code);
                return t ? Number(t.rgpd_blocked||0) : 0;
              }).reduce((a,b) => a+b, 0).toLocaleString('pt-PT');
            })()}
          </button>
        </div>
      )}

      {/* ─── FILTER BAR ─────────────────────────────────────────────────── */}
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, marginBottom: 20, alignItems: 'center' }}>
        <FilterPill label="PESQUISA">
          <input
            value={q} onChange={e => setQ(e.target.value)}
            placeholder="nome / email"
            style={{ border: 'none', outline: 'none', background: 'transparent', fontSize: 12, color: 'var(--text)', width: 130 }}
          />
        </FilterPill>
        <CRMDropdown label="PAÍS" options={meta?.paises || []} selected={pais} onChange={setPais} />
        <CRMDropdown label="MARCA / EQUIPA" options={meta?.equipas || []} selected={equipa} onChange={setEquipa} />
        <CRMDropdown label="STAGE OP" options={meta?.stages || []} selected={stages} onChange={setStages} />
        <FilterPill label="TELEFONE">
          <select value={temTel} onChange={e => setTemTel(e.target.value)}
            style={{ border: 'none', outline: 'none', background: 'transparent', fontSize: 12, color: 'var(--text)', cursor: 'pointer' }}>
            <option value="">todos</option>
            <option value="true">com</option>
            <option value="false">sem</option>
          </select>
        </FilterPill>
        <span style={{ fontSize: 12, color: 'var(--text-muted)', marginLeft: 4 }}>
          {loading ? 'a actualizar...' : `${filteredCount.toLocaleString('pt-PT')} resultados`}
        </span>
        <button className="btn" onClick={() => setShowAdv(v => !v)}
          style={{ height: 28, padding: '0 10px', fontSize: 11, marginLeft: 4,
            ...(showAdv ? { borderColor: 'var(--ai-500)', color: 'var(--ai-500)' } : {}) }}>
          Filtros {Object.keys(filtros).filter(k => !['q','pais','equipa','stages','tem_telefone','tem_email','page','pageSize'].includes(k)).length > 0 ? '·' : ''}
        </button>
      </div>

      {/* Painel filtros avançados */}
      {showAdv && (
        <AdvancedFiltersPanel
          adv={adv} setAdv={setAdv}
          filteredCount={filteredCount}
          onClearAll={clearFilters}
          meta={meta}
          pais={pais} setPais={setPais}
          equipa={equipa} setEquipa={setEquipa}
          stages={stages} setStages={setStages}
          comOp={comOp} setComOp={setComOp}
          wonSemOp={wonSemOp} setWonSemOp={setWonSemOp}
          lostJanela={lostJanela} setLostJanela={setLostJanela}
        />
      )}

      {/* KPI Strip */}
      <KPIStrip cards={kpiCards} />

      {/* Painel de segmentação */}
      {segPainel && (
        <div data-crm-seg-painel>
          <PainelSegmentacao filtros={filtros} meta={meta} onClose={() => setSegPainel(false)} />
        </div>
      )}

      {/* Bulk bar (#43) */}
      {bulkToast && (
        <div style={{ position: 'fixed', bottom: 24, left: '50%', transform: 'translateX(-50%)', background: '#1e293b', color: '#fff', padding: '9px 18px', borderRadius: 8, fontSize: 12, fontWeight: 600, zIndex: 999, pointerEvents: 'none' }}>
          {bulkToast}
        </div>
      )}
      {selected.size > 0 && (
        <div style={{
          position: 'sticky', bottom: 16, zIndex: 80,
          margin: '0 0 12px',
          padding: '10px 16px', background: 'var(--navy, #112954)', color: '#fff',
          borderRadius: 10, display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap',
          boxShadow: '0 4px 24px rgba(17,41,84,0.28)',
        }}>
          <span style={{ fontSize: 12, fontWeight: 700, fontFamily: 'var(--font-mono)', whiteSpace: 'nowrap' }}>{selected.size} seleccionados</span>

          <div style={{ position: 'relative' }}>
            <button className="btn-xs" onClick={() => { setBulkListasOpen(v => !v); setBulkTagsOpen(false); }}
              style={{ background: 'rgba(255,255,255,0.12)', border: '1px solid rgba(255,255,255,0.2)', color: '#fff', borderRadius: 6, padding: '4px 10px', fontSize: 11, fontWeight: 600, cursor: 'pointer' }}>
              + Lista
            </button>
            {bulkListasOpen && (
              <>
                <div onClick={() => setBulkListasOpen(false)} style={{ position: 'fixed', inset: 0, zIndex: 90 }} />
                <div style={{ position: 'absolute', bottom: 'calc(100% + 6px)', left: 0, minWidth: 200, background: 'var(--bg-elev, #fff)', border: '1px solid var(--border)', borderRadius: 8, boxShadow: '0 8px 24px rgba(0,0,0,0.15)', zIndex: 91, overflow: 'hidden' }}>
                  {availListas.length === 0 && <div style={{ padding: '10px 14px', fontSize: 12, color: 'var(--text-muted)' }}>Sem listas criadas</div>}
                  {availListas.map(l => (
                    <button key={l.id} onClick={() => handleBulkLista(l.id)}
                      style={{ width: '100%', padding: '9px 14px', border: 'none', borderBottom: '1px solid var(--border-light, rgba(0,0,0,0.05))', background: 'transparent', cursor: 'pointer', textAlign: 'left', fontSize: 12, color: 'var(--text)' }}
                      onMouseEnter={ev => ev.currentTarget.style.background = 'var(--bg-sunken)'}
                      onMouseLeave={ev => ev.currentTarget.style.background = 'transparent'}>
                      {l.nome}
                    </button>
                  ))}
                </div>
              </>
            )}
          </div>

          <div style={{ position: 'relative' }}>
            <button className="btn-xs" onClick={() => { setBulkTagsOpen(v => !v); setBulkListasOpen(false); }}
              style={{ background: 'rgba(255,255,255,0.12)', border: '1px solid rgba(255,255,255,0.2)', color: '#fff', borderRadius: 6, padding: '4px 10px', fontSize: 11, fontWeight: 600, cursor: 'pointer' }}>
              Tag
            </button>
            {bulkTagsOpen && (
              <>
                <div onClick={() => setBulkTagsOpen(false)} style={{ position: 'fixed', inset: 0, zIndex: 90 }} />
                <div style={{ position: 'absolute', bottom: 'calc(100% + 6px)', left: 0, minWidth: 180, background: 'var(--bg-elev, #fff)', border: '1px solid var(--border)', borderRadius: 8, boxShadow: '0 8px 24px rgba(0,0,0,0.15)', zIndex: 91, overflow: 'hidden' }}>
                  {availTags.length === 0 && <div style={{ padding: '10px 14px', fontSize: 12, color: 'var(--text-muted)' }}>Sem tags criadas</div>}
                  {availTags.map(t => (
                    <button key={t.id} onClick={() => handleBulkTag(t.id)}
                      style={{ width: '100%', padding: '9px 14px', border: 'none', borderBottom: '1px solid var(--border-light, rgba(0,0,0,0.05))', background: 'transparent', cursor: 'pointer', textAlign: 'left', fontSize: 12, color: 'var(--text)', display: 'flex', alignItems: 'center', gap: 8 }}
                      onMouseEnter={ev => ev.currentTarget.style.background = 'var(--bg-sunken)'}
                      onMouseLeave={ev => ev.currentTarget.style.background = 'transparent'}>
                      {t.cor && <span style={{ width: 8, height: 8, borderRadius: 99, background: t.cor, flexShrink: 0 }} />}
                      {t.nome}
                    </button>
                  ))}
                </div>
              </>
            )}
          </div>

          <button className="btn-xs" onClick={() => showBulkToast('Disponivel na tab Activacao')}
            style={{ background: 'rgba(255,255,255,0.12)', border: '1px solid rgba(255,255,255,0.2)', color: '#fff', borderRadius: 6, padding: '4px 10px', fontSize: 11, fontWeight: 600, cursor: 'pointer' }}>
            Enviar WA
          </button>
          <button className="btn-xs" onClick={() => showBulkToast('Em desenvolvimento')}
            style={{ background: 'rgba(255,255,255,0.12)', border: '1px solid rgba(255,255,255,0.2)', color: '#fff', borderRadius: 6, padding: '4px 10px', fontSize: 11, fontWeight: 600, cursor: 'pointer' }}>
            Consentimento
          </button>
          <button className="btn-xs" onClick={() => CRMAPI.exportContactos(filtros)}
            style={{ background: 'rgba(255,255,255,0.12)', border: '1px solid rgba(255,255,255,0.2)', color: '#fff', borderRadius: 6, padding: '4px 10px', fontSize: 11, fontWeight: 600, cursor: 'pointer' }}>
            Export CSV
          </button>
          <button onClick={() => setSelected(new Set())}
            style={{ marginLeft: 'auto', background: 'none', border: '1px solid rgba(255,255,255,0.3)', borderRadius: 6, cursor: 'pointer', color: 'rgba(255,255,255,0.7)', fontSize: 11, padding: '4px 10px' }}>
            Limpar
          </button>
        </div>
      )}

      {/* Tabela */}
      <div>
        <table style={{ width: '100%', borderCollapse: 'collapse' }}>
          <thead>
            <tr>
              <th style={{ ...thS, width: 32 }}>
                <input type="checkbox" checked={allSelected} onChange={e => toggleAll(e.target.checked)} style={{ cursor: 'pointer', accentColor: 'var(--ai-500)' }} />
              </th>
              <th style={thS}>Nome</th>
              <th style={thS}>Consent</th>
              <th style={thS}>Estado</th>
              <th style={thS}>Sinais</th>
              <th style={thS}>Entidade</th>
              <th style={thS}>Telefone</th>
              <th style={thS}>Email</th>
              <th style={thS}>País</th>
              <th style={thS}>Stage</th>
            </tr>
          </thead>
          <tbody>
            {!data && !loading && <tr><td colSpan={10} style={{ ...tdS, textAlign: 'center', color: 'var(--text-dim)', padding: 40 }}>Sincronize o Gestor para ver dados</td></tr>}
            {data?.rows?.length === 0 && !loading && <tr><td colSpan={10} style={{ ...tdS, textAlign: 'center', color: 'var(--text-dim)', padding: 40 }}>Sem resultados</td></tr>}
            {data?.rows?.map((c, i) => {
              const isSel = selected.has(c.fm_id);
              const openCt  = () => onOpenPreview ? onOpenPreview('contacto', c.fm_id) : (onOpenContacto && onOpenContacto(c.fm_id));
              const openEnt = () => c.id_entidade && (onOpenPreview ? onOpenPreview('entidade', c.id_entidade) : (onOpenProfile && onOpenProfile(c.id_entidade)));
              const canOpenCt = !!(onOpenPreview || onOpenContacto);
              const sig = rowSignals[c.fm_id];
              return (
                <tr
                  key={c.fm_id || i}
                  style={{ cursor: canOpenCt ? 'pointer' : 'default', background: isSel ? 'color-mix(in oklch, var(--ai-500) 4%, transparent)' : 'transparent' }}
                  onMouseEnter={ev => { if (!isSel && canOpenCt) ev.currentTarget.style.background = 'var(--bg-sunken)'; }}
                  onMouseLeave={ev => { if (!isSel) ev.currentTarget.style.background = 'transparent'; }}
                >
                  <td style={tdS} onClick={ev => ev.stopPropagation()}>
                    <input type="checkbox" checked={isSel} onChange={() => toggleRow(c.fm_id)} style={{ cursor: 'pointer', accentColor: 'var(--ai-500)' }} />
                  </td>
                  <td style={{ ...tdS, fontWeight: 600 }} onClick={openCt}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 }}>
                      <Avatar name={nomeComposto(c)} size={26} />
                      <div style={{ minWidth: 0, flex: 1 }}>
                        <div style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                          {nomeComposto(c)}
                          {c.tenant && c.tenant !== 'PT' && (
                            <span style={{
                              fontSize: 9, fontWeight: 700, padding: '1px 5px', borderRadius: 3,
                              background: ({ ES:'#FED7AA', NS:'#BBF7D0', TAS:'#E2E8F0' }[c.tenant] || '#E2E8F0'),
                              color: ({ ES:'#C2410C', NS:'#15803D', TAS:'#475569' }[c.tenant] || '#475569'),
                              fontFamily: 'var(--font-mono)', letterSpacing: '0.05em', marginLeft: 6,
                            }}>{c.tenant}</span>
                          )}
                          {c.rgpd_blocked_sistema && (
                            <span style={{
                              fontSize: 9, fontWeight: 700, padding: '1px 5px', borderRadius: 3,
                              background: '#FFEDD5', color: '#C2410C',
                              fontFamily: 'var(--font-mono)', letterSpacing: '0.05em', marginLeft: 6,
                            }} title="Bloqueado pelo sistema (email falhou muitas vezes — má reputação servidor)">
                              RGPD SISTEMA
                            </span>
                          )}
                          {c.rgpd_blocked_cliente && (
                            <span style={{
                              fontSize: 9, fontWeight: 700, padding: '1px 5px', borderRadius: 3,
                              background: '#FEE2E2', color: '#B91C1C',
                              fontFamily: 'var(--font-mono)', letterSpacing: '0.05em', marginLeft: 6,
                            }} title="Bloqueado pelo cliente — opt-out RGPD legal. NÃO enviar emails">
                              RGPD CLIENTE
                            </span>
                          )}
                          {c.email_marketing_ok && !c.rgpd_blocked && (
                            <span style={{
                              fontSize: 9, fontWeight: 600, padding: '1px 5px', borderRadius: 3,
                              background: '#D1FAE5', color: '#065F46',
                              fontFamily: 'var(--font-mono)', letterSpacing: '0.05em', marginLeft: 6,
                            }} title="Elegível para email marketing">✓ EMAIL</span>
                          )}
                        </div>
                        {c.cargo && <div style={{ fontSize: 10, fontWeight: 400, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{c.cargo}</div>}
                      </div>
                    </div>
                  </td>
                  {/* Consent badge — visível e escaneável */}
                  <td style={tdS} onClick={openCt}>
                    <span title={`RGPD FM: ${rgpdLabel(c.opt_rgpd)}`}
                      style={{ display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: 10, fontWeight: 700,
                        fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '.03em',
                        padding: '2px 7px', borderRadius: 4,
                        background: c.opt_rgpd === '1' ? 'rgba(249,115,22,.1)' : c.opt_rgpd === '2' ? 'rgba(239,68,68,.1)' : 'var(--bg-sunken)',
                        color: c.opt_rgpd === '1' ? '#c2410c' : c.opt_rgpd === '2' ? '#b91c1c' : 'var(--text-dim)',
                      }}>
                      <span style={{ width: 5, height: 5, borderRadius: '50%', flexShrink: 0,
                        background: rgpdDotColor(c.opt_rgpd) }} />
                      {c.opt_rgpd === '1' ? 'sistema' : c.opt_rgpd === '2' ? 'cliente' : 'ok'}
                    </span>
                  </td>
                  <td style={tdS} onClick={openCt}>
                    {sig?.lifecycle
                      ? <LifecyclePill lifecycle={sig.lifecycle} size="sm" />
                      : <span style={{ color: 'var(--text-dim)', fontSize: 11 }}>—</span>}
                  </td>
                  <td style={tdS} onClick={openCt}>
                    <SignalDotsCompact signals={sig?.signals || []} />
                  </td>
                  <td style={{ ...tdS, color: 'var(--ai-500)', cursor: c.id_entidade ? 'pointer' : 'default' }}
                      onClick={(ev) => { ev.stopPropagation(); openEnt(); }}>{c.entidade_nome || '—'}</td>
                  <td style={{ ...tdS, fontFamily: 'var(--font-mono)', fontSize: 11 }} onClick={openCt}>{c.telefone || '—'}</td>
                  <td style={{ ...tdS, color: 'var(--text-muted)', fontSize: 11 }} onClick={openCt}>{c.email || '—'}</td>
                  <td style={{ ...tdS, color: 'var(--text-muted)' }} onClick={openCt}>{c.pais || '—'}</td>
                  <td style={tdS} onClick={openCt}>
                    {c.stage_activo ? <StageChip name={c.stage_activo} small /> : <span style={{ color: 'var(--text-dim)' }}>—</span>}
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>

      {data && data.total > 50 && (
        <div style={{ display: 'flex', gap: 8, justifyContent: 'center', marginTop: 20 }}>
          <button className="btn btn-xs" onClick={() => setPage(p => Math.max(1, p-1))} disabled={page <= 1}>Anterior</button>
          <span style={{ fontSize: 12, color: 'var(--text-muted)', alignSelf: 'center' }}>Pág. {page} / {Math.ceil(data.total/50)}</span>
          <button className="btn btn-xs" onClick={() => setPage(p => p+1)} disabled={page >= Math.ceil(data.total/50)}>Seguinte</button>
        </div>
      )}
    </div>
  );
}

// ── Painel de Segmentação (T6) ────────────────────────────────────────────
function PainelSegmentacao({ filtros, meta, onClose }) {
  const [preview, setPreview] = React.useState(null);
  const [loadingPreview, setLoadingPreview] = React.useState(false);
  const [aiTexto, setAiTexto] = React.useState('');
  const [aiLoading, setAiLoading] = React.useState(false);
  const [aiResult, setAiResult] = React.useState(null);
  const [nomeAud, setNomeAud] = React.useState('');
  const [descAud, setDescAud] = React.useState('');
  const [saving, setSaving] = React.useState(false);
  const [saved, setSaved] = React.useState(false);
  const [campanhas, setCampanhas] = React.useState([]);
  const [campSel, setCampSel] = React.useState('');
  const [pushing, setPushing] = React.useState(false);
  const [pushResult, setPushResult] = React.useState(null);
  const [confirmPush, setConfirmPush] = React.useState(null);

  // Construir definicao dos filtros activos
  const definicao = React.useMemo(() => {
    const d = {};
    if (filtros.q) d.q = filtros.q;
    if (filtros.pais?.length) d.pais = filtros.pais;
    if (filtros.equipa?.length) d.equipa = filtros.equipa;
    if (filtros.stages?.length) d.stages = filtros.stages;
    if (filtros.tem_telefone) d.tem_telefone = filtros.tem_telefone === 'true';
    return d;
  }, [filtros]);

  // Preview ao abrir / mudar filtros
  React.useEffect(() => {
    setLoadingPreview(true);
    CRMAPI.preview(definicao)
      .then(p => { setPreview(p); setLoadingPreview(false); })
      .catch(() => setLoadingPreview(false));
  }, [JSON.stringify(definicao)]);

  // Carregar campanhas activas
  React.useEffect(() => {
    CRMAPI.campanhasActivas()
      .then(d => setCampanhas((d.campanhas || d || []).filter(c => c.estrategia_approved_at)))
      .catch(() => {});
  }, []);

  const handleAI = async () => {
    if (!aiTexto.trim()) return;
    setAiLoading(true); setAiResult(null);
    try {
      const r = await CRMAPI.aiSegment(aiTexto);
      setAiResult(r);
    } catch (e) {
      setAiResult({ erro: e.message });
    }
    setAiLoading(false);
  };

  const handleSave = async () => {
    if (!nomeAud.trim()) return;
    setSaving(true);
    try {
      await CRMAPI.criarAudiencia({ nome: nomeAud, descricao: descAud, definicao });
      setSaved(true);
      setTimeout(() => setSaved(false), 2500);
      setNomeAud(''); setDescAud('');
    } catch (e) {
      alert('Erro: ' + e.message);
    }
    setSaving(false);
  };

  const handlePush = async (confirmar) => {
    if (!campSel) return;
    setPushing(true); setPushResult(null);
    try {
      const r = await CRMAPI.pushAudiencia('new', { campanha_id: campSel, confirmar, definicao });
      if (r.requires_confirmation) {
        setConfirmPush(r); setPushing(false); return;
      }
      if (r?.error || r?.erro || r?.ok === false) {
        setPushResult({ erro: r.error || r.erro || r.msg || 'Falhou' });
        setConfirmPush(null);
        setPushing(false);
        return;
      }
      setPushResult(r); setConfirmPush(null);
    } catch (e) {
      setPushResult({ erro: e.message });
    }
    setPushing(false);
  };

  return (
    <div style={{
      marginBottom: 20, padding: '16px 20px', background: 'var(--bg-sunken)', borderRadius: 10,
      border: '1px solid var(--border)',
    }}>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 }}>
        <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--text)' }}>Segmentacao</div>
        <button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-muted)', fontSize: 18 }}>×</button>
      </div>

      {/* Preview */}
      <div style={{ marginBottom: 16 }}>
        <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 8 }}>Preview com filtros activos</div>
        {loadingPreview && <AudCalcProgress />}
        {preview && !loadingPreview && (
          <div style={{ display: 'flex', gap: 20, marginBottom: 10 }}>
            {[
              ['Entidades', preview.entidades],
              ['Contactos', preview.contactos],
              ['Com telefone', preview.com_telefone],
            ].map(([l, v]) => (
              <div key={l} style={{ textAlign: 'center' }}>
                <div style={{ fontSize: 20, fontWeight: 700, color: 'var(--text)' }}>{(v||0).toLocaleString('pt-PT')}</div>
                <div style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>{l.toUpperCase()}</div>
              </div>
            ))}
          </div>
        )}
        {preview?.amostra?.length > 0 && (
          <div style={{ fontSize: 11, color: 'var(--text-muted)' }}>
            Amostra: {preview.amostra.slice(0,5).map(c => c.nome || c.entidade_nome).join(' · ')}
          </div>
        )}
      </div>

      {/* Guardar audiência */}
      <div style={{ marginBottom: 16, paddingTop: 12, borderTop: '1px solid var(--border)' }}>
        <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 8 }}>Guardar como Audiencia</div>
        <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
          <input
            value={nomeAud} onChange={e => setNomeAud(e.target.value)} placeholder="Nome da audiencia..."
            style={{ flex: 1, minWidth: 180, padding: '5px 10px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg-elev, #ffffff)', color: 'var(--text)', fontSize: 12 }}
          />
          <input
            value={descAud} onChange={e => setDescAud(e.target.value)} placeholder="Descricao (opcional)"
            style={{ flex: 1, minWidth: 180, padding: '5px 10px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg-elev, #ffffff)', color: 'var(--text)', fontSize: 12 }}
          />
          <button
            className="btn-ai" onClick={handleSave} disabled={saving || !nomeAud.trim()}
            style={{
              fontSize: 12, padding: '5px 14px', transition: 'all 0.15s',
              ...(saved ? { background: 'var(--success)', borderColor: 'var(--success)' } : {}),
            }}
          >{saving ? 'A guardar...' : saved ? 'Guardado' : 'Guardar Audiencia'}</button>
        </div>
      </div>

      {/* Push para campanha */}
      <div style={{ marginBottom: 16, paddingTop: 12, borderTop: '1px solid var(--border)' }}>
        <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 8 }}>Usar em Campanha</div>
        <div style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' }}>
          <select
            value={campSel} onChange={e => setCampSel(e.target.value)}
            style={{ flex: 1, minWidth: 200, padding: '5px 10px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg-elev, #ffffff)', color: 'var(--text)', fontSize: 12 }}
          >
            <option value="">Seleccionar campanha...</option>
            {campanhas.map(c => <option key={c.id} value={c.id}>{campanhaOptionLabel(c)}</option>)}
          </select>
          <button className="btn-ai" onClick={() => handlePush(false)} disabled={pushing || !campSel} style={{ fontSize: 12, padding: '5px 14px' }}>
            {pushing ? 'A processar...' : 'Usar em campanha'}
          </button>
        </div>
        {confirmPush && (
          <div style={{ marginTop: 10, padding: '10px 14px', background: 'color-mix(in oklch, var(--warning) 10%, transparent)', border: '1px solid color-mix(in oklch, var(--warning) 30%, transparent)', borderRadius: 8 }}>
            <div style={{ fontSize: 12, color: 'var(--text)', marginBottom: 8 }}>
              {confirmPush.msg} ({confirmPush.excluidos_antidup} excluidos por anti-duplicado)
            </div>
            <div style={{ display: 'flex', gap: 8 }}>
              <button className="btn-ai" onClick={() => handlePush(true)} style={{ fontSize: 12, padding: '4px 12px' }}>Confirmar</button>
              <button className="btn" onClick={() => setConfirmPush(null)} style={{ fontSize: 12, padding: '4px 12px' }}>Cancelar</button>
            </div>
          </div>
        )}
        {pushResult && (
          <div style={{ marginTop: 8, fontSize: 12, color: pushResult.erro ? 'var(--danger)' : 'var(--success)' }}>
            {pushResult.erro ? `Erro: ${pushResult.erro}` : `${pushResult.total} contactos enviados para segmentacao. ${pushResult.excluidos_antidup} excluidos (anti-dup).`}
          </div>
        )}
      </div>

      {/* Segmentacao AI */}
      <div style={{ paddingTop: 12, borderTop: '1px solid var(--border)' }}>
        <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 8 }}>Segmentacao AI</div>
        <div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
          <input
            value={aiTexto} onChange={e => setAiTexto(e.target.value)}
            placeholder="Descreve o segmento em linguagem natural..."
            style={{ flex: 1, padding: '5px 10px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg-elev, #ffffff)', color: 'var(--text)', fontSize: 12 }}
            onKeyDown={e => e.key === 'Enter' && handleAI()}
          />
          <button className="btn-ai" onClick={handleAI} disabled={aiLoading || !aiTexto.trim()} style={{ fontSize: 12, padding: '5px 14px' }}>
            {aiLoading ? 'A analisar...' : 'Segmentar com AI'}
          </button>
        </div>
        {aiResult && (
          <div style={{ padding: '10px 14px', background: 'var(--bg-card)', border: '1px solid var(--border)', borderRadius: 8, fontSize: 12 }}>
            {aiResult.impossivel
              ? <div style={{ color: 'var(--danger)' }}>{aiResult.razao}</div>
              : <>
                  <div style={{ color: 'var(--text)', marginBottom: 6 }}>{aiResult.explicacao_pt}</div>
                  {aiResult.preview && (
                    <div style={{ color: 'var(--text-muted)' }}>{aiResult.preview.entidades} entidades correspondentes</div>
                  )}
                  {aiResult.limitacoes?.length > 0 && (
                    <div style={{ marginTop: 6, color: 'var(--warning)' }}>
                      Limitacoes: {aiResult.limitacoes.join(' · ')}
                    </div>
                  )}
                  {aiResult.erro && <div style={{ color: 'var(--danger)' }}>{aiResult.erro}</div>}
                </>
            }
          </div>
        )}
      </div>
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════════════════
// Templates de audiência (Fix 2.3) — 5 seeds comuns
// ═══════════════════════════════════════════════════════════════════════════
const AUD_BRANDS = [
  { slug: 'alldecor', re: /\balldecor\b/i, name: 'AllDecor', color: '#7C3AED' },
  { slug: 'netscreen', re: /\bnetscreen\b/i, name: 'NetScreen', color: '#0EA5E9' },
  { slug: 'digidelta', re: /\bdigidelta\b/i, name: 'Digidelta', color: '#3859D0' },
  { slug: 'mimaki', re: /\bmimaki\b/i, name: 'Mimaki', color: '#0066CC' },
  { slug: 'sensek', re: /\bsensek\b/i, name: 'Sensek', color: '#DC2626' },
  { slug: 'decal', re: /\bdecal\b/i, name: 'Decal', color: '#00A86B' },
  { slug: 'biond', re: /\bbiond\b/i, name: 'BIOND', color: '#E8A000' },
];
const AUD_BRAND_TABS = ['mimaki', 'biond', 'decal', 'alldecor', 'sensek', 'netscreen', 'digidelta']
  .map(slug => AUD_BRANDS.find(b => b.slug === slug))
  .filter(Boolean);

function audienceBrand(aud) {
  const hay = `${aud?.nome || ''} ${aud?.descricao || ''}`;
  return AUD_BRANDS.find(b => b.re.test(hay)) || null;
}

function listaMarca(l) {
  const marca = String(l?.marca || '').trim();
  const byName = AUD_BRANDS.find(b => b.slug === marca.toLowerCase() || b.name.toLowerCase() === marca.toLowerCase());
  if (byName) return byName;
  const hay = `${marca} ${l?.nome || ''} ${l?.campaign_name || ''}`;
  return AUD_BRANDS.find(b => b.re.test(hay)) || null;
}

function audienceEmpresa(aud) {
  const d = audDefinicao(aud);
  if (d.origem === 'prospeccao') {
    const raw = Array.isArray(d.marca) ? d.marca : (d.marca ? [d.marca] : []);
    const slug = String(raw[0] || '').toLowerCase();
    if (slug) return AUD_BRANDS.find(b => b.slug === slug) || { slug, name: String(raw[0]), color: '#3859D0' };
    return audienceBrand(aud);
  }
  const slug = String(aud?.brand_slug || '').toLowerCase();
  if (slug || aud?.brand_name) {
    const known = AUD_BRANDS.find(b => b.slug === slug);
    if (known) return known;
    if (aud.brand_name) return { slug: slug || 'marca', name: aud.brand_name, color: '#3859D0' };
  }
  return audienceBrand(aud);
}

function audienceMarcas(aud) {
  const d = audDefinicao(aud);
  if (d.origem === 'prospeccao') {
    const raw = Array.isArray(d.marca) ? d.marca : (d.marca ? [d.marca] : []);
    if (raw.length) {
      return raw.map(m => {
        const slug = String(m).toLowerCase();
        return AUD_BRANDS.find(b => b.slug === slug) || { slug, name: String(m), color: '#3859D0' };
      });
    }
    const peloNome = audienceBrand(aud);
    return peloNome ? [peloNome] : [];
  }
  const one = audienceEmpresa(aud);
  return one ? [one] : [];
}

const CRM_SEGMENTO_POR_ID = { '1': 'SignGraphics', '2': 'Textil', '3': 'Decal Mimaki Partners' };

function audDefinicao(aud) {
  let d = aud?.definicao;
  if (typeof d === 'string') { try { d = JSON.parse(d); } catch { d = null; } }
  return d && typeof d === 'object' ? d : {};
}

function audienceCanal(aud) {
  const d = audDefinicao(aud);
  if (d.origem === 'prospeccao') {
    if (d.canal === 'email') return 'Email';
    if (d.canal === 'telemovel') return 'WhatsApp';
    if (d.canal === 'ambos') return 'Email · WhatsApp';
    return '';
  }
  const canal = String(aud?.segmento_canal || '').toLowerCase();
  if (canal === 'email') return 'Email';
  if (canal === 'whatsapp') return 'WhatsApp';
  const nome = aud?.nome || '';
  if (/\bemail\b/i.test(nome)) return 'Email';
  if (/\bwhatsapp\b|\bwa\b/i.test(nome)) return 'WhatsApp';
  return '';
}

const CRM_TENANT_NOME = {
  PTDIG: 'Digidelta PT',
  DIGES: 'Digidelta ES',
  NSC: 'NetScreen',
  TAS: 'TAS',
};

function tenantNome(code) {
  const key = String(code || '').toUpperCase();
  return CRM_TENANT_NOME[key] || code;
}

function audienceTenants(aud) {
  const d = audDefinicao(aud);
  if (d.origem === 'prospeccao') {
    return (Array.isArray(d.pais) ? d.pais : []).map(p => String(p).toUpperCase()).filter(Boolean);
  }
  const picked = Array.isArray(d.tenant) ? d.tenant.map(t => String(t).toUpperCase()).filter(Boolean) : [];
  if (picked.length) return picked;
  const brand = String(aud?.brand_slug || '').toLowerCase();
  if (brand === 'netscreen') return ['NSC'];
  const pais = Array.isArray(d.pais) ? d.pais.map(p => String(p).toUpperCase()) : [];
  const out = [];
  if (pais.some(p => p.startsWith('PORTUG'))) out.push('PTDIG');
  if (pais.some(p => p.startsWith('ESPA'))) out.push('DIGES');
  if (!out.length) {
    const nome = aud?.nome || '';
    if (/\[PT\]/i.test(nome)) out.push('PTDIG');
    if (/\[ES\]/i.test(nome)) out.push('DIGES');
  }
  return out;
}

function audienceSegmento(aud) {
  const d = audDefinicao(aud);
  if (d.origem === 'prospeccao') {
    const segs = Array.isArray(d.segmento) ? d.segmento : [];
    const tipos = Array.isArray(d.segmento_tipo) ? d.segmento_tipo : [];
    return [...segs, ...tipos].filter(Boolean).join(' · ');
  }
  const brand = String(aud?.brand_slug || '').toLowerCase();
  if (brand === 'netscreen') return 'NetScreen';
  if (Array.isArray(d.profile_seg) && d.profile_seg.length) {
    return d.profile_seg.map(id => {
      if (String(id) === '1' && brand === 'netscreen') return 'NetScreen';
      return CRM_SEGMENTO_POR_ID[String(id)] || String(id);
    }).join(' · ');
  }
  const hay = `${aud?.nome || ''} ${aud?.campanha_titulo || ''} ${(d.profile_seg1 || []).join(' ')}`;
  if (/estamparia|têxtil|textil|txf|sublim|\bts\d/i.test(hay)) return 'Textil';
  if (brand === 'decal' && /partner/i.test(hay)) return 'Decal Mimaki Partners';
  if ((d.profile_seg1 || []).length || brand) return 'SignGraphics';
  return '';
}

function brandFromCampaign(c) {
  if (!c) return null;
  const slug = String(c.brand_slug || '').toLowerCase();
  const known = AUD_BRANDS.find(b => b.slug === slug);
  if (known) return known;
  if (c.brand_name) return { slug: slug || 'marca', name: c.brand_name, color: '#3859D0' };
  return audienceBrand({ nome: c.titulo || c.nome || '' });
}

function campanhaOptionLabel(c) {
  const title = c.titulo || c.commercial_name || c.nome || 'Campanha';
  const brand = c.brand_name || brandFromCampaign(c)?.name;
  return brand ? `${brand} · ${title}` : title;
}

function BrandMark({ brand, empty = '—', mono = false }) {
  if (!brand) return <span style={{ fontSize: 11, color: 'var(--text-dim)' }}>{empty}</span>;
  return (
    <span style={{ fontSize: mono ? 10 : 11, fontFamily: mono ? 'var(--font-mono)' : 'var(--font-display)', fontWeight: mono ? 600 : 700, letterSpacing: '0.06em', color: brand.color, whiteSpace: 'nowrap' }}>
      {String(brand.name).toUpperCase()}
    </span>
  );
}

function audienceCrmCampos(aud) {
  const d = audDefinicao(aud);
  const rows = [];
  const push = (label, value) => {
    const text = value == null ? '' : String(value).trim();
    if (text) rows.push({ label, value: text });
  };
  const lista = (v) => Array.isArray(v) ? v.filter(Boolean).join(' · ') : (v == null ? '' : String(v));
  const ligado = (v) => v === true || v === 'true';
  const desligado = (v) => v === false || v === 'false';
  const consent = {
    opt_in: 'Opt-in explícito',
    sem_opt_out: 'Exclui só este contacto',
    opt_out: 'Opt-out',
    desconhecido: 'Desconhecido',
  };
  const rgpd = {
    ok: 'OK enviar (vazio/0/NULL)',
    bloqueado: 'Bloqueado (sistema + cliente)',
    bloqueado_sistema: 'Bloqueado sistema (=1)',
    bloqueado_cliente: 'Bloqueado cliente (=2)',
  };
  const usados = new Set();
  const mark = (key, label, value) => { usados.add(key); push(label, value); };

  if (d.origem === 'prospeccao') {
    mark('origem', 'Base', 'Prospeção');
    if (d.marca?.length) mark('marca', 'Marca', lista(d.marca));
    if (d.segmento?.length) mark('segmento', 'Segmento', lista(d.segmento));
    if (d.segmento_tipo?.length) mark('segmento_tipo', 'Tipo', lista(d.segmento_tipo));
    if (d.estado?.length) mark('estado', 'Estado da empresa', lista(d.estado));
    if (d.fonte?.length) mark('fonte', 'Fonte', lista(d.fonte));
    if (d.canal) mark('canal', 'Canal', d.canal === 'email' ? 'Email' : d.canal === 'telemovel' ? 'Telemóvel' : d.canal === 'ambos' ? 'Email e telemóvel' : d.canal);
    if (d.papel?.length) mark('papel', 'Papel', lista(d.papel));
    if (d.distrito?.length) mark('distrito', 'Distrito', lista(d.distrito));
    if (d.comercial?.length) mark('comercial', 'Comercial', lista(d.comercial));
    if (d.zona?.length) mark('zona', 'Zona', lista(d.zona));
    if (d.incluir_ja_no_gestor) mark('incluir_ja_no_gestor', 'Já no Gestor', 'Incluídos');
    if (d._logic && typeof d._logic === 'object') {
      const nomes = { groups: 'Entre grupos', empresa: 'Empresa', segmento: 'Segmento', contacto: 'Contacto', atribuicao: 'Atribuição' };
      const bits = Object.entries(d._logic).filter(([, v]) => v === 'OR').map(([k]) => `${nomes[k] || k}: OU`);
      usados.add('_logic');
      if (bits.length) push('Combinação', bits.join(' · '));
    }
    if (d.pesquisa_id) mark('pesquisa_id', 'Pesquisa', String(d.pesquisa_id));
    if (d.lista_id) mark('lista_id', 'Lista', String(d.lista_id));
  }

  if (d.pais?.length) mark('pais', 'País', lista(d.pais));
  if (d.tenant?.length) mark('tenant', 'Tenant', d.tenant.map(tenantNome).join(' · '));
  if (d.cidade?.length) mark('cidade', 'Cidade', lista(d.cidade));
  if (d.zip_prefix) mark('zip_prefix', 'Código postal', `Começa com ${d.zip_prefix}`);
  if (d.profile_seg?.length) mark('profile_seg', 'Segmento', d.profile_seg.map(id => CRM_SEGMENTO_POR_ID[String(id)] || id).join(' · '));
  if (d.profile_seg1?.length) mark('profile_seg1', 'Tipo indústria (SEG1)', lista(d.profile_seg1));
  if (d.profile_seg2) mark('profile_seg2', 'Impressão digital (SEG2)', lista(d.profile_seg2));
  if (d.profile_seg3?.length) mark('profile_seg3', 'Dimensão (SEG3)', lista(d.profile_seg3));
  if (d.cargo_contacto?.length) mark('cargo_contacto', 'Cargo', lista(d.cargo_contacto));
  if (d.tem_email != null && d.tem_email !== '') mark('tem_email', 'Tem email', ligado(d.tem_email) ? 'Sim — com email válido' : desligado(d.tem_email) ? 'Não — sem email' : String(d.tem_email));
  if (d.tem_telefone != null && d.tem_telefone !== '') mark('tem_telefone', 'Tem telefone', ligado(d.tem_telefone) ? 'Sim — com telefone' : desligado(d.tem_telefone) ? 'Não — sem telefone' : String(d.tem_telefone));
  if (d.opt_rgpd) mark('opt_rgpd', 'Opt_Rgpd', rgpd[d.opt_rgpd] || d.opt_rgpd);
  if (d.consent_email) mark('consent_email', 'Consent email', consent[d.consent_email] || d.consent_email);
  if (d.consent_wa) mark('consent_wa', 'Consent WA', consent[d.consent_wa] || d.consent_wa);
  if (d.consent_tel) mark('consent_tel', 'Consent tel', consent[d.consent_tel] || d.consent_tel);
  if (d.equipa?.length) mark('equipa', 'Equipa', lista(d.equipa));
  if (d.stages?.length) mark('stages', 'Stage', lista(d.stages));
  if (d.com_op_activa) mark('com_op_activa', 'Com OP activa', 'Sim');
  if (d.won_sem_op_nova) mark('won_sem_op_nova', 'WON sem OP nova', 'Sim');
  if (d.lost_janela && typeof d.lost_janela === 'object') mark('lost_janela', 'LOST janela', `${d.lost_janela.min ?? 6}–${d.lost_janela.max ?? 24} meses`);
  if (d.lost_produto?.length) mark('lost_produto', 'Produto LOST', lista(d.lost_produto));
  if (d.produto?.length) mark('produto', 'Produto', lista(d.produto));
  if (d.op_status?.length) mark('op_status', 'Status', lista(d.op_status));
  if (d.sources_grupo?.length) mark('sources_grupo', 'Origem', lista(d.sources_grupo));
  if (d.sources?.length) mark('sources', 'Origem', lista(d.sources));
  if (d.id_vendedor?.length) mark('id_vendedor', 'Vendedor', lista(d.id_vendedor));
  if (d.id_gestor_op?.length) mark('id_gestor_op', 'Comercial atribuído', lista(d.id_gestor_op));
  if (d.op_criada_janela_dias) mark('op_criada_janela_dias', 'OP criada há', `≤ ${d.op_criada_janela_dias} dias`);
  if (d.op_fechada_janela_dias) mark('op_fechada_janela_dias', 'OP fechada há', `≤ ${d.op_fechada_janela_dias} dias`);
  if (d.valor_op_min != null && d.valor_op_min !== '') mark('valor_op_min', 'Valor OP min', `≥ €${d.valor_op_min}K`);
  if (d.valor_op_max != null && d.valor_op_max !== '') mark('valor_op_max', 'Valor OP max', `≤ €${d.valor_op_max}K`);
  if (d.nunca_contactado) mark('nunca_contactado', 'Nunca contactado', 'Sim');
  if (d.contactado_janela_dias) mark('contactado_janela_dias', 'Contactado há', `≤ ${d.contactado_janela_dias} dias`);
  if (d.tem_notas_mkt === true || d.tem_notas_mkt === false) mark('tem_notas_mkt', 'Notas MKT', d.tem_notas_mkt ? 'Com notas' : 'Sem notas');
  for (const [k, v] of Object.entries(d)) {
    if (usados.has(k) || k.startsWith('_')) continue;
    if (v == null || v === '' || (Array.isArray(v) && !v.length)) continue;
    push(k, typeof v === 'object' ? lista(v) || JSON.stringify(v) : String(v));
  }
  return rows;
}

function AudCalcProgress({ message = 'A calcular a segmentação…' }) {
  return (
    <div style={{ padding: 16, background: 'linear-gradient(135deg, #eef2ff 0%, #f5f3ff 100%)', border: '1px solid #c7d2fe', borderRadius: 8 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 }}>
        <div style={{ display: 'flex', alignItems: 'flex-end', gap: 2, height: 20, flexShrink: 0 }}>
          {[0, 1, 2, 3, 4].map(i => (
            <div key={i} style={{ width: 3, height: 16, background: '#6366f1', borderRadius: 2, animation: 'audWave 1s ease-in-out infinite', animationDelay: `${i * 0.12}s`, transformOrigin: 'bottom' }} />
          ))}
        </div>
        <div style={{ fontSize: 13, color: '#3730a3', fontWeight: 600 }}>{message}</div>
      </div>
      <div style={{ height: 6, background: '#e0e7ff', borderRadius: 3, overflow: 'hidden' }}>
        <div style={{ width: '40%', height: '100%', background: 'linear-gradient(90deg, #6366f1, #8b5cf6)', animation: 'audCalcBar 1.4s ease-in-out infinite' }} />
      </div>
      <style>{`@keyframes audWave { 0%,100%{transform:scaleY(0.3)} 50%{transform:scaleY(1)} } @keyframes audCalcBar { 0%{transform:translateX(-120%)} 100%{transform:translateX(280%)} }`}</style>
    </div>
  );
}

function audienceFilterSummary(def) {
  let d = def;
  if (!d) return [];
  if (typeof d === 'string') {
    try { d = JSON.parse(d); } catch { return []; }
  }
  if (d.origem === 'prospeccao') {
    const chips = ['Prospeção'];
    if (d.marca?.length) chips.push(d.marca.slice(0, 2).join(' · '));
    if (d.pais?.length) chips.push(d.pais.slice(0, 2).join(' · '));
    if (d.segmento?.length) chips.push(d.segmento.slice(0, 2).join(' · '));
    if (d.canal === 'email') chips.push('email');
    else if (d.canal === 'telemovel') chips.push('telemóvel');
    else if (d.canal === 'ambos') chips.push('email + telemóvel');
    if (d.papel?.length) chips.push(d.papel.join(' · '));
    if (d._logic?.groups === 'OR') chips.push('grupos em OU');
    return chips.slice(0, 6);
  }
  const chips = [];
  if (d.pais?.length) chips.push(d.pais.slice(0, 2).join(' · '));
  if (d.tenant?.length) chips.push(d.tenant.join(' · '));
  if (d.tem_email) chips.push('com email');
  if (d.tem_telefone) chips.push('com telefone');
  if (d.consent_email === 'opt_in') chips.push('email opt-in');
  if (d.consent_wa === 'opt_in' || d.consent_wa === 'sem_opt_out') chips.push('WA');
  if (d.op_status?.length) chips.push(d.op_status.join('/'));
  if (d.com_op_activa) chips.push('OP activa');
  if (d.op_fechada_janela_dias) chips.push(`OP ${d.op_fechada_janela_dias}d`);
  if (d.stages?.length) chips.push(d.stages.slice(0, 2).join(' · '));
  if (d.nunca_contactado) chips.push('nunca contactado');
  return chips.slice(0, 5);
}

const AUDIENCE_TEMPLATES = [
  {
    id: 'email_ok',
    label: 'Email enviável',
    icon: '✉',
    descricao: 'Email com @, RGPD vazio ou 0, opt-out só desse contacto',
    def: { tem_email: 'true', opt_rgpd: 'ok', consent_email: 'sem_opt_out' },
  },
  {
    id: 'email_pt',
    label: 'Email · Portugal',
    icon: '✉',
    descricao: 'Email enviável em Portugal',
    def: { pais: ['PORTUGAL'], tem_email: 'true', opt_rgpd: 'ok', consent_email: 'sem_opt_out' },
  },
  {
    id: 'email_es',
    label: 'Email · Espanha',
    icon: '✉',
    descricao: 'Email enviável em Espanha',
    def: { pais: ['ESPAÑA'], tem_email: 'true', opt_rgpd: 'ok', consent_email: 'sem_opt_out' },
  },
  {
    id: 'wa_tel',
    label: 'WhatsApp com telemóvel',
    icon: '💬',
    descricao: 'Número com dígitos. Sem Opt_Rgpd',
    def: { tem_telefone: 'true', consent_wa: 'sem_opt_out' },
  },
  {
    id: 'wa_op',
    label: 'WhatsApp · OP activa',
    icon: '↑',
    descricao: 'Telemóvel e oportunidade aberta em Demo, AG Decisão ou AG Financeiro',
    def: { tem_telefone: 'true', consent_wa: 'sem_opt_out', com_op_activa: true, stages: ['DEMO', 'AG DECISAO', 'AG FINANCEIRO'] },
  },
  {
    id: 'wa_lost',
    label: 'WhatsApp · LOST 6–24m',
    icon: '↻',
    descricao: 'Telemóvel e oportunidade perdida entre 6 e 24 meses',
    def: { tem_telefone: 'true', consent_wa: 'sem_opt_out', op_status: ['LOST'], lost_janela: { min: 6, max: 24 } },
  },
  {
    id: 'won_12m',
    label: 'Oportunidades WON 12m',
    icon: '★',
    descricao: 'Ganhas nos últimos 12 meses. O canal escolhe-se a seguir',
    def: { op_status: ['WON'], op_fechada_janela_dias: 365 },
  },
  {
    id: 'lost_24m',
    label: 'Oportunidades LOST 24m',
    icon: '↻',
    descricao: 'Perdidas nos últimos 24 meses. O canal escolhe-se a seguir',
    def: { op_status: ['LOST'], op_fechada_janela_dias: 730 },
  },
];

// Sugere nome para audiência baseado nos filtros (Fix 2.2)
function suggestAudienceName(def, meta) {
  const parts = [];
  if (def.pais?.length)   parts.push(def.pais.slice(0, 2).join('+'));
  if (def.equipa?.length) parts.push(def.equipa[0]);
  if (def.stages?.length) parts.push(def.stages[0]);
  if (def.consent_wa === 'opt_in')     parts.push('WA opt-in');
  if (def.consent_email === 'opt_in')  parts.push('email opt-in');
  if (def.nunca_contactado) parts.push('nunca contactado');
  if (def.tags?.length) parts.push(`${def.tags.length} tag${def.tags.length > 1 ? 's' : ''}`);
  if (parts.length === 0) return 'Nova audiência';
  return parts.join(' · ');
}

const ESTADOS_PROSP = [
  ['novo', 'Novo'],
  ['validado', 'Validado'],
  ['atribuido', 'Atribuído'],
  ['em_contacto', 'Em contacto'],
  ['qualificado', 'Qualificado'],
  ['convertido', 'Convertido'],
  ['descartado', 'Descartado'],
  ['opt_out', 'Opt-out'],
  ['ja_no_gestor', 'Já no Gestor'],
];
const FONTE_LABEL_PROSP = { apollo: 'Apollo', apify: 'Apify', excel: 'Excel', pagina: 'Página', manual: 'Manual', import: 'Importação' };
const PAPEL_LABEL_PROSP = { decisor: 'Decisor', influenciador: 'Influenciador' };

function emptyProsp() {
  return {
    marca: [], pais: [], distrito: [], cidade: [],
    segmento: [], segmento_tipo: [], estado: [],
    incluir_ja_no_gestor: false,
    fonte: [], canal: '', papel: [],
    equipa: [], comercial: [], zona: [],
    _logic: {},
  };
}

function prospFromDef(def) {
  const d = def && def.origem === 'prospeccao' ? def : {};
  const arr = (v) => Array.isArray(v) ? v.map(x => String(x)) : (v ? [String(v)] : []);
  return {
    ...emptyProsp(),
    marca: arr(d.marca).map(s => s.toLowerCase()),
    pais: arr(d.pais).map(s => s.toUpperCase()),
    distrito: arr(d.distrito),
    cidade: arr(d.cidade),
    segmento: arr(d.segmento),
    segmento_tipo: arr(d.segmento_tipo),
    estado: arr(d.estado),
    fonte: arr(d.fonte).map(s => s.toLowerCase()),
    canal: d.canal || '',
    papel: arr(d.papel).map(s => s.toLowerCase()),
    equipa: arr(d.equipa),
    comercial: arr(d.comercial),
    zona: arr(d.zona),
    incluir_ja_no_gestor: !!d.incluir_ja_no_gestor,
    pesquisa_id: d.pesquisa_id || null,
    lista_id: d.lista_id || null,
    _logic: (d._logic && typeof d._logic === 'object') ? { ...d._logic } : {},
  };
}

function definicaoProspeccao(p) {
  const d = { origem: 'prospeccao' };
  for (const k of ['marca', 'pais', 'distrito', 'cidade', 'segmento', 'segmento_tipo', 'estado', 'fonte', 'papel', 'equipa', 'comercial', 'zona']) {
    if (p[k]?.length) d[k] = p[k];
  }
  if (p.canal) d.canal = p.canal;
  if (p.incluir_ja_no_gestor) d.incluir_ja_no_gestor = true;
  if (p.pesquisa_id) d.pesquisa_id = p.pesquisa_id;
  if (p.lista_id) d.lista_id = p.lista_id;
  const logic = {};
  if (p._logic && typeof p._logic === 'object') {
    for (const k of ['groups', 'empresa', 'segmento', 'contacto', 'atribuicao']) {
      if (p._logic[k] === 'OR') logic[k] = 'OR';
    }
  }
  if (Object.keys(logic).length) d._logic = logic;
  return d;
}

function contarFiltrosProspeccao(p) {
  let n = 0;
  for (const k of ['marca', 'pais', 'distrito', 'cidade', 'segmento', 'segmento_tipo', 'estado', 'fonte', 'papel', 'equipa', 'comercial', 'zona']) {
    if (p[k]?.length) n += 1;
  }
  if (p.canal) n += 1;
  if (p.incluir_ja_no_gestor) n += 1;
  return n;
}

function suggestProspeccaoNome(p) {
  const bits = [];
  if (p.marca?.length) bits.push(p.marca.join('/'));
  if (p.pais?.length) bits.push(p.pais.join('/'));
  if (p.segmento?.length) bits.push(p.segmento.join('/'));
  if (p.canal === 'email') bits.push('email');
  else if (p.canal === 'telemovel') bits.push('telemóvel');
  else if (p.canal === 'ambos') bits.push('email e telemóvel');
  return bits.length ? `Prospeção · ${bits.join(' · ')}` : 'Prospeção';
}

const PROSP_TEMPLATES = [
  { id: 'p_email', icon: '✉', label: 'Email enviável', descricao: 'Contactos com email válido', def: { canal: 'email' } },
  { id: 'p_tel', icon: '☎', label: 'Com telemóvel', descricao: 'Contactos com telemóvel', def: { canal: 'telemovel' } },
  { id: 'p_pt', icon: 'PT', label: 'Portugal · email', descricao: 'País PT e email válido', def: { pais: ['PT'], canal: 'email' } },
  { id: 'p_es', icon: 'ES', label: 'Espanha · email', descricao: 'País ES e email válido', def: { pais: ['ES'], canal: 'email' } },
];

function ProspeccaoAudienceFilters({ value, onChange, opcoes, erro }) {
  const set = (patch) => onChange({ ...value, ...patch });
  const [openGroup, setOpenGroup] = React.useState('empresa');
  const [openChip, setOpenChip] = React.useState(null);
  const chipRef = React.useRef({});
  const logic = value._logic || {};
  const logicOf = (g) => (logic[g] === 'OR' ? 'OR' : 'AND');
  const setLogic = (g, val) => {
    const next = { ...logic };
    if (val === 'OR') next[g] = 'OR';
    else delete next[g];
    set({ _logic: next });
  };

  const comoOpcoes = (rows, labelDe) => (rows || []).map(r => ({
    value: r.value,
    label: labelDe ? (labelDe[r.value] || r.label || r.value) : (r.label || r.value),
    count: r.n != null ? Number(r.n) : undefined,
  }));
  const contagemEstado = Object.fromEntries((opcoes?.estados || []).map(e => [e.value, e.n]));
  const estadoLabel = Object.fromEntries(ESTADOS_PROSP);
  const estadoOpts = ESTADOS_PROSP.map(([v, label]) => ({ value: v, label, count: contagemEstado[v] || 0 }));
  const segRows = opcoes?.segmentos || [];
  const segOpts = [...new Map(segRows.map(s => [s.segmento, { value: s.segmento, label: s.segmento }])).values()];
  const tipoOpts = [...new Map(segRows.filter(s => s.segmento_tipo && (!value.segmento.length || value.segmento.includes(s.segmento))).map(s => [s.segmento_tipo, { value: s.segmento_tipo, label: s.segmento_tipo }])).values()];
  const papeis = (opcoes?.papeis || []).length
    ? comoOpcoes(opcoes.papeis, PAPEL_LABEL_PROSP)
    : [{ value: 'decisor', label: 'Decisor' }, { value: 'influenciador', label: 'Influenciador' }];
  const resumo = (arr, many, labels) => {
    if (!arr?.length) return null;
    if (arr.length === 1) return labels?.[arr[0]] || arr[0];
    return `${arr.length} ${many}`;
  };
  const canalLabel = { email: 'Email válido', telemovel: 'Com telemóvel', ambos: 'Email e telemóvel' };

  const groupDefs = [
    {
      id: 'empresa',
      title: 'Empresa',
      hint: 'Marca, país, distrito, cidade, estado, fonte',
      chips: [
        { key: 'marca', label: 'Marca', hint: 'marca_alvo', multi: true, value: resumo(value.marca, 'marcas'), active: value.marca.length > 0, selectedValue: value.marca, options: comoOpcoes(opcoes?.marcas), onSelect: marca => set({ marca: marca || [] }) },
        { key: 'pais', label: 'País', multi: true, value: resumo(value.pais, 'países'), active: value.pais.length > 0, selectedValue: value.pais, options: comoOpcoes(opcoes?.paises), onSelect: pais => set({ pais: pais || [] }) },
        { key: 'distrito', label: 'Distrito', multi: true, value: resumo(value.distrito, 'distritos'), active: value.distrito.length > 0, selectedValue: value.distrito, options: comoOpcoes(opcoes?.distritos), onSelect: distrito => set({ distrito: distrito || [] }) },
        { key: 'cidade', label: 'Cidade', multi: true, value: resumo(value.cidade, 'cidades'), active: value.cidade.length > 0, selectedValue: value.cidade, options: comoOpcoes(opcoes?.cidades), onSelect: cidade => set({ cidade: cidade || [] }) },
        { key: 'estado', label: 'Estado', hint: 'Estado da empresa', multi: true, value: resumo(value.estado, 'estados', estadoLabel), active: value.estado.length > 0, selectedValue: value.estado, options: estadoOpts, onSelect: estado => set({ estado: estado || [] }) },
        { key: 'fonte', label: 'Fonte', multi: true, value: resumo(value.fonte, 'fontes', FONTE_LABEL_PROSP), active: value.fonte.length > 0, selectedValue: value.fonte, options: comoOpcoes(opcoes?.fontes, FONTE_LABEL_PROSP), onSelect: fonte => set({ fonte: fonte || [] }) },
      ],
    },
    {
      id: 'segmento',
      title: 'Segmento',
      hint: 'Segmento e tipo',
      chips: [
        {
          key: 'segmento', label: 'Segmento', multi: true,
          value: resumo(value.segmento, 'segmentos'), active: value.segmento.length > 0, selectedValue: value.segmento, options: segOpts,
          onSelect: (segmento) => {
            const next = segmento || [];
            const tiposValidos = new Set(segRows.filter(s => s.segmento_tipo && (!next.length || next.includes(s.segmento))).map(s => s.segmento_tipo));
            set({ segmento: next, segmento_tipo: value.segmento_tipo.filter(t => tiposValidos.has(t)) });
          },
        },
        { key: 'segmento_tipo', label: 'Tipo', hint: 'Tipo dentro do segmento', multi: true, value: resumo(value.segmento_tipo, 'tipos'), active: value.segmento_tipo.length > 0, selectedValue: value.segmento_tipo, options: tipoOpts, onSelect: segmento_tipo => set({ segmento_tipo: segmento_tipo || [] }) },
      ],
    },
    {
      id: 'contacto',
      title: 'Contacto',
      hint: 'Canal e papel',
      chips: [
        { key: 'canal', label: 'Canal', hint: 'Email, telemóvel ou ambos', value: canalLabel[value.canal] || null, active: !!value.canal, selectedValue: value.canal || undefined, options: [{ value: 'email', label: 'Email válido' }, { value: 'telemovel', label: 'Com telemóvel' }, { value: 'ambos', label: 'Email e telemóvel' }], onSelect: canal => set({ canal: canal || '' }) },
        { key: 'papel', label: 'Papel', multi: true, value: resumo(value.papel, 'papéis', PAPEL_LABEL_PROSP), active: value.papel.length > 0, selectedValue: value.papel, options: papeis, onSelect: papel => set({ papel: papel || [] }) },
      ],
    },
    {
      id: 'atribuicao',
      title: 'Atribuição',
      hint: 'Equipa, comercial, zona',
      chips: [
        { key: 'equipa', label: 'Equipa', multi: true, value: resumo(value.equipa, 'equipas'), active: value.equipa.length > 0, selectedValue: value.equipa, options: comoOpcoes(opcoes?.equipas), onSelect: equipa => set({ equipa: equipa || [] }) },
        { key: 'comercial', label: 'Comercial', multi: true, value: resumo(value.comercial, 'comerciais'), active: value.comercial.length > 0, selectedValue: value.comercial, options: comoOpcoes(opcoes?.comerciais), onSelect: comercial => set({ comercial: comercial || [] }) },
        { key: 'zona', label: 'Zona', multi: true, value: resumo(value.zona, 'zonas'), active: value.zona.length > 0, selectedValue: value.zona, options: comoOpcoes(opcoes?.zonas), onSelect: zona => set({ zona: zona || [] }) },
      ],
    },
  ];

  const LogicToggle = ({ val, onPick, size = 'sm' }) => {
    const sm = size === 'sm';
    const btnStyle = (active) => ({
      padding: sm ? '2px 8px' : '3px 10px',
      fontSize: sm ? 10 : 11,
      fontWeight: 700,
      fontFamily: 'var(--font-mono)',
      cursor: 'pointer',
      border: 'none',
      background: active ? 'var(--ai-500)' : 'transparent',
      color: active ? '#fff' : 'var(--text-muted)',
    });
    return (
      <div onClick={(e) => e.stopPropagation()}
        style={{ display: 'inline-flex', border: '1px solid var(--border)', borderRadius: 4, overflow: 'hidden', background: 'var(--bg-sunken)' }}>
        <button type="button" onClick={() => onPick('AND')} style={btnStyle(val !== 'OR')} title="Todos os filtros deste grupo têm de cumprir-se">E</button>
        <button type="button" onClick={() => onPick('OR')} style={btnStyle(val === 'OR')} title="Basta um filtro deste grupo cumprir-se">OU</button>
      </div>
    );
  };

  return (
    <div style={{ padding: 16, borderRadius: 12, background: 'var(--bg-elev, #fff)', border: '1px solid var(--border)' }}>
      {erro && <div style={{ marginBottom: 12, fontSize: 12, color: 'var(--danger)', lineHeight: 1.4 }}>{erro}</div>}
      {!opcoes && !erro && <div style={{ marginBottom: 12, fontSize: 12, color: 'var(--text-muted)' }}>A carregar opções da prospeção…</div>}
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10, paddingBottom: 12, borderBottom: '1px dashed var(--border)', flexWrap: 'wrap' }}>
        <span style={{ fontSize: 11, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '.05em', textTransform: 'uppercase', fontWeight: 700 }}>Entre grupos:</span>
        <LogicToggle val={logicOf('groups')} onPick={(v) => setLogic('groups', v)} size="md" />
        <span style={{ fontSize: 11, color: 'var(--text-muted)', fontStyle: 'italic' }}>
          {logicOf('groups') === 'OR'
            ? 'Contacto entra se cumprir qualquer grupo abaixo'
            : 'Contacto entra se cumprir todos os grupos abaixo'}
        </span>
      </div>
      <label style={{ display: 'flex', gap: 8, alignItems: 'flex-start', fontSize: 12, color: 'var(--text)', marginBottom: 8 }}>
        <input type="checkbox" checked={!!value.incluir_ja_no_gestor} disabled={value.estado?.length > 0}
          onChange={e => set({ incluir_ja_no_gestor: e.target.checked })} />
        <span>Incluir empresas que já estão no Gestor</span>
      </label>
      <div style={{ marginBottom: 14, fontSize: 11, color: 'var(--text-muted)', lineHeight: 1.4 }}>
        {value.estado?.length
          ? 'A lista de estados escolhida substitui a exclusão por omissão.'
          : 'Sem estado escolhido ficam de fora descartadas, convertidas, opt-out e já no Gestor.'}
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
        {groupDefs.map(g => {
          const collapsed = openGroup !== g.id;
          const activeInGroup = g.chips.filter(c => c.active).length;
          return (
            <div key={g.id}>
              <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
                <div onClick={() => setOpenGroup(cur => cur === g.id ? null : g.id)}
                  style={{ display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', flex: 1, minWidth: 0 }}
                  role="button" aria-expanded={!collapsed} tabIndex={0}
                  onKeyDown={e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setOpenGroup(cur => cur === g.id ? null : g.id); } }}>
                  <span style={{ fontSize: 14, color: 'var(--text-dim)', transform: collapsed ? 'rotate(-90deg)' : 'rotate(0deg)', display: 'inline-block' }}>▾</span>
                  <span style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)' }}>{g.title}</span>
                  {activeInGroup > 0 && (
                    <span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', fontWeight: 700, padding: '1px 6px', borderRadius: 3, background: 'var(--ai-500)', color: '#fff' }}>{activeInGroup}</span>
                  )}
                  {collapsed && <span style={{ fontSize: 11, color: 'var(--text-dim)', fontStyle: 'italic', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{g.hint}</span>}
                </div>
                {activeInGroup > 1 && <LogicToggle val={logicOf(g.id)} onPick={(v) => setLogic(g.id, v)} />}
              </div>
              {!collapsed && (
                <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, minmax(0, 1fr))', gap: 8, paddingLeft: 22, paddingTop: 10 }}>
                  {g.chips.map(chip => (
                    <div key={chip.key} ref={el => { chipRef.current[chip.key] = el; }}>
                      <CRMFilterChip
                        label={chip.label} hint={chip.hint} value={chip.value} active={chip.active}
                        onClick={() => setOpenChip({ key: chip.key, anchor: chipRef.current[chip.key] })}
                        onClear={chip.active ? () => chip.onSelect(chip.multi ? [] : undefined) : undefined}
                      />
                      {openChip?.key === chip.key && (
                        <CRMFilterPopover
                          anchor={openChip.anchor}
                          onClose={() => setOpenChip(null)}
                          title={chip.label}
                          options={chip.options}
                          selected={chip.selectedValue}
                          onChange={chip.onSelect}
                          multi={!!chip.multi}
                          allowFallback={true}
                        />
                      )}
                    </div>
                  ))}
                </div>
              )}
            </div>
          );
        })}
      </div>
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════════════════
// AudienceBuilderModal — modal fullscreen para criar audiência com todos os filtros
// ═══════════════════════════════════════════════════════════════════════════
function AudienceBuilderModal({ meta, initialDef, initialName, initialDescricao, audienceId, campaignContext, onClose, onSaved, userEmail }) {
  // Splitter: extrai top-level fields (pais/equipa/stages) do initialDef e mete o resto em adv
  const parseInitial = (def) => {
    if (!def) return { pais: [], equipa: [], stages: [], comOp: false, wonSemOp: false, adv: {} };
    const advKeys = new Set([
      'tags','tags_mode','sector','dimensao','maturidade_digital',
      'consent_wa','consent_email','consent_tel','opt_rgpd',
      'em_lista','tem_notas_mkt','contactado_janela_dias','nunca_contactado',
      'sources','sources_grupo','preview',
      'profile_seg','profile_seg1','profile_seg2','profile_seg3',
      'cargo','cargo_contacto',
      // Ampliação Nova Audiência
      'produto','id_vendedor','id_gestor_op','valor_op_min','valor_op_max','op_criada_janela_dias',
      'op_status','op_fechada_janela_dias',
      'tenant',
      'cidade','zip_prefix','tem_website','tem_nif','tem_morada','busca_contacto',
      'tem_email','tem_telefone',
      'lost_produto','lost_janela',
      // Query builder logic
      '_logic',
    ]);
    const adv = {};
    // Se def.segmentos vier como objecto, distribui pelos 3 fields
    if (def.segmentos) {
      if (def.segmentos.sector)              adv.sector = def.segmentos.sector;
      if (def.segmentos.dimensao)             adv.dimensao = def.segmentos.dimensao;
      if (def.segmentos.maturidade_digital)   adv.maturidade_digital = def.segmentos.maturidade_digital;
    }
    for (const k of Object.keys(def)) {
      if (advKeys.has(k)) adv[k] = def[k];
    }
    return {
      pais: def.pais || [],
      equipa: def.equipa || [],
      stages: def.stages || [],
      comOp: !!def.com_op_activa,
      wonSemOp: !!def.won_sem_op_nova,
      adv,
    };
  };
  const init0 = parseInitial(initialDef);

  // Estado dos filtros — mesmo esquema que TabEntidades usa
  const [q, setQ] = React.useState('');
  const [pais, setPais]         = React.useState(init0.pais);
  const [equipa, setEquipa]     = React.useState(init0.equipa);
  const [stages, setStages]     = React.useState(init0.stages);
  const [comOp, setComOp]       = React.useState(init0.comOp);
  const [wonSemOp, setWonSemOp] = React.useState(init0.wonSemOp);
  const [lostJanela, setLostJanela] = React.useState(false);
  const [adv, setAdv]           = React.useState(init0.adv);
  const [origem, setOrigem]     = React.useState(initialDef?.origem === 'prospeccao' ? 'prospeccao' : 'gestor');
  const [prosp, setProsp]       = React.useState(() => prospFromDef(initialDef));
  const [prospOpcoes, setProspOpcoes] = React.useState(null);
  const [prospOpcoesErro, setProspOpcoesErro] = React.useState('');
  const origemLocked = !!audienceId;

  const [preview, setPreview]   = React.useState(null);
  const [loadingPreview, setLoadingPreview] = React.useState(false);
  const [nome, setNome]         = React.useState(initialName || '');
  const [nomeSugerido, setNomeSugerido] = React.useState(false);  // auto-fill state
  const [descricao, setDescricao] = React.useState(initialDescricao || '');
  const [showTemplates, setShowTemplates] = React.useState(false);
  const [saving, setSaving]     = React.useState(false);
  const [savedAud, setSavedAud] = React.useState(null);
  const [campanhas, setCampanhas]   = React.useState([]);
  const [campSel, setCampSel]       = React.useState(campaignContext?.id || '');
  const [pushing, setPushing]       = React.useState(false);
  const [pushResult, setPushResult] = React.useState(null);
  const [confirmPush, setConfirmPush] = React.useState(null);

  // AI segmentar
  const [aiTexto, setAiTexto]   = React.useState('');
  const [aiCanal, setAiCanal]   = React.useState('');
  const [aiLoading, setAiLoading] = React.useState(false);
  const [aiResult, setAiResult] = React.useState(null);

  // Construir `definicao` a partir do estado — inclui TUDO (bug crítico corrigido)
  const definicao = React.useMemo(() => {
    if (origem === 'prospeccao') return definicaoProspeccao(prosp);
    const d = {};
    if (q) d.q = q;
    if (pais?.length)   d.pais = pais;
    if (equipa?.length) d.equipa = equipa;
    if (stages?.length) d.stages = stages;
    if (comOp) d.com_op_activa = true;
    if (wonSemOp) d.won_sem_op_nova = true;
    // Advanced filters Fase A
    if (adv.tags?.length) d.tags = adv.tags;
    if (adv.tags_mode)    d.tags_mode = adv.tags_mode;
    // Segmentos combinados em objecto único (backend espera assim)
    const segs = {};
    if (adv.sector)              segs.sector = adv.sector;
    if (adv.dimensao)             segs.dimensao = adv.dimensao;
    if (adv.maturidade_digital)   segs.maturidade_digital = adv.maturidade_digital;
    if (Object.keys(segs).length) d.segmentos = segs;
    if (adv.consent_wa)    d.consent_wa = adv.consent_wa;
    if (adv.consent_email) d.consent_email = adv.consent_email;
    if (adv.consent_tel)   d.consent_tel = adv.consent_tel;
    if (adv.opt_rgpd)      d.opt_rgpd = adv.opt_rgpd;
    if (adv.em_lista?.length) d.em_lista = adv.em_lista;
    if (adv.tem_notas_mkt !== undefined) d.tem_notas_mkt = adv.tem_notas_mkt;
    if (adv.contactado_janela_dias) d.contactado_janela_dias = adv.contactado_janela_dias;
    if (adv.nunca_contactado) d.nunca_contactado = true;
    if (adv.sources?.length)  d.sources = adv.sources;
    if (adv.sources_grupo?.length) d.sources_grupo = adv.sources_grupo;
    // Ampliação Nova Audiência
    if (adv.profile_seg?.length)  d.profile_seg = adv.profile_seg;
    if (adv.profile_seg1?.length) d.profile_seg1 = adv.profile_seg1;
    if (adv.profile_seg2)         d.profile_seg2 = Array.isArray(adv.profile_seg2) ? adv.profile_seg2 : [adv.profile_seg2];
    if (adv.profile_seg3?.length) d.profile_seg3 = adv.profile_seg3;
    if (adv.lost_produto?.length) d.lost_produto = adv.lost_produto;
    if (adv.cargo_contacto?.length) d.cargo_contacto = adv.cargo_contacto;
    if (adv.produto?.length)      d.produto = adv.produto;
    if (adv.id_vendedor?.length)  d.id_vendedor = adv.id_vendedor;
    if (adv.id_gestor_op?.length) d.id_gestor_op = adv.id_gestor_op;
    if (adv.valor_op_min != null && adv.valor_op_min !== '') d.valor_op_min = adv.valor_op_min;
    if (adv.valor_op_max != null && adv.valor_op_max !== '') d.valor_op_max = adv.valor_op_max;
    if (adv.op_criada_janela_dias) d.op_criada_janela_dias = adv.op_criada_janela_dias;
    if (adv.op_status?.length) d.op_status = adv.op_status;
    if (adv.op_fechada_janela_dias) d.op_fechada_janela_dias = adv.op_fechada_janela_dias;
    if (adv.cidade?.length)   d.cidade = adv.cidade;
    if (adv.zip_prefix)       d.zip_prefix = adv.zip_prefix;
    if (adv.tem_website !== undefined) d.tem_website = adv.tem_website;
    if (adv.tem_nif !== undefined)     d.tem_nif = adv.tem_nif;
    if (adv.tem_morada !== undefined)  d.tem_morada = adv.tem_morada;
    if (adv.tenant?.length)   d.tenant = adv.tenant;
    if (adv.busca_contacto)   d.busca_contacto = adv.busca_contacto;
    if (adv.tem_email)        d.tem_email = adv.tem_email;
    if (adv.tem_telefone)     d.tem_telefone = adv.tem_telefone;
    if (adv.lost_janela)      d.lost_janela = adv.lost_janela;
    else if (lostJanela)      d.lost_janela = { min: 6, max: 24 };
    // Query builder — AND/OR entre grupos e dentro de cada grupo
    if (adv._logic && Object.keys(adv._logic).length > 0) d._logic = adv._logic;
    return d;
  }, [origem, prosp, q, pais, equipa, stages, comOp, wonSemOp, lostJanela, adv]);

  // Preview automático (debounce 400ms). Respostas atrasadas de outra base são ignoradas.
  React.useEffect(() => {
    let cancel = false;
    setLoadingPreview(true);
    const t = setTimeout(() => {
      CRMAPI.preview(definicao)
        .then(p => { if (!cancel) { setPreview(p); setLoadingPreview(false); } })
        .catch(() => { if (!cancel) setLoadingPreview(false); });
    }, 400);
    return () => { cancel = true; clearTimeout(t); };
  }, [JSON.stringify(definicao)]);

  // Campanhas activas (só as com estrategia aprovada)
  React.useEffect(() => {
    CRMAPI.campanhasActivas()
      .then(d => setCampanhas((d.campanhas || d || []).filter(c => c.estrategia_approved_at)))
      .catch(() => {});
  }, []);

  React.useEffect(() => {
    if (origem !== 'prospeccao' || prospOpcoes || prospOpcoesErro) return;
    let cancel = false;
    fetch('/api/marketing/crm/audiences/prospeccao/opcoes')
      .then(r => r.json().then(d => ({ ok: r.ok, d })))
      .then(({ ok, d }) => {
        if (cancel) return;
        if (!ok || d?.error) setProspOpcoesErro(d?.error || 'Não foi possível carregar as opções da prospeção.');
        else setProspOpcoes(d);
      })
      .catch(() => { if (!cancel) setProspOpcoesErro('Não foi possível carregar as opções da prospeção.'); });
    return () => { cancel = true; };
  }, [origem, prospOpcoes, prospOpcoesErro]);

  // ESC fecha
  React.useEffect(() => {
    const onKey = (ev) => { if (ev.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);

  const nFiltros = React.useMemo(() => {
    if (origem === 'prospeccao') return contarFiltrosProspeccao(prosp);
    let n = 0;
    if (q) n++;
    n += pais.length + equipa.length + stages.length;
    if (comOp) n++;
    if (wonSemOp) n++;
    if (adv.tags?.length) n++;
    if (adv.sector) n++;
    if (adv.dimensao) n++;
    if (adv.maturidade_digital) n++;
    if (adv.consent_wa) n++;
    if (adv.consent_email) n++;
    if (adv.consent_tel) n++;
    if (adv.opt_rgpd) n++;
    if (adv.em_lista?.length) n++;
    if (adv.tem_notas_mkt !== undefined) n++;
    if (adv.contactado_janela_dias) n++;
    if (adv.nunca_contactado) n++;
    if (adv.sources?.length) n++;
    if (adv.sources_grupo?.length) n++;
    if (adv.profile_seg?.length) n++;
    if (adv.profile_seg1?.length) n++;
    if (adv.profile_seg2) n++;
    if (adv.profile_seg3?.length) n++;
    if (adv.lost_produto?.length) n++;
    if (adv.cargo?.length) n++;
    if (adv.cargo_contacto?.length) n++;
    // Novos — Entidade + OP ampliado
    if (adv.produto?.length) n++;
    if (adv.id_vendedor?.length) n++;
    if (adv.id_gestor_op?.length) n++;
    if (adv.op_criada_janela_dias) n++;
    if (adv.op_status?.length) n++;
    if (adv.op_fechada_janela_dias) n++;
    if (adv.valor_op_min != null && adv.valor_op_min !== '') n++;
    if (adv.valor_op_max != null && adv.valor_op_max !== '') n++;
    if (adv.cidade?.length) n++;
    if (adv.zip_prefix) n++;
    if (adv.tem_website !== undefined) n++;
    if (adv.tem_nif !== undefined) n++;
    if (adv.tem_morada !== undefined) n++;
    if (adv.busca_contacto) n++;
    if (adv.tem_email) n++;
    if (adv.tem_telefone) n++;
    if (adv.tenant?.length) n++;
    return n;
  }, [origem, prosp, q, pais, equipa, stages, comOp, wonSemOp, adv]);

  const handleSave = async () => {
    if (!nome.trim()) return;
    setSaving(true);
    try {
      const body = { nome, descricao, definicao, created_by: userEmail };
      const aud = audienceId
        ? await CRMAPI.atualizarAudiencia(audienceId, body)
        : await CRMAPI.criarAudiencia(body);
      if (aud?.error) throw new Error(aud.error);
      setSavedAud(aud);
      if (onSaved) onSaved(aud);
      if (audienceId) onClose();
    } catch (e) {
      alert('Erro ao guardar: ' + e.message);
    } finally {
      setSaving(false);
    }
  };

  const handleSaveAndPush = async () => {
    if (!nome.trim() || !campSel) return;
    setSaving(true);
    try {
      const body = { nome, descricao, definicao, created_by: userEmail };
      const aud = audienceId
        ? await CRMAPI.atualizarAudiencia(audienceId, body)
        : await CRMAPI.criarAudiencia(body);
      if (aud?.error) throw new Error(aud.error);
      setSavedAud(aud);
      // Continua com push
      setPushing(true);
      const r = await CRMAPI.pushAudiencia(aud.id, { campanha_id: campSel, confirmar: false });
      if (r.requires_confirmation) {
        setConfirmPush(r);
        setPushing(false); setSaving(false);
        return;
      }
      if (r?.error || r?.erro || r?.ok === false) {
        setPushResult({ erro: r.error || r.erro || r.msg || 'Falhou' });
        setPushing(false); setSaving(false);
        return;
      }
      setPushResult(r);
      if (onSaved) onSaved(aud);
    } catch (e) {
      alert('Erro: ' + e.message);
    } finally {
      setPushing(false); setSaving(false);
    }
  };

  const handleConfirmPush = async () => {
    if (!savedAud) return;
    setPushing(true);
    try {
      const r = await CRMAPI.pushAudiencia(savedAud.id, { campanha_id: campSel, confirmar: true });
      if (r?.error || r?.erro || r?.ok === false) setPushResult({ erro: r.error || r.erro || r.msg || 'Falhou' });
      else setPushResult(r);
      setConfirmPush(null);
    } catch (e) {
      setPushResult({ erro: e.message });
    } finally {
      setPushing(false);
    }
  };

  // Aplicar template — reset + set dos filtros do template (Fix 2.3)
  const applyTemplate = (tpl) => {
    if (origem === 'prospeccao') {
      setProsp({ ...emptyProsp(), ...tpl.def });
      setNome(tpl.label);
      setNomeSugerido(false);
      setShowTemplates(false);
      return;
    }
    const parsed = parseInitial(tpl.def);
    setPais(parsed.pais); setEquipa(parsed.equipa); setStages(parsed.stages);
    setComOp(parsed.comOp); setWonSemOp(parsed.wonSemOp);
    setLostJanela(!!parsed.adv.lost_janela);
    setAdv(parsed.adv);
    setNome(tpl.label);
    setNomeSugerido(true);
    setShowTemplates(false);
  };

  // Auto-sugere nome quando filtros mudam (só se user não editou manualmente)
  React.useEffect(() => {
    if (!nomeSugerido) return;
    if (origem === 'prospeccao') {
      setNome(suggestProspeccaoNome(prosp));
      return;
    }
    const sugestao = suggestAudienceName(definicao, meta);
    if (sugestao !== 'Nova audiência') setNome(sugestao);
  }, [JSON.stringify(definicao), nomeSugerido, origem]);

  // Detecta quando user digita à mão — desactiva auto-suggest
  const handleNomeChange = (e) => {
    setNome(e.target.value);
    setNomeSugerido(false);
  };

  const handleSuggestName = () => {
    setNome(origem === 'prospeccao' ? suggestProspeccaoNome(prosp) : suggestAudienceName(definicao, meta));
    setNomeSugerido(true);
  };

  // Warnings para preview (Fix 2.2)
  const warnings = React.useMemo(() => {
    if (!preview) return [];
    const w = [];
    if (preview.error) {
      w.push({ level: 'error', txt: origem === 'prospeccao' ? String(preview.error) : 'Não foi possível calcular a segmentação. Tenta outra vez.' });
      return w;
    }
    const total = preview.contactos || 0;
    if (total === 0)   w.push({ level: 'error', txt: 'Sem contactos — filtros muito restritivos.' });
    else if (total < 5) w.push({ level: 'warn', txt: `Apenas ${total} contactos. Considera relaxar os filtros.` });
    else if (total > 5000) w.push({ level: 'warn', txt: `${total.toLocaleString('pt-PT')} contactos. Custo estimado WA ~${((total * 0.015)|0)}€ — considera dividir em waves.` });
    if (preview.contactos > 0) {
      const comTel = preview.com_telefone_ok != null ? preview.com_telefone_ok : (preview.com_telefone || 0);
      const pctSemTel = 1 - comTel / preview.contactos;
      if (pctSemTel > 0.3) w.push({ level: 'info', txt: `${(pctSemTel * 100)|0}% sem telefone — só campanhas email.` });
    }
    return w;
  }, [preview, origem]);

  const handleAI = async () => {
    if (!aiTexto.trim() || !aiCanal) return;
    setAiLoading(true); setAiResult(null);
    try {
      const r = await CRMAPI.aiSegment(aiTexto, userEmail, aiCanal);
      setAiResult(r);
      // Aplicar def sugerida automaticamente (se AI conseguiu)
      if (r.definicao && !r.impossivel) {
        if (r.definicao.pais)   setPais(Array.isArray(r.definicao.pais) ? r.definicao.pais : [r.definicao.pais]);
        if (r.definicao.equipa) setEquipa(Array.isArray(r.definicao.equipa) ? r.definicao.equipa : [r.definicao.equipa]);
        if (r.definicao.stages) setStages(Array.isArray(r.definicao.stages) ? r.definicao.stages : [r.definicao.stages]);
        if (r.definicao.com_op_activa != null) setComOp(!!r.definicao.com_op_activa);
        if (r.definicao.lost_janela) setLostJanela(true);
        const advPatch = {};
        for (const k of ['tags','tags_mode','sector','dimensao','maturidade_digital','consent_wa','consent_email','consent_tel','opt_rgpd','em_lista','tem_notas_mkt','contactado_janela_dias','nunca_contactado','sources','sources_grupo','profile_seg','profile_seg1','profile_seg2','profile_seg3','cargo_contacto','op_status','op_fechada_janela_dias','tem_email','tem_telefone','lost_janela','lost_produto','produto']) {
          if (r.definicao[k] !== undefined) advPatch[k] = r.definicao[k];
        }
        if (Object.keys(advPatch).length > 0) setAdv(a => ({ ...a, ...advPatch }));
      }
    } catch (e) {
      setAiResult({ erro: e.message });
    }
    setAiLoading(false);
  };

  return (
    <>
      {/* Backdrop */}
      <div onClick={onClose} style={{
        position: 'fixed', inset: 0, background: 'rgba(17,41,84,0.4)', zIndex: 500,
        animation: 'aud-fade 200ms ease-out',
      }}>
        <style>{`@keyframes aud-fade { from { opacity: 0; } to { opacity: 1; } }`}</style>
      </div>

      {/* Modal */}
      <div role="dialog" aria-label="Nova audiência" style={{
        position: 'fixed', top: '4vh', left: '50%', transform: 'translateX(-50%)',
        width: 'min(1200px, 94vw)', maxHeight: '92vh',
        background: 'var(--bg, #fff)', borderRadius: 12,
        boxShadow: '0 24px 64px rgba(17,41,84,0.24)',
        zIndex: 510, display: 'flex', flexDirection: 'column', overflow: 'hidden',
        animation: 'aud-in 250ms cubic-bezier(0.16, 1, 0.3, 1)',
      }}>
        <style>{`@keyframes aud-in { from { opacity: 0; transform: translate(-50%, -20px); } to { opacity: 1; transform: translate(-50%, 0); } }`}</style>

        {/* Header — com templates dropdown */}
        <div style={{ padding: '18px 24px 14px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexShrink: 0 }}>
          <div>
            <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '0.08em', textTransform: 'uppercase', marginBottom: 4 }}>Audiências · Segmentar</div>
            <h2 style={{ margin: 0, fontSize: 20, fontWeight: 700, fontFamily: 'var(--font-display)', color: 'var(--text)', letterSpacing: '-0.015em' }}>
              {audienceId ? 'Editar audiência' : 'Nova audiência'}
            </h2>
            {origem === 'prospeccao'
              ? (audienceEmpresa({ definicao, nome, descricao }) && <div style={{ marginTop: 6 }}><BrandMark mono brand={audienceEmpresa({ definicao, nome, descricao })} /></div>)
              : (audienceBrand({ nome, descricao }) && <div style={{ marginTop: 6 }}><BrandMark mono brand={audienceBrand({ nome, descricao })} /></div>)}
          </div>
          <div style={{ display: 'flex', gap: 8, alignItems: 'center', position: 'relative' }}>
            <button onClick={() => setShowTemplates(v => !v)}
              style={{ background: 'var(--bg-sunken)', border: '1px solid var(--border)', borderRadius: 6, padding: '7px 12px', fontSize: 12, fontWeight: 600, cursor: 'pointer', color: 'var(--text)', display: 'flex', alignItems: 'center', gap: 6 }}>
              <span>◇ Começar de template</span>
              <span style={{ fontSize: 9, color: 'var(--text-dim)' }}>▾</span>
            </button>
            {showTemplates && (
              <>
                <div onClick={() => setShowTemplates(false)} style={{ position: 'fixed', inset: 0, zIndex: 520 }} />
                <div style={{
                  position: 'absolute', top: 'calc(100% + 4px)', right: 0, minWidth: 320,
                  background: 'var(--bg-elev)', border: '1px solid var(--border)', borderRadius: 8,
                  boxShadow: '0 12px 32px rgba(17,41,84,0.14)', zIndex: 521, overflow: 'hidden',
                }}>
                  <div style={{ padding: '10px 14px 6px', fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '0.08em', textTransform: 'uppercase', fontWeight: 700, borderBottom: '1px solid var(--border)' }}>
                    Templates
                  </div>
                  {(origem === 'prospeccao' ? PROSP_TEMPLATES : AUDIENCE_TEMPLATES).map(tpl => (
                    <button key={tpl.id} onClick={() => applyTemplate(tpl)}
                      style={{ width: '100%', padding: '10px 14px', border: 'none', borderBottom: '1px solid var(--border-light, rgba(0,0,0,0.05))', background: 'transparent', cursor: 'pointer', textAlign: 'left', display: 'flex', gap: 10, alignItems: 'flex-start' }}
                      onMouseEnter={ev => ev.currentTarget.style.background = 'var(--bg-sunken)'}
                      onMouseLeave={ev => ev.currentTarget.style.background = 'transparent'}>
                      <span style={{ fontSize: 16, color: 'var(--ai-500)', flexShrink: 0, marginTop: -2 }}>{tpl.icon}</span>
                      <div style={{ minWidth: 0 }}>
                        <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text)' }}>{tpl.label}</div>
                        <div style={{ fontSize: 10, color: 'var(--text-muted)', marginTop: 2, lineHeight: 1.4 }}>{tpl.descricao}</div>
                      </div>
                    </button>
                  ))}
                </div>
              </>
            )}
            <button onClick={onClose} aria-label="Fechar"
              style={{ background: 'none', border: '1px solid var(--border)', borderRadius: 6, padding: '5px 10px', fontSize: 13, cursor: 'pointer', color: 'var(--text-muted)' }}>
              ✕
            </button>
          </div>
        </div>

        {/* Body — split filters | preview+save */}
        <style>{`
          .aud-builder-grid { display: grid; grid-template-columns: minmax(0, 1fr) 380px; flex: 1; overflow: hidden; min-height: 0; }
          @media (max-width: 1100px) {
            .aud-builder-grid { grid-template-columns: 1fr; overflow: auto; }
          }
        `}</style>
        <div className="aud-builder-grid">
          {/* Coluna esquerda — Filtros */}
          <div className="scrollbar" style={{ overflowY: 'auto', padding: '20px 24px', borderRight: '1px solid var(--border)' }}>
            <div style={{ marginBottom: 16 }}>
              <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '0.08em', textTransform: 'uppercase', fontWeight: 700, marginBottom: 8 }}>Base</div>
              <div style={{ display: 'flex', gap: 8 }}>
                {[{ id: 'gestor', label: 'Gestor' }, { id: 'prospeccao', label: 'Prospeção' }].map(opt => {
                  const on = origem === opt.id;
                  return (
                    <button key={opt.id} type="button" disabled={origemLocked && !on}
                      onClick={() => { if (!origemLocked && origem !== opt.id) { setLoadingPreview(true); setOrigem(opt.id); } }}
                      style={{ height: 32, padding: '0 14px', borderRadius: 8, border: `1px solid ${on ? '#0f766e' : 'var(--border)'}`, background: on ? '#0f766e' : '#fff', color: on ? '#fff' : 'var(--text)', fontSize: 12, fontWeight: 700, cursor: origemLocked && !on ? 'not-allowed' : 'pointer', opacity: origemLocked && !on ? 0.45 : 1 }}>
                      {opt.label}
                    </button>
                  );
                })}
              </div>
              {origem === 'prospeccao' && (
                <div style={{ marginTop: 8, fontSize: 11, color: 'var(--text-muted)', lineHeight: 1.45 }}>
                  Conta contactos da base de prospeção. Arquivados e opt-out ficam de fora. Sem estado escolhido, também ficam de fora descartadas, convertidas e quem já está no Gestor.
                </div>
              )}
            </div>
            {origem === 'prospeccao' ? (
              <>
                <div style={{ marginBottom: 12, display: 'flex', alignItems: 'baseline', justifyContent: 'space-between' }}>
                  <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '0.08em', textTransform: 'uppercase', fontWeight: 700 }}>
                    Filtros ({nFiltros})
                  </div>
                  {nFiltros > 0 && (
                    <button onClick={() => setProsp(emptyProsp())}
                      style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 11, color: 'var(--text-muted)' }}>
                      Limpar tudo
                    </button>
                  )}
                </div>
                <ProspeccaoAudienceFilters value={prosp} onChange={setProsp} opcoes={prospOpcoes} erro={prospOpcoesErro} />
              </>
            ) : (
            <>
            {/* AI segmentar — colapsado por default, feature secundária */}
            <details style={{ marginBottom: 14, padding: '8px 12px', borderRadius: 8, background: 'color-mix(in oklch, var(--ai-500) 3%, var(--bg-sunken))', border: '1px dashed color-mix(in oklch, var(--ai-500) 25%, var(--border))' }}>
              <summary style={{ fontSize: 11, fontFamily: 'var(--font-mono)', color: 'var(--ai-500)', letterSpacing: '0.06em', textTransform: 'uppercase', fontWeight: 700, cursor: 'pointer', listStyle: 'none', display: 'flex', alignItems: 'center', gap: 6 }}>
                <span>✨</span>
                <span>Descrever segmento em linguagem natural</span>
                <span style={{ color: 'var(--text-dim)', fontSize: 10, fontWeight: 400, marginLeft: 'auto', fontStyle: 'italic' }}>opcional</span>
              </summary>
              <div style={{ display: 'flex', gap: 8, marginTop: 10, alignItems: 'center' }}>
                <span style={{ fontSize: 11, color: 'var(--text-muted)' }}>Envio</span>
                {[{ id: 'email', label: 'Email' }, { id: 'whatsapp', label: 'WhatsApp' }].map(opt => (
                  <button key={opt.id} type="button" onClick={() => setAiCanal(opt.id)}
                    style={{ height: 28, padding: '0 10px', borderRadius: 99, border: `1px solid ${aiCanal === opt.id ? '#3859D0' : 'var(--border)'}`, background: aiCanal === opt.id ? '#3859D0' : '#fff', color: aiCanal === opt.id ? '#fff' : 'var(--text)', fontSize: 12, fontWeight: 600, cursor: 'pointer' }}>
                    {opt.label}
                  </button>
                ))}
              </div>
              <div style={{ display: 'flex', gap: 8, marginTop: 8 }}>
                <input
                  value={aiTexto} onChange={e => setAiTexto(e.target.value)}
                  onKeyDown={e => e.key === 'Enter' && handleAI()}
                  placeholder="Ex: impressão digital em Espanha"
                  disabled={aiLoading}
                  style={{ flex: 1, padding: '7px 10px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg-elev)', color: 'var(--text)', fontSize: 12 }}
                />
                <button onClick={handleAI} disabled={aiLoading || !aiTexto.trim() || !aiCanal}
                  style={{ padding: '7px 14px', borderRadius: 6, border: 'none', background: 'var(--ai-500)', color: '#fff', fontSize: 12, fontWeight: 600, cursor: 'pointer', opacity: aiLoading || !aiTexto.trim() || !aiCanal ? 0.5 : 1 }}>
                  Sugerir
                </button>
              </div>
              {!aiCanal && <div style={{ marginTop: 6, fontSize: 11, color: 'var(--text-muted)' }}>Escolhe Email ou WhatsApp para segmentar só contactos a que podemos enviar.</div>}
              {aiLoading && <div style={{ marginTop: 10 }}><AudCalcProgress message="A interpretar o pedido…" /></div>}
              {aiResult && !aiLoading && (
                <div style={{ marginTop: 8, fontSize: 11, color: aiResult.impossivel || aiResult.erro ? 'var(--danger)' : 'var(--text-muted)', lineHeight: 1.5 }}>
                  {aiResult.erro || aiResult.impossivel ? (aiResult.erro || aiResult.razao) : (
                    <>
                      <strong style={{ color: 'var(--text)' }}>{aiResult.explicacao_pt}</strong>
                      {aiResult.limitacoes?.length > 0 && <div style={{ marginTop: 4, color: '#d97706' }}>Limitações: {aiResult.limitacoes.join(' · ')}</div>}
                    </>
                  )}
                </div>
              )}
            </details>

            {/* Filtros manuais */}
            <div style={{ marginBottom: 12, display: 'flex', alignItems: 'baseline', justifyContent: 'space-between' }}>
              <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '0.08em', textTransform: 'uppercase', fontWeight: 700 }}>
                Filtros ({nFiltros})
              </div>
              {nFiltros > 0 && (
                <button onClick={() => { setQ(''); setPais([]); setEquipa([]); setStages([]); setComOp(false); setWonSemOp(false); setLostJanela(false); setAdv({}); }}
                  style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 11, color: 'var(--text-muted)' }}>
                  Limpar tudo
                </button>
              )}
            </div>

            <AdvancedFiltersPanel
              adv={adv} setAdv={setAdv}
              filteredCount={preview?.entidades || 0}
              meta={meta}
              pais={pais} setPais={setPais}
              equipa={equipa} setEquipa={setEquipa}
              stages={stages} setStages={setStages}
              comOp={comOp} setComOp={setComOp}
              wonSemOp={wonSemOp} setWonSemOp={setWonSemOp}
              lostJanela={lostJanela} setLostJanela={setLostJanela}
              onClearAll={() => { setQ(''); setPais([]); setEquipa([]); setStages([]); setComOp(false); setWonSemOp(false); setAdv({}); }}
            />
            </>
            )}
          </div>

          {/* Coluna direita — Preview + Save */}
          <div className="scrollbar" style={{ overflowY: 'auto', padding: '20px 24px', background: 'var(--bg-sunken)' }}>
            {/* Preview */}
            <div style={{ marginBottom: 20 }}>
              {audienceFilterSummary(definicao).length > 0 && (
                <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: 12 }}>
                  {audienceFilterSummary(definicao).map(chip => (
                    <span key={chip} style={{ fontSize: 10, fontFamily: 'var(--font-mono)', padding: '3px 8px', borderRadius: 99, background: 'var(--bg-elev)', border: '1px solid var(--border)', color: 'var(--text-muted)' }}>{chip}</span>
                  ))}
                </div>
              )}
              <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '0.08em', textTransform: 'uppercase', fontWeight: 700, marginBottom: 10 }}>
                Preview
              </div>
              {loadingPreview && <div style={{ marginBottom: 12 }}><AudCalcProgress /></div>}
              {preview && !loadingPreview && (
                <>
                  {/* Warnings visibility */}
                  {warnings.length > 0 && (
                    <div style={{ display: 'flex', flexDirection: 'column', gap: 5, marginBottom: 10 }}>
                      {warnings.map((w, i) => {
                        const color = w.level === 'error' ? '#dc2626' : w.level === 'warn' ? '#d97706' : '#0284c7';
                        return (
                          <div key={i} style={{ padding: '7px 10px', borderRadius: 6, background: `${color}10`, borderLeft: `3px solid ${color}`, fontSize: 11, lineHeight: 1.45, color: 'var(--text)' }}>
                            <span style={{ color, fontWeight: 700, marginRight: 6 }}>!</span>
                            {w.txt}
                          </div>
                        );
                      })}
                    </div>
                  )}
                  <div style={{ padding: '16px 18px', borderRadius: 8, background: 'var(--bg-elev)', border: '1px solid var(--border)', marginBottom: 10 }}>
                    <div style={{ fontSize: 32, fontWeight: 700, fontFamily: 'var(--font-display)', color: 'var(--ai-500)', letterSpacing: '-0.02em', lineHeight: 1 }}>
                      {(preview.contactos || 0).toLocaleString('pt-PT')}
                    </div>
                    <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 4, fontFamily: 'var(--font-mono)', textTransform: 'uppercase' }}>
                      contactos elegíveis
                    </div>
                    <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 8, marginTop: 12, fontSize: 11 }}>
                      <div>
                        <div style={{ color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>Entidades</div>
                        <div style={{ color: 'var(--text)', fontWeight: 700 }}>{(preview.entidades || 0).toLocaleString('pt-PT')}</div>
                      </div>
                      <div>
                        <div style={{ color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>Com email</div>
                        <div style={{ color: 'var(--success)', fontWeight: 700 }}>{(preview.com_email_ok ?? preview.com_email ?? 0).toLocaleString('pt-PT')}</div>
                      </div>
                      <div>
                        <div style={{ color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>Com telefone</div>
                        <div style={{ color: 'var(--success)', fontWeight: 700 }}>{(preview.com_telefone_ok ?? preview.com_telefone ?? 0).toLocaleString('pt-PT')}</div>
                      </div>
                    </div>
                  </div>
                  {preview.amostra?.length > 0 && (
                    <div>
                      <div style={{ fontSize: 9, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '0.05em', textTransform: 'uppercase', marginBottom: 6 }}>{origem === 'prospeccao' ? 'Amostra recente' : 'Amostra aleatória'}</div>
                      <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
                        {preview.amostra.slice(0, 5).map((c, i) => (
                          <div key={i} style={{ padding: '6px 8px', fontSize: 11, background: 'var(--bg-elev)', border: '1px solid var(--border)', borderRadius: 4 }}>
                            <div style={{ color: 'var(--text)', fontWeight: 500 }}>{c.nome || '(sem nome)'}</div>
                            <div style={{ color: 'var(--text-muted)', fontFamily: 'var(--font-mono)', fontSize: 10 }}>{c.entidade_nome} · {c.telefone}</div>
                          </div>
                        ))}
                      </div>
                    </div>
                  )}
                </>
              )}
            </div>

            {/* Save form */}
            <div style={{ padding: '14px 16px', borderRadius: 8, background: 'var(--bg-elev)', border: '1px solid var(--border)', marginBottom: 12 }}>
              <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
                <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '0.08em', textTransform: 'uppercase', fontWeight: 700 }}>
                  Guardar audiência
                </div>
                {nFiltros > 0 && (
                  <button onClick={handleSuggestName}
                    style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 10, color: 'var(--ai-500)', fontFamily: 'var(--font-mono)', fontWeight: 600, padding: 0 }}
                    title="Auto-sugerir nome baseado nos filtros">
                    ✨ Sugerir nome
                  </button>
                )}
              </div>
              <input
                value={nome} onChange={handleNomeChange}
                placeholder="Nome (obrigatório) — usa ✨ para auto-sugerir"
                style={{
                  width: '100%', padding: '8px 10px', borderRadius: 6,
                  border: nomeSugerido ? '1px solid var(--ai-500)' : '1px solid var(--border)',
                  background: nomeSugerido ? 'color-mix(in oklch, var(--ai-500) 4%, var(--bg))' : 'var(--bg)',
                  color: 'var(--text)', fontSize: 13, marginBottom: 8, boxSizing: 'border-box',
                }}
              />
              <textarea
                value={descricao} onChange={e => setDescricao(e.target.value)}
                placeholder="Descrição (opcional)"
                rows={2}
                style={{ width: '100%', padding: '8px 10px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg)', color: 'var(--text)', fontSize: 12, marginBottom: 10, resize: 'vertical', boxSizing: 'border-box' }}
              />
              <button onClick={handleSave} disabled={saving || !nome.trim() || savedAud}
                style={{
                  width: '100%', padding: '10px', borderRadius: 6, border: 'none',
                  background: savedAud ? 'var(--success)' : 'var(--ai-500)',
                  color: '#fff', fontSize: 13, fontWeight: 600, cursor: saving || !nome.trim() ? 'not-allowed' : 'pointer',
                  opacity: !nome.trim() && !savedAud ? 0.5 : 1, marginBottom: 6,
                }}>
                {saving ? 'A guardar...' : savedAud ? '✓ Audiência guardada' : (audienceId ? 'Guardar alterações' : 'Guardar audiência')}
              </button>
              {savedAud && (
                <div style={{ fontSize: 12, color: 'var(--success)', fontWeight: 600, textAlign: 'center', lineHeight: 1.4 }}>
                  Já está na lista. Fecha este ecrã para a ver.
                </div>
              )}
            </div>

            {/* Push para campanha */}
            <div style={{ padding: '14px 16px', borderRadius: 8, background: 'var(--bg-elev)', border: '1px solid var(--border)' }}>
              <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '0.08em', textTransform: 'uppercase', fontWeight: 700, marginBottom: 10 }}>
                Usar em campanha (opcional)
              </div>
              {campanhas.length === 0 ? (
                <div style={{ fontSize: 11, color: 'var(--text-muted)', fontStyle: 'italic' }}>Sem campanhas com estratégia aprovada disponíveis.</div>
              ) : (
                <>
                  <select value={campSel} onChange={e => setCampSel(e.target.value)}
                    style={{ width: '100%', padding: '8px 10px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg)', color: 'var(--text)', fontSize: 12, marginBottom: 10, boxSizing: 'border-box' }}>
                    <option value="">Escolher campanha...</option>
                    {campanhas.map(c => <option key={c.id} value={c.id}>{campanhaOptionLabel(c)}</option>)}
                  </select>
                  {campSel && (
                    <div style={{ marginTop: -4, marginBottom: 8 }}>
                      <BrandMark mono brand={brandFromCampaign(campanhas.find(c => String(c.id) === String(campSel)))} />
                    </div>
                  )}
                  <button onClick={savedAud ? handleConfirmPush : handleSaveAndPush}
                    disabled={pushing || !nome.trim() || !campSel}
                    style={{
                      width: '100%', padding: '9px', borderRadius: 6, border: '1px solid var(--ai-500)',
                      background: 'transparent', color: 'var(--ai-500)', fontSize: 12, fontWeight: 600,
                      cursor: pushing || !nome.trim() || !campSel ? 'not-allowed' : 'pointer',
                      opacity: !nome.trim() || !campSel ? 0.5 : 1,
                    }}>
                    {pushing ? 'A processar...' : savedAud ? 'Enviar para campanha' : 'Guardar e usar em campanha'}
                  </button>
                  {origem === 'prospeccao' && (
                    <div style={{ marginTop: 8, fontSize: 11, color: 'var(--text-muted)', lineHeight: 1.4 }}>
                      Grava os telemóveis desta audiência na campanha. O envio WhatsApp continua na activação.
                    </div>
                  )}
                </>
              )}

              {confirmPush && (
                <div style={{ marginTop: 10, padding: '10px 12px', borderRadius: 6, background: 'color-mix(in oklch, #d97706 8%, transparent)', border: '1px solid #d97706' }}>
                  <div style={{ fontSize: 11, color: 'var(--text)', marginBottom: 8, lineHeight: 1.5 }}>
                    {confirmPush.msg}
                    {confirmPush.excluidos_antidup > 0 && <div style={{ fontSize: 10, color: 'var(--text-muted)', marginTop: 4 }}>{confirmPush.excluidos_antidup} excluidos por anti-duplicado</div>}
                    {confirmPush.sem_telefone > 0 && <div style={{ fontSize: 10, color: 'var(--text-muted)' }}>{confirmPush.sem_telefone} sem telefone</div>}
                  </div>
                  <div style={{ display: 'flex', gap: 6 }}>
                    <button onClick={handleConfirmPush} style={{ flex: 1, padding: '6px 12px', borderRadius: 4, border: 'none', background: '#d97706', color: '#fff', fontSize: 11, fontWeight: 600, cursor: 'pointer' }}>Confirmar</button>
                    <button onClick={() => setConfirmPush(null)} style={{ flex: 1, padding: '6px 12px', borderRadius: 4, border: '1px solid var(--border)', background: 'transparent', color: 'var(--text-muted)', fontSize: 11, cursor: 'pointer' }}>Cancelar</button>
                  </div>
                </div>
              )}

              {pushResult && (
                <div style={{ marginTop: 10, padding: '10px 12px', borderRadius: 6, background: pushResult.erro ? 'color-mix(in oklch, var(--danger) 8%, transparent)' : 'color-mix(in oklch, var(--success) 8%, transparent)', border: `1px solid ${pushResult.erro ? 'var(--danger)' : 'var(--success)'}30`, fontSize: 11 }}>
                  {pushResult.erro
                    ? <span style={{ color: 'var(--danger)' }}>Erro: {pushResult.erro}</span>
                    : <span style={{ color: 'var(--success)' }}>{pushResult.modo === 'snapshot' ? `✓ ${pushResult.total} contactos gravados no WhatsApp da campanha. O envio faz-se na activação.` : `✓ ${pushResult.total} contactos enviados para a campanha.`}</span>}
                </div>
              )}
              {/* CTA "Continuar para Activação" quando push OK + vem de contexto de campanha */}
              {pushResult && !pushResult.erro && campaignContext && (
                <button
                  onClick={() => {
                    onClose();
                    window.location.hash = `#screen=marketing&sub=activacao&campanha=${campaignContext.id}`;
                  }}
                  className="btn-ai"
                  style={{ width: '100%', marginTop: 10, padding: '10px', fontSize: 12, fontWeight: 600 }}>
                  Continuar para Activação →
                </button>
              )}
            </div>

            {savedAud && !pushResult && !confirmPush && (
              <div style={{ marginTop: 12, textAlign: 'center' }}>
                <button onClick={onClose}
                  style={{ padding: '9px 20px', borderRadius: 6, border: '1px solid var(--border)', background: 'transparent', color: 'var(--text-muted)', fontSize: 12, fontWeight: 600, cursor: 'pointer' }}>
                  Fechar
                </button>
              </div>
            )}
          </div>
        </div>
      </div>
    </>
  );
}

// ── Toast de exportação (bottom-right, auto-dismiss) ────────────────────────
function ExportToast({ toast, onClose }) {
  const isErr = toast.variant === 'error';
  const accent = isErr ? 'var(--danger, #dc2626)' : 'var(--success, #16a34a)';
  return ReactDOM.createPortal(
    <div style={{
      position: 'fixed', right: 24, bottom: 24, zIndex: 20000,
      background: 'var(--bg-elev, #ffffff)', border: `1px solid ${accent}`, borderRadius: 10,
      boxShadow: '0 12px 32px rgba(17,41,84,0.16), 0 4px 8px rgba(17,41,84,0.08)',
      padding: '14px 16px', maxWidth: 360, minWidth: 300,
      animation: 'crm-toast-in 240ms cubic-bezier(0.16, 1, 0.3, 1)',
    }}>
      <style>{`@keyframes crm-toast-in { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }`}</style>
      <div style={{ display: 'flex', gap: 10, alignItems: 'flex-start' }}>
        <div style={{
          width: 22, height: 22, borderRadius: '50%', background: `${accent}20`,
          color: accent, display: 'flex', alignItems: 'center', justifyContent: 'center',
          fontSize: 14, fontWeight: 700, flexShrink: 0,
        }}>{isErr ? '!' : '✓'}</div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)', marginBottom: 2 }}>{toast.title}</div>
          {toast.subtitle && (
            <div style={{ fontSize: 11, color: 'var(--text-muted)', marginBottom: 6, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{toast.subtitle}</div>
          )}
          {toast.filename && (
            <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', background: 'var(--bg-sunken)', padding: '4px 8px', borderRadius: 4, marginBottom: 6, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
              ⬇ {toast.filename}
            </div>
          )}
          {toast.detalhes?.length > 0 && (
            <div style={{ marginTop: 4, fontSize: 11, color: 'var(--text-muted)' }}>
              {toast.detalhes.map((d, i) => (
                <div key={i} style={{ display: 'flex', justifyContent: 'space-between', gap: 8, padding: '2px 0' }}>
                  <span>{d.label}</span>
                  <span style={{ fontFamily: 'var(--font-mono)', color: 'var(--text)', fontWeight: 600 }}>{Number(d.value).toLocaleString('pt-PT')}</span>
                </div>
              ))}
            </div>
          )}
        </div>
        <button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-dim)', fontSize: 16, padding: 0, lineHeight: 1 }}>×</button>
      </div>
    </div>,
    document.body
  );
}

// ── Modal Contactos da Audiência (fullscreen, tabela paginada) ───────────────
function AudienceContactsModal({ audienciaId, audiencia, campanhas, onClose, onExportBrevo, exporting, onPushed, onEdit, embedded }) {
  const [loading, setLoading] = React.useState(true);
  const [data, setData] = React.useState(null); // { total, rows, limit, offset }
  const [stats, setStats] = React.useState(null);
  const [q, setQ] = React.useState('');
  const [canal, setCanal] = React.useState(''); // '' | 'ok_email' | 'ok_telefone' | 'rgpd_blocked'
  const [offset, setOffset] = React.useState(0);
  const [campSel, setCampSel] = React.useState('');
  const [pushing, setPushing] = React.useState(false);
  const [confirmPush, setConfirmPush] = React.useState(null);
  const [pushResult, setPushResult] = React.useState(null);
  const [limit, setLimit] = React.useState(50);
  const [lote, setLote] = React.useState(null);
  const [histAberto, setHistAberto] = React.useState(false);

  const handlePush = async (confirmar) => {
    if (!campSel) return;
    setPushing(true);
    try {
      const r = await CRMAPI.pushAudiencia(audienciaId, { campanha_id: campSel, confirmar: !!confirmar });
      if (r?.requires_confirmation) {
        setConfirmPush(r);
        setPushResult(null);
        return;
      }
      if (r?.error || r?.erro || r?.ok === false) {
        setPushResult({ erro: r.error || r.erro || r.msg || 'Falhou' });
        setConfirmPush(null);
        return;
      }
      setConfirmPush(null);
      setPushResult(r);
      if (onPushed) onPushed();
    } catch (e) {
      setPushResult({ erro: e.message });
    } finally {
      setPushing(false);
    }
  };

  const dq = useDebounce(q, 300);

  React.useEffect(() => {
    CRMAPI.audiencia(audienciaId).then(d => setStats(d.stats || {})).catch(() => {});
  }, [audienciaId]);

  React.useEffect(() => {
    setLoading(true);
    const params = { limit, offset };
    if (dq) params.q = dq;
    if (canal) params.canal = canal;
    if (lote?.run_id) params.run_id = lote.run_id;
    else if (lote?.dia) params.dia = lote.dia;
    CRMAPI.audienciaContactos(audienciaId, params)
      .then(d => { setData(d); setLoading(false); })
      .catch(() => setLoading(false));
  }, [audienciaId, dq, canal, offset, limit, lote]);

  React.useEffect(() => { setOffset(0); }, [dq, canal, limit, lote]);
  React.useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, [onClose]);

  const filterChip = (val, label, color) => (
    <button onClick={() => setCanal(canal === val ? '' : val)}
      style={{
        padding: '5px 10px', borderRadius: 5, fontSize: 11, fontWeight: 600, cursor: 'pointer',
        border: `1px solid ${canal === val ? color : 'var(--border)'}`,
        background: canal === val ? `color-mix(in oklch, ${color} 10%, transparent)` : 'var(--bg-elev, #ffffff)',
        color: canal === val ? color : 'var(--text-muted)',
        fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '.03em',
      }}>{label}</button>
  );

  const rgpdBadge = (val) => {
    if (val === '1') return <span title="Bloqueado pelo sistema (bounce)" style={{ fontSize: 9, fontWeight: 700, fontFamily: 'var(--font-mono)', padding: '2px 5px', borderRadius: 3, background: 'rgba(249,115,22,0.15)', color: '#c2410c', letterSpacing: '.04em' }}>SIST</span>;
    if (val === '2') return <span title="Cancelou a subscrição ou está em opt-out" style={{ fontSize: 9, fontWeight: 700, fontFamily: 'var(--font-mono)', padding: '2px 5px', borderRadius: 3, background: 'rgba(239,68,68,0.15)', color: '#b91c1c', letterSpacing: '.04em' }}>CLI</span>;
    const v = val == null ? '' : String(val).trim();
    if (v === '' || v === '0') return <span title="OK enviar" style={{ fontSize: 9, fontWeight: 700, fontFamily: 'var(--font-mono)', padding: '2px 5px', borderRadius: 3, background: 'rgba(22,163,74,0.12)', color: '#15803d', letterSpacing: '.04em' }}>ok</span>;
    return null;
  };

  const camposCrm = audienceCrmCampos(audiencia);
  const origemProsp = audDefinicao(audiencia).origem === 'prospeccao';
  const historico = data?.historico || [];
  const audFonte = (fonte) => ({ import: 'Ficheiro', apollo: 'Apollo', apify: 'Apify', pagina: 'Web Scrapping', manual: 'Manual', excel: 'Excel' }[fonte] || fonte || 'Prospeção');
  const audQuando = (iso) => {
    if (!iso) return '—';
    const d = new Date(iso);
    return Number.isNaN(d.getTime()) ? '—' : d.toLocaleString('pt-PT', { dateStyle: 'short', timeStyle: 'short' });
  };
  const loteIgual = (a, b) => !!a && !!b && String(a.run_id || '') === String(b.run_id || '') && String(a.dia || '') === String(b.dia || '');
  const total = data?.total || 0;
  const rows = data?.rows || [];
  const nPages = Math.ceil(total / limit);
  const curPage = Math.floor(offset / limit) + 1;

  const painel = (
    <div onClick={embedded ? undefined : onClose} style={embedded
      ? { display: 'flex', flexDirection: 'column', minHeight: '72vh', background: '#fff', border: '1px solid var(--border)', borderRadius: 8, overflow: 'hidden' }
      : { position: 'fixed', inset: 0, background: 'rgba(15,23,42,0.72)', backdropFilter: 'blur(2px)', WebkitBackdropFilter: 'blur(2px)', zIndex: 15000, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20 }}>
      <div onClick={e => e.stopPropagation()} style={embedded
        ? { display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0, background: '#fff' }
        : {
        background: 'var(--bg-elev, #ffffff)', borderRadius: 12, boxShadow: '0 24px 64px rgba(15,23,42,0.5)',
        width: '95vw', maxWidth: 1400, height: '92vh', display: 'flex', flexDirection: 'column',
        border: '1px solid var(--border)', overflow: 'hidden',
        animation: 'crm-modal-in 220ms cubic-bezier(0.16, 1, 0.3, 1)',
      }}>
        <style>{`@keyframes crm-modal-in { from { opacity: 0; transform: scale(0.98) translateY(8px); } to { opacity: 1; transform: scale(1) translateY(0); } }`}</style>

        {/* Header */}
        <div style={{ padding: '18px 24px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
          <div>
            <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '.08em', textTransform: 'uppercase', marginBottom: 4 }}>{audDefinicao(audiencia).origem === 'prospeccao' ? 'Audiências · Prospeção' : 'Audiências · Segmento'}</div>
            <div style={{ fontSize: 18, fontWeight: 700, color: 'var(--text)' }}>{audiencia?.nome || 'A carregar...'}</div>
            {audienceEmpresa(audiencia) && <div style={{ marginTop: 6 }}><BrandMark mono brand={audienceEmpresa(audiencia)} /></div>}
            {audiencia?.descricao && <div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 2 }}>{audiencia.descricao}</div>}
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap', justifyContent: 'flex-end' }}>
            <select value={campSel} onChange={e => { setCampSel(e.target.value); setConfirmPush(null); setPushResult(null); }}
              style={{ height: 30, padding: '0 10px', borderRadius: 6, border: '1px solid var(--border)', background: '#fff', color: 'var(--text)', fontSize: 12, maxWidth: 220 }}>
              <option value="">Campanha...</option>
              {campanhas.map(c => <option key={c.id} value={c.id}>{campanhaOptionLabel(c)}</option>)}
            </select>
            <button
              onClick={() => handlePush(false)}
              disabled={pushing || !campSel}
              title="Usar em campanha"
              style={{ height: 30, padding: '0 12px', borderRadius: 6, border: '1px solid var(--ai-500)', background: 'transparent', color: 'var(--ai-500)', fontWeight: 600, fontSize: 12, cursor: pushing || !campSel ? 'not-allowed' : 'pointer', opacity: !campSel ? 0.5 : 1 }}
            >{pushing ? 'A processar...' : 'Usar'}</button>
            <button
              onClick={() => audiencia && onExportBrevo(audiencia)}
              disabled={exporting === audienciaId}
              title="Exportar Brevo (CSV)"
              style={{ height: 30, padding: '0 12px', borderRadius: 6, border: '1px solid color-mix(in oklch, var(--success) 30%, transparent)', background: 'color-mix(in oklch, var(--success) 10%, transparent)', color: 'var(--success)', fontWeight: 600, fontSize: 12, cursor: 'pointer' }}
            >{exporting === audienciaId ? 'A exportar...' : 'Exportar'}</button>
            {origemProsp && (
              <button type="button" onClick={() => setHistAberto(true)} title="Histórico de contactos adicionados" style={{ height: 30, padding: '0 12px', borderRadius: 6, border: '1px solid #3859D0', background: '#fff', color: '#3859D0', fontSize: 12, fontWeight: 600, cursor: 'pointer' }}>Histórico</button>
            )}
            {onEdit && (
              <button onClick={() => onEdit(audiencia)} title="Editar segmentação" style={{ height: 30, padding: '0 12px', borderRadius: 6, border: '1px solid #3859D0', background: '#fff', color: '#3859D0', fontSize: 12, fontWeight: 600, cursor: 'pointer' }}>Editar</button>
            )}
            <button onClick={onClose} title={embedded ? 'Voltar' : 'Fechar'} style={{ background: 'none', border: '1px solid #e2e8f0', cursor: 'pointer', height: 30, padding: embedded ? '0 12px' : 0, width: embedded ? 'auto' : 30, borderRadius: 6, color: '#64748b', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: embedded ? 12 : 16, fontWeight: 600 }}>{embedded ? 'Voltar' : '×'}</button>
          </div>
        </div>

        {camposCrm.length > 0 && (
          <div style={{ padding: '12px 24px', borderBottom: '1px solid var(--border)', background: '#fff' }}>
            <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '.08em', textTransform: 'uppercase', fontWeight: 700, marginBottom: 8 }}>{audDefinicao(audiencia).origem === 'prospeccao' ? 'Segmentação · Prospeção' : 'Segmentação CRM'}</div>
            <div className="scrollbar" style={{ display: 'flex', flexWrap: 'wrap', gap: 8, maxHeight: 108, overflowY: 'auto' }}>
              {camposCrm.map(c => (
                <div key={c.label} style={{ display: 'flex', flexDirection: 'column', gap: 2, padding: '6px 10px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg-sunken, #f8fafc)', maxWidth: 520 }}>
                  <span style={{ fontSize: 9, fontFamily: 'var(--font-mono)', letterSpacing: '.06em', textTransform: 'uppercase', color: 'var(--text-dim)' }}>{c.label}</span>
                  <span style={{ fontSize: 12, color: 'var(--text)', lineHeight: 1.35 }}>{c.value}</span>
                </div>
              ))}
            </div>
          </div>
        )}

        {/* Stats + filtros */}
        <div style={{ padding: '14px 24px', borderBottom: '1px solid var(--border)', background: 'var(--bg-sunken)' }}>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: 12, marginBottom: 12 }}>
            {[
              { label: 'Contactos', val: stats?.total_contactos ?? audiencia?.contagem_ultima ?? 0, color: 'var(--ai-500)' },
              { label: 'Com email OK', val: stats?.com_email_ok || 0, color: 'var(--success)' },
              { label: 'Com telefone OK', val: stats?.com_telefone_ok || 0, color: 'var(--success)' },
              { label: 'RGPD blocked', val: stats?.rgpd_blocked || 0, color: 'var(--danger, #dc2626)' },
              { label: 'Entidades', val: stats?.entidades || 0, color: 'var(--text-muted)' },
            ].map((s, i) => (
              <div key={i} style={{ padding: '10px 12px', background: 'var(--bg-elev, #ffffff)', borderRadius: 6, border: '1px solid var(--border)' }}>
                <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '.05em', textTransform: 'uppercase', marginBottom: 2 }}>{s.label}</div>
                <div style={{ fontSize: 18, fontWeight: 700, color: s.color, fontFamily: 'var(--font-display)' }}>{Number(s.val).toLocaleString('pt-PT')}</div>
              </div>
            ))}
          </div>
          <div style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' }}>
            <input
              value={q} onChange={e => setQ(e.target.value)}
              placeholder="Pesquisar contacto, email, telefone ou entidade..."
              style={{ flex: 1, minWidth: 260, padding: '8px 12px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg-elev, #ffffff)', color: 'var(--text)', fontSize: 12 }}
            />
            {filterChip('', 'Todos', 'var(--text)')}
            {filterChip('ok_email', '✓ Email', 'var(--success)')}
            {filterChip('ok_telefone', '✓ Telefone', 'var(--success)')}
            {filterChip('rgpd_blocked', 'RGPD blocked', 'var(--danger, #dc2626)')}
            {lote && (
              <button type="button" onClick={() => setLote(null)} style={{ height: 30, padding: '0 10px', borderRadius: 6, border: '1px solid var(--ai-500)', background: 'color-mix(in oklch, var(--ai-500) 8%, #fff)', color: 'var(--ai-500)', fontSize: 12, fontWeight: 600, cursor: 'pointer' }}>Lote activo · limpar</button>
            )}
            <div style={{ marginLeft: 'auto', fontSize: 11, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>
              {loading ? '...' : `${total.toLocaleString('pt-PT')} contactos`}
            </div>
          </div>
        </div>

        {/* Tabela */}
        <div className="scrollbar" style={{ flex: 1, overflowY: 'auto', padding: '0 24px' }}>
          <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12.5 }}>
            <thead style={{ position: 'sticky', top: 0, background: 'var(--bg-elev, #ffffff)', zIndex: 1 }}>
              <tr>{['Nome', 'Entidade', 'Email', 'Telefone', 'Cargo', 'Cidade', 'Tenant', 'RGPD', ...(origemProsp ? ['Adicionado'] : [])].map(h => (
                <th key={h} style={{ fontSize: 10, fontWeight: 700, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', padding: '10px 8px', letterSpacing: '.06em', textTransform: 'uppercase', textAlign: 'left', borderBottom: '1px solid var(--border)' }}>{h}</th>
              ))}</tr>
            </thead>
            <tbody>
              {loading && (
                <tr><td colSpan={origemProsp ? 9 : 8} style={{ padding: 30, textAlign: 'center', color: 'var(--text-dim)' }}>A carregar...</td></tr>
              )}
              {!loading && rows.length === 0 && (
                <tr><td colSpan={origemProsp ? 9 : 8} style={{ padding: 30, textAlign: 'center', color: 'var(--text-dim)' }}>Sem contactos com estes filtros</td></tr>
              )}
              {!loading && rows.map(c => (
                <tr key={c.fm_id} style={{ borderBottom: '1px solid var(--border-light, rgba(0,0,0,0.05))' }}>
                  <td style={{ padding: '8px', fontWeight: 500 }}>{c.nome || '—'}</td>
                  <td style={{ padding: '8px', color: 'var(--text-muted)' }}>{c.entidade_nome || '—'}</td>
                  <td style={{ padding: '8px', color: c.email ? 'var(--text)' : 'var(--text-dim)', fontFamily: 'var(--font-mono)', fontSize: 11 }}>{c.email || '—'}</td>
                  <td style={{ padding: '8px', color: c.telefone ? 'var(--text)' : 'var(--text-dim)', fontFamily: 'var(--font-mono)', fontSize: 11 }}>{c.telefone || '—'}</td>
                  <td style={{ padding: '8px', color: 'var(--text-muted)' }}>{c.cargo || '—'}</td>
                  <td style={{ padding: '8px', color: 'var(--text-muted)' }}>{c.cidade || '—'}</td>
                  <td style={{ padding: '8px' }}><span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', padding: '2px 6px', borderRadius: 3, background: 'rgba(29,78,216,.08)', color: '#1d4ed8', fontWeight: 700 }}>{c.tenant || '—'}</span></td>
                  <td style={{ padding: '8px' }}>{rgpdBadge(c.opt_rgpd)}</td>
                  {origemProsp && <td style={{ padding: '8px', color: 'var(--text-muted)', whiteSpace: 'nowrap' }}>{audQuando(c.criado_em)}</td>}
                </tr>
              ))}
            </tbody>
          </table>
        </div>

        {/* Footer — paginação + acções */}
        <div style={{ padding: '12px 24px', borderTop: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            <button disabled={offset === 0} onClick={() => setOffset(Math.max(0, offset - limit))}
              style={{ padding: '5px 10px', borderRadius: 5, border: '1px solid var(--border)', background: 'var(--bg-card)', cursor: offset === 0 ? 'not-allowed' : 'pointer', opacity: offset === 0 ? 0.4 : 1, fontSize: 12 }}>← Anterior</button>
            <span style={{ fontSize: 11, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>Página {curPage} / {nPages || 1}</span>
            <button disabled={offset + limit >= total} onClick={() => setOffset(offset + limit)}
              style={{ padding: '5px 10px', borderRadius: 5, border: '1px solid var(--border)', background: 'var(--bg-card)', cursor: offset + limit >= total ? 'not-allowed' : 'pointer', opacity: offset + limit >= total ? 0.4 : 1, fontSize: 12 }}>Seguinte →</button>
            <select value={limit} onChange={e => setLimit(Number(e.target.value))}
              style={{ height: 30, padding: '0 8px', borderRadius: 5, border: '1px solid var(--border)', background: 'var(--bg-elev, #ffffff)', color: 'var(--text)', fontSize: 12 }}>
              {[50, 100, 200, 500].map(n => <option key={n} value={n}>{n} / página</option>)}
            </select>
          </div>
          {(confirmPush || pushResult) && (
            <div style={{ flexBasis: '100%', padding: '10px 12px', borderRadius: 6, background: pushResult?.erro ? 'color-mix(in oklch, var(--danger) 8%, transparent)' : 'var(--bg-sunken)', border: '1px solid var(--border)', fontSize: 12, lineHeight: 1.5 }}>
              {confirmPush && !pushResult && (
                <>
                  <div style={{ marginBottom: 8 }}>{confirmPush.msg}</div>
                  {confirmPush.excluidos_antidup > 0 && <div style={{ color: 'var(--text-muted)' }}>{confirmPush.excluidos_antidup} excluídos por anti-duplicado (14 dias)</div>}
                  {confirmPush.sem_telefone > 0 && <div style={{ color: 'var(--text-muted)' }}>{confirmPush.sem_telefone} sem telefone</div>}
                  <div style={{ display: 'flex', gap: 6, marginTop: 8 }}>
                    <button onClick={() => handlePush(true)} disabled={pushing} style={{ padding: '6px 12px', borderRadius: 4, border: 'none', background: '#d97706', color: '#fff', fontSize: 11, fontWeight: 600, cursor: 'pointer' }}>Confirmar</button>
                    <button onClick={() => setConfirmPush(null)} style={{ padding: '6px 12px', borderRadius: 4, border: '1px solid var(--border)', background: 'transparent', color: 'var(--text-muted)', fontSize: 11, cursor: 'pointer' }}>Cancelar</button>
                  </div>
                </>
              )}
              {pushResult && (
                pushResult.erro
                  ? <span style={{ color: 'var(--danger)' }}>{pushResult.erro}</span>
                  : <span>{pushResult.modo === 'snapshot' ? 'Gravados' : 'Enviados'} {Number(pushResult.total || 0).toLocaleString('pt-PT')} contactos{pushResult.modo === 'snapshot' ? ' na campanha' : ''}{pushResult.excluidos_antidup ? ` · ${pushResult.excluidos_antidup} excluídos (14 dias)` : ''}{pushResult.sem_telefone ? ` · ${pushResult.sem_telefone} sem telefone` : ''}.</span>
              )}
            </div>
          )}
        </div>
      </div>
      {histAberto && (
        <div onClick={e => { e.stopPropagation(); setHistAberto(false); }} style={{ position: 'fixed', inset: 0, zIndex: 16000, background: 'rgba(15,23,42,0.55)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24 }}>
          <div onClick={e => e.stopPropagation()} style={{ width: 'min(640px, 100%)', maxHeight: 'min(72vh, 640px)', overflow: 'auto', background: '#fff', borderRadius: 12, border: '1px solid var(--border)', boxShadow: '0 24px 64px rgba(15,23,42,0.35)', padding: '18px 20px' }}>
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, marginBottom: 14 }}>
              <div style={{ fontSize: 16, fontWeight: 700, color: 'var(--text)' }}>Histórico de contactos adicionados</div>
              <button type="button" onClick={() => setHistAberto(false)} style={{ height: 30, padding: '0 12px', borderRadius: 6, border: '1px solid #e2e8f0', background: '#fff', color: '#64748b', fontSize: 12, fontWeight: 600, cursor: 'pointer' }}>Fechar</button>
            </div>
            {!historico.length && <div style={{ fontSize: 13, color: 'var(--text-muted)' }}>Ainda não há contactos adicionados nesta audiência.</div>}
            <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
              {historico.map(h => {
                const activo = loteIgual(lote, h);
                const origem = [audFonte(h.fonte), h.referencia].filter(Boolean).join(' · ');
                return (
                  <button key={`${h.run_id || 'dia'}-${h.dia || h.quando}`} type="button" onClick={() => { setLote(activo ? null : { run_id: h.run_id, dia: h.dia }); setHistAberto(false); }}
                    style={{ display: 'flex', justifyContent: 'space-between', gap: 12, textAlign: 'left', padding: '10px 12px', borderRadius: 8, cursor: 'pointer', border: `1px solid ${activo ? 'var(--ai-500)' : 'var(--border)'}`, background: activo ? 'color-mix(in oklch, var(--ai-500) 8%, #fff)' : '#fff', color: 'var(--text)', font: 'inherit' }}>
                    <span style={{ fontSize: 13, lineHeight: 1.4 }}>{audQuando(h.quando)} · {origem}</span>
                    <strong style={{ whiteSpace: 'nowrap' }}>{Number(h.contactos).toLocaleString('pt-PT')} {Number(h.contactos) === 1 ? 'contacto' : 'contactos'}</strong>
                  </button>
                );
              })}
            </div>
          </div>
        </div>
      )}
    </div>
  );
  return embedded ? painel : ReactDOM.createPortal(painel, document.body);
}

// ── Tab Audiencias ─────────────────────────────────────────────────────────
function AudFilterDropdown({ label, value, options, onChange }) {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => {
    if (!open) return undefined;
    const onClick = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', onClick);
    return () => document.removeEventListener('mousedown', onClick);
  }, [open]);
  const current = options.find(o => o.value === value) || options[0];
  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <button type="button" onClick={() => setOpen(v => !v)} style={{
        background: 'var(--bg-elev, #ffffff)', color: 'var(--text, #112954)',
        border: '1px solid var(--border, #dde3ef)', borderRadius: 6,
        padding: '6px 10px', fontSize: 11.5,
        display: 'inline-flex', alignItems: 'center', gap: 8, cursor: 'pointer', fontFamily: 'inherit',
      }}>
        <span style={{ color: 'var(--text-dim, #475569)', fontSize: 10, textTransform: 'uppercase', letterSpacing: '.06em', fontFamily: 'var(--font-mono, monospace)' }}>{label}</span>
        <span style={{ fontWeight: 500 }}>{current?.label || '—'}</span>
        <span style={{ color: 'var(--text-muted, #64748b)', fontSize: 9 }}>▾</span>
      </button>
      {open && (
        <div style={{
          position: 'absolute', top: 'calc(100% + 4px)', left: 0, zIndex: 50,
          background: 'var(--bg-elev, #ffffff)', border: '1px solid var(--border, #dde3ef)', borderRadius: 6,
          boxShadow: '0 8px 24px rgba(17,41,84,0.12)', minWidth: 180, padding: 4,
        }}>
          {options.map(o => (
            <button key={o.value} type="button" onClick={() => { onChange(o.value); setOpen(false); }} style={{
              display: 'block', width: '100%', textAlign: 'left',
              background: o.value === value ? 'color-mix(in oklch, var(--ai-500, #3859D0) 12%, transparent)' : 'transparent',
              color: o.value === value ? 'var(--ai-500, #3859D0)' : 'var(--text, #112954)',
              border: 'none', padding: '7px 10px', borderRadius: 4,
              fontSize: 12, fontWeight: o.value === value ? 600 : 500, cursor: 'pointer', fontFamily: 'inherit',
            }}>{o.label}</button>
          ))}
        </div>
      )}
    </div>
  );
}

function AudienceRowMenu({ a, onOpen, onEdit, onDuplicate, onExport, onArchive, onDelete, exporting, eliminando, arquivando }) {
  const [open, setOpen] = React.useState(false);
  const [pos, setPos] = React.useState({ top: 0, right: 0 });
  const menuRef = React.useRef(null);
  const btnRef = React.useRef(null);
  const dropdownRef = React.useRef(null);
  React.useEffect(() => {
    if (!open) return undefined;
    const close = (e) => {
      const inMenu = menuRef.current && menuRef.current.contains(e.target);
      const inDrop = dropdownRef.current && dropdownRef.current.contains(e.target);
      if (!inMenu && !inDrop) setOpen(false);
    };
    document.addEventListener('mousedown', close);
    return () => document.removeEventListener('mousedown', close);
  }, [open]);
  const item = { display: 'block', width: '100%', textAlign: 'left', background: 'transparent', border: 'none', padding: '8px 12px', borderRadius: 4, fontSize: 13, color: 'var(--text, #283252)', cursor: 'pointer', fontFamily: 'inherit', whiteSpace: 'nowrap' };
  const hover = e => { e.currentTarget.style.background = 'rgba(0,0,0,0.04)'; };
  const leave = e => { e.currentTarget.style.background = 'transparent'; };
  const run = (fn) => { setOpen(false); fn(); };
  return (
    <div ref={menuRef} style={{ display: 'inline-block' }} onClick={e => e.stopPropagation()}>
      <button ref={btnRef} type="button" aria-label="Acções" onClick={() => {
        if (!open && btnRef.current) {
          const r = btnRef.current.getBoundingClientRect();
          setPos({ top: r.bottom + 4, right: window.innerWidth - r.right });
        }
        setOpen(v => !v);
      }}
        style={{
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          width: 28, height: 28, borderRadius: 6, cursor: 'pointer',
          background: open ? 'rgba(0,0,0,0.06)' : 'transparent',
          border: 'none', outline: 'none', color: 'var(--text-muted)', fontSize: 16, lineHeight: 1,
        }}
        onMouseEnter={e => { if (!open) e.currentTarget.style.background = 'rgba(0,0,0,0.06)'; }}
        onMouseLeave={e => { if (!open) e.currentTarget.style.background = 'transparent'; }}
      >⋮</button>
      {open && ReactDOM.createPortal(
        <div ref={dropdownRef} style={{
          position: 'fixed', top: pos.top, right: pos.right, zIndex: 9999,
          background: '#ffffff', border: '1px solid var(--border, #ECEFF5)',
          borderRadius: 8, minWidth: 176, padding: 4,
          boxShadow: '0 4px 16px rgba(17,41,84,0.10)',
        }}>
          <button type="button" style={item} onMouseEnter={hover} onMouseLeave={leave} onClick={() => run(onOpen)}>Ver contactos</button>
          {onEdit && <button type="button" style={item} onMouseEnter={hover} onMouseLeave={leave} onClick={() => run(() => onEdit(a))}>Editar</button>}
          {onDuplicate && <button type="button" style={item} onMouseEnter={hover} onMouseLeave={leave} onClick={() => run(() => onDuplicate(a))}>Duplicar</button>}
          <button type="button" style={item} disabled={exporting} onMouseEnter={hover} onMouseLeave={leave} onClick={() => run(() => onExport(a))}>{exporting ? 'A exportar...' : 'Exportar Brevo'}</button>
          {onArchive && (
            <button type="button" style={item} disabled={arquivando} onMouseEnter={hover} onMouseLeave={leave} onClick={() => run(() => onArchive(a))}>
              {arquivando ? 'A actualizar...' : (a.arquivado_at ? 'Restaurar' : 'Arquivar')}
            </button>
          )}
          <div style={{ height: 1, background: 'var(--border, #ECEFF5)', margin: '4px 0' }} />
          <button type="button" style={{ ...item, color: 'var(--danger, #DC2626)' }} disabled={eliminando}
            onMouseEnter={e => { e.currentTarget.style.background = 'rgba(220,38,38,0.06)'; }}
            onMouseLeave={leave}
            onClick={() => run(() => onDelete(a.id))}>Eliminar</button>
        </div>,
        document.body
      )}
    </div>
  );
}

function AudIcone({ children, onClick, activo, label }) {
  return (
    <button type="button" aria-label={label} onMouseDown={e => e.stopPropagation()} onClick={onClick} style={{
      width: 30, height: 30, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
      border: `1px solid ${activo ? 'var(--ai-500)' : 'var(--border)'}`, borderRadius: 6,
      background: activo ? 'color-mix(in oklch, var(--ai-500) 10%, #fff)' : '#fff',
      color: activo ? 'var(--ai-500)' : 'var(--text)', cursor: 'pointer', padding: 0,
    }}>{children}</button>
  );
}

function AudMenu({ onCreate }) {
  const [open, setOpen] = React.useState(false);
  const [foco, setFoco] = React.useState(-1);
  const ref = React.useRef(null);
  const items = onCreate ? [{ id: 'nova', label: 'Nova audiência', sub: 'Segmentar contactos para uma campanha', onClick: onCreate }] : [];
  React.useEffect(() => {
    if (!open) return undefined;
    const onClick = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    const onKey = (e) => { if (e.key === 'Escape') setOpen(false); };
    document.addEventListener('mousedown', onClick);
    document.addEventListener('keydown', onKey);
    return () => { document.removeEventListener('mousedown', onClick); document.removeEventListener('keydown', onKey); };
  }, [open]);
  if (!items.length) return null;
  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <AudIcone label="Menu" activo={open} onClick={() => { setOpen(v => !v); setFoco(-1); }}>
        <svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"><path d="M2.5 4h11M2.5 8h11M2.5 12h11" /></svg>
      </AudIcone>
      {open && (
        <div role="menu" style={{
          position: 'absolute', top: 'calc(100% + 6px)', right: 0, zIndex: 70,
          background: '#fff', border: '1px solid var(--border)', borderRadius: 8,
          boxShadow: '0 12px 32px rgba(17,41,84,0.14)', minWidth: 260, padding: 4,
        }}>
          {items.map((it, i) => (
            <button key={it.id} type="button" role="menuitem"
              onClick={() => { setOpen(false); it.onClick(); }}
              onMouseEnter={() => setFoco(i)} onMouseLeave={() => setFoco(-1)}
              style={{
                display: 'flex', alignItems: 'center', gap: 8, width: '100%', textAlign: 'left',
                background: foco === i ? 'color-mix(in oklch, var(--ai-500) 10%, #fff)' : 'transparent',
                color: foco === i ? 'var(--ai-500)' : 'var(--text)',
                border: 'none', padding: '8px 10px', borderRadius: 6, cursor: 'pointer', fontFamily: 'inherit',
              }}>
              <span style={{ flex: 1, minWidth: 0 }}>
                <span style={{ display: 'block', fontSize: 13, fontWeight: 650 }}>{it.label}</span>
                {it.sub && <span style={{ display: 'block', fontSize: 11, color: 'var(--text-muted)', fontWeight: 400, marginTop: 1 }}>{it.sub}</span>}
              </span>
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

function AudCheck({ checked, indeterminate, onChange, label }) {
  const ref = React.useRef(null);
  React.useEffect(() => { if (ref.current) ref.current.indeterminate = !!indeterminate; }, [indeterminate, checked]);
  return (
    <input
      ref={ref}
      type="checkbox"
      aria-label={label}
      checked={!!checked}
      onChange={onChange}
      style={{ width: 15, height: 15, accentColor: 'var(--ai-500, #3859D0)', cursor: 'pointer' }}
    />
  );
}

function TabAudiencias({ meta, refreshPing, onCreateNew, onDuplicate, onEdit, onLibrary, marcaFiltro = 'todas', onMarca }) {
  const [audiencias, setAudiencias] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  window.useMktZoneLoading(loading && audiencias == null);
  const [openId, setOpenId] = React.useState(null); // audiência aberta em modal fullscreen
  const [qLista, setQLista] = React.useState('');
  const [busca, setBusca] = React.useState(false);
  const [usoFiltro, setUsoFiltro] = React.useState('todas');
  const [segmentoFiltro, setSegmentoFiltro] = React.useState('');
  const [canalFiltro, setCanalFiltro] = React.useState('');
  const [campanhaFiltro, setCampanhaFiltro] = React.useState('');
  const [sort, setSort] = React.useState({ col: 'nome', dir: 'asc' });
  const [campanhas, setCampanhas] = React.useState([]);
  const [eliminando, setEliminando] = React.useState(null);
  const [exporting, setExporting] = React.useState(null);
  const [estadoLista, setEstadoLista] = React.useState('activas');
  const [seleccionadas, setSeleccionadas] = React.useState(() => new Set());
  const [accaoLote, setAccaoLote] = React.useState(null);
  const [arquivandoId, setArquivandoId] = React.useState(null);
  const [toast, setToast] = React.useState(null); // { title, subtitle, filename, detalhes: [], variant: 'success'|'error' }

  const load = () => {
    setLoading(true);
    CRMAPI.audiencias(estadoLista).then(d => {
      const rows = Array.isArray(d) ? d : [];
      setAudiencias(rows);
      setSeleccionadas(prev => {
        const vivos = new Set(rows.map(r => r.id));
        return new Set([...prev].filter(id => vivos.has(id)));
      });
      if (onLibrary) onLibrary(rows);
      setLoading(false);
    }).catch(() => setLoading(false));
  };
  React.useEffect(() => { load(); }, [refreshPing, estadoLista]);

  React.useEffect(() => {
    CRMAPI.campanhasActivas()
      .then(d => setCampanhas((d.campanhas || d || []).filter(c => c.estrategia_approved_at)))
      .catch(() => {});
  }, []);

  const handleEliminar = async (id) => {
    if (!confirm('Eliminar esta audiencia?')) return;
    setEliminando(id);
    try {
      const r = await CRMAPI.eliminarAudiencia(id);
      if (r?.error) {
        alert(r.error);
        return;
      }
      if (openId === id) setOpenId(null);
      load();
    } catch (e) {
      alert(e.message || 'Não foi possível eliminar a audiência.');
    } finally {
      setEliminando(null);
    }
  };

  const showToast = (t) => {
    setToast(t);
    setTimeout(() => setToast(null), 6000);
  };

  const aplicarAccao = async (ids, acao) => {
    if (!ids.length) return null;
    setAccaoLote(acao);
    try {
      const r = await CRMAPI.audienciasAccao(ids, acao);
      if (r?.error) {
        alert(r.error);
        return r;
      }
      if (acao === 'eliminar' && r?.bloqueadas?.length) {
        const nomes = r.bloqueadas.slice(0, 4).map(b => b.nome).filter(Boolean).join(', ');
        showToast({
          variant: 'error',
          title: `${r.eliminadas || 0} eliminadas, ${r.bloqueadas.length} mantidas`,
          subtitle: nomes ? `Ligadas a campanhas: ${nomes}` : 'Estão ligadas a campanhas. Arquiva para as tirar da lista.',
        });
      } else {
        const n = acao === 'eliminar' ? (r?.eliminadas || 0) : (r?.alteradas || ids.length);
        const verbo = acao === 'eliminar'
          ? (n === 1 ? 'eliminada' : 'eliminadas')
          : acao === 'restaurar'
            ? (n === 1 ? 'restaurada' : 'restauradas')
            : (n === 1 ? 'arquivada' : 'arquivadas');
        showToast({ variant: 'success', title: `${n} ${n === 1 ? 'audiência' : 'audiências'} ${verbo}` });
      }
      setSeleccionadas(new Set());
      load();
      return r;
    } catch (e) {
      alert(e.message || 'Não foi possível actualizar as audiências.');
      return null;
    } finally {
      setAccaoLote(null);
    }
  };

  const handleArquivarUma = async (a) => {
    const restaurar = !!a.arquivado_at;
    if (!restaurar && !window.confirm(`Arquivar "${a.nome}"?\nDeixa de aparecer na lista e nos selectores. As campanhas que já a usam mantêm-se.`)) return;
    setArquivandoId(a.id);
    try { await aplicarAccao([a.id], restaurar ? 'restaurar' : 'arquivar'); }
    finally { setArquivandoId(null); }
  };

  const handleBulkArquivar = () => {
    const ids = [...seleccionadas];
    if (!ids.length || accaoLote) return;
    const restaurar = estadoLista === 'arquivadas';
    if (!restaurar && !window.confirm(`Arquivar ${ids.length} ${ids.length === 1 ? 'audiência' : 'audiências'}?\nDeixam de aparecer na lista e nos selectores. As campanhas que já as usam mantêm-se.`)) return;
    aplicarAccao(ids, restaurar ? 'restaurar' : 'arquivar');
  };

  const handleBulkEliminar = () => {
    const ids = [...seleccionadas];
    if (!ids.length || accaoLote) return;
    if (!window.confirm(`Eliminar ${ids.length} ${ids.length === 1 ? 'audiência' : 'audiências'}?\nAs que estão ligadas a campanhas ficam. Para as tirar da lista sem as apagar, arquiva.`)) return;
    aplicarAccao(ids, 'eliminar');
  };

  const toggleSel = (id) => {
    setSeleccionadas(prev => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id);
      else next.add(id);
      return next;
    });
  };

  const handleExportBrevo = async (audiencia) => {
    setExporting(audiencia.id);
    try {
      const definicao = typeof audiencia.definicao === 'string' ? JSON.parse(audiencia.definicao) : (audiencia.definicao || {});
      const r = await fetch('/api/marketing/crm/audiencia/export-brevo', {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ filters: definicao })
      });
      if (!r.ok) {
        const err = await r.json().catch(() => ({ error: r.statusText }));
        showToast({
          variant: 'error',
          title: 'Exportação falhou',
          subtitle: err.error || 'export falhou',
          detalhes: err.audit ? [
            { label: 'RGPD blocked', value: err.audit.rgpd_blocked_excluidos },
            { label: 'Sem email', value: err.audit.sem_email_excluidos },
          ] : [],
        });
        return;
      }
      const total = parseInt(r.headers.get('X-Audience-Total') || '0');
      const blocked = parseInt(r.headers.get('X-Rgpd-Blocked-Excluded') || '0');
      const noEmail = parseInt(r.headers.get('X-No-Email-Excluded') || '0');
      const blob = await r.blob();
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      const slug = (audiencia.nome || 'audiencia').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 40);
      const filename = `brevo-${slug}-${new Date().toISOString().slice(0,10)}.csv`;
      a.href = url; a.download = filename;
      a.click(); URL.revokeObjectURL(url);
      showToast({
        variant: 'success',
        title: `${total.toLocaleString('pt-PT')} contactos exportados`,
        subtitle: audiencia.nome,
        filename,
        detalhes: [
          { label: 'RGPD blocked excluídos', value: blocked },
          { label: 'Sem email válido excluídos', value: noEmail },
        ],
      });
    } finally {
      setExporting(null);
    }
  };

  const thS = { textAlign: 'left', padding: '8px 12px', fontSize: 10, fontFamily: 'var(--font-display)', fontWeight: 600, letterSpacing: '0.10em', textTransform: 'uppercase', color: 'var(--text-muted)', whiteSpace: 'nowrap', position: 'sticky', top: 0, background: 'var(--bg-elev)', zIndex: 2 };
  const tdS = { padding: '10px 12px', borderBottom: '1px solid var(--border)', overflow: 'hidden' };
  const fmtAudDate = (iso) => {
    if (!iso) return '—';
    const d = new Date(iso);
    if (Number.isNaN(d.getTime())) return '—';
    const dd = String(d.getDate()).padStart(2, '0');
    const mm = String(d.getMonth() + 1).padStart(2, '0');
    const yy = String(d.getFullYear()).slice(-2);
    return `${dd}/${mm}/${yy}`;
  };

  const daMarca = (audiencias || []).filter(a => marcaFiltro === 'todas' || audienceMarcas(a).some(b => b.slug === marcaFiltro));
  const usadas = daMarca.filter(a => (a.n_pushes || 0) > 0).length;
  const nunca = daMarca.filter(a => !(a.n_pushes > 0)).length;
  const opcoesDe = (vals) => [...new Set(vals.filter(Boolean))].sort((a, b) => a.localeCompare(b, 'pt'));
  const segmentosOp = opcoesDe(daMarca.map(audienceSegmento));
  const canaisOp = opcoesDe(daMarca.map(audienceCanal));
  const campanhasOp = opcoesDe(daMarca.map(a => a.campanha_titulo));
  const qNorm = qLista.trim().toLowerCase();
  const toggleSort = (col) => setSort(s => ({ col, dir: s.col === col && s.dir === 'asc' ? 'desc' : 'asc' }));
  const ordemValor = sort.col === 'contactos' ? 'contactos' : sort.col === 'actualizado' ? 'recentes' : 'nome';
  const setOrdem = (v) => {
    if (v === 'contactos') setSort({ col: 'contactos', dir: 'desc' });
    else if (v === 'recentes') setSort({ col: 'actualizado', dir: 'desc' });
    else setSort({ col: 'nome', dir: 'asc' });
  };
  const visiveis = daMarca.filter(a => {
    if (qNorm && !`${a.nome || ''} ${a.descricao || ''}`.toLowerCase().includes(qNorm)) return false;
    if (usoFiltro === 'usadas' && !(a.n_pushes > 0)) return false;
    if (usoFiltro === 'nunca' && (a.n_pushes > 0)) return false;
    if (segmentoFiltro === '__sem__' && audienceSegmento(a)) return false;
    if (segmentoFiltro && segmentoFiltro !== '__sem__' && audienceSegmento(a) !== segmentoFiltro) return false;
    if (canalFiltro === '__sem__' && audienceCanal(a)) return false;
    if (canalFiltro && canalFiltro !== '__sem__' && audienceCanal(a) !== canalFiltro) return false;
    if (campanhaFiltro === '__sem__' && a.campanha_titulo) return false;
    if (campanhaFiltro && campanhaFiltro !== '__sem__' && a.campanha_titulo !== campanhaFiltro) return false;
    return true;
  }).slice().sort((a, b) => {
    const dir = sort.dir === 'asc' ? 1 : -1;
    if (sort.col === 'contactos') return ((a.contagem_ultima || 0) - (b.contagem_ultima || 0)) * dir;
    if (sort.col === 'actualizado') {
      const ta = new Date(a.refreshed_at || a.created_at || 0).getTime();
      const tb = new Date(b.refreshed_at || b.created_at || 0).getTime();
      return (ta - tb) * dir;
    }
    return String(a.nome || '').localeCompare(String(b.nome || ''), 'pt') * dir;
  });
  const filtroActivo = !!qNorm || marcaFiltro !== 'todas' || usoFiltro !== 'todas' || !!segmentoFiltro || !!canalFiltro || !!campanhaFiltro || estadoLista !== 'activas';
  const idsVisiveis = visiveis.map(a => a.id);
  const todosMarcados = idsVisiveis.length > 0 && idsVisiveis.every(id => seleccionadas.has(id));
  const algunsMarcados = idsVisiveis.some(id => seleccionadas.has(id)) && !todosMarcados;
  const marcarVisiveis = () => {
    setSeleccionadas(prev => {
      const next = new Set(prev);
      if (todosMarcados) idsVisiveis.forEach(id => next.delete(id));
      else idsVisiveis.forEach(id => next.add(id));
      return next;
    });
  };
  const limparFiltros = () => {
    setQLista(''); setBusca(false); setUsoFiltro('todas'); setSegmentoFiltro(''); setCanalFiltro(''); setCampanhaFiltro(''); setEstadoLista('activas'); setSeleccionadas(new Set());
    if (onMarca) onMarca('todas');
  };

  const totalN = daMarca.length;
  const kpiCards = [
    { label: 'Total', value: totalN, accent: '#3859D0', filter: 'todas' },
    { label: 'Usadas', value: usadas, accent: '#15803d', filter: 'usadas' },
    { label: 'Nunca usadas', value: nunca, accent: '#b45309', filter: 'nunca' },
  ];

  const SortTh = ({ label, col }) => (
    <th style={{ ...thS, cursor: 'pointer', userSelect: 'none' }} onClick={() => toggleSort(col)}>
      <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}>
        {label}
        {sort.col === col && window.Icon && <window.Icon name={sort.dir === 'asc' ? 'arrowUp' : 'arrowDown'} size={9} style={{ color: 'var(--ai-500)' }} />}
      </span>
    </th>
  );

  if (openId) {
    const audAberta = (audiencias || []).find(a => a.id === openId);
    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
        <AudienceContactsModal
          embedded
          audienciaId={openId}
          audiencia={audAberta}
          campanhas={campanhas}
          onClose={() => setOpenId(null)}
          onEdit={onEdit}
          onExportBrevo={handleExportBrevo}
          exporting={exporting}
          onPushed={load}
        />
        {toast && <ExportToast toast={toast} onClose={() => setToast(null)} />}
      </div>
    );
  }

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
      {loading && (
        <div>
          {[1, 2, 3, 4].map(i => (
            <div key={i} style={{ height: 44, borderRadius: 6, background: 'var(--bg-sunken, #eef1f6)', marginBottom: 6, opacity: 1 - i * 0.2 }} />
          ))}
        </div>
      )}
      {!loading && audiencias && (
        <>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
            <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>
              <AudFilterDropdown label="Lista" value={estadoLista} onChange={(v) => { setEstadoLista(v); setSeleccionadas(new Set()); }} options={[
                { value: 'activas', label: 'Activas' },
                { value: 'arquivadas', label: 'Arquivadas' },
              ]} />
              <AudFilterDropdown label="Uso" value={usoFiltro} onChange={setUsoFiltro} options={[
                { value: 'todas', label: 'Todas' },
                { value: 'usadas', label: 'Usadas' },
                { value: 'nunca', label: 'Nunca usadas' },
              ]} />
              <AudFilterDropdown label="Segmento" value={segmentoFiltro} onChange={setSegmentoFiltro} options={[
                { value: '', label: 'Todos' },
                { value: '__sem__', label: 'Sem segmento' },
                ...segmentosOp.map(s => ({ value: s, label: s })),
              ]} />
              <AudFilterDropdown label="Canal" value={canalFiltro} onChange={setCanalFiltro} options={[
                { value: '', label: 'Todos' },
                { value: '__sem__', label: 'Sem canal' },
                ...canaisOp.map(s => ({ value: s, label: s })),
              ]} />
              <AudFilterDropdown label="Campanha" value={campanhaFiltro} onChange={setCampanhaFiltro} options={[
                { value: '', label: 'Todas' },
                { value: '__sem__', label: 'Sem campanha' },
                ...campanhasOp.map(s => ({ value: s, label: s })),
              ]} />
              <AudFilterDropdown label="Ordem" value={ordemValor} onChange={setOrdem} options={[
                { value: 'nome', label: 'Nome' },
                { value: 'contactos', label: 'Contactos' },
                { value: 'recentes', label: 'Recentes' },
              ]} />
            </div>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginLeft: 'auto', position: 'relative' }}>
              <AudIcone label="Procurar audiência" activo={busca || !!qNorm} onClick={() => setBusca(v => !v)}>
                <svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"><circle cx="7" cy="7" r="4.25" /><path d="M10.5 10.5L13.5 13.5" /></svg>
              </AudIcone>
              <AudMenu onCreate={onCreateNew} />
              {busca && (
                <div style={{ position: 'absolute', top: 'calc(100% + 6px)', right: 0, zIndex: 40, background: '#fff', border: '1px solid var(--border)', borderRadius: 8, padding: 8, width: 260, boxShadow: '0 12px 32px rgba(17,41,84,0.14)' }}>
                  <input
                    autoFocus
                    value={qLista}
                    onChange={e => setQLista(e.target.value)}
                    onKeyDown={e => { if (e.key === 'Escape') setBusca(false); }}
                    placeholder="Procurar audiência"
                    style={{ width: '100%', padding: '7px 10px', borderRadius: 6, border: '1px solid var(--border)', background: '#fff', color: 'var(--text)', fontSize: 13, boxSizing: 'border-box', fontFamily: 'inherit' }}
                  />
                </div>
              )}
            </div>
          </div>

          <div style={{ display: 'flex', gap: 12 }}>
            {kpiCards.map(k => {
              const isActive = usoFiltro === k.filter;
              return (
                <div key={k.filter} onClick={() => setUsoFiltro(k.filter)}
                  style={{
                    flex: '1 1 0', background: '#fff',
                    border: `1px solid ${isActive ? k.accent : 'var(--border)'}`,
                    borderRadius: 8, padding: '12px 16px 0',
                    display: 'flex', flexDirection: 'column', gap: 4, overflow: 'hidden', cursor: 'pointer',
                  }}>
                  <div style={{ fontSize: 9.5, fontWeight: 600, letterSpacing: '0.10em', textTransform: 'uppercase', color: isActive ? k.accent : 'var(--text-muted)', fontFamily: 'var(--font-mono)', whiteSpace: 'nowrap' }}>{k.label}</div>
                  <div style={{ fontSize: 22, fontWeight: 700, fontFamily: 'var(--font-display)', color: k.accent, lineHeight: 1, paddingBottom: 10 }}>{Number(k.value).toLocaleString('pt-PT')}</div>
                  <div style={{ height: 3, background: 'var(--bg-sunken)', overflow: 'hidden' }}>
                    <div style={{ height: '100%', width: '100%', background: k.accent }} />
                  </div>
                </div>
              );
            })}
          </div>
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap' }}>
            <div style={{ fontSize: 13, color: 'var(--text-muted)' }}>{visiveis.length.toLocaleString('pt-PT')} audiência{visiveis.length !== 1 ? 's' : ''}</div>
            {seleccionadas.size > 0 && (
              <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
                <span style={{ fontSize: 12, fontWeight: 650, color: 'var(--text)' }}>{seleccionadas.size} seleccionada{seleccionadas.size === 1 ? '' : 's'}</span>
                <button type="button" className="btn" disabled={!!accaoLote} onClick={handleBulkArquivar} style={{ height: 30, padding: '0 12px', fontSize: 12 }}>
                  {accaoLote === 'arquivar' || accaoLote === 'restaurar' ? 'A actualizar...' : (estadoLista === 'arquivadas' ? 'Restaurar' : 'Arquivar')}
                </button>
                <button type="button" disabled={!!accaoLote} onClick={handleBulkEliminar} style={{ height: 30, padding: '0 12px', fontSize: 12, borderRadius: 6, border: '1px solid #fecaca', background: '#fff', color: 'var(--danger, #dc2626)', cursor: accaoLote ? 'default' : 'pointer', fontFamily: 'inherit' }}>
                  {accaoLote === 'eliminar' ? 'A eliminar...' : 'Eliminar'}
                </button>
                <button type="button" className="btn" disabled={!!accaoLote} onClick={() => setSeleccionadas(new Set())} style={{ height: 30, padding: '0 12px', fontSize: 12 }}>Limpar</button>
              </div>
            )}
          </div>

          {visiveis.length === 0 ? (
            <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8, color: 'var(--text-muted)', marginTop: 18 }}>
              <div style={{ fontSize: 14 }}>{audiencias.length === 0 ? (estadoLista === 'arquivadas' ? 'Não há audiências arquivadas.' : 'Ainda não há audiências.') : 'Sem resultados para os filtros actuais.'}</div>
              {audiencias.length === 0 && estadoLista !== 'arquivadas' && onCreateNew && (
                <button className="btn btn-ai" style={{ height: 30, padding: '0 14px', fontSize: 12 }} onClick={onCreateNew}>+ Nova Audiência</button>
              )}
              {(audiencias.length > 0 || filtroActivo) && (
                <button className="btn" style={{ height: 30, padding: '0 14px', fontSize: 12 }} onClick={limparFiltros}>Limpar filtros</button>
              )}
            </div>
          ) : (
            <div style={{ overflowX: 'auto', width: '100%', paddingBottom: 48 }}>
            <table style={{ width: 1944, tableLayout: 'fixed', borderCollapse: 'collapse', fontSize: 12.5 }}>
              <colgroup>
                <col style={{ width: 44 }} />
                <col style={{ width: 480 }} />
                <col style={{ width: 110 }} />
                <col style={{ width: 120 }} />
                <col style={{ width: 180 }} />
                <col style={{ width: 120 }} />
                <col style={{ width: 240 }} />
                <col style={{ width: 140 }} />
                <col style={{ width: 140 }} />
                <col style={{ width: 110 }} />
                <col style={{ width: 72 }} />
              </colgroup>
              <thead style={{ position: 'sticky', top: 0, zIndex: 2, background: 'var(--bg-elev)' }}>
                <tr style={{ borderBottom: '1px solid var(--border)' }}>
                  <th style={{ ...thS, textAlign: 'center', width: 44 }}>
                    <AudCheck checked={todosMarcados} indeterminate={algunsMarcados} onChange={marcarVisiveis} label="Seleccionar audiências visíveis" />
                  </th>
                  <SortTh label="Nome" col="nome" />
                  <SortTh label="Contactos" col="contactos" />
                  <th style={thS}>Marca</th>
                  <th style={thS}>Segmento</th>
                  <th style={thS}>Canal</th>
                  <th style={thS}>Campanha</th>
                  <th style={thS}>Tenant</th>
                  <th style={thS}>Uso</th>
                  <SortTh label="Actualizado" col="actualizado" />
                  <th style={{ ...thS, textAlign: 'center' }}>Acções</th>
                </tr>
              </thead>
              <tbody>
                {visiveis.map(a => {
                  const empresa = audienceEmpresa(a);
                  const segmento = audienceSegmento(a);
                  const canal = audienceCanal(a);
                  const usada = (a.n_pushes || 0) > 0;
                  const marcada = seleccionadas.has(a.id);
                  return (
                    <tr
                      key={a.id}
                      onClick={() => setOpenId(a.id)}
                      style={{ cursor: 'pointer', background: marcada ? 'color-mix(in oklch, var(--ai-500) 8%, #fff)' : 'transparent', transition: 'background 150ms ease' }}
                      onMouseEnter={ev => { if (!marcada) ev.currentTarget.style.background = 'var(--bg-hover)'; }}
                      onMouseLeave={ev => { ev.currentTarget.style.background = marcada ? 'color-mix(in oklch, var(--ai-500) 8%, #fff)' : 'transparent'; }}
                    >
                      <td style={{ ...tdS, textAlign: 'center' }} onClick={e => e.stopPropagation()}>
                        <AudCheck checked={marcada} onChange={() => toggleSel(a.id)} label={`Seleccionar ${a.nome || 'audiência'}`} />
                      </td>
                      <td style={{ ...tdS, overflow: 'visible', whiteSpace: 'normal' }}>
                        <div style={{ fontWeight: 500, color: 'var(--text)', fontSize: 13, whiteSpace: 'normal', lineHeight: 1.35 }}>{a.nome}</div>
                        {audDefinicao(a).origem === 'prospeccao' && (
                          <div style={{ marginTop: 3, fontSize: 9, fontFamily: 'var(--font-mono)', fontWeight: 700, letterSpacing: '0.06em', color: '#0f766e' }}>PROSPEÇÃO</div>
                        )}
                        {a.arquivado_at && (
                          <div style={{ marginTop: 3, fontSize: 9, fontFamily: 'var(--font-mono)', fontWeight: 700, letterSpacing: '0.06em', color: '#b45309' }}>ARQUIVADA</div>
                        )}
                      </td>
                      <td style={tdS}>
                        <span style={{ fontSize: 12, fontFamily: 'var(--font-mono)', fontWeight: 700, color: 'var(--text)' }}>{(a.contagem_ultima ?? 0).toLocaleString('pt-PT')}</span>
                      </td>
                      <td style={tdS}>
                        {audienceMarcas(a).length
                          ? audienceMarcas(a).map(b => <div key={b.slug}><BrandMark brand={b} /></div>)
                          : <BrandMark brand={empresa} />}
                      </td>
                      <td style={tdS}>
                        {segmento
                          ? <span style={{ fontSize: 12, color: 'var(--text-muted)', display: 'block', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{segmento}</span>
                          : <span style={{ color: 'var(--text-dim)', fontSize: 12 }}>—</span>}
                      </td>
                      <td style={tdS}>
                        {canal
                          ? <span style={{ fontSize: 12, color: 'var(--text)', whiteSpace: 'nowrap' }}>{canal}</span>
                          : <span style={{ color: 'var(--text-dim)', fontSize: 12 }}>—</span>}
                      </td>
                      <td style={tdS}>
                        {a.campanha_titulo
                          ? <span style={{ fontSize: 12, color: 'var(--text)', display: 'block', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{a.campanha_titulo}</span>
                          : <span style={{ color: 'var(--text-dim)', fontSize: 12 }}>—</span>}
                      </td>
                      <td style={tdS}>
                        <div style={{ display: 'flex', flexWrap: 'nowrap', alignItems: 'center' }}>
                          {audienceTenants(a).map((t, i, arr) => (
                            <React.Fragment key={t}>
                              <span style={{ fontSize: 12, color: 'var(--text)', whiteSpace: 'nowrap' }}>{tenantNome(t)}</span>
                              {i < arr.length - 1 && <span style={{ color: 'var(--text-dim)', margin: '0 4px' }}>·</span>}
                            </React.Fragment>
                          ))}
                        </div>
                      </td>
                      <td style={tdS}>
                        <div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
                          <span style={{ fontSize: 11, fontFamily: 'var(--font-mono)', fontWeight: 600, padding: '2px 8px', borderRadius: 4, display: 'inline-block', width: 'fit-content', color: usada ? 'var(--ai-500, #3859D0)' : 'var(--text-muted, #4a5e7a)', background: usada ? 'color-mix(in srgb, #3859D0 16%, transparent)' : 'var(--bg-sunken, #eef1f6)' }}>{usada ? 'USADA' : 'NUNCA'}</span>
                          {usada && <span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)' }}>{a.n_pushes} campanha{a.n_pushes > 1 ? 's' : ''}</span>}
                        </div>
                      </td>
                      <td style={tdS}>
                        <span style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--text-muted)' }}>{fmtAudDate(a.refreshed_at || a.created_at)}</span>
                      </td>
                      <td style={{ ...tdS, textAlign: 'right', overflow: 'visible' }}>
                        <AudienceRowMenu
                          a={a}
                          onOpen={() => setOpenId(a.id)}
                          onEdit={onEdit}
                          onDuplicate={onDuplicate}
                          onExport={handleExportBrevo}
                          onArchive={handleArquivarUma}
                          arquivando={arquivandoId === a.id}
                          onDelete={handleEliminar}
                          exporting={exporting === a.id}
                          eliminando={eliminando === a.id}
                        />
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
            </div>
          )}
        </>
      )}

      {/* Toast */}
      {toast && <ExportToast toast={toast} onClose={() => setToast(null)} />}
    </div>
  );
}

// ── useDebounce ────────────────────────────────────────────────────────────
function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = React.useState(value);
  React.useEffect(() => {
    const handler = setTimeout(() => setDebouncedValue(value), delay);
    return () => clearTimeout(handler);
  }, [value, delay]);
  return debouncedValue;
}

// ═══════════════════════════════════════════════════════════════════════════
// PÁGINA DE PERFIL DA ENTIDADE (A4)
// ═══════════════════════════════════════════════════════════════════════════
function EntidadeProfile({ fmId, tenant, onBack, onOpenContacto, onOpenOp, userEmail, userName }) {
  const [data, setData] = React.useState(null);
  const [kpis, setKpis] = React.useState(null);
  const [signals, setSignals] = React.useState(null);
  const [score, setScore] = React.useState(null);
  const [apreciacao, setApreciacao] = React.useState(null);
  const [tags, setTags] = React.useState([]);
  const [segs, setSegs] = React.useState([]);
  const [loading, setLoading] = React.useState(true);

  const loadAll = React.useCallback(() => {
    setLoading(true);
    Promise.all([
      CRMAPI.ent360(fmId, tenant),
      CRMAPI.entTags(fmId),
      CRMAPI.entSegs(fmId),
      CRMAPI.entKpis(fmId, tenant).catch(() => null),
      CRMAPI.entApreciacao(fmId, tenant).catch(() => null),
      CRMAPI.entSignals(fmId).catch(() => null),
      CRMAPI.entScore(fmId).catch(() => null),
    ]).then(([d, ts, ss, k, a, sig, sc]) => {
      setData(d); setTags(ts || []); setSegs(ss || []);
      setKpis(k); setApreciacao(a); setSignals(sig); setScore(sc);
      setLoading(false);
    }).catch(() => setLoading(false));
  }, [fmId, tenant]);

  React.useEffect(() => { loadAll(); }, [loadAll]);

  if (loading || !data) {
    return (
      <div style={{ padding: 40, textAlign: 'center', color: 'var(--text-muted)', fontSize: 13 }}>
        A carregar perfil...
        <div style={{ marginTop: 20 }}>
          <button className="btn btn-xs" onClick={onBack}>Voltar</button>
        </div>
      </div>
    );
  }

  const e = data.entidade;
  const subtitle = [
    e.nif    ? <span key="nif"    style={{ fontFamily: 'var(--font-mono)' }}>NIF {e.nif}</span> : null,
    e.cidade ? <span key="cidade">{e.cidade}</span> : null,
    e.pais   ? <span key="pais">{e.pais}</span>   : null,
    e.website ? <a key="w" href={e.website.startsWith('http') ? e.website : 'https://' + e.website} target="_blank" rel="noopener noreferrer" style={{ color: 'var(--ai-500)' }}>{e.website}</a> : null,
  ].filter(Boolean);

  // Quick actions bar (top sticky centro) — enable/disable com base em signals
  const primaryEmail = data.contactos?.find(c => c.email)?.email;
  const primaryTel   = data.contactos?.find(c => c.telefone)?.telefone;
  const quickActions = [
    { icon: '✉', label: 'Email', tooltip: 'Enviar email ao contacto principal',
      disabled: !primaryEmail, onClick: () => primaryEmail && (window.location.href = `mailto:${primaryEmail}`) },
    { icon: '📞', label: 'Ligar', tooltip: 'Chamada telefónica',
      disabled: !primaryTel, onClick: () => primaryTel && (window.location.href = `tel:${primaryTel}`) },
    { icon: '💬', label: 'WhatsApp', tooltip: 'Abrir WA (contacto principal)',
      disabled: !primaryTel, onClick: () => primaryTel && window.open(`https://wa.me/${primaryTel.replace(/\D/g,'')}`) },
    { icon: '✎', label: 'Nota', primary: true, tooltip: 'Registar nota marketing',
      onClick: () => document.querySelector('[data-crm-tab="notas"]')?.click() },
    { icon: '✓', label: 'Tarefa', tooltip: 'Criar tarefa (em breve)', disabled: true, onClick: () => {} },
    { icon: '+', label: 'Lista', tooltip: 'Adicionar a lista', onClick: () => {} },
  ];

  const tabs = [
    { id: 'overview', label: 'Overview',
      render: () => <EntOverview data={data} kpis={kpis} signals={signals} score={score} tags={tags} segs={segs} fmId={fmId} /> },
    { id: 'contactos', label: 'Contactos', count: data.contactos?.length || 0,
      render: () => <ProfileContactos data={data} userEmail={userEmail} onReload={loadAll} onOpenContacto={onOpenContacto} /> },
    { id: 'ops', label: 'Oportunidades', count: data.oportunidades?.length || 0,
      render: () => <ProfileOpsList ops={data.oportunidades || []} onOpenOp={onOpenOp} /> },
    { id: 'timeline', label: 'Timeline', count: kpis?.interacoes_total || 0,
      render: () => <ProfileTimeline fmId={fmId} /> },
    { id: 'tags', label: 'Tags & Segmentos', render: () => <ProfileTagsSegmentos fmId={fmId} tags={tags} segs={segs} onReload={loadAll} userEmail={userEmail} /> },
    { id: 'notas', label: 'Notas', render: () => <ProfileNotas fmId={fmId} userEmail={userEmail} userName={userName} /> },
    { id: 'campanhas', label: 'Campanhas', render: () => <ProfileCampanhas fmId={fmId} /> },
  ];

  return (
    <ProfileLayout
      left={<ProfileHeader type="entidade" data={e} kpis={kpis} signals={signals} score={score} tags={tags} segs={segs} onBack={onBack} subtitle={subtitle} />}
      centre={<ProfileActivity tabs={tabs} defaultTab="overview" storageKey={`crm_ent_tab_${fmId}`} quickActions={quickActions} />}
      right={
        <div style={{ display: 'flex', flexDirection: 'column' }}>
          <DigiAIPanel apreciacao={apreciacao} loading={!apreciacao && !kpis} />
          <div style={{ height: 1, background: 'var(--border)', margin: '0 20px' }} />
          <div style={{ padding: '16px 20px 24px' }}>
            <ProfileSidebarActions fmId={fmId} userEmail={userEmail} userName={userName} onReload={loadAll} />
          </div>
        </div>
      }
    />
  );
}

// ── EntOverview — activity feed inline + property groups colapsáveis (HubSpot style)
function EntOverview({ data, kpis, signals, score, tags, segs, fmId }) {
  const [activity, setActivity] = React.useState(null);
  React.useEffect(() => {
    CRMAPI.entActivity(fmId, { limit: 15 }).then(setActivity).catch(() => setActivity({ events: [] }));
  }, [fmId]);

  const e = data.entidade;
  const enderecoPartes = [e.address1, e.address2, [e.zip, e.cidade].filter(Boolean).join(' '), e.pais].filter(Boolean);
  const active = (data.oportunidades || []).filter(o => o.status === 'OPEN').slice(0, 3);

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 22 }}>
      {/* Activity Feed HERO — o primeiro elemento visível */}
      <div>
        <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 10 }}>
          <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '0.08em', textTransform: 'uppercase', fontWeight: 700 }}>Actividade recente</div>
          {activity && <div style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>{activity.total} eventos</div>}
        </div>
        <ActivityFeed events={activity?.events || []} loading={!activity} emptyCTA={{ label: 'Registar primeira nota', onClick: () => document.querySelector('[data-crm-tab="notas"]')?.click() }} />
      </div>

      {/* OPs em curso (top 3) — quick view */}
      {active.length > 0 && (
        <div>
          <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 10 }}>
            <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '0.08em', textTransform: 'uppercase', fontWeight: 700 }}>OPs em curso · top {active.length}</div>
          </div>
          <ProfileOpsList ops={active} />
        </div>
      )}

      {/* Property groups colapsáveis */}
      <PropertyGroup title="Detalhes da empresa">
        <div style={{ padding: '4px 0' }}>
          <PropRow label="NIF"       value={e.nif} mono />
          <PropRow label="Nome"      value={e.nome} />
          <PropRow label="Morada"    value={enderecoPartes.join(', ') || null} />
          <PropRow label="Cidade"    value={e.cidade} />
          <PropRow label="País"      value={e.pais} />
          <PropRow label="Website"   value={e.website ? <a href={e.website.startsWith('http') ? e.website : 'https://' + e.website} target="_blank" rel="noopener noreferrer" style={{ color: 'var(--ai-500)' }}>{e.website}</a> : null} />
          <PropRow label="Sync"      value={e.synced_at ? new Date(e.synced_at).toLocaleString('pt-PT') : null} mono />
        </div>
      </PropertyGroup>

      {/* Perfil FM — segmentação da entidade (novo campo do Gestor) */}
      {data.perfil && data.perfil.nome_segmento && (
        <PropertyGroup title="Perfil Gestor" defaultOpen={true}>
          <div style={{ padding: '4px 0' }}>
            <PropRow label="Segmento" value={data.perfil.nome_segmento} />
            {data.perfil.seg1 && <PropRow label={data.perfil.label_seg1 || 'Seg. 1'} value={data.perfil.seg1} />}
            {data.perfil.seg2 && <PropRow label={data.perfil.label_seg2 || 'Seg. 2'} value={data.perfil.seg2} />}
            {data.perfil.seg3 && <PropRow label={data.perfil.label_seg3 || 'Seg. 3'} value={data.perfil.seg3} />}
          </div>
        </PropertyGroup>
      )}

      {/* Notas Gestor recentes — suporta formato novo (note+created_at_fm) e legado (Note+CreationTimestamp) */}
      {Array.isArray(data.notas) && data.notas.length > 0 && (
        <PropertyGroup title="Notas Gestor" count={data.notas.length} defaultOpen={false}>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            {data.notas.slice(0, 5).map((n, i) => {
              const texto = n.note || n.Note || n.Nota || '(sem texto)';
              const data_nota = n.created_at_fm || n.CreationTimestamp || '';
              const op_id = n.id_oportunidade ? ` · OP ${n.id_oportunidade}` : '';
              return (
                <div key={n.fm_id || i} style={{ padding: '10px 12px', borderRadius: 6, background: 'var(--bg-sunken)', border: '1px solid var(--border)' }}>
                  <div style={{ fontSize: 10, color: 'var(--text-dim)', marginBottom: 3, fontFamily: 'var(--font-mono)' }}>{data_nota}{op_id}</div>
                  <div style={{ fontSize: 12, color: 'var(--text)', lineHeight: 1.55, whiteSpace: 'pre-wrap' }}>{texto}</div>
                </div>
              );
            })}
          </div>
        </PropertyGroup>
      )}
      {data.notas === null && (
        <div style={{ fontSize: 11, color: 'var(--text-dim)', fontStyle: 'italic', padding: '8px 0' }}>Notas Gestor indisponíveis (verificar ligação FM).</div>
      )}
    </div>
  );
}

// Lista simples de OPs — usada dentro do tab Oportunidades do perfil
function ProfileOpsList({ ops, onOpenOp }) {
  if (!ops || ops.length === 0) {
    return <div style={{ padding: 24, fontSize: 12, color: 'var(--text-muted)', textAlign: 'center' }}>Sem oportunidades registadas.</div>;
  }
  const stageColor = (name) => {
    const s = (name || '').toLowerCase();
    if (s.includes('won'))       return 'var(--success)';
    if (s.includes('lost'))      return 'var(--danger)';
    if (s.includes('aprovado'))  return 'var(--success)';
    if (s.includes('financ'))    return '#d97706';
    if (s.includes('decis'))     return '#f59e0b';
    if (s.includes('demo'))      return '#8b5cf6';
    if (s.includes('evento'))    return '#0ea5e9';
    return '#64748b';
  };
  const groupOpen = ops.filter(o => o.status === 'OPEN');
  const groupWon  = ops.filter(o => o.status === 'WON');
  const groupLost = ops.filter(o => o.status === 'LOST');

  const renderRow = (o) => (
    <div key={o.fm_id}
      onClick={onOpenOp ? () => onOpenOp(o.fm_id) : undefined}
      style={{
        padding: '10px 12px', borderRadius: 6, background: 'var(--bg-elev)',
        border: '1px solid var(--border)', display: 'flex', gap: 12, alignItems: 'center', marginBottom: 6,
        cursor: onOpenOp ? 'pointer' : 'default', transition: 'background 0.15s, border-color 0.15s',
      }}
      onMouseEnter={onOpenOp ? (ev) => { ev.currentTarget.style.background = 'var(--bg-sunken)'; ev.currentTarget.style.borderColor = 'var(--ai-500)'; } : undefined}
      onMouseLeave={onOpenOp ? (ev) => { ev.currentTarget.style.background = 'var(--bg-elev)';   ev.currentTarget.style.borderColor = 'var(--border)';  } : undefined}>
      <span style={{
        fontSize: 9, fontFamily: 'var(--font-mono)', fontWeight: 700, letterSpacing: '0.05em',
        padding: '2px 6px', borderRadius: 3, textTransform: 'uppercase',
        background: `color-mix(in oklch, ${stageColor(o.stage_name)} 14%, transparent)`,
        color: stageColor(o.stage_name),
        minWidth: 90, textAlign: 'center',
      }}>{o.stage_name}</span>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 13, fontWeight: 600, color: onOpenOp ? 'var(--ai-500)' : 'var(--text)' }}>{o.produto_name || '—'}</div>
        <div style={{ fontSize: 11, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>
          {o.equipa_name || '—'}{o.start_date ? ` · ${o.start_date}` : ''}
        </div>
      </div>
      <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)', fontFamily: 'var(--font-display)' }}>
        {fmtK(o.produto_valor_k)}
      </div>
    </div>
  );

  return (
    <div>
      {groupOpen.length > 0 && <>
        <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '0.08em', textTransform: 'uppercase', marginBottom: 8 }}>
          Em curso ({groupOpen.length})
        </div>
        {groupOpen.map(renderRow)}
      </>}
      {groupWon.length > 0 && <>
        <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--success)', letterSpacing: '0.08em', textTransform: 'uppercase', marginTop: 16, marginBottom: 8 }}>
          Ganhas ({groupWon.length})
        </div>
        {groupWon.map(renderRow)}
      </>}
      {groupLost.length > 0 && <>
        <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '0.08em', textTransform: 'uppercase', marginTop: 16, marginBottom: 8 }}>
          Perdidas ({groupLost.length})
        </div>
        {groupLost.map(renderRow)}
      </>}
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════════════════
// ContactoProfile — mesmo layout 3 colunas, ancorado no contacto (PA5)
// ═══════════════════════════════════════════════════════════════════════════
function ContactoProfile({ fmId, onBack, onOpenEntity, onOpenOp, userEmail, userName }) {
  const [data, setData]             = React.useState(null);
  const [kpis, setKpis]             = React.useState(null);
  const [signals, setSignals]       = React.useState(null);
  const [score, setScore]           = React.useState(null);
  const [apreciacao, setApreciacao] = React.useState(null);
  const [ctTags, setCtTags]         = React.useState([]);
  const [loading, setLoading]       = React.useState(true);

  const loadAll = React.useCallback(() => {
    setLoading(true);
    Promise.all([
      CRMAPI.ct360(fmId),
      CRMAPI.ctTags(fmId).catch(() => []),
      CRMAPI.ctKpis(fmId).catch(() => null),
      CRMAPI.ctApreciacao(fmId).catch(() => null),
      CRMAPI.ctSignals(fmId).catch(() => null),
      CRMAPI.ctScore(fmId).catch(() => null),
    ]).then(([d, ts, k, a, sig, sc]) => {
      setData(d); setCtTags(ts || []);
      setKpis(k); setApreciacao(a); setSignals(sig); setScore(sc);
      setLoading(false);
    }).catch(() => setLoading(false));
  }, [fmId]);

  React.useEffect(() => { loadAll(); }, [loadAll]);

  if (loading || !data) {
    return (
      <div style={{ padding: 40, textAlign: 'center', color: 'var(--text-muted)', fontSize: 13 }}>
        A carregar contacto...
        <div style={{ marginTop: 20 }}>
          <button className="btn btn-xs" onClick={onBack}>Voltar</button>
        </div>
      </div>
    );
  }

  const c = data.contacto;
  const e = data.entidade;
  const subtitle = [
    c.telefone ? <span key="tel" style={{ fontFamily: 'var(--font-mono)' }}>{c.telefone}</span> : null,
    c.email    ? <a key="mail" href={`mailto:${c.email}`} style={{ color: 'var(--ai-500)' }}>{c.email}</a> : null,
  ].filter(Boolean);

  const quickActions = [
    { icon: '✉', label: 'Email', disabled: !c.email, onClick: () => c.email && (window.location.href = `mailto:${c.email}`) },
    { icon: '📞', label: 'Ligar', disabled: !c.telefone, onClick: () => c.telefone && (window.location.href = `tel:${c.telefone}`) },
    { icon: '💬', label: 'WhatsApp', disabled: !c.telefone, onClick: () => c.telefone && window.open(`https://wa.me/${c.telefone.replace(/\D/g,'')}`) },
    { icon: '✎', label: 'Nota', primary: true, onClick: () => document.querySelector('[data-crm-tab="notas"]')?.click() },
    { icon: '⚿', label: 'Consent', onClick: () => document.querySelector('[data-crm-tab="consent"]')?.click() },
  ];

  const tabs = [
    { id: 'overview', label: 'Overview',
      render: () => <CtOverview data={data} kpis={kpis} onOpenEntity={onOpenEntity} onOpenOp={onOpenOp} fmId={fmId} /> },
    { id: 'ops', label: 'OPs da entidade', count: data.oportunidades?.length || 0,
      render: () => <ProfileOpsList ops={data.oportunidades || []} onOpenOp={onOpenOp} /> },
    { id: 'timeline', label: 'Timeline', count: kpis?.interacoes_total || 0,
      render: () => <CtTimeline fmId={fmId} /> },
    { id: 'consent', label: 'Consentimentos',
      render: () => <CtConsent data={data} kpis={kpis} onReload={loadAll} userEmail={userEmail} /> },
    { id: 'notas', label: 'Notas',
      render: () => <ProfileNotas fmId={e?.fm_id} userEmail={userEmail} userName={userName} scopedContactoId={fmId} contactoNome={c.nome} /> },
  ];

  return (
    <ProfileLayout
      left={<ProfileHeader type="contacto" data={c} kpis={kpis} signals={signals} score={score} tags={ctTags} segs={[]} onBack={onBack} subtitle={subtitle} />}
      centre={<ProfileActivity tabs={tabs} defaultTab="overview" storageKey={`crm_ct_tab_${fmId}`} quickActions={quickActions} />}
      right={<DigiAIPanel apreciacao={apreciacao} loading={!apreciacao && !kpis} />}
    />
  );
}

// ── CtOverview — activity feed + card empresa + property groups
function CtOverview({ data, kpis, onOpenEntity, onOpenOp, fmId }) {
  const c = data.contacto;
  const e = data.entidade;
  const o = data.override;
  const [activity, setActivity] = React.useState(null);
  React.useEffect(() => {
    CRMAPI.ctActivity(fmId, { limit: 15 }).then(setActivity).catch(() => setActivity({ events: [] }));
  }, [fmId]);

  const label = { fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '0.08em', textTransform: 'uppercase', fontWeight: 700, marginBottom: 10 };
  const activeOps = (data.oportunidades || []).filter(o => o.status === 'OPEN').slice(0, 3);

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 22 }}>
      {/* Empresa card (hero) */}
      {e && (
        <div>
          <div style={label}>Empresa</div>
          <div onClick={() => onOpenEntity && onOpenEntity(e.fm_id, e.tenant)}
               style={{
                 padding: '12px 14px', borderRadius: 8,
                 background: 'linear-gradient(135deg, color-mix(in oklch, var(--ai-500) 6%, var(--bg-sunken)), var(--bg-sunken))',
                 border: '1px solid var(--ai-500)', cursor: onOpenEntity ? 'pointer' : 'default',
                 display: 'flex', gap: 10, alignItems: 'center',
               }}>
            <Avatar name={e.nome} size={36} />
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 14, fontWeight: 700, color: 'var(--text)' }}>{e.nome}</div>
              <div style={{ fontSize: 10, color: 'var(--text-muted)', marginTop: 2, display: 'flex', gap: 8, flexWrap: 'wrap', fontFamily: 'var(--font-mono)' }}>
                {e.nif && <span>NIF {e.nif}</span>}
                {e.cidade && <span>· {e.cidade}</span>}
                {e.pais && <span>· {e.pais}</span>}
              </div>
            </div>
            {onOpenEntity && <span style={{ color: 'var(--ai-500)', fontSize: 18, flexShrink: 0 }}>→</span>}
          </div>
        </div>
      )}

      {/* Activity feed */}
      <div>
        <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 10 }}>
          <div style={label}>Actividade recente</div>
          {activity && <div style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>{activity.total} eventos</div>}
        </div>
        <ActivityFeed events={activity?.events || []} loading={!activity} emptyCTA={{ label: 'Registar primeira nota', onClick: () => document.querySelector('[data-crm-tab="notas"]')?.click() }} />
      </div>

      {/* OPs em curso na entidade */}
      {activeOps.length > 0 && (
        <div>
          <div style={label}>OPs em curso na entidade</div>
          <ProfileOpsList ops={activeOps} onOpenOp={onOpenOp} />
        </div>
      )}

      {/* Property group — contacto */}
      <PropertyGroup title="Contacto">
        <div style={{ padding: '4px 0' }}>
          <PropRow label="Nome"     value={nomeComposto(c)} />
          {(c.nome_proprio || c.apelido) && c.nome && c.nome !== nomeComposto(c) && (
            <PropRow label="Nome Gestor" value={c.nome} />
          )}
          {c.cargo && <PropRow label="Cargo" value={c.cargo} />}
          <PropRow label="Telefone" value={c.telefone} mono />
          <PropRow label="Email"    value={c.email ? <a href={`mailto:${c.email}`} style={{ color: 'var(--ai-500)' }}>{c.email}</a> : null} />
          {o?.email_override    && <PropRow label="Email (override)" value={<span style={{ color: '#d97706' }}>{o.email_override}</span>} mono />}
          {o?.telefone_override && <PropRow label="Tel (override)"   value={<span style={{ color: '#d97706' }}>{o.telefone_override}</span>} mono />}
          {o?.canal_preferido   && <PropRow label="Canal preferido"  value={o.canal_preferido.toUpperCase()} />}
          <PropRow label="RGPD FM" value={
            <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
              <span style={{ width: 8, height: 8, borderRadius: 99, background: rgpdDotColor(c.opt_rgpd) }} />
              <span style={{ fontSize: 11, color: 'var(--text)', fontFamily: 'var(--font-mono)' }}>{rgpdLabel(c.opt_rgpd)}</span>
            </span>
          } />
          <PropRow label="Sync" value={c.synced_at ? new Date(c.synced_at).toLocaleString('pt-PT') : null} mono />
        </div>
      </PropertyGroup>
    </div>
  );
}

// ── CtConsent — vista completa dos consentimentos + inline edit
function CtConsent({ data, kpis, onReload, userEmail }) {
  const consent = data.consent || {};
  const [saving, setSaving] = React.useState('');
  const canais = [
    { id: 'wa',    label: 'WhatsApp' },
    { id: 'email', label: 'Email' },
    { id: 'tel',   label: 'Telefone' },
  ];
  const estados = [
    { v: 'opt_in',       label: 'Opt-in',       color: 'var(--success)' },
    { v: 'opt_out',      label: 'Opt-out',      color: 'var(--danger)' },
    { v: 'desconhecido', label: 'Desconhecido', color: 'var(--text-muted)' },
  ];

  const setConsent = async (canal, estado) => {
    setSaving(canal);
    try {
      await CRMAPI.setCtConsent(data.contacto.fm_id, { canal, estado, updated_by: userEmail });
      onReload();
    } finally {
      setSaving('');
    }
  };

  const cardStyle = { padding: '14px 16px', borderRadius: 8, background: 'var(--bg-sunken)', border: '1px solid var(--border)' };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
      <div style={{ fontSize: 12, color: 'var(--text-muted)', lineHeight: 1.6 }}>
        RGPD — cada consentimento aplica-se automaticamente às campanhas. Opt-out exclui de envios futuros.
      </div>
      {canais.map(canal => {
        const c = consent[canal.id] || { estado: 'desconhecido' };
        return (
          <div key={canal.id} style={cardStyle}>
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
              <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)' }}>{canal.label}</div>
              {c.data && (
                <div style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>
                  {new Date(c.data).toLocaleDateString('pt-PT')} · {c.updated_by || c.fonte || 'sistema'}
                </div>
              )}
            </div>
            <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
              {estados.map(e => {
                const isActive = c.estado === e.v;
                return (
                  <button key={e.v} onClick={() => !isActive && setConsent(canal.id, e.v)} disabled={saving === canal.id}
                    style={{
                      padding: '6px 12px', borderRadius: 6, fontSize: 11, fontWeight: 600,
                      cursor: isActive ? 'default' : 'pointer',
                      background: isActive ? e.color : 'transparent',
                      color: isActive ? '#fff' : e.color,
                      border: `1px solid ${e.color}`,
                      opacity: saving === canal.id && !isActive ? 0.5 : 1,
                    }}>
                    {e.label}
                  </button>
                );
              })}
            </div>
          </div>
        );
      })}
    </div>
  );
}

// ── CtTimeline — timeline scoped ao contacto (usa endpoint PA2)
function CtTimeline({ fmId }) {
  const [rows, setRows] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  React.useEffect(() => {
    CRMAPI.ctTimeline(fmId, { limit: 100 }).then(d => { setRows(d.rows || []); setLoading(false); }).catch(() => setLoading(false));
  }, [fmId]);
  if (loading) return <div style={{ padding: 24, fontSize: 12, color: 'var(--text-muted)' }}>A carregar timeline...</div>;
  if (rows.length === 0) return <div style={{ padding: 24, fontSize: 12, color: 'var(--text-muted)', textAlign: 'center' }}>Sem interacções registadas com este contacto.</div>;
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
      {rows.map(r => (
        <div key={r.id} style={{
          padding: '10px 12px', borderRadius: 6, background: 'var(--bg-sunken)', border: '1px solid var(--border)',
        }}>
          <div style={{ display: 'flex', gap: 8, alignItems: 'baseline', marginBottom: 3 }}>
            <span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', fontWeight: 700, color: 'var(--ai-500)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>{r.tipo}</span>
            <span style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>{new Date(r.ocorreu_em).toLocaleString('pt-PT')}</span>
          </div>
          {r.contexto?.mensagem && <div style={{ fontSize: 12, color: 'var(--text)', lineHeight: 1.5 }}>{r.contexto.mensagem}</div>}
        </div>
      ))}
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════════════════
// QuickPreview — drawer lateral com prévia compacta (PA6 + PA9)
// ═══════════════════════════════════════════════════════════════════════════
// Padrão HubSpot/Apollo: click "peek" numa linha da lista → drawer 380px direita
// com 5-8 factos essenciais + botão "Perfil completo →" para fullscreen.
// Suporta type: 'entidade' | 'contacto' | 'oportunidade'.
function QuickPreview({ type, fmId, onClose, onOpenFull, onOpenRelated }) {
  const [data, setData] = React.useState(null);
  const [kpis, setKpis] = React.useState(null);
  const [signals, setSignals] = React.useState(null);
  const [loading, setLoading] = React.useState(true);

  React.useEffect(() => {
    setLoading(true); setData(null); setKpis(null); setSignals(null);
    const load360   = type === 'entidade' ? CRMAPI.ent360(fmId)   : type === 'contacto' ? CRMAPI.ct360(fmId)   : CRMAPI.op360(fmId);
    const loadKpis  = type === 'entidade' ? CRMAPI.entKpis(fmId)  : type === 'contacto' ? CRMAPI.ctKpis(fmId)  : CRMAPI.opKpis(fmId);
    const loadSig   = type === 'entidade' ? CRMAPI.entSignals(fmId) : type === 'contacto' ? CRMAPI.ctSignals(fmId) : CRMAPI.opSignals(fmId);
    Promise.all([load360, loadKpis.catch(() => null), loadSig.catch(() => null)])
      .then(([d, k, s]) => { setData(d); setKpis(k); setSignals(s); setLoading(false); })
      .catch(() => setLoading(false));
  }, [type, fmId]);

  // ESC fecha o drawer
  React.useEffect(() => {
    const onKey = (ev) => { if (ev.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);

  const typeLabel = { entidade: 'Entidade', contacto: 'Contacto', oportunidade: 'Oportunidade' }[type];
  const label = { fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 6 };

  return (
    <>
      {/* Backdrop */}
      <div onClick={onClose} style={{
        position: 'fixed', inset: 0, background: 'rgba(17,41,84,0.25)', zIndex: 350,
        animation: 'crm-qp-fade 150ms ease-out',
      }}>
        <style>{`@keyframes crm-qp-fade { from { opacity: 0; } to { opacity: 1; } }`}</style>
      </div>

      {/* Drawer */}
      <aside role="dialog" aria-label={`Prévia ${typeLabel}`}
        style={{
          position: 'fixed', top: 0, right: 0, bottom: 0, width: 400, maxWidth: '100vw',
          background: 'var(--bg-elev, #fff)', zIndex: 360, display: 'flex', flexDirection: 'column',
          boxShadow: '-8px 0 32px rgba(17,41,84,0.15)',
          animation: 'crm-qp-slide 250ms cubic-bezier(0.16, 1, 0.3, 1)',
        }}>
        <style>{`@keyframes crm-qp-slide { from { transform: translateX(20px); opacity: 0; } to { transform: translateX(0); opacity: 1; } }`}</style>

        {/* Header */}
        <div style={{ padding: '16px 20px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
          <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '0.08em', textTransform: 'uppercase' }}>
            Prévia · {typeLabel}
          </div>
          <button onClick={onClose} aria-label="Fechar"
            style={{ background: 'none', border: '1px solid var(--border)', borderRadius: 6, padding: '4px 8px', fontSize: 12, cursor: 'pointer', color: 'var(--text-muted)' }}>
            ✕
          </button>
        </div>

        {/* Body */}
        <div className="scrollbar" style={{ flex: 1, overflowY: 'auto', padding: '20px' }}>
          {loading ? (
            <div style={{ padding: 40, textAlign: 'center', color: 'var(--text-muted)', fontSize: 12 }}>A carregar prévia...</div>
          ) : !data ? (
            <div style={{ padding: 40, textAlign: 'center', color: 'var(--text-muted)', fontSize: 12 }}>Não foi possível carregar.</div>
          ) : (
            <>
              {type === 'entidade'     && <QpEntidade    data={data} kpis={kpis} signals={signals} label={label} onOpenRelated={onOpenRelated} />}
              {type === 'contacto'     && <QpContacto    data={data} kpis={kpis} signals={signals} label={label} onOpenRelated={onOpenRelated} />}
              {type === 'oportunidade' && <QpOportunidade data={data} kpis={kpis} signals={signals} label={label} onOpenRelated={onOpenRelated} />}
            </>
          )}
        </div>

        {/* Footer com CTA fullscreen */}
        <div style={{ padding: '14px 20px', borderTop: '1px solid var(--border)', background: 'var(--bg-sunken)' }}>
          <button onClick={onOpenFull}
            className="btn-ai"
            style={{ width: '100%', padding: '9px 12px', fontSize: 13, fontWeight: 600 }}>
            Perfil completo →
          </button>
        </div>
      </aside>
    </>
  );
}

// ── Painéis de conteúdo por tipo (mantidos compactos)
function QpEntidade({ data, kpis, signals, label, onOpenRelated }) {
  const e = data.entidade;
  const ops = data.oportunidades || [];
  const activas = ops.filter(o => o.status === 'OPEN').slice(0, 3);
  const perfil = data.perfil || null;
  const tags = data.tags || [];
  const ultimaNota = data.notas?.[0] || null;
  const sec = { fontSize: 9, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 4, fontWeight: 700 };
  const row = { display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '5px 0', borderBottom: '1px solid var(--border-light,#f1f5f9)' };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
      {/* Hero */}
      <div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
        <Avatar name={e.nome} size={40} />
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 15, fontWeight: 700, color: 'var(--text)', fontFamily: 'var(--font-display)', lineHeight: 1.2 }}>{e.nome}</div>
          {signals?.lifecycle && <div style={{ marginTop: 4 }}><LifecyclePill lifecycle={signals.lifecycle} size="sm" /></div>}
        </div>
      </div>

      {/* Identificação */}
      <div>
        <div style={sec}>Identificação</div>
        {e.nif && <div style={row}><span style={{ fontSize: 11, color: 'var(--text-dim)' }}>NIF</span><span style={{ fontSize: 11, fontFamily: 'var(--font-mono)', fontWeight: 600, color: 'var(--text)' }}>{e.nif}</span></div>}
        {(e.cidade || e.pais) && <div style={row}><span style={{ fontSize: 11, color: 'var(--text-dim)' }}>Localização</span><span style={{ fontSize: 11, color: 'var(--text)' }}>{[e.cidade, e.pais].filter(Boolean).join(', ')}</span></div>}
        {perfil?.seg1 && <div style={row}><span style={{ fontSize: 11, color: 'var(--text-dim)' }}>Indústria</span><span style={{ fontSize: 11, color: 'var(--text)' }}>{perfil.seg1}</span></div>}
      </div>

      {/* KPIs */}
      {kpis && (
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6 }}>
          <QpStat label="OPs abertas" value={kpis.ops_abertas ?? 0} accent="ai" />
          <QpStat label="Pipeline" value={fmtK(kpis.pipeline_valor_k)} />
          <QpStat label="Marca" value={kpis.marca_dominante || '—'} small />
          <QpStat label="Ult. interact." value={fmtDias(kpis.dias_desde_ultima_interacao)} small />
        </div>
      )}

      {/* Sinais */}
      {signals?.signals?.length > 0 && (
        <div>
          <div style={sec}>Sinais</div>
          <SignalDots signals={signals.signals} size="sm" />
        </div>
      )}

      {/* OP activa top */}
      {activas[0] && (
        <div>
          <div style={sec}>OP activa</div>
          <div onClick={() => onOpenRelated && onOpenRelated('oportunidade', activas[0].fm_id)}
            style={{ padding: '7px 10px', borderRadius: 6, background: 'var(--bg-sunken)', border: '1px solid var(--border)', cursor: onOpenRelated ? 'pointer' : 'default', display: 'flex', gap: 6, alignItems: 'center' }}>
            <StageChip name={activas[0].stage_name} small />
            <div style={{ fontSize: 11, fontWeight: 600, color: onOpenRelated ? 'var(--ai-500)' : 'var(--text)', flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{activas[0].produto_name || '—'}</div>
            <div style={{ fontSize: 11, fontWeight: 700, fontFamily: 'var(--font-mono)', flexShrink: 0 }}>{fmtK(activas[0].produto_valor_k)}</div>
          </div>
          {activas.length > 1 && <div style={{ fontSize: 10, color: 'var(--text-dim)', marginTop: 3 }}>+{activas.length - 1} mais</div>}
        </div>
      )}

      {/* Contactos */}
      {data.contactos?.length > 0 && (
        <div>
          <div style={sec}>Contactos ({data.contactos.length})</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
            {data.contactos.slice(0, 3).map(c => (
              <div key={c.fm_id} onClick={() => onOpenRelated && onOpenRelated('contacto', c.fm_id)}
                style={{ padding: '5px 8px', borderRadius: 5, background: 'var(--bg-sunken)', border: '1px solid var(--border)', cursor: onOpenRelated ? 'pointer' : 'default', display: 'flex', alignItems: 'center', gap: 6 }}>
                <span title={`RGPD: ${rgpdLabel(c.opt_rgpd)}`} style={{ width: 6, height: 6, borderRadius: '50%', background: rgpdDotColor(c.opt_rgpd), flexShrink: 0 }} />
                <div style={{ minWidth: 0, flex: 1 }}>
                  <div style={{ fontSize: 11, fontWeight: 600, color: onOpenRelated ? 'var(--ai-500)' : 'var(--text)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{nomeComposto(c)}</div>
                  {c.cargo && <div style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{c.cargo}</div>}
                </div>
              </div>
            ))}
          </div>
        </div>
      )}

      {/* Tags */}
      {tags.length > 0 && (
        <div>
          <div style={sec}>Tags</div>
          <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
            {tags.map(t => (
              <span key={t.id} style={{ fontSize: 10, padding: '2px 7px', borderRadius: 4, fontFamily: 'var(--font-mono)', fontWeight: 600, background: t.cor ? t.cor + '18' : 'var(--bg-sunken)', color: t.cor || 'var(--text-dim)', border: `1px solid ${t.cor || 'var(--border)'}30` }}>{t.nome}</span>
            ))}
          </div>
        </div>
      )}

      {/* Ultima nota */}
      {ultimaNota && (
        <div>
          <div style={sec}>Última nota</div>
          <div style={{ padding: '7px 9px', borderRadius: 6, background: 'var(--bg-sunken)', border: '1px solid var(--border)', fontSize: 11, color: 'var(--text)', lineHeight: 1.5 }}>
            {ultimaNota.note || '—'}
          </div>
        </div>
      )}
    </div>
  );
}

function QpContacto({ data, kpis, signals, label, onOpenRelated }) {
  const c = data.contacto;
  const e = data.entidade;
  const consent = data.consent || {};
  const tags = data.tags || [];
  const ultimaNota = data.notas?.[0] || null;
  const ops = data.oportunidades || [];
  const topOp = ops.find(o => o.status === 'OPEN') || null;

  const sec = { fontSize: 9, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 4, fontWeight: 700 };
  const row = { display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '5px 0', borderBottom: '1px solid var(--border-light,#f1f5f9)' };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
      {/* Hero */}
      <div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
        <Avatar name={nomeComposto(c)} size={40} />
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
            <div style={{ fontSize: 15, fontWeight: 700, color: 'var(--text)', fontFamily: 'var(--font-display)', lineHeight: 1.2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{nomeComposto(c)}</div>
            <span title={`RGPD: ${rgpdLabel(c.opt_rgpd)}`} style={{ width: 7, height: 7, borderRadius: '50%', background: rgpdDotColor(c.opt_rgpd), flexShrink: 0 }} />
          </div>
          {c.cargo && <div style={{ fontSize: 11, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', marginTop: 2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{c.cargo}</div>}
          {signals?.lifecycle && <div style={{ marginTop: 4 }}><LifecyclePill lifecycle={signals.lifecycle} size="sm" /></div>}
        </div>
      </div>

      {/* Contacto */}
      <div>
        <div style={sec}>Contacto</div>
        {c.telefone && <div style={row}><span style={{ fontSize: 11, color: 'var(--text-dim)' }}>Telefone</span><span style={{ fontSize: 11, fontFamily: 'var(--font-mono)', fontWeight: 600 }}>{c.telefone}</span></div>}
        {c.email && <div style={row}><span style={{ fontSize: 11, color: 'var(--text-dim)' }}>Email</span><a href={`mailto:${c.email}`} style={{ fontSize: 11, color: 'var(--ai-500)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: 180 }}>{c.email}</a></div>}
        {topOp && <div style={row}><span style={{ fontSize: 11, color: 'var(--text-dim)' }}>Stage</span><StageChip name={topOp.stage_name} small /></div>}
      </div>

      {/* Empresa */}
      {e && (
        <div>
          <div style={sec}>Empresa</div>
          <div onClick={() => onOpenRelated && onOpenRelated('entidade', e.fm_id)}
            style={{ padding: '8px 10px', borderRadius: 6, background: 'var(--bg-sunken)', border: '1px solid var(--border)', cursor: onOpenRelated ? 'pointer' : 'default' }}>
            <div style={{ fontSize: 12, fontWeight: 600, color: onOpenRelated ? 'var(--ai-500)' : 'var(--text)' }}>{e.nome}</div>
            <div style={{ fontSize: 10, color: 'var(--text-muted)', marginTop: 2, fontFamily: 'var(--font-mono)' }}>
              {[e.nif ? `NIF ${e.nif}` : null, e.cidade, e.pais].filter(Boolean).join(' · ')}
            </div>
          </div>
        </div>
      )}

      {/* Consentimentos */}
      <div>
        <div style={sec}>Consentimentos RGPD</div>
        <div style={{ display: 'flex', gap: 6 }}>
          {['wa', 'email', 'tel'].map(canal => (
            <div key={canal} style={{ flex: 1, padding: '6px 8px', borderRadius: 6, background: 'var(--bg-sunken)', border: '1px solid var(--border)', textAlign: 'center' }}>
              <div style={{ fontSize: 9, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', textTransform: 'uppercase', marginBottom: 4 }}>{canal}</div>
              <span style={{ width: 8, height: 8, borderRadius: '50%', background: consentDot(consent[canal]?.estado), display: 'inline-block' }} />
            </div>
          ))}
        </div>
      </div>

      {/* KPIs */}
      {kpis && (
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6 }}>
          <QpStat label="Interacções" value={kpis.interacoes_total ?? 0} />
          <QpStat label="Ult. interact." value={fmtDias(kpis.dias_desde_ultima_interacao)} />
          <QpStat label="OPs entidade" value={kpis.ops_abertas_entidade ?? 0} accent="ai" />
          <QpStat label="Pipeline" value={fmtK(kpis.pipeline_valor_k_entidade)} />
        </div>
      )}

      {/* Sinais */}
      {signals?.signals?.length > 0 && (
        <div>
          <div style={sec}>Sinais</div>
          <SignalDots signals={signals.signals} size="sm" />
        </div>
      )}

      {/* Tags */}
      {tags.length > 0 && (
        <div>
          <div style={sec}>Tags</div>
          <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
            {tags.map(t => (
              <span key={t.id} style={{ fontSize: 10, padding: '2px 7px', borderRadius: 4, fontFamily: 'var(--font-mono)', fontWeight: 600, background: t.cor ? t.cor + '18' : 'var(--bg-sunken)', color: t.cor || 'var(--text-dim)', border: `1px solid ${t.cor || 'var(--border)'}30` }}>{t.nome}</span>
            ))}
          </div>
        </div>
      )}

      {/* RGPD badge explícito */}
      <div>
        <div style={sec}>RGPD / Opt-In</div>
        <span style={{ fontSize: 11, fontWeight: 600, padding: '3px 10px', borderRadius: 99, fontFamily: 'var(--font-mono)',
          background: c.opt_rgpd === '1' ? 'rgba(249,115,22,0.12)' : c.opt_rgpd === '2' ? 'rgba(239,68,68,0.12)' : 'rgba(16,185,129,0.12)',
          color: c.opt_rgpd === '1' ? '#c2410c' : c.opt_rgpd === '2' ? '#b91c1c' : '#059669' }}>
          {rgpdLabel(c.opt_rgpd)}
        </span>
      </div>

      {/* Segmentação Marketing */}
      {(c.profile_seg1 || c.profile_seg2 || c.profile_seg3) && (
        <div>
          <div style={sec}>Segmentação Marketing</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
            {[['SEG1', c.profile_seg1], ['SEG2', c.profile_seg2], ['SEG3', c.profile_seg3]].filter(([, v]) => v).map(([k, v]) => (
              <div key={k} style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
                <span style={{ fontSize: 9, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', width: 32, flexShrink: 0 }}>{k}</span>
                <span style={{ fontSize: 11, padding: '2px 8px', borderRadius: 4, background: 'rgba(124,58,237,.08)', color: '#7C3AED', fontWeight: 600 }}>{v}</span>
              </div>
            ))}
          </div>
        </div>
      )}

      {/* Oportunidades */}
      {ops.length > 0 && (
        <div>
          <div style={sec}>Oportunidades ({ops.length})</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
            {ops.slice(0, 4).map(op => (
              <div key={op.id || op.fm_id} style={{ padding: '7px 10px', borderRadius: 6, background: 'var(--bg-sunken)', border: '1px solid var(--border)', display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8 }}>
                <div style={{ minWidth: 0 }}>
                  <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{op.produto_name || '—'}</div>
                  <div style={{ fontSize: 10, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)', marginTop: 1 }}>{op.stage_name}</div>
                </div>
                {op.produto_valor_k && <div style={{ fontSize: 12, fontWeight: 700, color: 'var(--ai-500)', fontFamily: 'var(--font-display)', flexShrink: 0 }}>{fmtK(op.produto_valor_k)}</div>}
              </div>
            ))}
          </div>
        </div>
      )}

      {/* Notas (todas, não só a última) */}
      {data.notas?.length > 0 && (
        <div>
          <div style={sec}>Notas ({data.notas.length})</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
            {data.notas.slice(0, 5).map(n => (
              <div key={n.id} style={{ padding: '8px 10px', borderRadius: 6, background: 'var(--bg-sunken)', border: '1px solid var(--border)', fontSize: 11 }}>
                <div style={{ color: 'var(--text)', lineHeight: 1.5, marginBottom: 3 }}>{n.corpo || n.note || '—'}</div>
                <div style={{ fontSize: 9, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>
                  {n.autor_nome || n.autor_email || 'sistema'} · {n.created_at ? new Date(n.created_at).toLocaleDateString('pt-PT') : '—'}
                </div>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

function QpOportunidade({ data, kpis, signals, label, onOpenRelated }) {
  const op = data.oportunidade;
  const e  = data.entidade;

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
      {/* Stage + status pill row */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
        {signals?.lifecycle && <LifecyclePill lifecycle={signals.lifecycle} size="sm" />}
        <span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: '#d97706', fontWeight: 700, letterSpacing: '0.05em', textTransform: 'uppercase' }}>{op.stage_name}</span>
      </div>
      <div>
        <div style={{ fontSize: 16, fontWeight: 700, color: 'var(--text)', fontFamily: 'var(--font-display)', lineHeight: 1.25 }}>{op.produto_name || '—'}</div>
        <div style={{ fontSize: 20, fontWeight: 700, color: 'var(--ai-500)', marginTop: 6, fontFamily: 'var(--font-display)', letterSpacing: '-0.02em' }}>{fmtK(op.produto_valor_k)}</div>
        <div style={{ fontSize: 10, color: 'var(--text-muted)', marginTop: 6, display: 'flex', gap: 8, flexWrap: 'wrap', fontFamily: 'var(--font-mono)' }}>
          {op.equipa_name && <span>{op.equipa_name}</span>}
          {op.start_date && <span>· {op.start_date}</span>}
        </div>
      </div>
      {signals?.signals?.length > 0 && <SignalDots signals={signals.signals} size="sm" />}

      {/* Empresa (link) */}
      {e && (
        <div>
          <div style={label}>Empresa</div>
          <div onClick={() => onOpenRelated && onOpenRelated('entidade', e.fm_id)}
            style={{ padding: '10px 12px', borderRadius: 6, background: 'var(--bg-sunken)', border: '1px solid var(--border)', cursor: onOpenRelated ? 'pointer' : 'default' }}>
            <div style={{ fontSize: 13, fontWeight: 600, color: onOpenRelated ? 'var(--ai-500)' : 'var(--text)' }}>{e.nome}</div>
            <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 2, fontFamily: 'var(--font-mono)' }}>
              {e.nif ? `NIF ${e.nif} · ` : ''}{e.cidade || e.pais}
            </div>
          </div>
        </div>
      )}

      {/* KPIs */}
      {kpis && (
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
          <QpStat label="Dias em curso" value={fmtDias(kpis.dias_desde_inicio)} />
          <QpStat label="Última interacção" value={fmtDias(kpis.dias_desde_ultima_interacao)} />
          <QpStat label="Média equipa" value={fmtK(kpis.media_valor_equipa_k)} small />
          <QpStat label="OPs abertas entidade" value={kpis.entidade_ops_abertas ?? 0} />
        </div>
      )}

      {/* Contactos rápidos */}
      {data.contactos?.length > 0 && (
        <div>
          <div style={label}>Contactos ligados ({data.contactos.length})</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            {data.contactos.slice(0, 3).map(c => (
              <div key={c.fm_id}
                onClick={() => onOpenRelated && onOpenRelated('contacto', c.fm_id)}
                style={{
                  padding: '8px 10px', borderRadius: 6, background: 'var(--bg-sunken)', border: '1px solid var(--border)',
                  cursor: onOpenRelated ? 'pointer' : 'default',
                }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 5 }}>
                  <div style={{ fontSize: 12, fontWeight: 600, color: onOpenRelated ? 'var(--ai-500)' : 'var(--text)' }}>{nomeComposto(c)}</div>
                  <span title={`RGPD: ${rgpdLabel(c.opt_rgpd)}`}
                    style={{ width: 7, height: 7, borderRadius: 99, background: rgpdDotColor(c.opt_rgpd), flexShrink: 0, cursor: 'help' }} />
                </div>
                {c.cargo && (
                  <div style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', marginTop: 1 }}>{c.cargo}</div>
                )}
                <div style={{ fontSize: 10, color: 'var(--text-muted)', marginTop: 2, fontFamily: 'var(--font-mono)' }}>{c.telefone || c.email || '—'}</div>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

function QpStat({ label, value, accent, small }) {
  return (
    <div style={{ padding: '10px 12px', borderRadius: 6, background: 'var(--bg-sunken)', border: '1px solid var(--border)' }}>
      <div style={{ fontSize: 9, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 3 }}>
        {label}
      </div>
      <div style={{
        fontSize: small ? 12 : 14, fontWeight: 700, fontFamily: 'var(--font-display)',
        color: accent === 'ai' ? 'var(--ai-500)' : 'var(--text)',
      }}>
        {value}
      </div>
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════════════════
// OportunidadeProfile — mesmo layout 3 colunas, ancorado no deal (PA8)
// ═══════════════════════════════════════════════════════════════════════════
function OportunidadeProfile({ fmId, onBack, onOpenEntity, onOpenContacto, userEmail, userName }) {
  const [data, setData]             = React.useState(null);
  const [kpis, setKpis]             = React.useState(null);
  const [signals, setSignals]       = React.useState(null);
  const [apreciacao, setApreciacao] = React.useState(null);
  const [loading, setLoading]       = React.useState(true);

  const loadAll = React.useCallback(() => {
    setLoading(true);
    Promise.all([
      CRMAPI.op360(fmId),
      CRMAPI.opKpis(fmId).catch(() => null),
      CRMAPI.opApreciacao(fmId).catch(() => null),
      CRMAPI.opSignals(fmId).catch(() => null),
    ]).then(([d, k, a, sig]) => {
      setData(d); setKpis(k); setApreciacao(a); setSignals(sig); setLoading(false);
    }).catch(() => setLoading(false));
  }, [fmId]);

  React.useEffect(() => { loadAll(); }, [loadAll]);

  if (loading || !data) {
    return (
      <div style={{ padding: 40, textAlign: 'center', color: 'var(--text-muted)', fontSize: 13 }}>
        A carregar oportunidade...
        <div style={{ marginTop: 20 }}>
          <button className="btn btn-xs" onClick={onBack}>Voltar</button>
        </div>
      </div>
    );
  }

  const op = data.oportunidade;
  const subtitle = [
    op.stage_name && <span key="stage" style={{ fontFamily: 'var(--font-mono)', color: '#d97706' }}>{op.stage_name}</span>,
    op.equipa_name && <span key="eq">{op.equipa_name}</span>,
    op.start_date && <span key="dt" style={{ fontFamily: 'var(--font-mono)' }}>{op.start_date}</span>,
  ].filter(Boolean);

  const primaryEmail = data.contactos?.find(c => c.email)?.email;
  const primaryTel   = data.contactos?.find(c => c.telefone)?.telefone;
  const quickActions = [
    { icon: '✉', label: 'Email', disabled: !primaryEmail, onClick: () => primaryEmail && (window.location.href = `mailto:${primaryEmail}`) },
    { icon: '📞', label: 'Ligar', disabled: !primaryTel,   onClick: () => primaryTel && (window.location.href = `tel:${primaryTel}`) },
    { icon: '💬', label: 'WhatsApp', disabled: !primaryTel, onClick: () => primaryTel && window.open(`https://wa.me/${primaryTel.replace(/\D/g,'')}`) },
    { icon: '✎', label: 'Nota', primary: true, onClick: () => onOpenEntity && data.entidade && onOpenEntity(data.entidade.fm_id, data.entidade.tenant) },
    { icon: '↗', label: 'Ver empresa', disabled: !data.entidade, onClick: () => onOpenEntity && data.entidade && onOpenEntity(data.entidade.fm_id, data.entidade.tenant) },
  ];

  const tabs = [
    { id: 'overview', label: 'Overview',
      render: () => <OpOverview data={data} kpis={kpis} onOpenEntity={onOpenEntity} onOpenContacto={onOpenContacto} fmId={fmId} /> },
    { id: 'notas', label: 'Notas Gestor', count: Array.isArray(data.notas) ? data.notas.length : 0,
      render: () => <OpNotasFM notas={data.notas} /> },
    { id: 'contactos', label: 'Contactos', count: data.contactos?.length || 0,
      render: () => (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
          {(data.contactos || []).map(c => (
            <ContactoCard key={c.fm_id} contacto={c}
              onEdit={() => {}} onOpen={onOpenContacto ? () => onOpenContacto(c.fm_id) : null} />
          ))}
          {(data.contactos || []).length === 0 && (
            <div style={{ padding: 24, fontSize: 12, color: 'var(--text-muted)', textAlign: 'center' }}>Sem contactos ligados à entidade.</div>
          )}
        </div>
      ) },
    { id: 'timeline', label: 'Timeline', render: () => <OpTimeline fmId={fmId} /> },
  ];

  return (
    <ProfileLayout
      left={<ProfileHeader type="oportunidade" data={op} kpis={kpis} signals={signals} onBack={onBack} subtitle={subtitle} />}
      centre={<ProfileActivity tabs={tabs} defaultTab="overview" storageKey={`crm_op_tab_${fmId}`} quickActions={quickActions} />}
      right={<DigiAIPanel apreciacao={apreciacao} loading={!apreciacao && !kpis} />}
    />
  );
}

// ── OpOverview — deal details + card empresa clicável + source
function OpOverview({ data, kpis, onOpenEntity, onOpenContacto, fmId }) {
  const [activity, setActivity] = React.useState(null);
  React.useEffect(() => {
    if (!fmId) return;
    CRMAPI.opActivity(fmId, { limit: 10 }).then(setActivity).catch(() => setActivity({ events: [] }));
  }, [fmId]);
  const op = data.oportunidade;
  const e  = data.entidade;
  const src = data.source;
  const cardStyle = { padding: '14px 16px', borderRadius: 8, background: 'var(--bg-sunken)', border: '1px solid var(--border)' };
  const label     = { fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 8 };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 22 }}>
      {/* Activity feed no topo */}
      <div>
        <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 10 }}>
          <div style={label}>Actividade recente</div>
          {activity && <div style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>{activity.total} eventos</div>}
        </div>
        <ActivityFeed events={activity?.events || []} loading={!activity} />
      </div>

      {/* Empresa (link) */}
      {e && (
        <div>
          <div style={label}>Empresa</div>
          <div style={{ ...cardStyle, cursor: onOpenEntity ? 'pointer' : 'default' }}
               onClick={() => onOpenEntity && onOpenEntity(e.fm_id, e.tenant)}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
              <Avatar name={e.nome} size={36} />
              <div style={{ minWidth: 0, flex: 1 }}>
                <div style={{ fontSize: 14, fontWeight: 700, color: 'var(--text)' }}>{e.nome}</div>
                <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 3, display: 'flex', gap: 10, flexWrap: 'wrap' }}>
                  {e.nif && <span style={{ fontFamily: 'var(--font-mono)' }}>NIF {e.nif}</span>}
                  {e.cidade && <span>{e.cidade}</span>}
                  {e.pais && <span>{e.pais}</span>}
                </div>
              </div>
              {onOpenEntity && <span style={{ color: 'var(--ai-500)', fontSize: 18 }}>→</span>}
            </div>
          </div>
        </div>
      )}

      {/* Deal details */}
      <div>
        <div style={label}>Deal</div>
        <div style={cardStyle}>
          <div style={{ display: 'grid', gridTemplateColumns: '110px 1fr', gap: '8px 12px', fontSize: 12 }}>
            <div style={{ color: 'var(--text-dim)' }}>Produto</div>
            <div style={{ color: 'var(--text)', fontWeight: 500 }}>{op.produto_name || '—'}</div>
            <div style={{ color: 'var(--text-dim)' }}>Stage</div>
            <div style={{ color: '#d97706', fontWeight: 700, fontFamily: 'var(--font-mono)' }}>{op.stage_name || '—'} {op.stage_number ? `(${op.stage_number})` : ''}</div>
            <div style={{ color: 'var(--text-dim)' }}>Status</div>
            <div style={{ color: op.status === 'WON' ? 'var(--success)' : op.status === 'LOST' ? 'var(--danger)' : 'var(--text)', fontWeight: 600 }}>{op.status || '—'}</div>
            <div style={{ color: 'var(--text-dim)' }}>Valor</div>
            <div style={{ color: 'var(--text)', fontWeight: 600, fontFamily: 'var(--font-display)' }}>{fmtK(op.produto_valor_k)}</div>
            <div style={{ color: 'var(--text-dim)' }}>Equipa</div>
            <div style={{ color: 'var(--text)' }}>{op.equipa_name || '—'}</div>
            <div style={{ color: 'var(--text-dim)' }}>Início</div>
            <div style={{ color: 'var(--text)', fontFamily: 'var(--font-mono)' }}>{op.start_date || '—'}{op.ano ? ` (${op.ano})` : ''}</div>
            {src && <>
              <div style={{ color: 'var(--text-dim)' }}>Origem</div>
              <div style={{ color: 'var(--text)' }}>{src.source_name}{src.grupo ? <span style={{ color: 'var(--text-dim)', marginLeft: 6, fontSize: 11 }}>· {src.grupo}</span> : null}</div>
            </>}
            {op.id_vendedor && <>
              <div style={{ color: 'var(--text-dim)' }}>Vendedor</div>
              <div style={{ color: 'var(--text)', fontFamily: 'var(--font-mono)' }}>{op.id_vendedor}</div>
            </>}
            <div style={{ color: 'var(--text-dim)' }}>Sincronizado</div>
            <div style={{ color: 'var(--text-muted)', fontFamily: 'var(--font-mono)', fontSize: 11 }}>{op.synced_at ? new Date(op.synced_at).toLocaleString('pt-PT') : '—'}</div>
          </div>
        </div>
      </div>

      {/* Contactos rápido (link para tab) */}
      {(data.contactos?.length || 0) > 0 && (
        <div>
          <div style={label}>Contactos ({data.contactos.length})</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            {data.contactos.slice(0, 4).map(c => (
              <div key={c.fm_id}
                onClick={() => onOpenContacto && onOpenContacto(c.fm_id)}
                style={{ ...cardStyle, cursor: onOpenContacto ? 'pointer' : 'default', display: 'flex', gap: 8, alignItems: 'center' }}>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 5 }}>
                    <div style={{ fontSize: 13, fontWeight: 600, color: onOpenContacto ? 'var(--ai-500)' : 'var(--text)' }}>{nomeComposto(c)}</div>
                    <span title={`RGPD: ${rgpdLabel(c.opt_rgpd)}`}
                      style={{ width: 7, height: 7, borderRadius: 99, background: rgpdDotColor(c.opt_rgpd), flexShrink: 0, cursor: 'help' }} />
                  </div>
                  {c.cargo && <div style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', marginTop: 1 }}>{c.cargo}</div>}
                  <div style={{ fontSize: 11, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>{c.telefone || '—'}{c.email ? ` · ${c.email}` : ''}</div>
                </div>
                {onOpenContacto && <span style={{ color: 'var(--ai-500)', fontSize: 16 }}>→</span>}
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

function OpNotasFM({ notas }) {
  const cardStyle = { padding: '12px 14px', borderRadius: 8, background: 'var(--bg-sunken)', border: '1px solid var(--border)', marginBottom: 8 };
  if (notas === null) return <div style={{ padding: 24, fontSize: 12, color: 'var(--text-dim)', fontStyle: 'italic', textAlign: 'center' }}>Notas Gestor indisponíveis (verificar ligação FM).</div>;
  if (!notas?.length)   return <div style={{ padding: 24, fontSize: 12, color: 'var(--text-muted)', textAlign: 'center' }}>Sem notas registadas para esta OP.</div>;
  return (
    <div>
      {notas.map((n, i) => {
        const texto = n.note || n.Note || n.Nota || '(sem texto)';
        const ts = n.created_at_fm || n.CreationTimestamp || '';
        const autor = n.Author ? ` · ${n.Author}` : '';
        return (
          <div key={n.fm_id || i} style={cardStyle}>
            <div style={{ fontSize: 10, color: 'var(--text-dim)', marginBottom: 4, fontFamily: 'var(--font-mono)' }}>
              {ts}{autor}
            </div>
            <div style={{ fontSize: 12, color: 'var(--text)', lineHeight: 1.55, whiteSpace: 'pre-wrap' }}>{texto}</div>
          </div>
        );
      })}
    </div>
  );
}

function OpTimeline({ fmId }) {
  const [rows, setRows] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  React.useEffect(() => {
    CRMAPI.opTimeline(fmId, { limit: 100 }).then(d => { setRows(d.rows || []); setLoading(false); }).catch(() => setLoading(false));
  }, [fmId]);
  if (loading) return <div style={{ padding: 24, fontSize: 12, color: 'var(--text-muted)' }}>A carregar timeline...</div>;
  if (rows.length === 0) return <div style={{ padding: 24, fontSize: 12, color: 'var(--text-muted)', textAlign: 'center' }}>Sem interacções registadas nesta entidade.</div>;
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
      {rows.map(r => (
        <div key={r.id} style={{
          padding: '10px 12px', borderRadius: 6,
          background: r.ligada_a_op ? 'color-mix(in oklch, var(--ai-500) 6%, var(--bg-sunken))' : 'var(--bg-sunken)',
          border: `1px solid ${r.ligada_a_op ? 'var(--ai-500)' : 'var(--border)'}`,
        }}>
          <div style={{ display: 'flex', gap: 8, alignItems: 'baseline', marginBottom: 3 }}>
            <span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', fontWeight: 700, color: 'var(--ai-500)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>{r.tipo}</span>
            {r.contacto_nome && <span style={{ fontSize: 11, color: 'var(--text-muted)' }}>· {r.contacto_nome}</span>}
            {r.ligada_a_op && <span style={{ fontSize: 9, fontFamily: 'var(--font-mono)', color: 'var(--ai-500)', border: '1px solid var(--ai-500)', padding: '1px 5px', borderRadius: 3 }}>ESTA OP</span>}
            <span style={{ marginLeft: 'auto', fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>{new Date(r.ocorreu_em).toLocaleString('pt-PT')}</span>
          </div>
          {r.contexto?.mensagem && <div style={{ fontSize: 12, color: 'var(--text)', lineHeight: 1.5 }}>{r.contexto.mensagem}</div>}
        </div>
      ))}
    </div>
  );
}

// ─── Sub-componentes do perfil ──────────────────────────────────────────────
function ProfileOverview({ data, tags, segs }) {
  const e = data.entidade;
  const ops = data.oportunidades || [];
  const active = ops.filter(o => o.status === 'OPEN');

  const cardStyle = { padding: '14px 16px', borderRadius: 8, background: 'var(--bg-sunken)', border: '1px solid var(--border)' };
  const label     = { fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 8 };

  const enderecoPartes = [e.address1, e.address2, [e.zip, e.cidade].filter(Boolean).join(' '), e.pais].filter(Boolean);

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
      {/* Detalhes da entidade (dados que não estão no header) */}
      <div>
        <div style={label}>Detalhes</div>
        <div style={cardStyle}>
          <div style={{ display: 'grid', gridTemplateColumns: '110px 1fr', gap: '8px 12px', fontSize: 12 }}>
            <div style={{ color: 'var(--text-dim)' }}>Morada</div>
            <div style={{ color: 'var(--text)' }}>{enderecoPartes.length > 0 ? enderecoPartes.join(', ') : '—'}</div>
            <div style={{ color: 'var(--text-dim)' }}>Website</div>
            <div>{e.website ? <a href={e.website.startsWith('http') ? e.website : 'https://' + e.website} target="_blank" rel="noopener noreferrer" style={{ color: 'var(--ai-500)' }}>{e.website}</a> : <span style={{ color: 'var(--text-dim)' }}>—</span>}</div>
            <div style={{ color: 'var(--text-dim)' }}>Sincronizado</div>
            <div style={{ color: 'var(--text-muted)', fontFamily: 'var(--font-mono)', fontSize: 11 }}>{e.synced_at ? new Date(e.synced_at).toLocaleString('pt-PT') : '—'}</div>
          </div>
        </div>
      </div>

      {/* OPs em Curso — top 5, resto no tab Oportunidades */}
      {active.length > 0 && (
        <div>
          <div style={label}>OPs em curso · top {Math.min(5, active.length)} de {active.length}</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            {active.slice(0, 5).map((o, i) => (
              <div key={i} style={cardStyle}>
                <div style={{ display: 'flex', gap: 8, alignItems: 'center', marginBottom: 3 }}>
                  <StageChip name={o.stage_name} small />
                  <span style={{ fontWeight: 600, fontSize: 13, flex: 1 }}>{o.produto_name || '—'}</span>
                  {o.produto_valor_k && <span style={{ fontSize: 12, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>{fmtK(o.produto_valor_k)}</span>}
                </div>
                <div style={{ fontSize: 11, color: 'var(--text-dim)' }}>{o.equipa_name}{o.start_date ? ` · ${o.start_date}` : ''}</div>
              </div>
            ))}
          </div>
        </div>
      )}

      {/* Notas Gestor recentes */}
      {data.notas === null && (
        <div style={{ ...cardStyle, fontSize: 12, color: 'var(--text-dim)', fontStyle: 'italic' }}>
          Notas Gestor indisponíveis (verificar ligação FM).
        </div>
      )}
      {Array.isArray(data.notas) && data.notas.length > 0 && (
        <div>
          <div style={label}>
            Notas Gestor{data.notas_op?.produto ? ` · OP ${data.notas_op.produto}` : ''}
          </div>
          {data.notas.slice(0, 5).map((n, i) => (
            <div key={n.fm_id || i} style={{ ...cardStyle, marginBottom: 6 }}>
              <div style={{ fontSize: 10, color: 'var(--text-dim)', marginBottom: 3, fontFamily: 'var(--font-mono)' }}>
                {n.created_at_fm || n.CreationTimestamp || ''}
                {n.id_oportunidade ? ` · OP ${n.id_oportunidade}` : ''}
              </div>
              <div style={{ fontSize: 12, color: 'var(--text)', lineHeight: 1.55 }}>{n.note || n.Note || n.Nota || '(sem texto)'}</div>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

function ProfileContactos({ data, userEmail, onReload, onOpenContacto }) {
  const [editingCt, setEditingCt] = React.useState(null);
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
      {(data.contactos || []).map((c, i) => (
        <ContactoCard key={c.fm_id || i} contacto={c}
          onEdit={() => setEditingCt(c.fm_id)}
          onOpen={onOpenContacto ? () => onOpenContacto(c.fm_id) : null} />
      ))}
      {editingCt && <ContactoEditModal ctId={editingCt} onClose={() => { setEditingCt(null); onReload(); }} userEmail={userEmail} />}
    </div>
  );
}

function ContactoCard({ contacto, onEdit, onOpen }) {
  const [tags, setTags] = React.useState([]);
  const [override, setOverride] = React.useState(null);
  React.useEffect(() => {
    CRMAPI.ctTags(contacto.fm_id).then(setTags).catch(() => {});
    CRMAPI.ctOverride(contacto.fm_id).then(setOverride).catch(() => {});
  }, [contacto.fm_id]);

  const nomeDisplay = nomeComposto(contacto);
  const rgpdColor = rgpdDotColor(contacto.opt_rgpd);

  return (
    <div style={{
      padding: '12px 14px', borderRadius: 8, background: 'var(--bg-sunken)', border: '1px solid var(--border)',
      cursor: onOpen ? 'pointer' : 'default', transition: 'background 0.15s',
    }}
      onClick={onOpen ? (ev) => { if (!ev.target.closest('button')) onOpen(); } : undefined}
      role={onOpen ? 'button' : undefined}>
      <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 12 }}>
        <div style={{ flex: 1, minWidth: 0 }}>
          {/* Nome + opt_rgpd dot */}
          <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
            <div style={{ fontSize: 13, fontWeight: 600, color: onOpen ? 'var(--ai-500)' : 'var(--text)' }}>{nomeDisplay}</div>
            <span
              title={`RGPD: ${rgpdLabel(contacto.opt_rgpd)}`}
              style={{ width: 8, height: 8, borderRadius: 99, background: rgpdColor, flexShrink: 0, cursor: 'help' }}
            />
          </div>
          {/* Cargo */}
          {contacto.cargo && (
            <div style={{ fontSize: 11, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', marginTop: 2, letterSpacing: '0.02em' }}>
              {contacto.cargo}
            </div>
          )}
          <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 3 }}>
            {(override?.telefone_override || contacto.telefone) && <span style={{ marginRight: 10, fontFamily: 'var(--font-mono)' }}>{override?.telefone_override || contacto.telefone}{override?.telefone_override && ' *'}</span>}
            {(override?.email_override || contacto.email) && <span>{override?.email_override || contacto.email}{override?.email_override && ' *'}</span>}
          </div>
          {override?.canal_preferido && (
            <div style={{ fontSize: 10, color: 'var(--ai-500)', fontFamily: 'var(--font-mono)', marginTop: 4 }}>
              Canal preferido: {override.canal_preferido.toUpperCase()}
            </div>
          )}
          {tags.length > 0 && (
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: 3, marginTop: 6 }}>
              {tags.map(t => (
                <span key={t.id} style={{
                  fontSize: 9, fontFamily: 'var(--font-mono)', fontWeight: 600,
                  padding: '2px 6px', borderRadius: 3,
                  background: `color-mix(in oklch, ${t.cor} 14%, transparent)`,
                  color: t.cor, border: `1px solid ${t.cor}30`,
                }}>#{t.nome}</span>
              ))}
            </div>
          )}
        </div>
        <button className="btn btn-xs" onClick={(ev) => { ev.stopPropagation(); onEdit(); }} style={{ fontSize: 11, flexShrink: 0 }}>Editar</button>
      </div>
    </div>
  );
}

function ContactoEditModal({ ctId, onClose, userEmail }) {
  const [override, setOverride] = React.useState(null);
  const [consent, setConsent] = React.useState([]);
  const [saving, setSaving] = React.useState(false);
  const [saved, setSaved] = React.useState(false);

  React.useEffect(() => {
    Promise.all([CRMAPI.ctOverride(ctId), CRMAPI.ctConsent(ctId)]).then(([o, c]) => {
      setOverride(o || { email_override: '', telefone_override: '', canal_preferido: '', motivo: '' });
      setConsent(c || []);
    });
  }, [ctId]);

  const consentOf = (canal) => consent.find(c => c.canal === canal)?.estado || 'desconhecido';

  const setConsentEstado = async (canal, estado) => {
    await CRMAPI.setCtConsent(ctId, { canal, estado, updated_by: userEmail });
    const c = await CRMAPI.ctConsent(ctId);
    setConsent(c || []);
  };

  const handleSave = async () => {
    setSaving(true);
    await CRMAPI.setCtOverride(ctId, { ...override, updated_by: userEmail });
    setSaved(true); setSaving(false);
    setTimeout(() => onClose(), 1200);
  };

  if (!override) return null;

  return (
    <>
      <div onClick={onClose} style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.4)', zIndex: 199 }} />
      <div style={{
        position: 'fixed', top: 0, right: 0, bottom: 0, width: 420,
        background: 'var(--bg-card)', borderLeft: '1px solid var(--border)',
        zIndex: 200, display: 'flex', flexDirection: 'column', boxShadow: '-8px 0 32px rgba(0,0,0,0.12)',
      }}>
        <div style={{ padding: '16px 20px', borderBottom: '1px solid var(--border)', display: 'flex', justifyContent: 'space-between' }}>
          <div style={{ fontSize: 13, fontWeight: 600 }}>Editar contacto</div>
          <button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 18, color: 'var(--text-muted)' }}>×</button>
        </div>
        <div className="scrollbar" style={{ flex: 1, overflowY: 'auto', padding: 20, display: 'flex', flexDirection: 'column', gap: 16 }}>
          <div>
            <label style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.08em', display: 'block', marginBottom: 5 }}>Override Email</label>
            <input type="text" value={override.email_override || ''} onChange={e => setOverride({ ...override, email_override: e.target.value })}
              style={{ width: '100%', padding: '6px 10px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg)', color: 'var(--text)', fontSize: 12 }} />
          </div>
          <div>
            <label style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.08em', display: 'block', marginBottom: 5 }}>Override Telefone</label>
            <input type="text" value={override.telefone_override || ''} onChange={e => setOverride({ ...override, telefone_override: e.target.value })}
              style={{ width: '100%', padding: '6px 10px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg)', color: 'var(--text)', fontSize: 12 }} />
          </div>
          <div>
            <label style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.08em', display: 'block', marginBottom: 5 }}>Canal preferido</label>
            <div style={{ display: 'flex', gap: 8 }}>
              {['wa', 'email', 'tel', ''].map(c => (
                <button key={c || 'none'} onClick={() => setOverride({ ...override, canal_preferido: c })}
                  style={{
                    padding: '4px 10px', borderRadius: 6, border: '1px solid var(--border)', cursor: 'pointer', fontSize: 11,
                    background: override.canal_preferido === c ? 'var(--ai-500)' : 'transparent',
                    color: override.canal_preferido === c ? '#fff' : 'var(--text-muted)',
                  }}>{c || 'nenhum'}</button>
              ))}
            </div>
          </div>
          <div>
            <label style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.08em', display: 'block', marginBottom: 5 }}>Motivo do override</label>
            <input type="text" value={override.motivo || ''} onChange={e => setOverride({ ...override, motivo: e.target.value })}
              placeholder="Email antigo devolveu bounce..."
              style={{ width: '100%', padding: '6px 10px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg)', color: 'var(--text)', fontSize: 12 }} />
          </div>

          <div style={{ paddingTop: 12, borderTop: '1px solid var(--border)' }}>
            <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 10 }}>Consentimentos RGPD</div>
            {['wa', 'email', 'tel'].map(canal => (
              <div key={canal} style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
                <span style={{ fontSize: 12, minWidth: 60, textTransform: 'uppercase', fontFamily: 'var(--font-mono)', color: 'var(--text-muted)' }}>{canal}</span>
                {['opt_in', 'opt_out', 'desconhecido'].map(estado => (
                  <button key={estado} onClick={() => setConsentEstado(canal, estado)}
                    style={{
                      padding: '3px 8px', borderRadius: 6, border: '1px solid var(--border)', cursor: 'pointer', fontSize: 10,
                      background: consentOf(canal) === estado ?
                        (estado === 'opt_in' ? 'var(--success)' : estado === 'opt_out' ? 'var(--danger)' : 'var(--text-dim)')
                        : 'transparent',
                      color: consentOf(canal) === estado ? '#fff' : 'var(--text-muted)',
                    }}>{estado.replace('_', '-')}</button>
                ))}
              </div>
            ))}
          </div>
        </div>
        <div style={{ padding: 16, borderTop: '1px solid var(--border)' }}>
          <button className="btn-ai" onClick={handleSave} disabled={saving} style={{ width: '100%',
            ...(saved ? { background: 'var(--success)', borderColor: 'var(--success)' } : {}) }}>
            {saving ? 'A guardar...' : saved ? 'Guardado' : 'Guardar'}
          </button>
        </div>
      </div>
    </>
  );
}

function ProfileTimeline({ fmId }) {
  const [rows, setRows] = React.useState([]);
  const [total, setTotal] = React.useState(0);
  const [tipos, setTipos] = React.useState([]);
  const [loading, setLoading] = React.useState(true);

  React.useEffect(() => {
    setLoading(true);
    CRMAPI.timeline(fmId, { tipos, limit: 100 })
      .then(d => { setRows(d.rows || []); setTotal(d.total || 0); setLoading(false); })
      .catch(() => setLoading(false));
  }, [fmId, tipos.join(',')]);

  const tipoLabels = {
    wa_campanha_enviada: 'WA · Campanha enviada',
    wa_resposta: 'WA · Resposta',
    digi_ai_session: 'Digi AI · Sessao',
    nota_gestor: 'Nota Gestor',
    nota_marketing: 'Nota Marketing',
    email_enviado: 'Email enviado',
    ad_impressao: 'Ad · Impressao',
  };

  const toggleTipo = (t) => {
    setTipos(ts => ts.includes(t) ? ts.filter(x => x !== t) : [...ts, t]);
  };

  return (
    <div>
      {/* Filtros por tipo */}
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: 16 }}>
        {Object.entries(tipoLabels).map(([id, label]) => (
          <button key={id} onClick={() => toggleTipo(id)}
            style={{
              padding: '3px 10px', borderRadius: 4, border: '1px solid var(--border)', cursor: 'pointer',
              fontSize: 11, fontFamily: 'var(--font-mono)',
              background: tipos.includes(id) ? 'var(--ai-500)' : 'transparent',
              color: tipos.includes(id) ? '#fff' : 'var(--text-muted)',
            }}>{label}</button>
        ))}
      </div>

      {loading && <div style={{ color: 'var(--text-dim)', fontSize: 12 }}>A carregar...</div>}
      {!loading && rows.length === 0 && (
        <div style={{ padding: 40, textAlign: 'center', color: 'var(--text-dim)', fontSize: 12 }}>Sem interacções registadas</div>
      )}

      <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
        {rows.map(r => (
          <div key={r.id} style={{ padding: '10px 14px', background: 'var(--bg-sunken)', borderRadius: 8, border: '1px solid var(--border)' }}>
            <div style={{ display: 'flex', gap: 8, alignItems: 'center', marginBottom: 4 }}>
              <span style={{
                fontSize: 9, fontFamily: 'var(--font-mono)', fontWeight: 700,
                padding: '2px 7px', borderRadius: 4, letterSpacing: '0.04em',
                background: 'color-mix(in oklch, var(--ai-500) 14%, transparent)',
                color: 'var(--ai-500)', border: '1px solid color-mix(in oklch, var(--ai-500) 30%, transparent)',
              }}>{tipoLabels[r.tipo] || r.tipo}</span>
              {r.contacto_nome && <span style={{ fontSize: 11, color: 'var(--text-muted)' }}>· {r.contacto_nome}</span>}
              <span style={{ marginLeft: 'auto', fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>
                {new Date(r.ocorreu_em).toLocaleString('pt-PT', { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' })}
              </span>
            </div>
            {r.contexto && (r.contexto.campanha_nome || r.contexto.corpo_preview) && (
              <div style={{ fontSize: 12, color: 'var(--text)', lineHeight: 1.4 }}>
                {r.contexto.campanha_nome && <span style={{ fontWeight: 500 }}>{r.contexto.campanha_nome}</span>}
                {r.contexto.corpo_preview && <span>{r.contexto.corpo_preview}</span>}
              </div>
            )}
          </div>
        ))}
      </div>

      {total > rows.length && (
        <div style={{ marginTop: 10, fontSize: 11, color: 'var(--text-dim)' }}>
          Mostrando {rows.length} de {total} interacções
        </div>
      )}
    </div>
  );
}

function ProfileTagsSegmentos({ fmId, tags, segs, onReload, userEmail }) {
  const [allTags, setAllTags] = React.useState([]);
  const [allSegs, setAllSegs] = React.useState([]);
  const [newTagInput, setNewTagInput] = React.useState('');

  React.useEffect(() => {
    CRMAPI.tags().then(setAllTags).catch(() => {});
    CRMAPI.segmentos().then(setAllSegs).catch(() => {});
  }, []);

  const applyTag = async (tagId) => { await CRMAPI.addEntTag(fmId, tagId); onReload(); CRMAPI.tags().then(setAllTags); };
  const removeTag = async (tagId) => { await CRMAPI.delEntTag(fmId, tagId); onReload(); };

  const createAndApply = async () => {
    if (!newTagInput.trim()) return;
    const t = await CRMAPI.criarTag({ nome: newTagInput.trim(), created_by: userEmail });
    setNewTagInput('');
    await applyTag(t.id);
  };

  const setSeg = async (segId, valorId) => { await CRMAPI.setEntSeg(fmId, segId, valorId, userEmail); onReload(); };
  const clearSeg = async (segId) => { await CRMAPI.delEntSeg(fmId, segId); onReload(); };

  const tagIds = new Set(tags.map(t => t.id));
  const segMap = {}; segs.forEach(s => { segMap[s.segmento_id] = s; });

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
      {/* Segmentos */}
      <div>
        <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 10 }}>Segmentos estruturados</div>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
          {allSegs.map(s => (
            <div key={s.id} style={{ padding: '10px 14px', background: 'var(--bg-sunken)', borderRadius: 8, border: '1px solid var(--border)' }}>
              <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text)', marginBottom: 6 }}>{s.nome}</div>
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
                {(s.valores || []).map(v => {
                  const active = segMap[s.id]?.valor_id === v.id;
                  return (
                    <button key={v.id} onClick={() => active ? clearSeg(s.id) : setSeg(s.id, v.id)}
                      style={{
                        fontSize: 10, fontFamily: 'var(--font-mono)', fontWeight: 600,
                        padding: '4px 10px', borderRadius: 4, cursor: 'pointer',
                        background: active ? `color-mix(in oklch, ${v.cor} 24%, transparent)` : 'transparent',
                        color: active ? v.cor : 'var(--text-muted)',
                        border: `1px solid ${active ? v.cor : 'var(--border)'}`,
                      }}>{v.valor}</button>
                  );
                })}
              </div>
            </div>
          ))}
        </div>
      </div>

      {/* Tags */}
      <div>
        <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 10 }}>Tags aplicadas</div>
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, marginBottom: 12 }}>
          {tags.map(t => (
            <span key={t.id} style={{
              fontSize: 11, fontFamily: 'var(--font-mono)', fontWeight: 600,
              padding: '3px 4px 3px 9px', borderRadius: 4,
              background: `color-mix(in oklch, ${t.cor} 14%, transparent)`,
              color: t.cor, border: `1px solid ${t.cor}30`,
              display: 'inline-flex', alignItems: 'center', gap: 4,
            }}>
              #{t.nome}
              <button onClick={() => removeTag(t.id)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: t.cor, fontSize: 14, padding: '0 4px', opacity: 0.6 }}>×</button>
            </span>
          ))}
          {tags.length === 0 && <span style={{ fontSize: 12, color: 'var(--text-dim)' }}>Sem tags aplicadas</span>}
        </div>

        <div style={{ display: 'flex', gap: 6, marginBottom: 10 }}>
          <input value={newTagInput} onChange={e => setNewTagInput(e.target.value)}
            placeholder="Nova tag ou pesquisar..."
            onKeyDown={e => e.key === 'Enter' && createAndApply()}
            style={{ flex: 1, padding: '5px 10px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg-elev, #ffffff)', color: 'var(--text)', fontSize: 12 }} />
          <button className="btn-ai" onClick={createAndApply} style={{ fontSize: 11, padding: '5px 12px' }}>Criar</button>
        </div>

        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
          {allTags.filter(t => !tagIds.has(t.id) && (!newTagInput || t.nome.toLowerCase().includes(newTagInput.toLowerCase()))).slice(0, 20).map(t => (
            <button key={t.id} onClick={() => applyTag(t.id)}
              style={{
                fontSize: 10, fontFamily: 'var(--font-mono)', fontWeight: 600,
                padding: '3px 8px', borderRadius: 4, cursor: 'pointer',
                background: 'transparent', color: 'var(--text-muted)', border: `1px solid var(--border)`,
              }}>+ #{t.nome}</button>
          ))}
        </div>
      </div>
    </div>
  );
}

function ProfileNotas({ fmId, userEmail, userName, scopedContactoId, contactoNome }) {
  const [notas, setNotas] = React.useState([]);
  const [novaNota, setNovaNota] = React.useState('');
  const [tipo, setTipo] = React.useState(scopedContactoId ? 'contacto' : 'geral');
  const [saving, setSaving] = React.useState(false);

  const load = () => {
    if (!fmId) { setNotas([]); return; }
    CRMAPI.entNotas(fmId).then(all => {
      const filtered = scopedContactoId ? (all || []).filter(n => n.contacto_fm_id === scopedContactoId) : (all || []);
      setNotas(filtered);
    }).catch(() => {});
  };
  React.useEffect(() => { load(); }, [fmId, scopedContactoId]);

  const handleSave = async () => {
    if (!novaNota.trim() || !fmId) return;
    setSaving(true);
    await CRMAPI.criarNota(fmId, {
      corpo: novaNota, tipo,
      contacto_fm_id: scopedContactoId || null,
      autor_email: userEmail, autor_nome: userName,
    });
    setNovaNota(''); setSaving(false); load();
  };

  const handleDel = async (id) => {
    if (!confirm('Eliminar esta nota?')) return;
    await CRMAPI.eliminarNota(id); load();
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
      <div style={{ padding: '14px 16px', background: 'var(--bg-sunken)', borderRadius: 8, border: '1px solid var(--border)' }}>
        <div style={{ display: 'flex', gap: 6, marginBottom: 8 }}>
          {['geral', 'contacto', 'followup'].map(t => (
            <button key={t} onClick={() => setTipo(t)}
              style={{
                padding: '3px 10px', borderRadius: 6, border: '1px solid var(--border)', cursor: 'pointer',
                fontSize: 10, fontFamily: 'var(--font-mono)', textTransform: 'uppercase',
                background: tipo === t ? 'var(--ai-500)' : 'transparent',
                color: tipo === t ? '#fff' : 'var(--text-muted)',
              }}>{t}</button>
          ))}
        </div>
        <textarea value={novaNota} onChange={e => setNovaNota(e.target.value)}
          placeholder="Nova nota..."
          rows={3}
          style={{ width: '100%', padding: '8px 10px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg)', color: 'var(--text)', fontSize: 12, resize: 'vertical' }} />
        <div style={{ marginTop: 8, textAlign: 'right' }}>
          <button className="btn-ai" onClick={handleSave} disabled={saving || !novaNota.trim()} style={{ fontSize: 12 }}>
            {saving ? 'A guardar...' : 'Registar nota'}
          </button>
        </div>
      </div>

      {notas.map(n => (
        <div key={n.id} style={{ padding: '10px 14px', background: 'var(--bg-sunken)', borderRadius: 8, border: '1px solid var(--border)' }}>
          <div style={{ display: 'flex', gap: 8, alignItems: 'center', marginBottom: 6 }}>
            <span style={{ fontSize: 9, fontFamily: 'var(--font-mono)', fontWeight: 700, padding: '2px 6px', borderRadius: 3, background: 'color-mix(in oklch, var(--ai-500) 14%, transparent)', color: 'var(--ai-500)', textTransform: 'uppercase' }}>{n.tipo}</span>
            <span style={{ fontSize: 11, color: 'var(--text-muted)' }}>{n.autor_nome || n.autor_email}</span>
            <span style={{ marginLeft: 'auto', fontSize: 10, color: 'var(--text-dim)' }}>
              {new Date(n.created_at).toLocaleString('pt-PT', { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' })}
            </span>
            <button onClick={() => handleDel(n.id)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--danger)', fontSize: 11 }}>Eliminar</button>
          </div>
          <div style={{ fontSize: 12, color: 'var(--text)', lineHeight: 1.5, whiteSpace: 'pre-wrap' }}>{n.corpo}</div>
        </div>
      ))}

      {notas.length === 0 && (
        <div style={{ padding: 20, textAlign: 'center', color: 'var(--text-dim)', fontSize: 12 }}>Sem notas Marketing registadas</div>
      )}
    </div>
  );
}

function ProfileCampanhas({ fmId }) {
  const [rows, setRows] = React.useState([]);
  const [loading, setLoading] = React.useState(true);

  React.useEffect(() => {
    CRMAPI.timeline(fmId, { tipos: ['wa_campanha_enviada'], limit: 500 })
      .then(d => {
        // agrupar por campanha_id
        const groups = {};
        for (const r of (d.rows || [])) {
          const key = r.contexto?.campanha_id || 'sem-id';
          if (!groups[key]) groups[key] = { campanha_id: key, nome: r.contexto?.campanha_nome || 'Campanha', contactos: new Set(), ultima: r.ocorreu_em };
          if (r.contacto_fm_id) groups[key].contactos.add(r.contacto_fm_id);
        }
        setRows(Object.values(groups).map(g => ({ ...g, n_contactos: g.contactos.size })));
        setLoading(false);
      })
      .catch(() => setLoading(false));
  }, [fmId]);

  if (loading) return <div style={{ color: 'var(--text-dim)', fontSize: 12 }}>A carregar...</div>;
  if (rows.length === 0) return <div style={{ padding: 20, textAlign: 'center', color: 'var(--text-dim)', fontSize: 12 }}>Sem campanhas registadas para esta entidade</div>;

  return (
    <div style={{ border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden' }}>
      <table style={{ width: '100%', borderCollapse: 'collapse' }}>
        <thead>
          <tr>
            {['Campanha', 'Contactos tocados', 'Última acção'].map(h => (
              <th key={h} style={{ fontSize: 10, fontWeight: 700, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', padding: '6px 10px', background: 'var(--bg-sunken)', borderBottom: '1px solid var(--border)', textAlign: 'left' }}>{h}</th>
            ))}
          </tr>
        </thead>
        <tbody>
          {rows.map(r => (
            <tr key={r.campanha_id}>
              <td style={{ fontSize: 12, padding: '9px 10px', borderBottom: '1px solid var(--border)', fontWeight: 600 }}>{r.nome}</td>
              <td style={{ fontSize: 12, padding: '9px 10px', borderBottom: '1px solid var(--border)', textAlign: 'center' }}>{r.n_contactos}</td>
              <td style={{ fontSize: 11, padding: '9px 10px', borderBottom: '1px solid var(--border)', color: 'var(--text-muted)' }}>
                {new Date(r.ultima).toLocaleDateString('pt-PT')}
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

function ProfileSidebarActions({ fmId, userEmail, userName, onReload }) {
  const [listas, setListas] = React.useState([]);
  const [listaSel, setListaSel] = React.useState('');
  const [adding, setAdding] = React.useState(false);
  const [added, setAdded] = React.useState(false);
  const [quickInteracao, setQuickInteracao] = React.useState('');
  const [quickTipo, setQuickTipo] = React.useState('nota_marketing');

  React.useEffect(() => { CRMAPI.listas().then(d => setListas(d.rows || [])).catch(() => {}); }, []);

  const addToLista = async () => {
    if (!listaSel) return;
    setAdding(true);
    await CRMAPI.addItemLista(listaSel, { entidade_fm_id: fmId, added_by: userEmail });
    setAdded(true); setAdding(false);
    setTimeout(() => setAdded(false), 2500);
  };

  const cardBase = { padding: '14px 16px', borderRadius: 8, background: 'var(--bg-sunken)', border: '1px solid var(--border)' };
  const labelStyle = { fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 8 };

  return (
    <>
      <div style={cardBase}>
        <div style={labelStyle}>Adicionar a lista</div>
        <select value={listaSel} onChange={e => setListaSel(e.target.value)}
          style={{ width: '100%', padding: '5px 8px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg-elev, #ffffff)', color: 'var(--text)', fontSize: 12, marginBottom: 8 }}>
          <option value="">Seleccionar...</option>
          {listas.map(l => <option key={l.id} value={l.id}>{l.nome} ({l.n_items})</option>)}
        </select>
        <button className="btn-ai" onClick={addToLista} disabled={!listaSel || adding} style={{
          width: '100%', fontSize: 11,
          ...(added ? { background: 'var(--success)', borderColor: 'var(--success)' } : {}),
        }}>{adding ? '...' : added ? 'Adicionado' : 'Adicionar'}</button>
      </div>

      <div style={cardBase}>
        <div style={labelStyle}>Consentimentos</div>
        <div style={{ fontSize: 11, color: 'var(--text-muted)', lineHeight: 1.5 }}>
          Configurar consentimentos por contacto na tab CONTACTOS. Envios respeitam automaticamente opt-outs de WA/email.
        </div>
      </div>
    </>
  );
}

// ═══════════════════════════════════════════════════════════════════════════
// ── Score badge helper ────────────────────────────────────────────────────────
function ScoreBadge({ score, classification, small }) {
  const colors = { HOT: '#ef4444', QUALIFICADO: '#22c55e', NURTURE: '#f59e0b', NAO_QUALIFICADO: '#94a3b8' };
  const sz = small ? 9 : 10;
  if (score != null) {
    const color = colors[classification] || '#94a3b8';
    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
        <span style={{
          fontSize: sz, fontWeight: 700, fontFamily: 'var(--font-mono)',
          padding: small ? '1px 5px' : '2px 7px', borderRadius: 4, letterSpacing: '0.04em',
          background: `color-mix(in oklch, ${color} 14%, transparent)`,
          color, border: `1px solid ${color}30`, whiteSpace: 'nowrap', display: 'inline-block',
        }}>{score}/100{classification ? ` · ${classification}` : ''}</span>
        {small && (
          <div style={{ height: 3, borderRadius: 2, background: 'var(--border)', width: 60, overflow: 'hidden' }}>
            <div style={{ height: '100%', width: `${Math.min(100, score)}%`, background: color, borderRadius: 2 }} />
          </div>
        )}
      </div>
    );
  }
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
      <span style={{ fontSize: sz, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>0/100</span>
      {small && (
        <div style={{ height: 3, borderRadius: 2, background: 'var(--border)', width: 60 }} />
      )}
    </div>
  );
}

function CanalChip({ canal }) {
  const map = { ctwa: { label: 'CTWA', color: '#3859D0' }, lead_gen_form: { label: 'FORM', color: '#0ea5e9' }, site_biond: { label: 'SITE', color: '#22c55e' }, wa_direct: { label: 'WA', color: '#10b981' } };
  const c = map[canal] || { label: (canal || 'WA').toUpperCase(), color: '#64748b' };
  return <span style={{ fontSize: 9, fontWeight: 700, fontFamily: 'var(--font-mono)', padding: '1px 5px', borderRadius: 4, background: `color-mix(in oklch, ${c.color} 14%, transparent)`, color: c.color, border: `1px solid ${c.color}30` }}>{c.label}</span>;
}

// ═══════════════════════════════════════════════════════════════════════════
// EXTERNO LEAD PROFILE — ficha completa de lead externa (Digi AI WA)
// ═══════════════════════════════════════════════════════════════════════════
function ExternoLeadProfile({ externoId, onBack, userEmail, userName, embedded }) {
  const [data, setData] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [nota, setNota] = React.useState('');
  const [savingNota, setSavingNota] = React.useState(false);
  const [activeTab, setActiveTab] = React.useState('overview');
  const [enriching, setEnriching] = React.useState(false);
  const [enrichError, setEnrichError] = React.useState(null);
  const [debugOpen, setDebugOpen] = React.useState(false);
  const [campanhasLig, setCampanhasLig] = React.useState([]);
  React.useEffect(() => {
    fetch('/api/marketing/crm/leads/campanhas-activas').then(r => r.json()).then(d => setCampanhasLig(d.rows || [])).catch(() => {});
  }, []);

  const load = React.useCallback(() => {
    setLoading(true);
    CRMAPI.externo360(externoId).then(d => { setData(d); setLoading(false); }).catch(() => setLoading(false));
  }, [externoId]);

  React.useEffect(() => { load(); }, [load]);

  const addNota = async () => {
    if (!nota.trim()) return;
    setSavingNota(true);
    await CRMAPI.externoNota(externoId, { corpo: nota, autor_email: userEmail, autor_nome: userName }).catch(() => {});
    setNota(''); setSavingNota(false); load();
  };

  const runEnrich = async () => {
    if (enriching) return;
    setEnriching(true); setEnrichError(null);
    const r = await fetch(`/api/marketing/crm/externos/${externoId}/enrich`, { method: 'POST' }).then(r => r.json()).catch(e => ({ error: e.message }));
    setEnriching(false);
    if (r.error) { setEnrichError(r.error); } else { load(); }
  };

  const ligarCampanha = async (campanhaId) => {
    if (!campanhaId) return;
    await fetch(`/api/marketing/crm/externos/${externoId}/ligar-campanha`, {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ campanha_id: campanhaId, direccao: 'inbound' }),
    }).catch(() => {});
    load();
  };

  if (loading || !data) return (
    <div style={{ padding: 40, textAlign: 'center', color: 'var(--text-muted)', fontSize: 13 }}>
      A carregar...
      <div style={{ marginTop: 20 }}><button className="btn btn-xs" onClick={onBack}>Voltar</button></div>
    </div>
  );

  const { externo: x, session: s, wa_messages: msgs = [], wa_followups: fups = [], notas = [], wa_templates: tpls = [] } = data;
  const phone = x.telefone;
  const nome = x.nome || phone;
  // Dados de qualificação: heydigi session (CTWA) ou crm_contactos_externos (FORM)
  const q = {
    lead_score:          s?.lead_score          ?? x.lead_score,
    classification:      s?.classification      ?? x.classification,
    pain_identified:     s?.pain_identified      ?? x.pain_identified,
    timing_bucket:       s?.timing_bucket        ?? x.timing_bucket,
    volume_m2_mes:       s?.volume_m2_mes        ?? x.volume_m2_mes,
    aceita_demo:         s?.aceita_demo          ?? x.aceita_demo,
    wa_num_msgs:         s ? (s.messages?.length || x.wa_num_msgs) : x.wa_num_msgs,
    ultima_actividade:   s?.ultima_actividade    ?? x.wa_ultima_actividade,
    company_enrichment:  s?.company_enrichment   ?? x.company_enrichment,
    recomendacao_comercial: s?.recomendacao_comercial,
    score_criteria:      s?.score_criteria,
    budget_range:        s?.budget_range,
    decisor_role:        s?.decisor_role         ?? x.cargo,
    equipment_current:   s?.equipment_current,
    outsourcing_m2_mes:  s?.outsourcing_m2_mes,
    empresa_declarada:   s?.empresa_declarada    ?? x.empresa,
    objecao_principal:   s?.objecao_principal,
  };
  const subtitle = [
    phone && <span key="tel" style={{ fontFamily: 'var(--font-mono)' }}>{phone}</span>,
    x.email && <a key="mail" href={`mailto:${x.email}`} style={{ color: 'var(--ai-500)' }}>{x.email}</a>,
    x.origem_campanha && <span key="camp" style={{ fontSize: 11 }}>{x.origem_campanha}</span>,
  ].filter(Boolean);

  const thS = { fontSize: 10, fontWeight: 700, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', padding: '6px 10px', background: 'var(--bg-sunken)', borderBottom: '1px solid var(--border)', textAlign: 'left' };
  const tdS = { fontSize: 12, padding: '9px 10px', borderBottom: '1px solid var(--border)', color: 'var(--text)', verticalAlign: 'top' };
  const hasTpls = tpls.length > 0;
  const conversaCount = msgs.length + (x.t0_enviado ? 1 : 0);
  const tabs = ['overview', 'conversa', 'followups', 'notas', ...(hasTpls ? ['templates'] : [])];
  const tabLabel = { overview: 'Overview', conversa: `Conversa WA (${conversaCount})`, followups: `Follow-ups (${fups.length})`, notas: `Notas (${notas.length})`, templates: `Templates (${tpls.length})` };

  return (
    <div>
      {/* Header */}
      {!embedded && (
      <div style={{ display: 'flex', alignItems: 'flex-start', gap: 16, marginBottom: 24 }}>
        <button onClick={onBack} className="btn btn-xs" style={{ marginTop: 4, flexShrink: 0 }}>Voltar</button>
        <div style={{ flex: 1 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
            <span style={{ fontSize: 20, fontWeight: 700, color: 'var(--text)' }}>{nome}</span>
            <span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', fontWeight: 700, padding: '2px 8px', borderRadius: 4,
              background: (x.na_bd || x.entidade_nome || x.matched_entidade_fm_id) ? '#16a34a18' : '#d9770618',
              color: (x.na_bd || x.entidade_nome || x.matched_entidade_fm_id) ? '#15803d' : '#b45309',
              border: '1px solid currentColor' }}>
              {(x.na_bd || x.entidade_nome || x.matched_entidade_fm_id)
                ? `Na BD${x.entidade_nome ? ' · ' + x.entidade_nome : ''}${x.contacto_nome_bd ? ' · ' + x.contacto_nome_bd : ''}`
                : 'Nova · não está na BD'}
            </span>
            {(x.is_organic || !x.campanha_portal_id) && (
              <select defaultValue="" onChange={e => ligarCampanha(e.target.value)}
                style={{ fontSize: 11, padding: '3px 6px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg-card)', color: 'var(--text)' }}>
                <option value="">Ligar a campanha…</option>
                {campanhasLig.map(c => <option key={c.id} value={c.id}>{c.nome}</option>)}
              </select>
            )}
            {x.origem_canal && <CanalChip canal={x.origem_canal} />}
            {q.lead_score != null && <ScoreBadge score={q.lead_score} classification={q.classification} />}
            {s?.status === 'nurture' && <span style={{ fontSize: 9, fontFamily: 'var(--font-mono)', padding: '1px 5px', borderRadius: 4, background: '#f59e0b22', color: '#f59e0b', border: '1px solid #f59e0b30' }}>NURTURE</span>}
            {x.sdr_status && x.sdr_status !== 'pendente' && (
              <span style={{ fontSize: 9, fontFamily: 'var(--font-mono)', padding: '1px 6px', borderRadius: 4,
                background: { em_contacto:'#3b82f620',qualificado:'#22c55e20',sem_resposta:'#6b728020',rejeitado:'#ef444420',convertido:'#8b5cf620' }[x.sdr_status]||'#6b728010',
                color: { em_contacto:'#3b82f6',qualificado:'#16a34a',sem_resposta:'#6b7280',rejeitado:'#ef4444',convertido:'#8b5cf6' }[x.sdr_status]||'#6b7280',
                border: '1px solid currentColor', borderColor: 'inherit' }}>
                SDR · {x.sdr_status.replace('_',' ').toUpperCase()}
              </span>
            )}
          </div>
          <div style={{ display: 'flex', gap: 10, marginTop: 4, flexWrap: 'wrap' }}>
            {subtitle}
          </div>
          {x.empresa && <div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 2 }}>{x.empresa}</div>}
          {x.entidade_nome && <div style={{ fontSize: 12, color: 'var(--ai-500)', marginTop: 2 }}>{x.entidade_nome} (Gestor)</div>}
        </div>
        <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>
          {phone && <a href={`https://wa.me/${phone.replace(/\D/g,'')}`} target="_blank" rel="noopener noreferrer" className="btn btn-xs">WA</a>}
          {phone && <a href={`tel:${phone}`} className="btn btn-xs">Ligar</a>}
          {x.email && <a href={`mailto:${x.email}`} className="btn btn-xs">Email</a>}
          {(x.empresa || x.nome) && (
            <button className="btn btn-xs" onClick={runEnrich} disabled={enriching}
              style={{ fontSize: 10, background: enriching ? 'var(--bg-sunken)' : undefined }}>
              {enriching ? 'A enriquecer...' : q.company_enrichment ? 'Re-enriquecer' : 'Enriquecer Apollo'}
            </button>
          )}
          {enrichError && <span style={{ fontSize: 10, color: 'var(--danger, #ef4444)', fontFamily: 'var(--font-mono)' }}>{enrichError}</span>}
        </div>
      </div>
      )}

      {/* Tabs */}
      <div style={{ display: 'flex', gap: 2, borderBottom: '1px solid var(--border)', marginBottom: 20 }}>
        {tabs.map(t => (
          <button key={t} onClick={() => setActiveTab(t)}
            style={{ padding: '6px 14px', border: 'none', cursor: 'pointer', fontSize: 12, fontWeight: 600, borderRadius: '6px 6px 0 0',
              background: activeTab === t ? 'var(--bg-card)' : 'transparent',
              color: activeTab === t ? 'var(--text)' : 'var(--text-muted)',
              borderBottom: activeTab === t ? '2px solid var(--ai-500)' : '2px solid transparent' }}>
            {tabLabel[t]}
          </button>
        ))}
      </div>

      {/* Tab: Overview — Formato Rui (página 14 do PDF) */}
      {activeTab === 'overview' && (() => {
        const secLabel = (label) => (
          <tr><td colSpan={2} style={{ padding: '10px 10px 4px', fontSize: 9, fontFamily: 'var(--font-mono)', fontWeight: 700, letterSpacing: '0.12em', color: 'var(--text-dim)', textTransform: 'uppercase', background: 'var(--bg-sunken)', borderBottom: '1px solid var(--border)' }}>{label}</td></tr>
        );
        const row = (label, value, highlight) => value && value !== '—' ? (
          <tr key={label}>
            <td style={{ ...tdS, fontWeight: 600, width: 160, color: 'var(--text-muted)', fontSize: 11, verticalAlign: 'top' }}>{label}</td>
            <td style={{ ...tdS, fontSize: 12, color: highlight ? 'var(--text)' : 'var(--text)', fontWeight: highlight ? 600 : 400 }}>{value}</td>
          </tr>
        ) : null;
        const na = '—';
        const empresa = x.empresa || x.entidade_nome || q.empresa_declarada || na;
        const timing = q.timing_bucket ? ({ '0-3m': 'Próximos 0-3 meses', '3-6m': '3-6 meses', '6-12m': '6-12 meses', '+12m': 'Mais de 12 meses' }[q.timing_bucket] || q.timing_bucket) : na;
        const sdrColors = { em_contacto:'#3b82f6',qualificado:'#16a34a',sem_resposta:'#6b7280',rejeitado:'#ef4444',convertido:'#8b5cf6',pendente:'#d97706' };
        return (
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 20 }}>
            {/* Coluna esquerda */}
            <div style={{ border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden' }}>
              <table style={{ width: '100%', borderCollapse: 'collapse' }}>
                <tbody>
                  {secLabel('Contacto')}
                  {row('Empresa', empresa)}
                  {row('Cargo', q.decisor_role || na)}
                  {row('Telefone', x.telefone)}
                  {row('Email', x.email || na)}
                  {row('Canal origem', x.origem_canal ? x.origem_canal.toUpperCase() : na)}
                  {row('Campanha', x.origem_campanha || na)}
                  {row('País', x.pais || x.idioma || na)}
                  {row('Match Gestor', x.matched_entidade_fm_id ? `Ligado (${x.matched_entidade_fm_id})` : 'Sem match')}

                  {(x.ad_name || x.adset_name || x.campanha_meta_nome || x.platform) && <>
                    {secLabel('Origem Meta')}
                    {row('Anúncio', x.ad_name || na)}
                    {row('Ad Set', x.adset_name || na)}
                    {row('Campanha Meta', x.campanha_meta_nome || na)}
                    {row('Plataforma', x.platform ? x.platform.toUpperCase() : na)}
                    {row('Orgânico', x.is_organic != null ? (x.is_organic ? 'Sim' : 'Não') : na)}
                  </>}

                  {(q.pain_identified || q.volume_m2_mes || q.equipment_current) && <>
                    {secLabel('Negócio / Produção')}
                    {row('Volume m²/mês', q.volume_m2_mes ? `${q.volume_m2_mes} m²/mês` : na)}
                    {row('Produção externa', q.outsourcing_m2_mes ? `${q.outsourcing_m2_mes} m²/mês` : na)}
                    {row('Equipamento actual', q.equipment_current || na)}
                    {secLabel('Problema / Impacto / Necessidade')}
                    {row('Dor identificada', q.pain_identified || na, true)}
                  </>}

                  {(q.aceita_demo != null || q.timing_bucket || q.budget_range) && <>
                    {secLabel('Interesse / Timing / Decisão')}
                    {row('Interesse solução', q.aceita_demo ? 'Aceita demonstração' : 'Demo não confirmada')}
                    {row('Timing decisão', timing)}
                    {row('Budget range', q.budget_range || na)}
                    {row('Objecção', q.objecao_principal || na)}
                  </>}

                  {secLabel('SDR')}
                  {row('Estado SDR', x.sdr_status ? (
                    <span style={{ color: sdrColors[x.sdr_status]||'var(--text)', fontWeight: 600 }}>{x.sdr_status.replace(/_/g,' ').toUpperCase()}</span>
                  ) : na)}
                  {row('Tentativas', x.sdr_tentativas != null ? String(x.sdr_tentativas) : na)}
                  {row('Iniciado em', x.sdr_iniciado_at ? new Date(x.sdr_iniciado_at).toLocaleDateString('pt-PT') : na)}
                  {row('Última tentativa', x.sdr_ultima_tentativa_at ? new Date(x.sdr_ultima_tentativa_at).toLocaleString('pt-PT') : na)}
                  {x.sdr_notas && row('Notas SDR', x.sdr_notas)}

                  {secLabel('Próximo Passo')}
                  {row('Aceita demo', q.aceita_demo ? 'Sim — confirmar data' : 'Ainda não')}
                  {row('Msgs WA', q.wa_num_msgs != null ? String(q.wa_num_msgs) : na)}
                  {row('Captado em', x.captado_em ? new Date(x.captado_em).toLocaleDateString('pt-PT') : na)}
                  {row('Última actividade', q.ultima_actividade ? new Date(q.ultima_actividade).toLocaleString('pt-PT') : na)}
                </tbody>
              </table>
            </div>

            {/* Coluna direita — Enriquecimento Empresa + Score + Breakdown */}
            <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
              {/* Enriquecimento empresa — Google Places (CTWA heydigi) ou Apollo */}
              {(() => {
                const enrich = q.company_enrichment;
                if (!enrich || enrich.source === 'apollo') return null;
                const ap = enrich.sources?.apollo_org;
                const apPerson = enrich.sources?.apollo_person;
                const gp = enrich.sources?.google_places;
                const dm = enrich.sources?.domain;
                const confScore = enrich.confidence_score || 0;
                const confColor = confScore >= 70 ? '#16a34a' : confScore >= 40 ? '#d97706' : '#ef4444';
                const vlinks = enrich.verification_links || {};
                const evidence = enrich.evidence || [];
                const penalties = enrich.penalties || [];
                return (
                  <div style={{ border: `1px solid ${confColor}30`, borderRadius: 10, overflow: 'hidden', fontSize: 12 }}>
                    {/* Header com score */}
                    <div style={{ padding: '10px 14px', background: `${confColor}08`, borderBottom: `1px solid ${confColor}20`, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                      <span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', fontWeight: 700, color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.1em' }}>Enriquecimento Empresa</span>
                      <span style={{ fontSize: 11, fontFamily: 'var(--font-mono)', fontWeight: 700, padding: '2px 10px', borderRadius: 20, background: `${confColor}15`, color: confColor, border: `1px solid ${confColor}30` }}>
                        {confScore}% · {enrich.confidence_label}
                      </span>
                    </div>
                    {/* Google Places */}
                    {gp && (
                      <table style={{ width: '100%', borderCollapse: 'collapse' }}>
                        <thead><tr><td colSpan={2} style={{ padding: '8px 14px 2px', fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.08em', fontWeight: 700 }}>Google Places</td></tr></thead>
                        <tbody>
                          {gp.name    && <tr style={{ borderBottom: '1px solid var(--border)' }}><td style={{ ...tdS, fontWeight: 600, width: 100, color: 'var(--text-muted)', fontSize: 11 }}>Nome</td><td style={tdS}>{gp.name}</td></tr>}
                          {gp.address && <tr style={{ borderBottom: '1px solid var(--border)' }}><td style={{ ...tdS, fontWeight: 600, width: 100, color: 'var(--text-muted)', fontSize: 11 }}>Morada</td><td style={{ ...tdS, fontSize: 11 }}>{gp.address}</td></tr>}
                          {gp.phone   && <tr style={{ borderBottom: '1px solid var(--border)' }}><td style={{ ...tdS, fontWeight: 600, width: 100, color: 'var(--text-muted)', fontSize: 11 }}>Tel fixo</td><td style={{ ...tdS, fontFamily: 'var(--font-mono)', fontSize: 11 }}>{gp.phone}</td></tr>}
                          {gp.website && <tr style={{ borderBottom: '1px solid var(--border)' }}><td style={{ ...tdS, fontWeight: 600, width: 100, color: 'var(--text-muted)', fontSize: 11 }}>Website</td><td style={tdS}><a href={gp.website} target="_blank" rel="noopener noreferrer" style={{ color: 'var(--ai-500)', fontSize: 11 }}>{gp.website}</a></td></tr>}
                          {gp.rating  && <tr style={{ borderBottom: '1px solid var(--border)' }}><td style={{ ...tdS, fontWeight: 600, width: 100, color: 'var(--text-muted)', fontSize: 11 }}>Rating</td><td style={{ ...tdS, fontSize: 11 }}>{gp.rating}/5 {gp.types?.length > 0 ? `· ${gp.types[0]}` : ''}</td></tr>}
                        </tbody>
                      </table>
                    )}
                    {/* Domain */}
                    {dm && (
                      <div style={{ padding: '6px 14px', borderTop: '1px solid var(--border)', fontSize: 11 }}>
                        <span style={{ fontWeight: 600, color: 'var(--text-muted)', marginRight: 8 }}>Web:</span>
                        {dm.found
                          ? <a href={dm.url} target="_blank" rel="noopener noreferrer" style={{ color: '#16a34a' }}>{dm.domain} ✓</a>
                          : <span style={{ color: 'var(--text-dim)' }}>Sem presença web detectada</span>}
                      </div>
                    )}
                    {/* Evidências + penalizações */}
                    {(evidence.length > 0 || penalties.length > 0) && (
                      <div style={{ padding: '6px 14px', borderTop: '1px solid var(--border)', fontSize: 10, lineHeight: 1.6 }}>
                        {evidence.map(e => <div key={e} style={{ color: '#16a34a' }}>✓ {e}</div>)}
                        {penalties.map(p => <div key={p} style={{ color: '#ef4444' }}>✗ {p}</div>)}
                      </div>
                    )}
                    {/* Links de verificação manual */}
                    {Object.keys(vlinks).length > 0 && (
                      <div style={{ padding: '8px 14px', borderTop: '1px solid var(--border)', background: 'var(--bg-sunken)' }}>
                        <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', marginBottom: 4, textTransform: 'uppercase', letterSpacing: '0.08em' }}>Verificar em:</div>
                        <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
                          {Object.entries(vlinks).map(([k,url]) => (
                            <a key={k} href={url} target="_blank" rel="noopener noreferrer"
                               style={{ fontSize: 10, padding: '2px 8px', borderRadius: 4, background: 'var(--bg-card)', border: '1px solid var(--border)', color: 'var(--ai-500)', textDecoration: 'none', fontFamily: 'var(--font-mono)' }}>
                              {k.replace(/_/g,' ')}
                            </a>
                          ))}
                        </div>
                      </div>
                    )}
                    {/* Aviso se não confirmado */}
                    {confScore < 70 && (
                      <div style={{ padding: '8px 14px', background: `${confColor}08`, borderTop: `1px solid ${confColor}20`, fontSize: 11, color: confColor }}>
                        {confScore >= 40 ? 'Verificar os links acima antes de usar estes dados' : 'Match incerto — confirmar com o lead antes de qualquer acção'}
                      </div>
                    )}
                  </div>
                );
              })()}
              {/* Apollo Enrichment */}
              {q.company_enrichment?.source === 'apollo' && (() => {
                const enrich = q.company_enrichment;
                const ap = enrich.sources?.apollo_org;
                const apPerson = enrich.sources?.apollo_person;
                const confScore = enrich.confidence_score || 0;
                const confColor = confScore >= 60 ? '#16a34a' : confScore >= 30 ? '#d97706' : '#ef4444';
                return (
                  <div style={{ border: `1px solid ${confColor}30`, borderRadius: 10, overflow: 'hidden', fontSize: 12 }}>
                    <div style={{ padding: '10px 14px', background: `${confColor}08`, borderBottom: `1px solid ${confColor}20`, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                      <span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', fontWeight: 700, color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.1em' }}>Apollo Enrichment</span>
                      <span style={{ fontSize: 11, fontFamily: 'var(--font-mono)', fontWeight: 700, padding: '2px 10px', borderRadius: 20, background: `${confColor}15`, color: confColor, border: `1px solid ${confColor}30` }}>
                        {confScore}% · {enrich.confidence_label}
                      </span>
                    </div>
                    {ap && (
                      <table style={{ width: '100%', borderCollapse: 'collapse' }}>
                        <thead><tr><td colSpan={2} style={{ padding: '6px 14px 2px', fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.08em', fontWeight: 700 }}>
                          Empresa {ap._unverified && <span style={{ color:'#d97706', fontSize:9, marginLeft:6 }}>⚠ match por nome — confirmar</span>}
                        </td></tr></thead>
                        <tbody>
                          {ap.name     && <tr style={{ borderBottom:'1px solid var(--border)' }}><td style={{ ...tdS,fontWeight:600,width:110,color:'var(--text-muted)',fontSize:11 }}>Nome</td><td style={tdS}>{ap.name}</td></tr>}
                          {ap.domain   && <tr style={{ borderBottom:'1px solid var(--border)' }}><td style={{ ...tdS,fontWeight:600,width:110,color:'var(--text-muted)',fontSize:11 }}>Domínio</td><td style={{ ...tdS,fontFamily:'var(--font-mono)',fontSize:11 }}><a href={`https://${ap.domain}`} target="_blank" rel="noopener noreferrer" style={{ color:'var(--ai-500)' }}>{ap.domain}</a></td></tr>}
                          {ap.sector   && <tr style={{ borderBottom:'1px solid var(--border)' }}><td style={{ ...tdS,fontWeight:600,width:110,color:'var(--text-muted)',fontSize:11 }}>Sector</td><td style={{ ...tdS,fontSize:11 }}>{ap.sector}</td></tr>}
                          {ap.employees && <tr style={{ borderBottom:'1px solid var(--border)' }}><td style={{ ...tdS,fontWeight:600,width:110,color:'var(--text-muted)',fontSize:11 }}>Empregados</td><td style={{ ...tdS,fontSize:11 }}>{ap.employees}</td></tr>}
                          {(ap.city||ap.state||ap.country) && <tr style={{ borderBottom:'1px solid var(--border)' }}><td style={{ ...tdS,fontWeight:600,width:110,color:'var(--text-muted)',fontSize:11 }}>Localização</td><td style={{ ...tdS,fontSize:11 }}>{[ap.city,ap.state,ap.country].filter(Boolean).join(', ')}</td></tr>}
                          {ap.phone    && <tr style={{ borderBottom:'1px solid var(--border)' }}><td style={{ ...tdS,fontWeight:600,width:110,color:'var(--text-muted)',fontSize:11 }}>Tel</td><td style={{ ...tdS,fontFamily:'var(--font-mono)',fontSize:11 }}>{ap.phone}</td></tr>}
                          {ap.founded  && <tr style={{ borderBottom:'1px solid var(--border)' }}><td style={{ ...tdS,fontWeight:600,width:110,color:'var(--text-muted)',fontSize:11 }}>Fundada</td><td style={{ ...tdS,fontSize:11 }}>{ap.founded}</td></tr>}
                          {ap.revenue  && <tr style={{ borderBottom:'1px solid var(--border)' }}><td style={{ ...tdS,fontWeight:600,width:110,color:'var(--text-muted)',fontSize:11 }}>Revenue</td><td style={{ ...tdS,fontSize:11 }}>{ap.revenue}</td></tr>}
                          {ap.funding  && <tr style={{ borderBottom:'1px solid var(--border)' }}><td style={{ ...tdS,fontWeight:600,width:110,color:'var(--text-muted)',fontSize:11 }}>Funding</td><td style={{ ...tdS,fontSize:11 }}>{ap.funding}</td></tr>}
                          {ap.description && <tr style={{ borderBottom:'1px solid var(--border)' }}><td style={{ ...tdS,fontWeight:600,width:110,color:'var(--text-muted)',fontSize:11 }}>Descrição</td><td style={{ ...tdS,fontSize:11,lineHeight:1.4 }}>{ap.description}</td></tr>}
                          {ap.keywords?.length > 0 && <tr style={{ borderBottom:'1px solid var(--border)' }}><td style={{ ...tdS,fontWeight:600,width:110,color:'var(--text-muted)',fontSize:11 }}>Keywords</td><td style={{ ...tdS,fontSize:10,color:'var(--text-muted)' }}>{ap.keywords.slice(0,5).join(' · ')}</td></tr>}
                          {ap.technologies?.length > 0 && <tr style={{ borderBottom:'1px solid var(--border)' }}><td style={{ ...tdS,fontWeight:600,width:110,color:'var(--text-muted)',fontSize:11 }}>Tecnologias</td><td style={{ ...tdS,fontSize:10,color:'var(--text-muted)' }}>{ap.technologies.join(' · ')}</td></tr>}
                        </tbody>
                      </table>
                    )}
                    <div style={{ borderTop: '1px solid var(--border)' }}>
                      <div style={{ padding: '8px 14px 4px', display:'flex', justifyContent:'space-between', alignItems:'center' }}>
                        <span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.08em', fontWeight: 700 }}>Contacto</span>
                        {apPerson?.apollo_found && apPerson?.org_cross_validated
                          ? <span style={{ fontSize:10, padding:'1px 7px', borderRadius:10, background:'#22c55e15', color:'#16a34a', border:'1px solid #22c55e30', fontFamily:'var(--font-mono)' }}>Apollo ✓ empresa confirmada</span>
                          : apPerson?.apollo_found
                          ? <span style={{ fontSize:10, padding:'1px 7px', borderRadius:10, background:'#f59e0b15', color:'#d97706', border:'1px solid #f59e0b30', fontFamily:'var(--font-mono)' }}>Apollo ✓ empresa não verificada</span>
                          : <span style={{ fontSize:10, padding:'1px 7px', borderRadius:10, background:'#ef444415', color:'#dc2626', border:'1px solid #ef444430', fontFamily:'var(--font-mono)' }}>Não encontrado</span>}
                      </div>
                      {apPerson && (
                        <table style={{ width: '100%', borderCollapse: 'collapse' }}>
                          <tbody>
                            {apPerson.name    && <tr style={{ borderBottom:'1px solid var(--border)' }}><td style={{ ...tdS,fontWeight:600,width:110,color:'var(--text-muted)',fontSize:11 }}>Nome</td><td style={{ ...tdS,fontSize:11 }}>{apPerson.name}</td></tr>}
                            {apPerson.empresa_confirmada && <tr style={{ borderBottom:'1px solid var(--border)' }}><td style={{ ...tdS,fontWeight:600,width:110,color:'var(--text-muted)',fontSize:11 }}>Empresa</td><td style={{ ...tdS,fontSize:11,color:'#16a34a' }}>✓ {apPerson.empresa_confirmada}</td></tr>}
                            {apPerson.cargo   && <tr style={{ borderBottom:'1px solid var(--border)' }}><td style={{ ...tdS,fontWeight:600,width:110,color:'var(--text-muted)',fontSize:11 }}>Cargo</td><td style={{ ...tdS,fontSize:11 }}>{apPerson.cargo}</td></tr>}
                            {apPerson.seniority && <tr style={{ borderBottom:'1px solid var(--border)' }}><td style={{ ...tdS,fontWeight:600,width:110,color:'var(--text-muted)',fontSize:11 }}>Seniority</td><td style={{ ...tdS,fontSize:11,textTransform:'capitalize' }}>{apPerson.seniority}</td></tr>}
                            {apPerson.department && <tr style={{ borderBottom:'1px solid var(--border)' }}><td style={{ ...tdS,fontWeight:600,width:110,color:'var(--text-muted)',fontSize:11 }}>Dept</td><td style={{ ...tdS,fontSize:11 }}>{apPerson.department}</td></tr>}
                            {apPerson.email   && <tr style={{ borderBottom:'1px solid var(--border)' }}><td style={{ ...tdS,fontWeight:600,width:110,color:'var(--text-muted)',fontSize:11 }}>Email</td><td style={{ ...tdS,fontSize:11 }}><a href={`mailto:${apPerson.email}`} style={{ color:'var(--ai-500)' }}>{apPerson.email}</a></td></tr>}
                            {apPerson.phone   && <tr style={{ borderBottom:'1px solid var(--border)' }}><td style={{ ...tdS,fontWeight:600,width:110,color:'var(--text-muted)',fontSize:11 }}>Tel</td><td style={{ ...tdS,fontFamily:'var(--font-mono)',fontSize:11 }}>{apPerson.phone}</td></tr>}
                            {apPerson.linkedin && <tr style={{ borderBottom:'1px solid var(--border)' }}><td style={{ ...tdS,fontWeight:600,width:110,color:'var(--text-muted)',fontSize:11 }}>LinkedIn</td><td style={{ ...tdS,fontSize:11 }}><a href={apPerson.linkedin} target="_blank" rel="noopener noreferrer" style={{ color:'var(--ai-500)' }}>Ver perfil</a></td></tr>}
                            {apPerson.city    && <tr style={{ borderBottom:'1px solid var(--border)' }}><td style={{ ...tdS,fontWeight:600,width:110,color:'var(--text-muted)',fontSize:11 }}>Cidade</td><td style={{ ...tdS,fontSize:11 }}>{apPerson.city}</td></tr>}
                          </tbody>
                        </table>
                      )}
                      {apPerson?.person_links && (
                        <div style={{ padding:'8px 14px', background:'var(--bg-sunken)', borderTop:'1px solid var(--border)' }}>
                          <div style={{ fontSize:10, fontFamily:'var(--font-mono)', color:'var(--text-dim)', marginBottom:4, textTransform:'uppercase', letterSpacing:'0.08em' }}>Verificar contacto:</div>
                          <div style={{ display:'flex', gap:8, flexWrap:'wrap' }}>
                            {Object.entries(apPerson.person_links).map(([k,url]) => (
                              <a key={k} href={url} target="_blank" rel="noopener noreferrer"
                                style={{ fontSize:10, padding:'2px 8px', borderRadius:4, background:'var(--bg-card)', border:'1px solid var(--border)', color:'var(--ai-500)', textDecoration:'none', fontFamily:'var(--font-mono)' }}>
                                {k.replace(/_/g,' ')}
                              </a>
                            ))}
                          </div>
                        </div>
                      )}
                    </div>
                    {enrich.verification_links && (
                      <div style={{ padding: '8px 14px', borderTop: '1px solid var(--border)', background: 'var(--bg-sunken)' }}>
                        <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', marginBottom: 4, textTransform: 'uppercase', letterSpacing: '0.08em' }}>Verificar em:</div>
                        <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
                          {Object.entries(enrich.verification_links).map(([k,url]) => (
                            <a key={k} href={url} target="_blank" rel="noopener noreferrer"
                               style={{ fontSize: 10, padding: '2px 8px', borderRadius: 4, background: 'var(--bg-card)', border: '1px solid var(--border)', color: 'var(--ai-500)', textDecoration: 'none', fontFamily: 'var(--font-mono)' }}>
                              {k.replace(/_/g,' ')}
                            </a>
                          ))}
                        </div>
                      </div>
                    )}
                  </div>
                );
              })()}
              {q.lead_score != null && (
                <div style={{ border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden' }}>
                  <table style={{ width: '100%', borderCollapse: 'collapse' }}>
                    <thead>
                      <tr><th style={{ ...thS, textAlign: 'center' }} colSpan={3}>
                        Score {q.lead_score || 0}/100 —{' '}
                        <span style={{ color: { HOT: '#ef4444', QUALIFICADO: '#22c55e', NURTURE: '#f59e0b', NAO_QUALIFICADO: '#94a3b8' }[q.classification] || '#94a3b8' }}>
                          {q.classification || 'NÃO QUALIFICADO'}
                        </span>
                      </th></tr>
                    </thead>
                    <tbody>
                      {q.score_criteria && Object.entries(q.score_criteria).filter(([, v]) => v && typeof v === 'object').map(([k, v]) => (
                        <tr key={k}>
                          <td style={{ ...tdS, fontSize: 11, color: v.hit ? 'var(--text)' : 'var(--text-dim)' }}>
                            {k.replace(/_/g, ' ')}
                          </td>
                          <td style={{ ...tdS, textAlign: 'right', width: 40, fontFamily: 'var(--font-mono)', fontSize: 10, color: v.hit ? '#22c55e' : 'var(--text-dim)' }}>
                            {v.hit ? `+${v.weight}` : '—'}
                          </td>
                          <td style={{ ...tdS, width: 24, textAlign: 'center' }}>
                            <span style={{ display: 'inline-block', width: 8, height: 8, borderRadius: '50%', background: v.hit ? '#22c55e' : 'var(--border)' }} />
                          </td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </div>
              )}
              {q.recomendacao_comercial && (
                <div style={{ border: '1px solid #f59e0b40', borderRadius: 10, padding: '12px 14px', background: '#f59e0b08' }}>
                  <div style={{ fontSize: 9, fontFamily: 'var(--font-mono)', color: '#f59e0b', fontWeight: 700, letterSpacing: '0.1em', marginBottom: 8 }}>RECOMENDAÇÃO AO COMERCIAL</div>
                  <div style={{ fontSize: 12, color: 'var(--text)', lineHeight: 1.5 }}>{q.recomendacao_comercial}</div>
                </div>
              )}
              {!q.company_enrichment && (x.empresa || x.nome) && (() => {
                const emp = x.empresa || '';
                const pais = x.pais || (x.telefone?.startsWith('+34') ? 'ES' : 'PT');
                const vlinks = {
                  'google search': `https://www.google.com/search?q=${encodeURIComponent(emp + (pais==='ES'?' empresa':(pais==='PT'?' empresa PT':'')))}`,
                  'linkedin': `https://www.linkedin.com/search/results/companies/?keywords=${encodeURIComponent(emp)}`,
                  ...(pais==='PT' ? {racius: `https://www.racius.com/pesquisa/?termo=${encodeURIComponent(emp)}`} : {}),
                  ...(pais==='ES' ? {einforma: `https://www.einforma.com/buscar?q=${encodeURIComponent(emp)}`} : {}),
                };
                return (
                  <div style={{ border: '1px solid var(--border)', borderRadius: 10, padding: '12px 14px', background: 'var(--bg-sunken)' }}>
                    <div style={{ fontSize: 9, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: 8 }}>
                      Verificar empresa manualmente
                    </div>
                    {emp && <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text)', marginBottom: 8 }}>{emp}</div>}
                    <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
                      {Object.entries(vlinks).map(([k,url]) => (
                        <a key={k} href={url} target="_blank" rel="noopener noreferrer"
                          style={{ fontSize: 10, padding: '2px 8px', borderRadius: 4, background: 'var(--bg-card)', border: '1px solid var(--border)', color: 'var(--ai-500)', textDecoration: 'none', fontFamily: 'var(--font-mono)' }}>
                          {k}
                        </a>
                      ))}
                    </div>
                    <div style={{ fontSize: 10, color: 'var(--text-dim)', marginTop: 8 }}>
                      Clica em "Enriquecer Apollo" para enrichment automático quando a API key estiver configurada.
                    </div>
                  </div>
                );
              })()}
              {(data.ad_creative || x.ad_id) && (
                <div style={{ border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden' }}>
                  <table style={{ width: '100%', borderCollapse: 'collapse' }}>
                    <tbody>
                      {secLabel('Criativo do Anuncio')}
                    </tbody>
                  </table>
                  <div style={{ padding: '10px 14px' }}>
                    {(data.ad_creative?.image_url || data.ad_creative?.thumbnail_url) && (
                      <img src={data.ad_creative.image_url || data.ad_creative.thumbnail_url} alt="Ad creative"
                        style={{ width: '100%', maxHeight: 300, objectFit: 'cover', objectPosition: 'center', borderRadius: 6, marginBottom: 10 }} />
                    )}
                    {data.ad_creative?.headline && (
                      <div style={{ fontSize: 12, fontWeight: 700, color: 'var(--text)', marginBottom: 6 }}>{data.ad_creative.headline}</div>
                    )}
                    {data.ad_creative?.body && (
                      <div style={{ fontSize: 11, color: 'var(--text)', lineHeight: 1.5, marginBottom: 6, display: '-webkit-box', WebkitLineClamp: 4, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>{data.ad_creative.body}</div>
                    )}
                    {data.ad_creative?.ad_name && (
                      <div style={{ fontSize: 10, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>{data.ad_creative.ad_name}</div>
                    )}
                    {!data.ad_creative && x.ad_id && (
                      <div style={{ fontSize: 11, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>ad_id: {x.ad_id}</div>
                    )}
                  </div>
                </div>
              )}
            </div>
          </div>
        );
      })()}

      {/* Tab: Conversa WA */}
      {activeTab === 'conversa' && (
        <div className="scrollbar" style={{ maxHeight: 600, overflowY: 'auto' }}>

          {/* Painel técnico — estado do SDR AI */}
          {(() => {
            const isTxF = (x.lista_campanha_nome || x.origem_campanha || '').toLowerCase().includes('txf')
                       || (x.campanha_meta_nome || '').toLowerCase().includes('txf');
            const campanha = x.lista_campanha_nome || s?.campaign_name || x.origem_campanha || null;
            const frames = ['SP_CLIENTES', 'FRAME-QUALIFICACAO-v3', ...(isTxF ? ['ficha_campanha_txf'] : [])];
            const hasSession = !!s;
            const noResponse = msgs.length > 0 && msgs.some(m => m.no_ai_response);
            const model = hasSession || x.t0_enviado ? 'claude-sonnet-4-6' : null;
            const ragActive = hasSession && s.pain_identified;
            const chip = (label, color = 'var(--text-dim)', bg = 'var(--bg-sunken)') => (
              <span key={label} style={{ fontSize: 9, fontFamily: 'var(--font-mono)', padding: '1px 6px',
                borderRadius: 3, background: bg, color, border: `1px solid ${color}22`, whiteSpace: 'nowrap' }}>
                {label}
              </span>
            );

            return (
              <div style={{ marginBottom: 14, border: '1px solid var(--border)', borderRadius: 8, overflow: 'hidden' }}>
                {/* Header clicável */}
                <div onClick={() => setDebugOpen(o => !o)}
                  style={{ padding: '6px 10px', background: 'var(--bg-sunken)', cursor: 'pointer',
                    display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
                  <span style={{ fontSize: 9, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)',
                    textTransform: 'uppercase', letterSpacing: '0.08em', flexShrink: 0 }}>
                    SDR AI {debugOpen ? '▲' : '▼'}
                  </span>
                  {chip(campanha ? `Camp: ${campanha}` : 'Camp: —')}
                  {isTxF && chip('TxF ✓', '#16a34a', '#22c55e10')}
                  {model && chip(`Model: ${model}`, '#3859D0', '#3859D015')}
                  {noResponse && chip('Offline 25/08 ⚠', '#d97706', '#f59e0b10')}
                  {hasSession && chip(`Score: ${s.lead_score ?? 0}/100`, s.lead_score > 50 ? '#ef4444' : s.lead_score > 20 ? '#f59e0b' : '#94a3b8')}
                </div>

                {/* Detalhe expandido */}
                {debugOpen && (
                  <div style={{ padding: '10px 12px', display: 'flex', flexDirection: 'column', gap: 8, fontSize: 11 }}>
                    <div>
                      <span style={{ fontSize: 9, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)',
                        textTransform: 'uppercase', letterSpacing: '0.06em' }}>System Prompts activos</span>
                      <div style={{ display: 'flex', gap: 5, flexWrap: 'wrap', marginTop: 4 }}>
                        {frames.map(f => chip(f, '#3859D0', '#3859D010'))}
                      </div>
                    </div>
                    <div>
                      <span style={{ fontSize: 9, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)',
                        textTransform: 'uppercase', letterSpacing: '0.06em' }}>Knowledge Base</span>
                      <div style={{ display: 'flex', gap: 5, flexWrap: 'wrap', marginTop: 4 }}>
                        {chip('MimakiKB RAG')}
                        {isTxF && chip('TxF Product Guide')}
                        {isTxF && chip('prices_tabela_pt_es')}
                        {!ragActive && chip('RAG: aguarda resposta', '#94a3b8')}
                        {ragActive && chip(`RAG: activo · dor="${s.pain_identified?.slice(0,30)}..."`, '#16a34a', '#22c55e10')}
                      </div>
                    </div>
                    {hasSession && (
                      <div>
                        <span style={{ fontSize: 9, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)',
                          textTransform: 'uppercase', letterSpacing: '0.06em' }}>Qualificação FRAME</span>
                        <div style={{ display: 'flex', gap: 5, flexWrap: 'wrap', marginTop: 4 }}>
                          {chip(`Score: ${s.lead_score ?? 0}/100`)}
                          {s.classification && chip(`Class: ${s.classification}`, '#3859D0')}
                          {s.timing_bucket && chip(`Timing: ${s.timing_bucket}`)}
                          {s.aceita_demo && chip('Demo: aceita ✓', '#16a34a', '#22c55e10')}
                          {s.pain_identified && chip(`Dor: ${s.pain_identified.slice(0,40)}...`)}
                        </div>
                      </div>
                    )}
                    {noResponse && (
                      <div style={{ padding: '6px 8px', background: '#fef3c7', borderRadius: 5, fontSize: 10,
                        color: '#92400e', border: '1px solid #fde68a' }}>
                        Modelo `claude-sonnet-4-20250514` descontinuado · Fix aplicado 26/08 ·
                        Próxima mensagem da lead processa normalmente com `claude-sonnet-4-6`
                      </div>
                    )}
                  </div>
                )}
              </div>
            );
          })()}
          {(() => {
            // Timeline unificada: outbound touches + mensagens ordenadas por data
            const touchItems = [
              { touch: 0, enviado: x.t0_enviado, label: 'T0 Outbound' },
              { touch: 2, enviado: x.t2_enviado, label: 'T2 Follow-up' },
              { touch: 3, enviado: x.t3_enviado, label: 'T3 Follow-up' },
              { touch: 4, enviado: x.t4_enviado, label: 'T4 Follow-up' },
            ].filter(t => t.enviado).map(t => ({ _type: 'touch', _ts: new Date(t.enviado).getTime(), ...t }));

            // Fonte de mensagens: wa_messages com created_at (preferência) ou s.messages sem timestamp
            const hasTsMessages = msgs.length > 0 && msgs.some(m => m.created_at);
            const msgItems = hasTsMessages
              ? msgs.map((m, i) => ({ _type: 'msg', _ts: new Date(m.created_at).getTime(), _i: i, ...m }))
              : (msgs.length === 0 && s?.messages && Array.isArray(s.messages))
                ? s.messages.map((m, i) => ({ _type: 'msg_sess', _ts: Infinity, _i: i, ...m }))
                : msgs.map((m, i) => ({ _type: 'msg', _ts: Infinity, _i: i, ...m }));

            // Unir e ordenar — itens sem timestamp ficam no fim na ordem original
            const timeline = [...touchItems, ...msgItems].sort((a, b) => a._ts - b._ts);

            if (timeline.length === 0) {
              return <div style={{ padding: 40, textAlign: 'center', color: 'var(--text-dim)', fontSize: 12 }}>Sem conversa disponível.</div>;
            }

            return timeline.map((item, idx) => {
              if (item._type === 'touch') {
                return (
                  <div key={`t${item.touch}`} style={{ marginBottom: 12, display: 'flex', justifyContent: 'flex-end' }}>
                    <div style={{ maxWidth: '75%', padding: '8px 12px', borderRadius: 10, fontSize: 12, lineHeight: 1.4,
                      background: 'color-mix(in oklch, var(--ai-500) 12%, transparent)',
                      color: 'var(--text)', border: '1px solid var(--border)' }}>
                      <div style={{ fontSize: 10, color: 'var(--text-muted)', marginBottom: 4, fontFamily: 'var(--font-mono)' }}>
                        Digi AI · {item.label} · {new Date(item.enviado).toLocaleString('pt-PT', { day:'2-digit', month:'2-digit', hour:'2-digit', minute:'2-digit' })}
                      </div>
                      {(data.wa_sequence?.touches?.find(t => t.touch_num === item.touch)?.body_preview || '(template enviado)').replace('{{1}}', x.nome || 'Cliente')}
                    </div>
                  </div>
                );
              }
              // Mensagem de sessão sem timestamp (s.messages)
              if (item._type === 'msg_sess') {
                return (
                  <div key={`ms${idx}`} style={{ marginBottom: 8, display: 'flex', justifyContent: item.role === 'user' ? 'flex-start' : 'flex-end' }}>
                    <div style={{ maxWidth: '75%', padding: '8px 12px', borderRadius: 10, fontSize: 12, lineHeight: 1.4,
                      background: item.role === 'user' ? 'var(--bg-sunken)' : 'color-mix(in oklch, var(--ai-500) 12%, transparent)',
                      color: 'var(--text)', border: '1px solid var(--border)' }}>
                      <div style={{ fontSize: 10, color: 'var(--text-muted)', marginBottom: 4, fontFamily: 'var(--font-mono)' }}>
                        {item.role === 'user' ? nome : 'Digi AI'}
                      </div>
                      {item.content}
                    </div>
                  </div>
                );
              }
              // Mensagem com timestamp (wa_messages / webhook_log)
              const isLog = item.source === 'webhook_log';
              const isUser = item.role === 'user' || isLog;
              const msgLabel = isLog
                ? (item.wa_name || nome) + (item.no_ai_response ? ' · sem resposta AI' : '')
                : isUser ? nome : 'Digi AI';
              const bg = isLog ? '#fffbeb' : isUser ? 'var(--bg-sunken)' : 'color-mix(in oklch, var(--ai-500) 12%, transparent)';
              const borderCol = isLog ? '1px solid #f59e0b40' : '1px solid var(--border)';
              return (
                <React.Fragment key={`m${idx}`}>
                  {item.no_ai_response && (
                    <div style={{ marginBottom: 8, padding: '6px 10px', borderRadius: 6,
                      background: '#f59e0b10', border: '1px solid #f59e0b30', fontSize: 11, color: '#d97706' }}>
                      Lead respondeu mas a Digi AI não processou (modelo offline 25 Ago). Com o fix activo, a próxima mensagem recebe resposta.
                    </div>
                  )}
                  <div style={{ marginBottom: 8, display: 'flex', justifyContent: isUser ? 'flex-start' : 'flex-end' }}>
                    <div style={{ maxWidth: '75%', padding: '8px 12px', borderRadius: 10, fontSize: 12,
                      lineHeight: 1.4, background: bg, color: 'var(--text)', border: borderCol }}>
                      <div style={{ fontSize: 10, color: isLog ? '#d97706' : 'var(--text-muted)', marginBottom: 4, fontFamily: 'var(--font-mono)' }}>
                        {msgLabel} · {item.created_at ? new Date(item.created_at).toLocaleString('pt-PT', { day:'2-digit', month:'2-digit', hour:'2-digit', minute:'2-digit' }) : ''}
                      </div>
                      {item.content}
                    </div>
                  </div>
                </React.Fragment>
              );
            });
          })()}
        </div>
      )}

      {/* Tab: Follow-ups */}
      {activeTab === 'followups' && (
        <div>
          {data.wa_sequence ? (
            <div>
              {data.wa_sequence.campanha_nome && (
                <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: 12 }}>
                  Sequencia SDR · {data.wa_sequence.campanha_nome}
                  {data.wa_sequence.lead_responded && (
                    <span style={{ marginLeft: 10, padding: '1px 7px', borderRadius: 10, background: '#22c55e15', color: '#16a34a', border: '1px solid #22c55e30' }}>lead respondeu</span>
                  )}
                </div>
              )}
              {(data.wa_sequence.touches || []).map((t) => {
                const isSent = t.sent;
                const isCancelled = t.cancelled;
                const dateStr = isSent
                  ? (t.sent_at ? new Date(t.sent_at).toLocaleString('pt-PT') : '—')
                  : (t.planned_at ? new Date(t.planned_at).toLocaleDateString('pt-PT') : '—');
                const daysLabel = { 2: 'D+2', 3: 'D+3', 4: 'D+7' };
                return (
                  <div key={t.touch_num} style={{ padding: '10px 14px', background: 'var(--bg-card)', borderRadius: 8, marginBottom: 6, border: '1px solid var(--border)', opacity: isCancelled ? 0.5 : 1 }}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: t.body_preview ? 6 : 0 }}>
                      <span style={{ fontSize: 11, fontFamily: 'var(--font-mono)', fontWeight: 700, color: 'var(--ai-500)', flexShrink: 0 }}>T{t.touch_num}</span>
                      {t.template_name && (
                        <span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-muted)', flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{t.template_name}</span>
                      )}
                      {isSent && (
                        <span style={{ fontSize: 9, fontFamily: 'var(--font-mono)', padding: '1px 7px', borderRadius: 10, background: '#22c55e15', color: '#16a34a', border: '1px solid #22c55e30', flexShrink: 0 }}>ENVIADO</span>
                      )}
                      {!isSent && !isCancelled && (
                        <span style={{ fontSize: 9, fontFamily: 'var(--font-mono)', padding: '1px 7px', borderRadius: 10, background: '#f59e0b15', color: '#d97706', border: '1px solid #f59e0b30', flexShrink: 0 }}>PENDENTE{daysLabel[t.touch_num] ? ' · ' + daysLabel[t.touch_num] : ''}</span>
                      )}
                      {isCancelled && (
                        <span style={{ fontSize: 9, fontFamily: 'var(--font-mono)', padding: '1px 7px', borderRadius: 10, background: '#6b728015', color: '#6b7280', border: '1px solid #6b728030', flexShrink: 0 }}>CANCELADO · lead respondeu</span>
                      )}
                      <span style={{ fontSize: 10, color: 'var(--text-muted)', flexShrink: 0 }}>{dateStr}</span>
                    </div>
                    {t.body_preview && (
                      <div style={{ fontSize: 11, color: 'var(--text)', lineHeight: 1.5, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>{t.body_preview}</div>
                    )}
                  </div>
                );
              })}
            </div>
          ) : (
            <div>
              {fups.length === 0 && <div style={{ padding: 40, textAlign: 'center', color: 'var(--text-dim)', fontSize: 12 }}>Sem follow-ups enviados.</div>}
              {fups.map((f, i) => (
                <div key={i} style={{ padding: '10px 14px', background: 'var(--bg-card)', borderRadius: 8, marginBottom: 6, border: '1px solid var(--border)' }}>
                  <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 4 }}>
                    <span style={{ fontSize: 11, fontFamily: 'var(--font-mono)', color: 'var(--ai-500)', fontWeight: 700 }}>T{f.touch_num}</span>
                    <span style={{ fontSize: 10, color: f.status === 'sent' || f.status === 'delivered' || f.status === 'read' ? '#22c55e' : '#ef4444', fontFamily: 'var(--font-mono)' }}>{(f.status || '').toUpperCase()}</span>
                    <span style={{ fontSize: 10, color: 'var(--text-muted)' }}>{f.enviado_at ? new Date(f.enviado_at).toLocaleString('pt-PT') : '—'}</span>
                  </div>
                  <div style={{ fontSize: 12, color: 'var(--text)', lineHeight: 1.4 }}>{f.content || '—'}</div>
                </div>
              ))}
            </div>
          )}
        </div>
      )}

      {/* Tab: Notas */}
      {activeTab === 'notas' && (
        <div>
          <div style={{ display: 'flex', gap: 8, marginBottom: 16 }}>
            <textarea value={nota} onChange={e => setNota(e.target.value)} placeholder="Adicionar nota..."
              style={{ flex: 1, padding: '8px 10px', borderRadius: 8, border: '1px solid var(--border)', background: 'var(--bg-elev, #ffffff)', color: 'var(--text)', fontSize: 12, resize: 'vertical', minHeight: 60 }} />
            <button className="btn-ai" onClick={addNota} disabled={!nota.trim() || savingNota} style={{ fontSize: 12, alignSelf: 'flex-end' }}>Guardar</button>
          </div>
          {notas.length === 0 && <div style={{ padding: 20, textAlign: 'center', color: 'var(--text-dim)', fontSize: 12 }}>Sem notas.</div>}
          {notas.map(n => (
            <div key={n.id} style={{ padding: '10px 14px', background: 'var(--bg-card)', borderRadius: 8, marginBottom: 6, border: '1px solid var(--border)' }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 4 }}>
                <span style={{ fontSize: 11, fontWeight: 600, color: 'var(--text)' }}>{n.autor_nome || n.autor_email || 'Marketing'}</span>
                <span style={{ fontSize: 10, color: 'var(--text-muted)' }}>{new Date(n.created_at).toLocaleString('pt-PT')}</span>
              </div>
              <div style={{ fontSize: 12, color: 'var(--text)', lineHeight: 1.4 }}>{n.corpo}</div>
            </div>
          ))}
        </div>
      )}

      {/* Tab: Templates WA */}
      {activeTab === 'templates' && (
        <div>
          {tpls.length === 0 && <div style={{ padding: 24, textAlign: 'center', color: 'var(--text-dim)', fontSize: 12 }}>Sem templates para esta campanha.</div>}
          {['outbound','inbound','followup'].map(tipo => {
            const grupo = tipo === 'outbound' ? tpls.filter(t => t.touch_num === 0)
                        : tipo === 'inbound'  ? tpls.filter(t => t.touch_num === 1)
                        : tpls.filter(t => t.touch_num >= 2);
            if (!grupo.length) return null;
            const labels = { outbound: 'T0 — Outbound inicial', inbound: 'T1 — Resposta / Inbound AI', followup: 'T2–T4 — Follow-up (Meta aprovados)' };
            // Normalizar touch_num para display (31 → T3b)
            const touchLabel = (n) => n === 31 ? 'T3b' : `T${n}`;
            return (
              <div key={tipo} style={{ marginBottom: 20 }}>
                <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: 10 }}>{labels[tipo]}</div>
                {grupo.map(t => (
                  <div key={t.id} style={{ padding: '12px 14px', background: 'var(--bg-card)', borderRadius: 8, marginBottom: 8, border: '1px solid var(--border)' }}>
                    <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 6 }}>
                      <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
                        <span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', padding: '1px 6px', borderRadius: 4, background: 'var(--bg-sunken)', color: 'var(--text-muted)', border: '1px solid var(--border)' }}>{touchLabel(t.touch_num)}</span>
                        <span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', padding: '1px 6px', borderRadius: 4, background: 'var(--bg-sunken)', color: 'var(--text-muted)' }}>{t.lang}</span>
                        <span style={{ fontSize: 11, fontWeight: 600, color: 'var(--text)', fontFamily: 'var(--font-mono)' }}>{t.template_name}</span>
                      </div>
                      <span style={{ fontSize: 10, padding: '1px 8px', borderRadius: 10,
                        background: t.status === 'APPROVED' ? '#22c55e15' : t.status === 'PENDING' ? '#f59e0b15' : '#ef444415',
                        color:      t.status === 'APPROVED' ? '#16a34a'   : t.status === 'PENDING' ? '#d97706'   : '#dc2626',
                        border: '1px solid currentColor' }}>{t.status || 'DRAFT'}</span>
                    </div>
                    {t.body_preview && <div style={{ fontSize: 12, color: 'var(--text)', lineHeight: 1.5, whiteSpace: 'pre-wrap', background: 'var(--bg-sunken)', padding: '8px 10px', borderRadius: 6, marginTop: 4 }}>{t.body_preview}</div>}
                    {t.nota && <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 6, fontStyle: 'italic' }}>{t.nota}</div>}
                  </div>
                ))}
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

// TAB LISTAS (redesenhado — campanhas + manuais + click-through profile)
// ═══════════════════════════════════════════════════════════════════════════
function inferLeadCoords(it) {
  const origem = String(it.origem_canal || '');
  const canal = it.canal || (
    /google/i.test(origem) ? 'google_ads' :
    /email/i.test(origem) ? 'email' :
    /blog/i.test(origem) ? 'blog' :
    'meta_ads'
  );
  const destino = it.destino || (
    it.meta_form_id || /form/i.test(origem) ? 'form_nativo' :
    it.ctwa_clid || /ctwa|whatsapp|wa/i.test(origem) ? 'ctwa' :
    'wa_digi'
  );
  const direccao = it.direccao || (it.is_organic ? null : (destino === 'wa_digi' ? 'outbound' : 'inbound'));
  const porAtribuir = !it.classification
    && it.sdr_status !== 'qualificado'
    && it.sdr_status !== 'convertido'
    && it.aceita_demo !== true
    && it.aceita_demo !== 'true';
  return { canal, destino, direccao, porAtribuir };
}

const LEAD_CANAL_LABEL = { meta_ads: 'Meta Ads', google_ads: 'Google Ads', email: 'Email', blog: 'Blog', whatsapp: 'WhatsApp' };
const LEAD_DESTINO_LABEL = { form_nativo: 'Form nativo', ctwa: 'CTWA', typeform: 'Typeform', wa_digi: 'WA Digi', wp_form: 'Form WP' };

function ListaRowMenu({ onOpen, onDelete }) {
  const [open, setOpen] = React.useState(false);
  const [pos, setPos] = React.useState({ top: 0, right: 0 });
  const menuRef = React.useRef(null);
  const btnRef = React.useRef(null);
  const dropdownRef = React.useRef(null);
  React.useEffect(() => {
    if (!open) return undefined;
    const close = (e) => {
      const inMenu = menuRef.current && menuRef.current.contains(e.target);
      const inDrop = dropdownRef.current && dropdownRef.current.contains(e.target);
      if (!inMenu && !inDrop) setOpen(false);
    };
    document.addEventListener('mousedown', close);
    return () => document.removeEventListener('mousedown', close);
  }, [open]);
  const item = { display: 'block', width: '100%', textAlign: 'left', background: 'transparent', border: 'none', padding: '8px 12px', borderRadius: 4, fontSize: 13, color: 'var(--text, #283252)', cursor: 'pointer', fontFamily: 'inherit', whiteSpace: 'nowrap' };
  const hover = e => { e.currentTarget.style.background = 'rgba(0,0,0,0.04)'; };
  const leave = e => { e.currentTarget.style.background = 'transparent'; };
  const run = (fn) => { setOpen(false); fn(); };
  return (
    <div ref={menuRef} style={{ display: 'inline-block' }} onClick={e => e.stopPropagation()}>
      <button ref={btnRef} type="button" aria-label="Acções" onClick={() => {
        if (!open && btnRef.current) {
          const r = btnRef.current.getBoundingClientRect();
          setPos({ top: r.bottom + 4, right: window.innerWidth - r.right });
        }
        setOpen(v => !v);
      }}
        style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: 28, height: 28, borderRadius: 6, cursor: 'pointer', background: open ? 'rgba(0,0,0,0.06)' : 'transparent', border: 'none', outline: 'none', color: 'var(--text-muted)', fontSize: 16, lineHeight: 1 }}
      >⋮</button>
      {open && ReactDOM.createPortal(
        <div ref={dropdownRef} style={{ position: 'fixed', top: pos.top, right: pos.right, zIndex: 9999, background: '#ffffff', border: '1px solid var(--border, #ECEFF5)', borderRadius: 8, minWidth: 160, padding: 4, boxShadow: '0 4px 16px rgba(17,41,84,0.10)' }}>
          <button type="button" style={item} onMouseEnter={hover} onMouseLeave={leave} onClick={() => run(onOpen)}>Abrir</button>
          <div style={{ height: 1, background: 'var(--border, #ECEFF5)', margin: '4px 0' }} />
          <button type="button" style={{ ...item, color: 'var(--danger, #DC2626)' }} onMouseEnter={e => { e.currentTarget.style.background = 'rgba(220,38,38,0.06)'; }} onMouseLeave={leave} onClick={() => run(onDelete)}>Eliminar</button>
        </div>,
        document.body
      )}
    </div>
  );
}

function TabListas({ onLibrary, marca = 'todas', direccao = 'inbound', onDireccao, initialListaId }) {
  const [listas, setListas] = React.useState([]);
  const [loadingListas, setLoadingListas] = React.useState(true);
  const [totalOportunidades, setTotalOportunidades] = React.useState(0);
  const [view, setView] = React.useState('index'); // 'index' | 'lista' | 'externo'
  const [selectedLista, setSelectedLista] = React.useState(null);
  const [listaDetail, setListaDetail] = React.useState(null);
  const [selectedExterno, setSelectedExterno] = React.useState(null);
  const [syncing, setSyncing] = React.useState(false);
  const [processingFup, setProcessingFup] = React.useState(false);
  const [fupResult, setFupResult] = React.useState(null);
  const [quality, setQuality] = React.useState(null);
  const [loadingQuality, setLoadingQuality] = React.useState(false);
  const [syncResult, setSyncResult] = React.useState(null);
  const fileInputRef = React.useRef(null);

  const load = () => CRMAPI.listas().then(d => {
    const rows = Array.isArray(d?.rows) ? d.rows : (Array.isArray(d) ? d : []);
    setListas(rows);
    setTotalOportunidades(d.total_oportunidades || 0);
    if (onLibrary) onLibrary(rows);
  }).catch(() => {}).finally(() => setLoadingListas(false));
  React.useEffect(() => { load(); }, []);
  const openedInitial = React.useRef(null);
  React.useEffect(() => {
    if (!initialListaId || openedInitial.current === String(initialListaId)) return;
    const l = listas.find(x => String(x.id) === String(initialListaId));
    if (!l) return;
    openedInitial.current = String(initialListaId);
    openLista(l);
  }, [initialListaId, listas]);

  // Auto-refresh da lista activa a cada 30s (actualiza wa_num_msgs e score em tempo real)
  React.useEffect(() => {
    if (view !== 'lista' || !selectedLista) return;
    const interval = setInterval(() => {
      CRMAPI.lista(selectedLista.id).then(setListaDetail).catch(() => {});
    }, 30000);
    return () => clearInterval(interval);
  }, [view, selectedLista]);

  const openLista = (l) => {
    setSelectedLista(l);
    setView('lista');
    setListaDetail(null);
    setSyncResult(null);
    setFupResult(null);
    setListFilter({ sdr: 'todos', score: 'todos', anuncio: 'todos', q: '', direccao: direccao === 'organicas' ? 'todas' : (direccao || 'todas'), canal: 'todos', destino: 'todos', porAtribuir: false });
    setShowTouches(false);
    setListSort({ col: 'captado_em', dir: 'desc' });
    CRMAPI.lista(l.id).then(setListaDetail).catch(() => {});
  };

  const openExterno = (externoId) => {
    setSelectedExterno(externoId);
    setView('externo');
  };

  const runQualityAnalysis = async () => {
    setLoadingQuality(true);
    const r = await fetch('/api/marketing/crm/listas/quality').then(r => r.json()).catch(() => null);
    setQuality(r);
    setLoadingQuality(false);
  };

  const eliminar = async (id, e) => {
    e.stopPropagation();
    if (!confirm('Eliminar esta lista?')) return;
    await CRMAPI.eliminarLista(id);
    if (selectedLista?.id === id) setView('index');
    load();
  };

  const importCSV = async (e) => {
    const file = e.target.files?.[0];
    if (!file || !selectedLista) return;
    const fd = new FormData();
    fd.append('csv', file);
    await CRMAPI.importCSV(selectedLista.id, fd);
    CRMAPI.lista(selectedLista.id).then(setListaDetail).catch(() => {});
    load();
  };

  const syncLeads = async () => {
    if (!selectedLista || syncing) return;
    setSyncing(true);
    setSyncResult(null);
    try {
      const r = await fetch(`/api/marketing/crm/listas/${selectedLista.id}/sync-leads`, { method: 'POST' });
      const data = await r.json();
      setSyncResult(data);
      if (data.ok) {
        // Se há leads novas, enviar T0 automaticamente
        if (data.novos > 0 && selectedLista.tipo === 'campanha') {
          await fetch(`/api/marketing/crm/listas/${selectedLista.id}/send-t0`, { method: 'POST' }).catch(() => {});
        }
        CRMAPI.lista(selectedLista.id).then(setListaDetail).catch(() => {});
        load();
      }
    } catch (e) {
      setSyncResult({ error: e.message });
    } finally {
      setSyncing(false);
    }
  };

  const processFollowups = async () => {
    if (!selectedLista || processingFup) return;
    setProcessingFup(true);
    setFupResult(null);
    try {
      const r = await fetch(`/api/marketing/crm/listas/${selectedLista.id}/process-followups`, { method: 'POST' });
      const data = await r.json();
      setFupResult(data);
      if (data.ok) CRMAPI.lista(selectedLista.id).then(setListaDetail).catch(() => {});
    } catch (e) {
      setFupResult({ error: e.message });
    } finally {
      setProcessingFup(false);
    }
  };

  const thS = { fontSize: 10, fontWeight: 700, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', padding: '6px 10px', background: 'var(--bg-sunken)', borderBottom: '1px solid var(--border)', textAlign: 'left' };
  const tdS = { fontSize: 12, color: 'var(--text)', padding: '10px 10px', borderBottom: '1px solid var(--border)', cursor: 'pointer' };

  const campanhaLists = listas.filter(l => l.tipo === 'campanha' && !l.organica
    && !(String(l.canal || '').toUpperCase() === 'FORM' && !(parseInt(l.n_items || 0, 10) > 0)));
  const organicaLists = listas.filter(l => l.organica);

  // ── Filtros e ordenação — hooks sempre chamados (antes de qualquer early return) ──
  const [listFilter, setListFilter] = React.useState({ sdr: 'todos', score: 'todos', anuncio: 'todos', q: '', direccao: 'todas', canal: 'todos', destino: 'todos', porAtribuir: false });
  const [listSort,   setListSort]   = React.useState({ col: 'captado_em', dir: 'desc' });
  const [showTouches, setShowTouches] = React.useState(false);

  // View: Detalhe externo
  if (view === 'externo') {
    return <ExternoLeadProfile externoId={selectedExterno} onBack={() => { setView('lista'); }} />;
  }

  // View: Detalhe lista
  if (view === 'lista' && selectedLista) {
    const rawItems = listaDetail?.items || [];

    const anuncioOpts = [...new Set(rawItems.map(i => i.ad_name).filter(Boolean))].sort();

    const filtered = rawItems.filter(it => {
      if (listFilter.q) {
        const q = listFilter.q.toLowerCase();
        const hay = [it.externo_nome, it.externo_empresa, it.externo_email, it.externo_tel].join(' ').toLowerCase();
        if (!hay.includes(q)) return false;
      }
      if (listFilter.anuncio !== 'todos' && it.ad_name !== listFilter.anuncio) return false;
      const _msgs = it.wa_num_msgs || it.num_msgs || 0;
      if (listFilter.sdr === 'sem_t0'     && it.t0_enviado)                  return false;
      if (listFilter.sdr === 'com_t0'     && !it.t0_enviado)                 return false;
      if (listFilter.sdr === 'respondeu'  && !(_msgs > 0))                   return false;
      if (listFilter.sdr === 'sem_resp'   && !it.t0_enviado && !(_msgs > 0)) return false;
      if (listFilter.sdr === 'sem_resp'   && _msgs > 0)                      return false;
      if (listFilter.sdr === 'no_wa'      && it.wa_status !== 'no_whatsapp') return false;
      if (listFilter.score === 'hot'      && it.classification !== 'HOT')  return false;
      if (listFilter.score === 'warm'     && it.classification !== 'WARM') return false;
      if (listFilter.score === 'cold'     && it.classification !== 'COLD') return false;
      if (listFilter.score === 'sem'      && it.lead_score != null)         return false;
      const coords = inferLeadCoords(it);
      if (listFilter.direccao !== 'todas' && coords.direccao !== listFilter.direccao) return false;
      if (listFilter.canal !== 'todos' && coords.canal !== listFilter.canal) return false;
      if (listFilter.destino !== 'todos' && coords.destino !== listFilter.destino) return false;
      if (listFilter.porAtribuir && !coords.porAtribuir) return false;
      return true;
    }).sort((a, b) => {
      const dir = listSort.dir === 'asc' ? 1 : -1;
      if (listSort.col === 'nome')      return dir * (a.externo_nome||'').localeCompare(b.externo_nome||'');
      if (listSort.col === 'empresa')   return dir * (a.externo_empresa||'').localeCompare(b.externo_empresa||'');
      if (listSort.col === 'score')     return dir * ((a.lead_score||0) - (b.lead_score||0));
      if (listSort.col === 'msgs')      return dir * ((a.wa_num_msgs||a.num_msgs||0) - (b.wa_num_msgs||b.num_msgs||0));
      if (listSort.col === 'captado_em') return dir * (new Date(a.captado_em||0) - new Date(b.captado_em||0));
      if (listSort.col === 't0')        return dir * (!!a.t0_enviado - !!b.t0_enviado);
      return 0;
    });

    const toggleSort = (col) => setListSort(s => ({ col, dir: s.col === col && s.dir === 'desc' ? 'asc' : 'desc' }));
    const sortIcon = (col) => listSort.col !== col ? '' : listSort.dir === 'asc' ? ' ↑' : ' ↓';

    const selS = { fontSize: 11, padding: '3px 6px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg-card)', color: 'var(--text)', cursor: 'pointer' };

    const items = filtered;
    return (
      <div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12 }}>
          <button onClick={() => setView('index')} className="btn btn-xs">Voltar</button>
          <span style={{ color: 'var(--text-muted)', fontSize: 12 }}>›</span>
          <span style={{ fontSize: 14, fontWeight: 600 }}>{selectedLista.nome}</span>
          {selectedLista.canal && <CanalChip canal={selectedLista.canal?.toLowerCase()} />}
          {selectedLista.marca && <span style={{ fontSize: 11, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>{selectedLista.marca}</span>}
          <span style={{ marginLeft: 'auto', fontSize: 11, color: 'var(--text-muted)' }}>{rawItems.length} leads</span>
          <input type="file" accept=".csv" ref={fileInputRef} onChange={importCSV} style={{ display: 'none' }} />
          {selectedLista.tipo === 'campanha' && (
            <button className="btn btn-xs btn-primary" onClick={syncLeads} disabled={syncing}
              style={{ fontSize: 11 }}>
              {syncing ? 'A sincronizar...' : 'Sincronizar leads'}
            </button>
          )}
          {selectedLista.tipo === 'campanha' && (
            <button className="btn btn-xs" onClick={processFollowups} disabled={processingFup}
              style={{ fontSize: 11, background: processingFup ? undefined : 'var(--bg-sunken)' }}>
              {processingFup ? 'A processar...' : 'Follow-ups'}
            </button>
          )}
          {selectedLista.tipo === 'campanha' && (
            <button className="btn btn-xs" onClick={() => setShowTouches(v => !v)}
              style={{ fontSize: 11, background: showTouches ? 'var(--ai-500)' : 'var(--bg-sunken)', color: showTouches ? '#fff' : 'var(--text)' }}>
              Templates de toque
            </button>
          )}
          {selectedLista.tipo !== 'campanha' && (
            <button className="btn btn-xs" onClick={() => fileInputRef.current?.click()}>Importar CSV</button>
          )}
        </div>

        {/* Barra filtros + ordenação — só listas campanha */}
        {selectedLista.tipo === 'campanha' && (
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10, flexWrap: 'wrap' }}>
            <input value={listFilter.q} onChange={e => setListFilter(f => ({...f, q: e.target.value}))}
              placeholder="Pesquisar nome, empresa..."
              style={{ ...selS, width: 180, padding: '4px 8px' }} />
            <select value={listFilter.sdr} onChange={e => setListFilter(f => ({...f, sdr: e.target.value}))} style={selS}>
              <option value="todos">Digi AI: todos</option>
              <option value="sem_t0">Sem T0</option>
              <option value="com_t0">T0 enviado</option>
              <option value="respondeu">Respondeu</option>
              <option value="sem_resp">T0 sem resposta</option>
              <option value="no_wa">Sem WhatsApp</option>
            </select>
            <select value={listFilter.score} onChange={e => setListFilter(f => ({...f, score: e.target.value}))} style={selS}>
              <option value="todos">Score: todos</option>
              <option value="hot">HOT</option>
              <option value="warm">WARM</option>
              <option value="cold">COLD</option>
              <option value="sem">Sem score</option>
            </select>
            {anuncioOpts.length > 1 && (
              <select value={listFilter.anuncio} onChange={e => setListFilter(f => ({...f, anuncio: e.target.value}))} style={selS}>
                <option value="todos">Anúncio: todos</option>
                {anuncioOpts.map(a => <option key={a} value={a}>{a}</option>)}
              </select>
            )}
            <select value={listFilter.direccao} onChange={e => setListFilter(f => ({...f, direccao: e.target.value}))} style={selS}>
              <option value="todas">Direcção: todas</option>
              <option value="inbound">Inbound</option>
              <option value="outbound">Outbound</option>
            </select>
            <select value={listFilter.canal} onChange={e => setListFilter(f => ({...f, canal: e.target.value}))} style={selS}>
              <option value="todos">Canal: todos</option>
              <option value="meta_ads">Meta Ads</option>
              <option value="google_ads">Google Ads</option>
              <option value="email">Email</option>
              <option value="blog">Blog</option>
              <option value="whatsapp">WhatsApp</option>
            </select>
            <select value={listFilter.destino} onChange={e => setListFilter(f => ({...f, destino: e.target.value}))} style={selS}>
              <option value="todos">Destino: todos</option>
              <option value="form_nativo">Form nativo</option>
              <option value="ctwa">CTWA</option>
              <option value="typeform">Typeform</option>
              <option value="wa_digi">WA Digi</option>
              <option value="wp_form">Form WP</option>
            </select>
            <button onClick={() => setListFilter(f => ({ ...f, porAtribuir: !f.porAtribuir }))}
              style={{ ...selS, background: listFilter.porAtribuir ? '#d97706' : 'var(--bg-card)', color: listFilter.porAtribuir ? '#fff' : 'var(--text)', border: '1px solid var(--border)' }}>
              Por atribuir
            </button>
            {(listFilter.q || listFilter.sdr !== 'todos' || listFilter.score !== 'todos' || listFilter.anuncio !== 'todos' || listFilter.direccao !== 'todas' || listFilter.canal !== 'todos' || listFilter.destino !== 'todos' || listFilter.porAtribuir) && (
              <button className="btn btn-xs" onClick={() => setListFilter({ sdr:'todos', score:'todos', anuncio:'todos', q:'', direccao:'todas', canal:'todos', destino:'todos', porAtribuir:false })}
                style={{ fontSize: 10, color: 'var(--text-muted)' }}>× Limpar</button>
            )}
            <span style={{ marginLeft: 'auto', fontSize: 11, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>
              {filtered.length}/{rawItems.length} leads
            </span>
          </div>
        )}

        {showTouches && selectedLista.tipo === 'campanha' && (
          <div style={{ marginBottom: 16 }}>
            <TabWaTemplates campanhaNome={selectedLista.campanha_nome || selectedLista.nome} />
          </div>
        )}

        {syncResult && (
          <div style={{ marginBottom: 8, padding: '8px 12px', borderRadius: 8,
            background: syncResult.error ? 'var(--danger-bg, #fef2f2)' : 'var(--success-bg, #f0fdf4)',
            border: `1px solid ${syncResult.error ? 'var(--danger, #ef4444)' : 'var(--success, #22c55e)'}`,
            fontSize: 12, color: syncResult.error ? 'var(--danger, #dc2626)' : 'var(--success, #16a34a)' }}>
            {syncResult.error
              ? `Erro: ${syncResult.error}`
              : `${syncResult.novos} leads novos importados · ${syncResult.duplicados} duplicados ignorados · total no form: ${syncResult.total}`}
          </div>
        )}

        {fupResult && (
          <div style={{ marginBottom: 8, padding: '8px 12px', borderRadius: 8,
            background: fupResult.error ? 'var(--danger-bg, #fef2f2)' : 'var(--bg-sunken)',
            border: `1px solid ${fupResult.error ? 'var(--danger, #ef4444)' : 'var(--border)'}`,
            fontSize: 12, color: fupResult.error ? 'var(--danger, #dc2626)' : 'var(--text-muted)' }}>
            {fupResult.error ? `Erro: ${fupResult.error}` : (() => {
              const r = fupResult.results || {};
              const total = (r.t2?.sent||0) + (r.t3?.sent||0) + (r.t4?.sent||0);
              const skipped = fupResult.responded || 0;
              if (total === 0) return `Nenhum follow-up enviado · ${fupResult.total_leads} leads verificadas · ${skipped} responderam`;
              return `${total} follow-up${total!==1?'s':''} enviados (T2:${r.t2?.sent||0} T3:${r.t3?.sent||0} T4:${r.t4?.sent||0}) · ${skipped} responderam`;
            })()}
          </div>
        )}

        {items.length === 0 && !syncing && <div style={{ padding: 40, textAlign: 'center', color: 'var(--text-dim)', fontSize: 12 }}>Sem leads nesta lista. Clica em "Sincronizar leads" para importar do Meta.</div>}
        {items.length > 0 && (
          <div style={{ border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden' }}>
            <table style={{ width: '100%', borderCollapse: 'collapse' }}>
              <thead>
                <tr>
                  {[['nome','Nome'],['empresa','Empresa'],['','Telefone'],['','Origem'],['msgs','Digi AI'],['score','Score'],['captado_em','Captado em'],['','']].map(([col,label]) => (
                    <th key={label} style={{ ...thS, cursor: col ? 'pointer' : 'default', userSelect: 'none' }}
                      onClick={() => col && toggleSort(col)}>
                      {label}{col ? sortIcon(col) : ''}
                    </th>
                  ))}
                </tr>
              </thead>
              <tbody>
                {items.map(it => {
                  const nome  = it.entidade_nome || it.contacto_nome || it.externo_nome || '(sem nome)';
                  const tel   = it.contacto_tel || it.externo_tel;
                  const extId = it.contacto_externo_id || it.externo_id || it.visitor_id;

                  // Coluna Digi AI — estado SDR compacto (FORM + CTWA)
                  const isForm = selectedLista.canal === 'FORM';
                  const DigiAICell = () => {
                    if (it.wa_status === 'no_whatsapp') return (
                      <div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
                        <span style={{ fontSize: 9, fontFamily: 'var(--font-mono)', padding: '1px 6px', borderRadius: 3,
                          background: '#ef444415', color: '#dc2626', border: '1px solid #ef444430' }}>Sem WA</span>
                        <span style={{ fontSize: 9, color: 'var(--text-dim)' }}>Carina notificada</span>
                      </div>
                    );
                    const responded = (it.wa_num_msgs || it.num_msgs || 0) > 0;
                    const msgCount  = it.wa_num_msgs || it.num_msgs || 0;
                    if (isForm) {
                      // FORM: mostra badges de outbound T0/T2/T3/T4
                      const touches = [
                        it.t0_enviado && { label: 'T0' },
                        it.t2_enviado && { label: 'T2' },
                        it.t3_enviado && { label: 'T3' },
                        it.t4_enviado && { label: 'T4' },
                      ].filter(Boolean);
                      if (!it.t0_enviado) return <span style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>Pendente</span>;
                      return (
                        <div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
                          <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
                            {touches.map(t => (
                              <span key={t.label} style={{ fontSize: 9, fontFamily: 'var(--font-mono)', padding: '1px 5px', borderRadius: 3,
                                background: '#22c55e15', color: '#16a34a', border: '1px solid #22c55e30' }}>{t.label} ✓</span>
                            ))}
                          </div>
                          {responded
                            ? <span style={{ fontSize: 10, color: 'var(--ai-500)', fontFamily: 'var(--font-mono)' }}>{msgCount} msgs</span>
                            : <span style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>Sem resposta</span>}
                        </div>
                      );
                    }
                    // CTWA/WA: lead iniciou contacto — mostra estado da conversa
                    if (!responded) return <span style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>Iniciado</span>;
                    return (
                      <div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
                        <span style={{ fontSize: 9, fontFamily: 'var(--font-mono)', padding: '1px 5px', borderRadius: 3,
                          background: '#3859D015', color: '#3859D0', border: '1px solid #3859D030' }}>WA activo</span>
                        <span style={{ fontSize: 10, color: 'var(--ai-500)', fontFamily: 'var(--font-mono)' }}>{msgCount} msgs</span>
                      </div>
                    );
                  };

                  return (
                    <tr key={it.id || it.visitor_id}
                      onClick={() => extId && openExterno(extId)}
                      onMouseEnter={ev => ev.currentTarget.style.background = 'var(--bg-sunken)'}
                      onMouseLeave={ev => ev.currentTarget.style.background = 'transparent'}
                      style={{ cursor: extId ? 'pointer' : 'default' }}>
                      <td style={{ ...tdS, fontWeight: 600 }}>{nome}</td>
                      <>
                        <td style={{ ...tdS, fontSize: 11, color: 'var(--text-muted)' }}>{it.externo_empresa || it.empresa || '—'}</td>
                        <td style={{ ...tdS, fontFamily: 'var(--font-mono)', fontSize: 11 }}>{tel || '—'}</td>
                        <td style={tdS}>
                          {(() => {
                            const c = inferLeadCoords(it);
                            return (
                              <div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
                                <span style={{ fontSize: 11 }}>{LEAD_CANAL_LABEL[c.canal] || c.canal} · {LEAD_DESTINO_LABEL[c.destino] || c.destino}</span>
                                <span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-muted)' }}>
                                  {c.direccao === 'outbound' ? 'Outbound' : c.direccao === 'inbound' ? 'Inbound' : 'Orgânica'}
                                  {it.ad_name ? ` · ${it.ad_name}` : ''}
                                </span>
                              </div>
                            );
                          })()}
                        </td>
                        <td style={tdS}><DigiAICell /></td>
                        <td style={tdS}><ScoreBadge score={it.lead_score} classification={it.classification} small /></td>
                        <td style={{ ...tdS, fontSize: 11, color: 'var(--text-muted)' }}>{(it.captado_em || it.ultima_actividade) ? new Date(it.captado_em || it.ultima_actividade).toLocaleDateString('pt-PT') : '—'}</td>
                      </>
                      <td style={{ ...tdS, textAlign: 'right' }}>
                        <span style={{ fontSize: 11, color: 'var(--ai-500)' }}>Ver ficha ›</span>
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
        )}
      </div>
    );
  }

  // View: Index
  const nInboundOf  = (l) => parseInt(l.n_inbound || 0, 10) || 0;
  const nOutboundOf = (l) => parseInt(l.n_outbound || 0, 10) || 0;
  const nOrgOf      = (l) => parseInt(l.n_organicas || l.n_items || 0, 10) || 0;
  const daMarcaCamp = campanhaLists.filter(l => marca === 'todas' || listaMarca(l)?.slug === marca);
  const daMarcaOrg  = organicaLists.filter(l => marca === 'todas' || listaMarca(l)?.slug === marca);
  const totInbound  = daMarcaCamp.reduce((s, l) => s + nInboundOf(l), 0);
  const totOutbound = daMarcaCamp.reduce((s, l) => s + nOutboundOf(l), 0);
  const totOrg      = daMarcaOrg.reduce((s, l) => s + nOrgOf(l), 0);
  const totAll      = totInbound + totOutbound + totOrg;
  const dirCards = [
    { id: 'inbound',  label: 'Inbound',   value: totInbound,  sub: 'Vieram pelo destino da campanha', accent: 'var(--ai-500, #3859D0)', fill: totAll ? totInbound / totAll : 0 },
    { id: 'outbound', label: 'Outbound',  value: totOutbound, sub: 'Estavam no target e escreveram', accent: 'var(--text-muted, #4a5e7a)', fill: totAll ? totOutbound / totAll : 0 },
    { id: 'organicas',label: 'Orgânicas', value: totOrg,      sub: 'Sem campanha, sem form, sem anúncio', accent: 'var(--text-muted, #4a5e7a)', fill: totAll ? totOrg / totAll : 0 },
  ];
  const cardsCampanha = direccao === 'organicas'
    ? []
    : daMarcaCamp.filter(l => (direccao === 'outbound' ? nOutboundOf(l) : nInboundOf(l)) > 0);
  const canalTxt = (l) => LEAD_CANAL_LABEL[String(l.canal || '').toLowerCase()] || l.canal || '—';
  const nDir = (l) => direccao === 'outbound' ? nOutboundOf(l) : nInboundOf(l);

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
      {loadingListas && (
        <div>
          {[1, 2, 3].map(i => (
            <div key={i} style={{ height: 72, borderRadius: 8, background: 'var(--bg-sunken, #eef1f6)', marginBottom: 8, opacity: 1 - i * 0.2 }} />
          ))}
        </div>
      )}
      {!loadingListas && (
        <>
          <div style={{ display: 'flex', gap: 12 }}>
            {dirCards.map(k => {
              const isActive = direccao === k.id;
              return (
                <div key={k.id} onClick={() => onDireccao && onDireccao(k.id)}
                  style={{
                    flex: '1 1 0', background: 'var(--bg-elev, #ffffff)',
                    border: `1px solid ${isActive ? 'var(--ai-500, #3859D0)' : 'var(--border, #dde3ef)'}`,
                    borderRadius: 8, padding: '12px 16px 0',
                    display: 'flex', flexDirection: 'column', gap: 4, overflow: 'hidden', cursor: 'pointer',
                  }}>
                  <div style={{ fontSize: 9.5, fontWeight: 600, letterSpacing: '0.10em', textTransform: 'uppercase', color: isActive ? 'var(--ai-500, #3859D0)' : 'var(--text-muted, #4a5e7a)', fontFamily: 'var(--font-mono, monospace)', whiteSpace: 'nowrap' }}>{k.label}</div>
                  <div style={{ fontSize: 22, fontWeight: 700, fontFamily: 'var(--font-display)', color: isActive ? 'var(--ai-500, #3859D0)' : k.accent, lineHeight: 1 }}>{Number(k.value).toLocaleString('pt-PT')}</div>
                  <div style={{ fontSize: 11, color: 'var(--text-muted)', paddingBottom: 10, lineHeight: 1.35 }}>{k.sub}</div>
                  <div style={{ height: 3, background: 'var(--bg-sunken, #eef1f6)', overflow: 'hidden' }}>
                    <div style={{ height: '100%', width: `${Math.min(100, Math.max(0, k.fill * 100))}%`, background: isActive ? 'var(--ai-500, #3859D0)' : k.accent }} />
                  </div>
                </div>
              );
            })}
          </div>

          {direccao === 'organicas' ? (
            <>
              <p style={{ margin: 0, fontSize: 13, color: 'var(--text-muted)', maxWidth: 720, lineHeight: 1.5 }}>
                Quem escreveu à Digi, não está no target de nenhuma campanha activa e não chegou por formulário nem por anúncio. Não entra nos resultados da campanha. Se mais tarde for ligada a uma campanha, sai daqui e passa a inbound dessa lista.
              </p>
              {daMarcaOrg.length === 0 ? (
                <div style={{ fontSize: 14, color: 'var(--text-muted)' }}>Ainda não há lista orgânica.</div>
              ) : daMarcaOrg.map(l => (
                <div key={l.id} onClick={() => openLista(l)}
                  style={{
                    background: 'var(--bg-elev, #ffffff)', border: '1px solid var(--border)', borderRadius: 10,
                    padding: '16px 18px', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 16,
                  }}
                  onMouseEnter={ev => ev.currentTarget.style.borderColor = 'var(--ai-500)'}
                  onMouseLeave={ev => ev.currentTarget.style.borderColor = 'var(--border)'}>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 15, fontWeight: 700, color: 'var(--text)', fontFamily: 'var(--font-display)' }}>{l.nome}</div>
                    <div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 4 }}>Orgânicas · {nOrgOf(l).toLocaleString('pt-PT')} leads</div>
                  </div>
                  <span style={{ fontSize: 12, color: 'var(--ai-500)' }}>Abrir ›</span>
                </div>
              ))}
            </>
          ) : cardsCampanha.length === 0 ? (
            <div style={{ fontSize: 14, color: 'var(--text-muted)', marginTop: 8 }}>
              {direccao === 'outbound' ? 'Ainda não há leads outbound nestas campanhas.' : 'Ainda não há leads inbound nestas campanhas.'}
            </div>
          ) : (
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(240px, 1fr))', gap: 12 }}>
              {cardsCampanha.map(l => (
                <div key={l.id} onClick={() => openLista(l)}
                  style={{
                    padding: '16px 18px', background: 'var(--bg-elev, #ffffff)', borderRadius: 10,
                    border: '1px solid var(--border)', cursor: 'pointer',
                  }}
                  onMouseEnter={ev => ev.currentTarget.style.borderColor = 'var(--ai-500)'}
                  onMouseLeave={ev => ev.currentTarget.style.borderColor = 'var(--border)'}>
                  <div style={{ fontSize: 14, fontWeight: 700, color: 'var(--text)', fontFamily: 'var(--font-display)', lineHeight: 1.3, marginBottom: 8 }}>{l.nome}</div>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
                    <BrandMark brand={listaMarca(l)} />
                    <span style={{ fontSize: 12, color: 'var(--text-muted)' }}>{canalTxt(l)}</span>
                  </div>
                  <div style={{ fontSize: 22, fontWeight: 700, fontFamily: 'var(--font-display)', color: 'var(--ai-500, #3859D0)', lineHeight: 1 }}>{nDir(l).toLocaleString('pt-PT')}</div>
                  <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 4 }}>{direccao === 'outbound' ? 'leads outbound' : 'leads inbound'}</div>
                </div>
              ))}
            </div>
          )}
        </>
      )}
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════════════════
// ═══════════════════════════════════════════════════════════════════════════
// TAB TEMPLATES WA (#25/#27) — gestão de templates Meta por campanha
// ═══════════════════════════════════════════════════════════════════════════
function TabWaTemplates({ marca = 'todas', campanhaNome } = {}) {
  const API = window.MARKETING_API || '';
  const [templates, setTemplates]   = React.useState([]);
  const [loading, setLoading]       = React.useState(true);
  const [editing, setEditing]       = React.useState({});   // { [id]: { template_name, status, body_preview, nota } }
  const [saving, setSaving]         = React.useState({});
  const [saved, setSaved]           = React.useState({});
  const [showNew, setShowNew]       = React.useState(false);
  const [newRow, setNewRow]         = React.useState({ campaign_name: '', touch_num: 0, lang: 'PT', template_name: '', body_preview: '' });
  const [newSaving, setNewSaving]   = React.useState(false);

  const STATUS_COLORS = {
    pending:   { bg: 'rgba(234,179,8,.12)',  text: '#92400e', label: 'Pendente' },
    submitted: { bg: 'rgba(56,89,208,.10)',  text: '#3859D0', label: 'Submetido' },
    approved:  { bg: 'rgba(22,163,74,.12)',  text: '#166534', label: 'Aprovado' },
    rejected:  { bg: 'rgba(220,38,38,.12)',  text: '#991b1b', label: 'Rejeitado' },
  };
  const TOUCH_LABELS = { 0: 'T0 Outbound', 1: 'T1 AI-gen', 2: 'T2', 3: 'T3', 4: 'T4', 31: 'T3b' };

  const load = () => {
    setLoading(true);
    fetch(`${API}/api/marketing/crm/wa-templates`)
      .then(r => r.json()).then(d => { setTemplates(d.templates || []); setLoading(false); })
      .catch(() => setLoading(false));
  };
  React.useEffect(load, []);

  const grouped = React.useMemo(() => {
    const marcaRe = marca === 'todas' ? null : AUD_BRANDS.find(b => b.slug === marca)?.re;
    const campRe = campanhaNome ? new RegExp(String(campanhaNome).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i') : null;
    const g = {};
    for (const t of templates) {
      if (marcaRe && !marcaRe.test(t.campaign_name || '')) continue;
      if (campRe && !campRe.test(t.campaign_name || '')) continue;
      if (!g[t.campaign_name]) g[t.campaign_name] = [];
      g[t.campaign_name].push(t);
    }
    return g;
  }, [templates, marca, campanhaNome]);

  const startEdit = (t) => setEditing(e => ({
    ...e, [t.id]: { template_name: t.template_name, status: t.status, body_preview: t.body_preview || '', nota: t.nota || '' }
  }));
  const cancelEdit = (id) => setEditing(e => { const n = {...e}; delete n[id]; return n; });

  const saveRow = async (id) => {
    setSaving(s => ({...s, [id]: true}));
    try {
      const res = await fetch(`${API}/api/marketing/crm/wa-templates/${id}`, {
        method: 'PUT', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(editing[id])
      });
      const data = await res.json();
      if (data.ok) {
        setTemplates(ts => ts.map(t => t.id === id ? data.template : t));
        cancelEdit(id);
        setSaved(s => ({...s, [id]: true}));
        setTimeout(() => setSaved(s => { const n={...s}; delete n[id]; return n; }), 2000);
      }
    } finally { setSaving(s => { const n={...s}; delete n[id]; return n; }); }
  };

  const createRow = async () => {
    setNewSaving(true);
    try {
      const res = await fetch(`${API}/api/marketing/crm/wa-templates`, {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(newRow)
      });
      const data = await res.json();
      if (data.ok) { load(); setShowNew(false); setNewRow({ campaign_name: '', touch_num: 0, lang: 'PT', template_name: '', body_preview: '' }); }
    } finally { setNewSaving(false); }
  };

  const thS = { fontSize: 10, fontWeight: 700, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', padding: '7px 10px', background: 'var(--bg-sunken)', borderBottom: '1px solid var(--border)', letterSpacing: '0.06em', textTransform: 'uppercase' };
  const tdS = { fontSize: 12, color: 'var(--text)', padding: '8px 10px', borderBottom: '1px solid var(--border)', verticalAlign: 'middle' };
  const inputS = { fontSize: 12, padding: '4px 8px', borderRadius: 5, border: '1px solid var(--ai-500)', background: 'var(--bg)', color: 'var(--text)', width: '100%', fontFamily: 'var(--font-mono)' };

  if (loading) return <div style={{ padding: 40, color: 'var(--text-muted)', fontSize: 13 }}>A carregar templates...</div>;

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 28 }}>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
        <div>
          <div style={{ fontSize: 15, fontWeight: 700, color: 'var(--text)', fontFamily: 'var(--font-display)', marginBottom: 3 }}>Templates WhatsApp por Campanha</div>
          <div style={{ fontSize: 12, color: 'var(--text-muted)' }}>T0 = outbound lead gen · T1 = AI gerado · T2-T4 = templates Meta aprovados para follow-up</div>
        </div>
        <button className="btn btn-ai" style={{ height: 30, padding: '0 14px', fontSize: 12 }} onClick={() => setShowNew(v => !v)}>
          {showNew ? 'Cancelar' : '+ Novo template'}
        </button>
      </div>

      {showNew && (
        <div style={{ background: 'var(--bg-card)', border: '1px solid var(--ai-500)', borderRadius: 10, padding: 18, display: 'flex', flexDirection: 'column', gap: 10 }}>
          <div style={{ fontSize: 11, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '0.06em' }}>NOVO TEMPLATE</div>
          <div style={{ display: 'grid', gridTemplateColumns: '2fr 1fr 1fr 2fr', gap: 10 }}>
            <div><label style={{ fontSize: 10, color: 'var(--text-dim)', display: 'block', marginBottom: 3 }}>CAMPANHA</label>
              <input style={inputS} value={newRow.campaign_name} onChange={e => setNewRow(r => ({...r, campaign_name: e.target.value}))} placeholder="ex: TS200-1600 PT" /></div>
            <div><label style={{ fontSize: 10, color: 'var(--text-dim)', display: 'block', marginBottom: 3 }}>TOUCH</label>
              <select style={inputS} value={newRow.touch_num} onChange={e => setNewRow(r => ({...r, touch_num: parseInt(e.target.value)}))}>
                {Object.entries(TOUCH_LABELS).map(([k,v]) => <option key={k} value={k}>{v}</option>)}
              </select></div>
            <div><label style={{ fontSize: 10, color: 'var(--text-dim)', display: 'block', marginBottom: 3 }}>IDIOMA</label>
              <select style={inputS} value={newRow.lang} onChange={e => setNewRow(r => ({...r, lang: e.target.value}))}>
                {['PT','ES','EN'].map(l => <option key={l}>{l}</option>)}
              </select></div>
            <div><label style={{ fontSize: 10, color: 'var(--text-dim)', display: 'block', marginBottom: 3 }}>NOME TEMPLATE META</label>
              <input style={inputS} value={newRow.template_name} onChange={e => setNewRow(r => ({...r, template_name: e.target.value}))} placeholder="ex: ts200_followup_t2_pt" /></div>
          </div>
          <div><label style={{ fontSize: 10, color: 'var(--text-dim)', display: 'block', marginBottom: 3 }}>PREVIEW CORPO</label>
            <input style={{...inputS, width: '100%'}} value={newRow.body_preview} onChange={e => setNewRow(r => ({...r, body_preview: e.target.value}))} placeholder="Olá {{1}}, ..." /></div>
          <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
            <button className="btn btn-ai" style={{ height: 28, padding: '0 14px', fontSize: 12 }} onClick={createRow} disabled={newSaving || !newRow.template_name}>
              {newSaving ? 'A guardar...' : 'Guardar'}
            </button>
          </div>
        </div>
      )}

      {Object.entries(grouped).map(([campanha, rows]) => (
        <div key={campanha} style={{ background: 'var(--bg-card)', border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden' }}>
          <div style={{ padding: '12px 16px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', gap: 8 }}>
            <span style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)', fontFamily: 'var(--font-display)' }}>{campanha}</span>
            <span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)' }}>{rows.length} templates</span>
          </div>
          <table style={{ width: '100%', borderCollapse: 'collapse' }}>
            <thead>
              <tr>
                <th style={{...thS, width: 90}}>Touch</th>
                <th style={{...thS, width: 50}}>Lang</th>
                <th style={thS}>Nome Template Meta</th>
                <th style={thS}>Preview corpo</th>
                <th style={{...thS, width: 100}}>Estado</th>
                <th style={{...thS, width: 90}}></th>
              </tr>
            </thead>
            <tbody>
              {rows.sort((a,b) => a.touch_num - b.touch_num || a.lang.localeCompare(b.lang)).map(t => {
                const ed = editing[t.id];
                const sc = STATUS_COLORS[t.status] || STATUS_COLORS.pending;
                const isSaved = saved[t.id];
                return (
                  <tr key={t.id} style={{ background: ed ? 'rgba(56,89,208,.03)' : undefined }}>
                    <td style={tdS}><span style={{ fontSize: 11, fontFamily: 'var(--font-mono)', background: 'var(--bg-sunken)', padding: '2px 7px', borderRadius: 4 }}>{TOUCH_LABELS[t.touch_num] || `T${t.touch_num}`}</span></td>
                    <td style={tdS}><span style={{ fontSize: 11, fontFamily: 'var(--font-mono)', color: 'var(--text-muted)' }}>{t.lang}</span></td>
                    <td style={tdS}>
                      {ed
                        ? <input style={inputS} value={ed.template_name} onChange={e => setEditing(v => ({...v, [t.id]: {...ed, template_name: e.target.value}}))} />
                        : <span style={{ fontFamily: 'var(--font-mono)', fontSize: 12 }}>{t.template_name}</span>}
                    </td>
                    <td style={{...tdS, color: 'var(--text-muted)', fontSize: 11}}>
                      {ed
                        ? <input style={inputS} value={ed.body_preview} onChange={e => setEditing(v => ({...v, [t.id]: {...ed, body_preview: e.target.value}}))} placeholder="Preview..." />
                        : (t.body_preview || '—')}
                    </td>
                    <td style={tdS}>
                      {ed
                        ? <select style={{...inputS, width: 110}} value={ed.status} onChange={e => setEditing(v => ({...v, [t.id]: {...ed, status: e.target.value}}))}>
                            {Object.keys(STATUS_COLORS).map(s => <option key={s} value={s}>{STATUS_COLORS[s].label}</option>)}
                          </select>
                        : <span style={{ fontSize: 10, fontWeight: 600, padding: '3px 8px', borderRadius: 20, background: sc.bg, color: sc.text }}>{sc.label}</span>}
                    </td>
                    <td style={{...tdS, textAlign: 'right'}}>
                      {isSaved
                        ? <span style={{ fontSize: 11, color: 'var(--success)', fontWeight: 600 }}>Guardado</span>
                        : ed
                          ? <div style={{ display: 'flex', gap: 4, justifyContent: 'flex-end' }}>
                              <button className="btn" style={{ height: 24, padding: '0 10px', fontSize: 11 }} onClick={() => cancelEdit(t.id)}>Cancelar</button>
                              <button className="btn btn-ai" style={{ height: 24, padding: '0 10px', fontSize: 11 }} onClick={() => saveRow(t.id)} disabled={saving[t.id]}>
                                {saving[t.id] ? '...' : 'Guardar'}
                              </button>
                            </div>
                          : <button className="btn" style={{ height: 24, padding: '0 10px', fontSize: 11 }} onClick={() => startEdit(t)}>Editar</button>}
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      ))}
      {!loading && Object.keys(grouped).length === 0 && (
        <div style={{ padding: 40, textAlign: 'center', color: 'var(--text-muted)', fontSize: 13 }}>Sem templates configurados. Clica em "+ Novo template" para começar.</div>
      )}
    </div>
  );
}

// TAB GESTÃO (A5 UI — tags, contactos externos)
// ═══════════════════════════════════════════════════════════════════════════
function TabGestao() {
  const [subTab, setSubTab] = React.useState('sync');
  const SUB_TABS = [
    { id: 'sync',       label: 'Sync & Logs' },
    { id: 'integracoes',label: 'Integrações' },
    { id: 'tags',       label: 'Tags' },
    { id: 'externos',   label: 'Contactos externos' },
  ];
  const tabBtn = (id, label) => (
    <button key={id} onClick={() => setSubTab(id)}
      style={{
        padding: '5px 14px', borderRadius: 6, border: '1px solid var(--border)', cursor: 'pointer',
        fontSize: 12, fontWeight: 600, fontFamily: 'var(--font-display)',
        background: subTab === id ? 'var(--ai-500)' : 'var(--bg-elev)',
        color: subTab === id ? '#fff' : 'var(--text-muted)',
      }}>{label}</button>
  );
  return (
    <div>
      <div style={{ display: 'flex', gap: 6, marginBottom: 24 }}>
        {SUB_TABS.map(t => tabBtn(t.id, t.label))}
      </div>
      {subTab === 'sync'        && <GestaoSyncLogs />}
      {subTab === 'integracoes' && <GestaoIntegracoes />}
      {subTab === 'tags'        && <GestaoTags />}
      {subTab === 'externos'    && <GestaoExternos />}
    </div>
  );
}

function GestaoSyncLogs() {
  const [logs, setLogs] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [expanded, setExpanded] = React.useState(null);

  const load = () => {
    setLoading(true);
    CRMAPI.syncLogs().then(d => { setLogs(d); setLoading(false); }).catch(() => setLoading(false));
  };
  React.useEffect(() => { load(); }, []);

  const fmtDur = ms => {
    if (!ms) return '—';
    if (ms < 60000) return `${Math.round(ms/1000)}s`;
    return `${Math.floor(ms/60000)}m ${Math.round((ms%60000)/1000)}s`;
  };
  const fmtDate = iso => iso ? new Date(iso).toLocaleString('pt-PT', { day:'2-digit', month:'short', year:'numeric', hour:'2-digit', minute:'2-digit' }) : '—';
  const fmtAgo  = iso => { if (!iso) return '—'; const h=Math.floor((Date.now()-new Date(iso))/3600000); const d=Math.floor(h/24); return h<1?'agora':h<24?`há ${h}h`:`há ${d}d`; };

  const STATUS_META = {
    success: { label:'Sucesso',   color:'#15803d', bg:'rgba(22,163,74,.1)'  },
    error:   { label:'Erro',      color:'#dc2626', bg:'rgba(220,38,38,.1)'  },
    partial: { label:'Parcial',   color:'#d97706', bg:'rgba(217,119,6,.1)'  },
    running: { label:'Em curso',  color:'#3859D0', bg:'rgba(56,89,208,.1)'  },
  };

  const sessions = logs?.sessions || [];
  const lastOk   = logs?.last_success;

  const thS = { fontSize: 9, fontWeight: 700, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', padding: '8px 12px', background: 'var(--bg-sunken)', borderBottom: '1px solid var(--border)', textAlign: 'left', textTransform: 'uppercase', letterSpacing: '.06em' };
  const tdS = { fontSize: 12, padding: '10px 12px', borderBottom: '1px solid var(--border-light,#f1f5f9)', verticalAlign: 'middle' };

  return (
    <div>
      {/* Resumo última sync */}
      {lastOk && (
        <div style={{ padding: '14px 18px', background: 'rgba(22,163,74,.06)', border: '1px solid rgba(22,163,74,.2)', borderRadius: 10, marginBottom: 20, display: 'flex', gap: 24, flexWrap: 'wrap' }}>
          <div>
            <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '.06em', marginBottom: 2 }}>Última sync com sucesso</div>
            <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)' }}>{fmtDate(lastOk.finished_at)} <span style={{ fontWeight: 400, color: 'var(--text-muted)', fontSize: 11 }}>({fmtAgo(lastOk.finished_at)})</span></div>
          </div>
          {[
            { lbl: 'Entidades', val: lastOk.entidades_total },
            { lbl: 'Contactos', val: lastOk.contactos_total },
            { lbl: 'OPs',       val: lastOk.ops_total },
            { lbl: 'Duração',   val: fmtDur(lastOk.duration_ms) },
          ].map(r => (
            <div key={r.lbl}>
              <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '.06em', marginBottom: 2 }}>{r.lbl}</div>
              <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)' }}>{r.val ?? '—'}</div>
            </div>
          ))}
        </div>
      )}

      {/* Timeline */}
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12 }}>
        <div style={{ fontSize: 11, fontWeight: 700, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '.06em' }}>
          Histórico — últimas {sessions.length} sessões
        </div>
        <button className="btn" onClick={load} style={{ fontSize: 11, height: 26, padding: '0 10px' }}>Actualizar</button>
      </div>

      {loading && <div style={{ padding: 40, textAlign: 'center', color: 'var(--text-dim)', fontSize: 12 }}>A carregar...</div>}
      {!loading && sessions.length === 0 && (
        <div style={{ padding: 40, textAlign: 'center', color: 'var(--text-dim)', fontSize: 12 }}>
          Nenhuma sessão de sync registada. Faz a primeira sincronização na tab BD Gestor.
        </div>
      )}

      {sessions.length > 0 && (
        <div style={{ border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden' }}>
          <table style={{ width: '100%', borderCollapse: 'collapse' }}>
            <thead>
              <tr>
                <th style={thS}>Data</th>
                <th style={thS}>Estado</th>
                <th style={thS}>Duração</th>
                <th style={thS}>Entidades</th>
                <th style={thS}>Contactos</th>
                <th style={thS}>OPs</th>
                <th style={thS}>Quem</th>
                <th style={{ ...thS, width: 32 }}></th>
              </tr>
            </thead>
            <tbody>
              {sessions.map((s, i) => {
                const sm = STATUS_META[s.status] || STATUS_META.running;
                const isExp = expanded === s.id;
                return (
                  <React.Fragment key={s.id}>
                    <tr style={{ cursor: 'pointer', background: isExp ? 'var(--bg-sunken)' : 'transparent' }}
                        onClick={() => setExpanded(isExp ? null : s.id)}>
                      <td style={tdS}>
                        <div style={{ fontSize: 12, fontWeight: 500 }}>{fmtDate(s.started_at)}</div>
                        <div style={{ fontSize: 10, color: 'var(--text-dim)' }}>{fmtAgo(s.started_at)}</div>
                      </td>
                      <td style={tdS}>
                        <span style={{ fontSize: 10, fontWeight: 700, fontFamily: 'var(--font-mono)', padding: '2px 7px', borderRadius: 4, background: sm.bg, color: sm.color, textTransform: 'uppercase', letterSpacing: '.04em' }}>
                          {sm.label}
                        </span>
                      </td>
                      <td style={{ ...tdS, fontFamily: 'var(--font-mono)', fontSize: 11 }}>{fmtDur(s.duration_ms)}</td>
                      <td style={{ ...tdS, fontFamily: 'var(--font-mono)', fontSize: 12, fontWeight: 600 }}>{s.entidades_total ?? '—'}</td>
                      <td style={{ ...tdS, fontFamily: 'var(--font-mono)', fontSize: 12, fontWeight: 600 }}>{s.contactos_total ?? '—'}</td>
                      <td style={{ ...tdS, fontFamily: 'var(--font-mono)', fontSize: 12, fontWeight: 600 }}>{s.ops_total ?? '—'}</td>
                      <td style={{ ...tdS, fontSize: 11, color: 'var(--text-muted)' }}>{s.actor_email?.split('@')[0] || 'sistema'}</td>
                      <td style={{ ...tdS, textAlign: 'center', color: 'var(--text-dim)', fontSize: 12 }}>{isExp ? '▲' : '▾'}</td>
                    </tr>
                    {isExp && (
                      <tr>
                        <td colSpan={8} style={{ background: 'var(--bg-sunken)', padding: '12px 16px', borderBottom: '1px solid var(--border)' }}>
                          {s.error_message && (
                            <div style={{ padding: '8px 12px', background: 'rgba(220,38,38,.08)', border: '1px solid rgba(220,38,38,.2)', borderRadius: 6, fontSize: 12, color: '#dc2626', fontFamily: 'var(--font-mono)', marginBottom: 8 }}>
                              {s.error_message}
                            </div>
                          )}
                          <div style={{ display: 'flex', gap: 20, fontSize: 11, color: 'var(--text-muted)' }}>
                            <span>Iniciado: {fmtDate(s.started_at)}</span>
                            <span>Concluído: {fmtDate(s.finished_at)}</span>
                            <span>Notas: {s.notas_total ?? '—'}</span>
                            <span>Trigger: {s.triggered_by || 'manual'}</span>
                          </div>
                        </td>
                      </tr>
                    )}
                  </React.Fragment>
                );
              })}
            </tbody>
          </table>
        </div>
      )}
    </div>
  );
}

function GestaoIntegracoes() {
  const [rows, setRows] = React.useState(null);
  const [err, setErr] = React.useState(null);
  React.useEffect(() => {
    const p = new URLSearchParams(window.location.search || '');
    const email = window.currentUser?.email || '';
    if (email && !p.get('email')) p.set('email', email);
    fetch('/api/marketing/ligacoes?' + p.toString(), { credentials: 'same-origin' })
      .then(r => r.json())
      .then(d => {
        if (d.error) throw new Error(d.error);
        const list = (d.providers || []).filter(x => x.category === 'negocio' || x.category === 'canais');
        setRows(list);
      })
      .catch(e => { setErr(e.message); setRows([]); });
  }, []);
  const STATUS_C = {
    ligado:       { label: 'Ligado',       color: '#15803d', bg: 'rgba(22,163,74,.1)' },
    atencao:      { label: 'Atenção',      color: '#92400E', bg: 'rgba(245,158,11,.16)' },
    app_guardada: { label: 'App guardada', color: '#92400E', bg: 'rgba(245,158,11,.16)' },
    por_ligar:    { label: 'Por ligar',    color: '#9A3412', bg: 'rgba(185,28,28,.08)' },
    em_breve:     { label: 'Em breve',     color: '#94a3b8', bg: 'var(--bg-sunken)' },
  };
  const go = (key) => {
    if (window.mktOpenLigacao) {
      if (window.mktNavToSub) window.mktNavToSub('ligacoes');
      window.mktOpenLigacao(key);
      return;
    }
    window.mktLigacaoPending = key;
    if (window.mktNavToSub) window.mktNavToSub('ligacoes');
  };
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
      {err && <div style={{ fontSize: 12, color: '#B91C1C' }}>{err}</div>}
      {rows === null && <div style={{ padding: 24, textAlign: 'center', color: 'var(--text-dim)', fontSize: 12 }}>A carregar…</div>}
      {(rows || []).map(ig => {
        const sc = STATUS_C[ig.status] || STATUS_C.por_ligar;
        const side = ig.phase || (ig.source === 'db' ? 'Cofre' : ig.source === 'env' ? '.env' : '');
        return (
          <div key={ig.key} style={{ display: 'flex', alignItems: 'center', gap: 16, padding: '14px 18px', background: 'var(--bg-card)', border: '1px solid var(--border)', borderRadius: 10 }}>
            <div style={{ flex: 1 }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 3 }}>
                <span style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)', fontFamily: 'var(--font-display)' }}>{ig.label}</span>
                <span style={{ fontSize: 10, fontWeight: 700, fontFamily: 'var(--font-mono)', padding: '2px 7px', borderRadius: 4, background: sc.bg, color: sc.color, textTransform: 'uppercase', letterSpacing: '.04em' }}>{sc.label}</span>
              </div>
              <div style={{ fontSize: 12, color: 'var(--text-muted)' }}>{ig.desc}</div>
            </div>
            <div style={{ fontSize: 11, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', flexShrink: 0 }}>{side}</div>
            <button type="button" className="btn btn-sm" onClick={() => go(ig.key)} style={{ height: 28, padding: '0 10px', fontSize: 11 }}>Configurar →</button>
          </div>
        );
      })}
    </div>
  );
}

function GestaoTags() {
  const [tags, setTags] = React.useState([]);
  const [nome, setNome] = React.useState('');
  const [cor, setCor] = React.useState('#3859D0');

  const load = () => CRMAPI.tags().then(setTags);
  React.useEffect(() => { load(); }, []);

  const criar = async () => {
    if (!nome.trim()) return;
    await CRMAPI.criarTag({ nome: nome.trim(), cor });
    setNome(''); load();
  };

  const eliminar = async (id) => {
    if (!confirm('Eliminar tag (será removida de todas as entidades/contactos)?')) return;
    await CRMAPI.eliminarTag(id); load();
  };

  return (
    <div>
      <div style={{ padding: '12px 14px', background: 'var(--bg-sunken)', borderRadius: 8, border: '1px solid var(--border)', marginBottom: 16, display: 'flex', gap: 6, alignItems: 'center' }}>
        <input value={nome} onChange={e => setNome(e.target.value)} placeholder="Nome da nova tag..." onKeyDown={e => e.key === 'Enter' && criar()}
          style={{ flex: 1, padding: '5px 10px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg-elev, #ffffff)', color: 'var(--text)', fontSize: 12 }} />
        <input type="color" value={cor} onChange={e => setCor(e.target.value)} style={{ width: 32, height: 30, border: '1px solid var(--border)', borderRadius: 6, cursor: 'pointer', background: 'transparent' }} />
        <button className="btn-ai" onClick={criar} disabled={!nome.trim()} style={{ fontSize: 12 }}>Criar tag</button>
      </div>

      {tags.length === 0 && <div style={{ padding: 40, textAlign: 'center', color: 'var(--text-dim)', fontSize: 12 }}>Sem tags criadas</div>}

      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
        {tags.map(t => (
          <div key={t.id} style={{
            padding: '6px 10px 6px 12px', borderRadius: 6,
            background: `color-mix(in oklch, ${t.cor} 12%, transparent)`,
            border: `1px solid ${t.cor}30`, display: 'inline-flex', alignItems: 'center', gap: 8,
          }}>
            <span style={{ fontSize: 12, fontWeight: 600, color: t.cor }}>#{t.nome}</span>
            <span style={{ fontSize: 10, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>
              {t.n_entidades} ent · {t.n_contactos} ct
            </span>
            <button onClick={() => eliminar(t.id)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-dim)', fontSize: 14, padding: '0 4px' }}>×</button>
          </div>
        ))}
      </div>
    </div>
  );
}

function GestaoExternos() {
  const [status, setStatus] = React.useState('pendente');
  const [rows, setRows] = React.useState([]);
  const [matching, setMatching] = React.useState(null);

  const load = () => CRMAPI.extContactos(status).then(setRows);
  React.useEffect(() => { load(); }, [status]);

  const ignorar = async (id) => { await CRMAPI.ignorarExt(id); load(); };

  const thS = { fontSize: 10, fontWeight: 700, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', padding: '6px 10px', background: 'var(--bg-sunken)', borderBottom: '1px solid var(--border)', textAlign: 'left' };
  const tdS = { fontSize: 12, padding: '8px 10px', borderBottom: '1px solid var(--border)' };

  return (
    <div>
      <div style={{ display: 'flex', gap: 8, marginBottom: 16 }}>
        {['pendente', 'matched', 'ignorar'].map(s => (
          <button key={s} onClick={() => setStatus(s)}
            style={{
              padding: '4px 10px', borderRadius: 6, border: '1px solid var(--border)', cursor: 'pointer',
              fontSize: 11, fontFamily: 'var(--font-mono)', textTransform: 'uppercase',
              background: status === s ? 'var(--ai-500)' : 'transparent',
              color: status === s ? '#fff' : 'var(--text-muted)',
            }}>{s}</button>
        ))}
      </div>

      {rows.length === 0 && <div style={{ padding: 40, textAlign: 'center', color: 'var(--text-dim)', fontSize: 12 }}>Sem contactos externos com este estado</div>}

      {rows.length > 0 && (
        <div style={{ border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden' }}>
          <table style={{ width: '100%', borderCollapse: 'collapse' }}>
            <thead><tr>{['Nome', 'Empresa', 'Email', 'Telefone', 'Fonte', ''].map(h => <th key={h} style={thS}>{h}</th>)}</tr></thead>
            <tbody>
              {rows.map(r => (
                <tr key={r.id}>
                  <td style={{ ...tdS, fontWeight: 500 }}>{r.nome || '—'}</td>
                  <td style={{ ...tdS, color: 'var(--text-muted)' }}>{r.empresa || '—'}</td>
                  <td style={{ ...tdS, fontSize: 11, color: 'var(--text-muted)' }}>{r.email || '—'}</td>
                  <td style={{ ...tdS, fontFamily: 'var(--font-mono)', fontSize: 11 }}>{r.telefone || '—'}</td>
                  <td style={{ ...tdS, fontSize: 11, color: 'var(--text-muted)' }}>{r.fonte || '—'}</td>
                  <td style={{ ...tdS, textAlign: 'right' }}>
                    {status === 'pendente' && (
                      <>
                        <button onClick={() => setMatching(r)} className="btn btn-xs" style={{ fontSize: 10, marginRight: 4 }}>Matchar</button>
                        <button onClick={() => ignorar(r.id)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-dim)', fontSize: 11 }}>Ignorar</button>
                      </>
                    )}
                    {status === 'matched' && r.matched_entidade_fm_id && (
                      <span style={{ fontSize: 10, color: 'var(--success)', fontFamily: 'var(--font-mono)' }}>{r.matched_entidade_fm_id.slice(0, 8)}...</span>
                    )}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}

      {matching && <CRMMatchModal externo={matching} onClose={() => { setMatching(null); load(); }} />}
    </div>
  );
}

function CRMMatchModal({ externo, onClose }) {
  const [q, setQ] = React.useState(externo.empresa || externo.nome || '');
  const [results, setResults] = React.useState([]);
  const debouncedQ = useDebounce(q, 300);

  React.useEffect(() => {
    if (!debouncedQ.trim()) { setResults([]); return; }
    CRMAPI.entidades({ q: debouncedQ, pageSize: 10 }).then(d => setResults(d.rows || []));
  }, [debouncedQ]);

  const match = async (entidade_fm_id) => {
    await CRMAPI.matchExt(externo.id, { entidade_fm_id });
    onClose();
  };

  return (
    <>
      <div onClick={onClose} style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.4)', zIndex: 199 }} />
      <div style={{
        position: 'fixed', top: 60, left: '50%', transform: 'translateX(-50%)', width: 500, maxHeight: 500,
        background: 'var(--bg-card)', border: '1px solid var(--border)', borderRadius: 10,
        zIndex: 200, display: 'flex', flexDirection: 'column', overflow: 'hidden',
      }}>
        <div style={{ padding: '14px 18px', borderBottom: '1px solid var(--border)', display: 'flex', justifyContent: 'space-between' }}>
          <div style={{ fontSize: 13, fontWeight: 600 }}>Matchar &laquo;{externo.nome || externo.empresa}&raquo;</div>
          <button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 18, color: 'var(--text-muted)' }}>×</button>
        </div>
        <div style={{ padding: 16 }}>
          <input value={q} onChange={e => setQ(e.target.value)} placeholder="Pesquisar entidade..."
            style={{ width: '100%', padding: '6px 10px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg)', color: 'var(--text)', fontSize: 12, marginBottom: 12 }} />
        </div>
        <div className="scrollbar" style={{ flex: 1, overflowY: 'auto', padding: '0 16px 16px' }}>
          {results.map(e => (
            <div key={e.fm_id} onClick={() => match(e.fm_id)}
              style={{ padding: '8px 10px', borderRadius: 6, cursor: 'pointer', marginBottom: 4 }}
              onMouseEnter={ev => ev.currentTarget.style.background = 'var(--bg-sunken)'}
              onMouseLeave={ev => ev.currentTarget.style.background = 'transparent'}>
              <div style={{ fontSize: 12, fontWeight: 600 }}>{e.nome}</div>
              <div style={{ fontSize: 11, color: 'var(--text-muted)' }}>{e.nif} · {e.cidade}{e.tenant ? ` · ${e.tenant}` : ''}</div>
            </div>
          ))}
          {results.length === 0 && q && <div style={{ padding: 20, textAlign: 'center', color: 'var(--text-dim)', fontSize: 12 }}>Sem resultados</div>}
        </div>
      </div>
    </>
  );
}

// ══════════════════════════════════════════════════════════════════════
// MktDigiCrmWidget — Hub conversacional Digi AI (Sprint 1 — UI mock)
// Appended ao fim do ficheiro. Não altera nenhum componente existente.
// Acesso: só colaboradores Digidelta com role marketing/admin (auth portal).
// ══════════════════════════════════════════════════════════════════════

// ── Channel icons ──────────────────────────────────────────────────────────
function _DigiChanIcon({ ch, size = 12 }) {
  const s = size;
  if (ch === 'wa') return (
    <svg width={s} height={s} viewBox="0 0 24 24" fill="#25D366">
      <path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347z"/><path d="M12 0C5.373 0 0 5.373 0 12c0 2.122.555 4.112 1.524 5.84L0 24l6.324-1.499A11.94 11.94 0 0 0 12 24c6.627 0 12-5.373 12-12S18.627 0 12 0zm0 21.818a9.81 9.81 0 0 1-5.002-1.369l-.36-.213-3.714.88.923-3.583-.234-.375A9.818 9.818 0 1 1 12 21.818z"/>
    </svg>
  );
  if (ch === 'email') return (
    <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="#4f86f7" strokeWidth="2">
      <rect x="2" y="4" width="20" height="16" rx="2"/><path d="m2 7 10 7 10-7"/>
    </svg>
  );
  if (ch === 'linkedin') return (
    <svg width={s} height={s} viewBox="0 0 24 24" fill="#0A66C2">
      <path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 0 1-2.063-2.065 2.064 2.064 0 1 1 2.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
    </svg>
  );
  if (ch === 'meta') return (
    <svg width={s} height={s} viewBox="0 0 24 24" fill="#1877F2">
      <path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/>
    </svg>
  );
  return (
    <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="var(--ai-500)" strokeWidth="2">
      <rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/>
      <rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/>
    </svg>
  );
}

const _DIGI_PRIORITY_C = { p0: '#dc2626', p1: '#f59e0b', p2: 'var(--ai-500)' };
const _DIGI_PRIORITY_L = { p0: 'Crítico', p1: 'Alto', p2: 'Info' };

// ── LeadListCard ───────────────────────────────────────────────────────────────
function _DigiLeadListCard({ leads = [] }) {
  return (
    <div style={{ border: '1px solid var(--border)', borderRadius: 8, overflow: 'hidden', marginTop: 6 }}>
      <div style={{ padding: '5px 10px', background: 'var(--bg-sunken)', borderBottom: '1px solid var(--border)', fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '.06em' }}>
        {leads.length} resultados · BD Gestor
      </div>
      {leads.map((l, i) => (
        <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '8px 10px', borderBottom: i < leads.length - 1 ? '1px solid var(--border)' : 'none', cursor: 'pointer' }}
          onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-hover)'}
          onMouseLeave={e => e.currentTarget.style.background = ''}>
          <div style={{ width: 28, height: 28, borderRadius: '50%', background: 'var(--ai-500)', color: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 11, fontWeight: 700, flexShrink: 0 }}>
            {(l.nome || '?').slice(0, 1)}
          </div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{l.nome}</div>
            <div style={{ fontSize: 10, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>{l.sector}</div>
          </div>
          <div style={{ fontSize: 10, fontWeight: 700, fontFamily: 'var(--font-mono)', padding: '2px 6px', borderRadius: 4, background: l.stage === 'DEMO' ? 'rgba(139,92,246,.12)' : l.stage === 'AG DECISAO' ? 'rgba(245,158,11,.12)' : 'var(--bg-sunken)', color: l.stage === 'DEMO' ? '#8b5cf6' : l.stage === 'AG DECISAO' ? '#f59e0b' : 'var(--text-muted)' }}>{l.stage}</div>
        </div>
      ))}
    </div>
  );
}

// ── LeadCampanhaCard ──────────────────────────────────────────────────────────
function _DigiLeadCampanhaCard({ lead }) {
  const cc = { HOT: '#dc2626', WARM: '#f59e0b', COLD: '#94a3b8' };
  const sc = { sem_t0: '#94a3b8', com_t0: '#3b82f6', respondeu: '#22c55e', sem_resp: '#f59e0b', no_wa: '#dc2626' };
  return (
    <div style={{ border: '1px solid var(--border)', borderRadius: 8, padding: '10px 12px', marginTop: 6 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>
        <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text)', flex: 1 }}>{lead.nome}</div>
        <span style={{ fontSize: 10, fontWeight: 700, fontFamily: 'var(--font-mono)', padding: '2px 7px', borderRadius: 4, background: `color-mix(in oklch, ${cc[lead.classification]} 14%, transparent)`, color: cc[lead.classification] }}>{lead.classification}</span>
      </div>
      <div style={{ fontSize: 11, color: 'var(--text-muted)', marginBottom: 8 }}>{lead.empresa} · {lead.ad_name}</div>
      <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
        <span style={{ fontSize: 10, padding: '2px 6px', borderRadius: 4, background: `color-mix(in oklch, ${sc[lead.sdr_state]} 12%, transparent)`, color: sc[lead.sdr_state], fontFamily: 'var(--font-mono)' }}>{lead.sdr_state}</span>
        <span style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>{lead.wa_num_msgs} msgs WA</span>
        <div style={{ marginLeft: 'auto', display: 'flex', gap: 5 }}>
          <button className="btn btn-xs">Match Gestor</button>
          <button className="btn btn-xs btn-ai">T0</button>
        </div>
      </div>
    </div>
  );
}

// ── AudienceCard ──────────────────────────────────────────────────────────────
function _DigiAudienceCard({ audience }) {
  return (
    <div style={{ border: '1px solid color-mix(in oklch, var(--ai-500) 28%, transparent)', borderRadius: 8, padding: '10px 12px', background: 'color-mix(in oklch, var(--ai-500) 5%, transparent)', marginTop: 6 }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 8 }}>
        <div>
          <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text)', marginBottom: 3 }}>{audience.nome}</div>
          <div style={{ fontSize: 10, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>{audience.entidades} entidades · {audience.contactos} contactos · {audience.com_tel} com tel</div>
        </div>
        <button className="btn btn-xs btn-ai" style={{ flexShrink: 0 }}>Push para campanha</button>
      </div>
    </div>
  );
}

// ── OutboundDraftCard ─────────────────────────────────────────────────────────
function _DigiOutboundDraftCard({ draft }) {
  const [copied, setCopied] = React.useState(false);
  const copy = () => { try { navigator.clipboard.writeText(draft.text); } catch (_) {} setCopied(true); setTimeout(() => setCopied(false), 2000); };
  return (
    <div style={{ border: '1px solid var(--border)', borderRadius: 8, overflow: 'hidden', marginTop: 6 }}>
      <div style={{ padding: '5px 10px', background: 'var(--bg-sunken)', borderBottom: '1px solid var(--border)', fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-muted)', display: 'flex', alignItems: 'center', gap: 6 }}>
        <_DigiChanIcon ch={draft.channel} size={11} />
        Draft · {draft.marca} · {draft.idioma}
      </div>
      <div style={{ padding: '10px 12px', fontSize: 12, color: 'var(--text)', lineHeight: 1.55, whiteSpace: 'pre-wrap' }}>{draft.text}</div>
      <div style={{ padding: '7px 10px', borderTop: '1px solid var(--border)', display: 'flex', gap: 5 }}>
        <button className="btn btn-xs" onClick={copy}>{copied ? 'Copiado' : 'Copiar'}</button>
        <button className="btn btn-xs">Enviar WA</button>
        <button className="btn btn-xs">Enviar Email</button>
      </div>
    </div>
  );
}

// ── TutorialCard ──────────────────────────────────────────────────────────────
function _DigiTutorialCard({ title, steps = [], actionLabel, onAction }) {
  const [exp, setExp] = React.useState(true);
  return (
    <div style={{ border: '1px solid color-mix(in oklch, #f59e0b 30%, transparent)', borderRadius: 8, overflow: 'hidden', marginTop: 6 }}>
      <button onClick={() => setExp(e => !e)} style={{ width: '100%', textAlign: 'left', padding: '8px 12px', background: 'color-mix(in oklch, #f59e0b 8%, transparent)', border: 'none', borderBottom: exp ? '1px solid color-mix(in oklch, #f59e0b 20%, transparent)' : 'none', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 8 }}>
        <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#f59e0b" strokeWidth="2.5"><circle cx="12" cy="12" r="10"/><path d="M12 8v4M12 16h.01"/></svg>
        <span style={{ fontSize: 12, fontWeight: 600, color: 'var(--text)', flex: 1 }}>{title}</span>
        <span style={{ fontSize: 9, color: 'var(--text-dim)' }}>{exp ? '▲' : '▼'}</span>
      </button>
      {exp && (
        <div style={{ padding: '8px 12px 10px' }}>
          {steps.map((step, i) => (
            <div key={i} style={{ display: 'flex', gap: 8, marginBottom: 6, alignItems: 'flex-start' }}>
              <div style={{ width: 18, height: 18, borderRadius: '50%', background: 'var(--ai-500)', color: '#fff', fontSize: 10, fontWeight: 700, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, marginTop: 1 }}>{i + 1}</div>
              <div style={{ fontSize: 12, color: 'var(--text)', lineHeight: 1.45 }}>{step}</div>
            </div>
          ))}
          {actionLabel && <button className="btn btn-xs btn-ai" style={{ marginTop: 4 }} onClick={onAction}>{actionLabel}</button>}
        </div>
      )}
    </div>
  );
}

// ── ChannelActionCard ─────────────────────────────────────────────────────────
function _DigiChannelActionCard({ channel, timeAgo, timeAbs, summary, canResend }) {
  const [exp, setExp] = React.useState(false);
  return (
    <div style={{ border: '1px solid var(--border)', borderRadius: 8, padding: '8px 12px', marginTop: 6 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 7, marginBottom: 5 }}>
        <_DigiChanIcon ch={channel} size={13} />
        <span style={{ fontSize: 11, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>{timeAgo}</span>
        <span style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', marginLeft: 'auto' }}>{timeAbs}</span>
      </div>
      <div style={{ fontSize: 12, color: 'var(--text)', lineHeight: 1.4, marginBottom: 7 }}>{summary}</div>
      <div style={{ display: 'flex', gap: 5 }}>
        <button className="btn btn-xs" onClick={() => setExp(e => !e)}>{exp ? 'Fechar' : 'Ver detalhe'}</button>
        {canResend && <button className="btn btn-xs">Reenviar</button>}
      </div>
      {exp && <div style={{ padding: '6px 8px', background: 'var(--bg-sunken)', borderRadius: 6, fontSize: 11, color: 'var(--text-muted)', lineHeight: 1.5, marginTop: 6 }}>Detalhe disponível quando backend ligado (Sprint 1 mock).</div>}
    </div>
  );
}

// ── InsightCard ───────────────────────────────────────────────────────────────
function _DigiInsightCard({ id, priority = 'p1', category = 'nova_campanha', title, summary, onDismiss, onAccept }) {
  const pc = _DIGI_PRIORITY_C[priority];
  const catL = { nova_campanha: 'Nova campanha', melhoria_live: 'Melhoria live', alerta_crm: 'Alerta CRM' };
  return (
    <div style={{ border: `1px solid color-mix(in oklch, ${pc} 30%, transparent)`, borderRadius: 8, padding: '10px 12px', background: `color-mix(in oklch, ${pc} 6%, transparent)`, marginTop: 6 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 5 }}>
        <span style={{ fontSize: 9, fontWeight: 700, fontFamily: 'var(--font-mono)', padding: '2px 6px', borderRadius: 4, background: `color-mix(in oklch, ${pc} 15%, transparent)`, color: pc, textTransform: 'uppercase', letterSpacing: '.06em' }}>{_DIGI_PRIORITY_L[priority]}</span>
        <span style={{ fontSize: 10, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>{catL[category]}</span>
      </div>
      <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text)', marginBottom: 4 }}>{title}</div>
      <div style={{ fontSize: 11, color: 'var(--text-muted)', lineHeight: 1.4, marginBottom: 8 }}>{summary}</div>
      <div style={{ display: 'flex', gap: 5 }}>
        {(category === 'reactivacao' || category === 'campanha_sugerida' || category === 'melhoria_live') && (
          <button className="btn btn-xs btn-ai" onClick={onAccept}>Criar briefing</button>
        )}
        {category === 'hot_sem_t0' && (
          <button className="btn btn-xs btn-ai" onClick={onAccept}>Ver leads HOT</button>
        )}
        {category === 'externos_pendentes' && (
          <button className="btn btn-xs btn-ai" onClick={onAccept}>Fazer matching</button>
        )}
        {category === 'audiencia_inactiva' && (
          <button className="btn btn-xs" onClick={onAccept}>Refrescar audiências</button>
        )}
        <button className="btn btn-xs" onClick={onDismiss}>Dispensar</button>
      </div>
    </div>
  );
}

// ── Mensagem ──────────────────────────────────────────────────────────────────
function _DigiWaSendCard({ action, msgId, onSent }) {
  // Estado inicial pode vir da action (persistente entre reopens)
  const initial = action.sent ? 'sent' : (action.cancelled ? 'cancelled' : 'pending');
  const [state, setState] = React.useState(initial);
  const names = { fabio:'Fábio Costa', paulino:'João Paulino', rui:'Rui Leitão', armando:'Armando Mota', bruno:'Bruno Rosa' };
  const send = async () => {
    setState('sending');
    try {
      const r = await fetch('/api/marketing/digi/wa/send', {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ to: action.to, message: action.message }),
      });
      if (!r.ok) throw new Error('Erro');
      setState('sent');
      if (onSent) onSent(msgId);
    } catch (_) { setState('error'); }
  };
  return (
    <div style={{ border: '1px solid color-mix(in oklch, #25D366 35%, transparent)', borderRadius: 8, padding: '10px 12px', marginTop: 6, background: 'color-mix(in oklch, #25D366 6%, transparent)' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 7, marginBottom: 6 }}>
        <_DigiChanIcon ch="wa" size={14} />
        <span style={{ fontSize: 11, fontWeight: 700, color: '#25D366', fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '.06em' }}>Enviar WA</span>
        <span style={{ fontSize: 10, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)', marginLeft: 4 }}>para {names[action.to] || action.to}</span>
      </div>
      <div style={{ fontSize: 11, color: 'var(--text)', lineHeight: 1.5, marginBottom: 8, padding: '6px 8px', background: 'var(--bg-elev)', borderRadius: 6, fontStyle: 'italic' }}>
        "{action.message}"
      </div>
      {state === 'pending' && (
        <div style={{ display: 'flex', gap: 6 }}>
          <button className="btn btn-xs" style={{ background: '#25D366', color: '#fff', border: 'none' }} onClick={send}>Confirmar e enviar</button>
          <button className="btn btn-xs" onClick={() => setState('cancelled')}>Cancelar</button>
        </div>
      )}
      {state === 'sending' && <div style={{ fontSize: 11, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>A enviar...</div>}
      {state === 'sent'    && <div style={{ fontSize: 11, color: '#25D366', fontFamily: 'var(--font-mono)' }}>Enviado.</div>}
      {state === 'error'   && <div style={{ fontSize: 11, color: '#dc2626', fontFamily: 'var(--font-mono)' }}>Erro ao enviar. Verifica o backend.</div>}
      {state === 'cancelled' && <div style={{ fontSize: 11, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>Cancelado.</div>}
    </div>
  );
}

function _DigiMsgBubble({ msg, showHeader = true, onWaSent }) {
  const isUser = msg.role === 'user';
  return (
    <div className="digi-msg" style={{ display: 'flex', flexDirection: 'column', alignItems: isUser ? 'flex-end' : 'flex-start', marginBottom: 6 }}>
      {!isUser && showHeader && (
        <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 3 }}>
          <div style={{ width: 18, height: 18, borderRadius: '50%', background: 'var(--ai-500)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
            <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="1.8">
              <path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96-.46 2.5 2.5 0 0 1-2.96-3.08 3 3 0 0 1-.34-5.58 2.5 2.5 0 0 1 1.32-4.24 2.5 2.5 0 0 1 4.44-1.14Z"/>
              <path d="M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96-.46 2.5 2.5 0 0 0 2.96-3.08 3 3 0 0 0 .34-5.58 2.5 2.5 0 0 0-1.32-4.24 2.5 2.5 0 0 0-4.44-1.14Z"/>
            </svg>
          </div>
          <span style={{ fontSize: 10, fontWeight: 700, color: 'var(--ai-500)', fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '.06em' }}>Digi AI</span>
        </div>
      )}
      <div style={{
        maxWidth: '90%', padding: isUser ? '7px 12px' : '8px 12px',
        background: isUser ? 'var(--ai-500)' : 'var(--bg-elev)',
        color: isUser ? '#fff' : 'var(--text)',
        borderRadius: isUser ? '12px 12px 4px 12px' : '4px 12px 12px 12px',
        fontSize: 12, lineHeight: 1.6,
        border: isUser ? 'none' : '1px solid var(--border)',
      }}>
        {(msg.text || '').split('\n\n').map((para, pi) => (
          <p key={pi} style={{ margin: pi === 0 ? 0 : '7px 0 0' }}>
            {para.split('\n').map((line, li) => li === 0 ? line : [React.createElement('br', { key: li }), line])}
          </p>
        ))}
      </div>
      {msg.ts && (
        <div style={{ fontSize: 9, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', marginTop: 3, alignSelf: isUser ? 'flex-end' : 'flex-start' }}>
          {new Date(msg.ts).toLocaleDateString('pt-PT', { day:'2-digit', month:'2-digit' })} {new Date(msg.ts).toLocaleTimeString('pt-PT', { hour:'2-digit', minute:'2-digit' })}
        </div>
      )}
      {!isUser && msg.waAction && (
        <_DigiWaSendCard action={msg.waAction} msgId={msg.id} onSent={onWaSent} />
      )}
      {!isUser && msg.cards && msg.cards.map((c, ci) => {
        if (c.type === 'leadList')    return <_DigiLeadListCard key={ci} {...c} />;
        if (c.type === 'leadCamp')    return <_DigiLeadCampanhaCard key={ci} {...c} />;
        if (c.type === 'audience')    return <_DigiAudienceCard key={ci} {...c} />;
        if (c.type === 'outbound')    return <_DigiOutboundDraftCard key={ci} {...c} />;
        if (c.type === 'tutorial')    return <_DigiTutorialCard key={ci} {...c} />;
        if (c.type === 'channel')     return <_DigiChannelActionCard key={ci} {...c} />;
        if (c.type === 'insight')     return <_DigiInsightCard key={ci} {...c} />;
        return null;
      })}
    </div>
  );
}

// ── Timeline day ──────────────────────────────────────────────────────────────
function _DigiTimelineDay({ day, events }) {
  return (
    <div style={{ marginBottom: 18 }}>
      <div style={{ fontSize: 10, fontWeight: 700, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '.08em', marginBottom: 8, paddingBottom: 4, borderBottom: '1px solid var(--border)' }}>{day}</div>
      {events.map((ev, i) => (
        <div key={i} style={{ display: 'flex', gap: 10, alignItems: 'flex-start', marginBottom: 8 }}>
          <div style={{ width: 26, height: 26, borderRadius: '50%', background: 'var(--bg-elev)', border: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
            <_DigiChanIcon ch={ev.channel} size={13} />
          </div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
              <span style={{ fontSize: 11, color: 'var(--text)', fontWeight: 500, flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{ev.summary}</span>
              <span style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', flexShrink: 0 }}>{ev.time}</span>
            </div>
            <div style={{ fontSize: 10, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)', marginTop: 2 }}>{ev.typeLabel}</div>
          </div>
        </div>
      ))}
    </div>
  );
}

// ── Mock data ─────────────────────────────────────────────────────────────────
const _DIGI_MOCK_LEADS = [
  { nome: 'Graficarte SA', stage: 'DEMO',      sector: 'Gráfica · ES' },
  { nome: 'Signwork Iberia', stage: 'INTERESSE', sector: 'Signmaker · PT' },
  { nome: 'PrintMax Barcelona', stage: 'AG DECISAO', sector: 'Produção · ES' },
];

const _DIGI_MOCK_MSGS = [
  { id: 1, role: 'digi', text: '3 insights aguardam análise. Queres começar por aí ou tens algo específico?', cards: [
    { type: 'insight', priority: 'p1', category: 'nova_campanha', title: 'Reactivação Mimaki UV · sector automóvel PT', summary: '47 entidades sem OP há >180d · engagement médio 62 · briefing pré-configurado disponível.' },
  ]},
  { id: 2, role: 'user', text: 'mostra leads Mimaki UV em INTERESSE há mais de 30 dias' },
  { id: 3, role: 'digi', text: 'Encontrei 3 entidades com este perfil na BD Gestor:', cards: [
    { type: 'leadList', leads: _DIGI_MOCK_LEADS },
  ]},
  { id: 4, role: 'user', text: 'enviaste o relatório da campanha BIOND?' },
  { id: 5, role: 'digi', text: 'Sim — enviei-te via WA ontem às 15h32:', cards: [
    { type: 'channel', channel: 'wa', timeAgo: 'Ontem 15h32', timeAbs: '25 Ago 2026', summary: 'Relatório semanal BIOND · spend €312 · 8 HOT · 12 WARM · 4 sem_resp a seguir', canResend: true },
  ]},
  { id: 6, role: 'user', text: 'como crio uma audiência a partir destes resultados?' },
  { id: 7, role: 'digi', text: 'Passo a passo:', cards: [
    { type: 'tutorial', title: 'Criar audiência a partir de leads', steps: [
      'Selecciona as entidades na lista (checkbox à esquerda)',
      'Clica em "Criar audiência" no canto superior direito',
      'Dá um nome — ex: "Mimaki UV · Interesse 30d · PT"',
      'Sistema calcula entidades + contactos + com telefone automaticamente',
      'Guarda e depois "Push para campanha" quando estiveres pronto',
    ], actionLabel: 'Abrir AudienceBuilder' },
  ]},
  { id: 8, role: 'user', text: 'redige mensagem de reactivação para estas 3 entidades' },
  { id: 9, role: 'digi', text: 'Draft pronto — revê antes de enviar:', cards: [
    { type: 'outbound', draft: { channel: 'wa', marca: 'Mimaki', idioma: 'PT', text: 'Bom dia, [Nome].\n\nSei que têm estado a avaliar soluções de impressão UV. Posso partilhar o que clientes do vosso sector estão a conseguir com o novo Mimaki UCJV330-160?\n\nDemora 15 minutos — vale a pena ver antes de decidir.' } },
  ]},
];

const _DIGI_MOCK_TIMELINE = {
  'Hoje': [
    { channel: 'portal', time: '09h30', summary: 'Insight P1 gerado — reactivação Mimaki UV PT', typeLabel: 'insight_pushed' },
    { channel: 'wa',     time: '09h05', summary: 'Notificação WA enviada para equipa marketing', typeLabel: 'message_sent' },
  ],
  'Ontem': [
    { channel: 'wa',       time: '15h32', summary: 'Relatório semanal BIOND enviado via WA', typeLabel: 'report_delivered' },
    { channel: 'email',    time: '11h15', summary: 'Briefing v3 BIOND CO2 enviado a Bruno', typeLabel: 'briefing_created' },
    { channel: 'linkedin', time: '10h00', summary: 'Sofia BIOND publicou 2 posts LinkedIn', typeLabel: 'campaign_action' },
  ],
  'Segunda-feira': [
    { channel: 'meta',   time: '14h20', summary: 'Campanha TS200 ES · 3 novos HOT sincronizados', typeLabel: 'sync_completed' },
    { channel: 'portal', time: '10h00', summary: 'Sync Gestor FM completo · 23 987 entidades', typeLabel: 'sync_completed' },
  ],
};

// ── Widget principal ──────────────────────────────────────────────────────────
function MktDigiCrmWidget({ tab, openProfile, campaignContext, userName }) {
  const [open, setOpen]         = React.useState(false);
  const [expanded, setExpanded] = React.useState(false);
  const [mode, setMode]         = React.useState('chat');
  const [msgs, setMsgs]         = React.useState([]);
  const [inp, setInp]           = React.useState('');
  const [loading, setLoad]      = React.useState(false);
  const _sessKey = `digi_crm_sess_${(userName || 'anon').replace(/\s+/g, '_')}`;
  const [sessId, setSessId]       = React.useState(() => localStorage.getItem(_sessKey) || null);
  const panelRef                  = React.useRef(null);
  const fabRef                    = React.useRef(null);
  const timeoutsRef               = React.useRef([]);
  const [pendingParas, setPending] = React.useState([]);
  const [insights, setInsights]   = React.useState([]);
  const [timelineEvs, setTimeline] = React.useState(null); // null=loading, []=no events
  const bodyRef                   = React.useRef(null);
  const INSIGHT_COUNT             = insights.length;

  React.useEffect(() => {
    if (open && mode === 'chat' && bodyRef.current) {
      bodyRef.current.scrollTop = bodyRef.current.scrollHeight;
    }
  }, [msgs, pendingParas, open, mode]);

  React.useEffect(() => {
    if (!open) return;
    let cancelled = false;
    const hour = new Date().getHours();
    const saudacao = hour < 12 ? 'Bom dia' : hour < 19 ? 'Boa tarde' : 'Boa noite';
    const firstName = (userName || '').split(' ')[0] || '';
    const nameStr = firstName ? `, ${firstName}` : '';

    fetch('/api/marketing/digi/events')
      .then(r => r.ok ? r.json() : { events: [] })
      .then(d => { if (!cancelled) setTimeline(d.events || []); })
      .catch(() => { if (!cancelled) setTimeline([]); });

    const storedSess = localStorage.getItem(_sessKey);
    const sessionPromise = storedSess
      ? fetch(`/api/marketing/digi/sessions/${storedSess}`).then(r => r.ok ? r.json() : null).catch(() => null)
      : Promise.resolve(null);
    const insightsPromise = fetch('/api/marketing/digi/insights')
      .then(r => r.ok ? r.json() : { insights: [] })
      .catch(() => ({ insights: [] }));

    Promise.all([sessionPromise, insightsPromise]).then(([sessionData, insightsData]) => {
      if (cancelled) return;
      const ins = insightsData.insights || [];
      setInsights(ins);

      const restored = sessionData?.messages?.length
        ? sessionData.messages.filter(m => m.role !== 'system').map((m, i) => ({
            id: `hist_${i}`, role: m.role === 'assistant' ? 'digi' : 'user', text: m.content, ts: m.ts,
          }))
        : [];

      setMsgs(prev => {
        if (prev.length > 0) return prev;
        if (restored.length > 0) return restored;
        const p0 = ins.filter(i => i.priority === 'p0').length;
        let text;
        if (p0 > 0)          text = `${saudacao}${nameStr}. Tenho ${p0} alerta${p0 > 1 ? 's' : ''} crítico${p0 > 1 ? 's' : ''} que precisam de atenção imediata.`;
        else if (ins.length) text = `${saudacao}${nameStr}. Tenho ${ins.length} insight${ins.length > 1 ? 's' : ''} com oportunidades para o CRM — vê abaixo.`;
        else                 text = `${saudacao}${nameStr}. BD Gestor sincronizada. Em que posso ajudar?`;
        return [{ id: 'greeting', role: 'digi', text, ts: new Date().toISOString() }];
      });
    });

    return () => { cancelled = true; };
  }, [open]);

  React.useEffect(() => {
    if (!open) return;
    const onKey = (e) => {
      if (e.key === 'Escape') {
        if (expanded) setExpanded(false);
        else setOpen(false);
      }
    };
    // Click outside — fecha só em modo compact (modal usa backdrop)
    const onClick = (e) => {
      if (expanded) return;
      if (panelRef.current && !panelRef.current.contains(e.target) &&
          fabRef.current && !fabRef.current.contains(e.target)) {
        setOpen(false);
      }
    };
    document.addEventListener('keydown', onKey);
    document.addEventListener('mousedown', onClick);
    return () => {
      document.removeEventListener('keydown', onKey);
      document.removeEventListener('mousedown', onClick);
    };
  }, [open, expanded]);

  // Cleanup dos setTimeouts pendentes quando o widget fecha
  React.useEffect(() => {
    if (!open) {
      timeoutsRef.current.forEach(clearTimeout);
      timeoutsRef.current = [];
    }
  }, [open]);

  const ctxLabel = openProfile
    ? (openProfile.type === 'entidade' ? 'Entidade aberta' : openProfile.type === 'contacto' ? 'Contacto aberto' : 'OP aberta')
    : tab === 'audiencias' ? 'Audiências' : tab === 'listas' ? 'Leads Campanhas'
    : tab === 'wa-templates' ? 'Templates WA' : tab === 'gestao' ? 'Definições' : 'BD Gestor';

  const quickActs = !openProfile
    ? ['Query CRM', 'Cria audiência', 'Última campanha', 'Ajuda']
    : openProfile.type === 'contacto'
      ? ['Reactivar', 'Timeline', 'Redige outbound', 'Ajuda']
      : ['Próx. acção', 'Documentos', 'Simula ROI', 'Ajuda'];

  const handleSend = async (text) => {
    const t = (text || inp).trim();
    if (!t || loading) return;
    setMsgs(m => [...m, { id: Date.now(), role: 'user', text: t, ts: new Date().toISOString() }]);
    setInp('');
    setLoad(true);
    setPending([]);
    try {
      const resp = await fetch('/api/marketing/digi/crm/chat', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ message: t, session_id: sessId, context: { tab, openProfile } }),
      });
      if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
      const reader  = resp.body.getReader();
      const decoder = new TextDecoder();
      let buf = '', accumulated = '';
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        buf += decoder.decode(value, { stream: true });
        const lines = buf.split('\n');
        buf = lines.pop() || '';
        for (const line of lines) {
          if (!line.startsWith('data: ')) continue;
          try {
            const evt = JSON.parse(line.slice(6));
            if (evt.type === 'delta') { accumulated += evt.text; }
            else if (evt.type === 'done') {
              setSessId(evt.session_id);
              localStorage.setItem(_sessKey, evt.session_id);
              // Detectar [WA_SEND:{...}] com brace-balanced parsing
              const parseWaAction = (str) => {
                const start = str.indexOf('[WA_SEND:');
                if (start < 0) return { action: null, cleanStart: -1, cleanEnd: -1 };
                let depth = 0, i = start + 9, jsonStart = i;
                for (; i < str.length; i++) {
                  if (str[i] === '{') depth++;
                  else if (str[i] === '}') { depth--; if (depth === 0) { i++; break; } }
                }
                if (str[i] !== ']') return { action: null, cleanStart: -1, cleanEnd: -1 };
                try {
                  const action = JSON.parse(str.slice(jsonStart, i));
                  return { action, cleanStart: start, cleanEnd: i + 1 };
                } catch (_) { return { action: null, cleanStart: -1, cleanEnd: -1 }; }
              };
              const { action: waAction, cleanStart, cleanEnd } = parseWaAction(accumulated);
              const cleanText = cleanStart >= 0
                ? (accumulated.slice(0, cleanStart) + accumulated.slice(cleanEnd)).trim()
                : accumulated;
              const textToSplit = cleanText || accumulated;
              const paras = textToSplit.split('\n\n').map(p => p.trim()).filter(Boolean);
              if (paras.length === 0 && !waAction) { setLoad(false); return; }
              const base = Date.now();
              const tsNow = new Date().toISOString();
              if (paras[0]) setMsgs(m => [...m, { id: base, role: 'digi', text: paras[0], ts: tsNow }]);
              for (let i = 1; i < paras.length; i++) {
                const idx = i;
                const tid = setTimeout(() => {
                  setMsgs(m => [...m, { id: base + idx, role: 'digi', text: paras[idx], ts: new Date().toISOString() }]);
                  if (idx === paras.length - 1 && !waAction) setLoad(false);
                }, idx * 420);
                timeoutsRef.current.push(tid);
              }
              const lastDelay = paras.length > 1 ? (paras.length - 1) * 420 : 0;
              if (waAction) {
                const tid = setTimeout(() => {
                  setMsgs(m => [...m, {
                    id: base + 9999, role: 'digi', text: '',
                    waAction: { to: waAction.to, message: waAction.message, sent: false },
                    ts: new Date().toISOString(),
                  }]);
                  setLoad(false);
                }, lastDelay + 100);
                timeoutsRef.current.push(tid);
              } else if (paras.length <= 1) {
                setLoad(false);
              }
            } else if (evt.type === 'error') {
              setMsgs(m => [...m, { id: Date.now() + 1, role: 'digi', text: `Erro: ${evt.message}`, ts: new Date().toISOString() }]);
              setLoad(false);
              await reader.cancel().catch(() => {});
              return;
            }
          } catch (_) {}
        }
      }
    } catch (e) {
      setMsgs(m => [...m, { id: Date.now() + 1, role: 'digi', text: 'Sem ligação ao servidor. Verifica que o backend está activo.' }]);
      setLoad(false);
    }
  };

  const handleKey = (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend(); } };

  const mBtn = (active) => ({
    padding: '3px 11px', borderRadius: 6, border: 'none', cursor: 'pointer',
    fontSize: 11, fontFamily: 'var(--font-mono)', fontWeight: 600, letterSpacing: '.04em',
    background: active ? 'var(--ai-500)' : 'transparent',
    color: active ? '#fff' : 'var(--text-muted)', transition: 'all .15s',
  });

  const iconBtn = (title, onClick, children) => (
    <button title={title} onClick={onClick} style={{
      width: 28, height: 28, borderRadius: 6, border: '1px solid var(--border)',
      background: 'transparent', color: 'var(--text-dim)', cursor: 'pointer',
      display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, transition: 'all .15s',
    }}
      onMouseEnter={e => { e.currentTarget.style.borderColor = 'var(--ai-500)'; e.currentTarget.style.color = 'var(--ai-500)'; }}
      onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border)'; e.currentTarget.style.color = 'var(--text-dim)'; }}>
      {children}
    </button>
  );

  const DigiHeader = ({ isExpanded }) => (
    <div style={{ padding: isExpanded ? '14px 20px 12px' : '12px 16px 10px', borderBottom: '1px solid var(--border)', background: 'var(--bg-elev)', flexShrink: 0 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
        <div style={{ width: 32, height: 32, borderRadius: 10, background: 'var(--ai-500)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, boxShadow: '0 2px 8px color-mix(in oklch, var(--ai-500) 35%, transparent)' }}>
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="1.8">
            <path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96-.46 2.5 2.5 0 0 1-2.96-3.08 3 3 0 0 1-.34-5.58 2.5 2.5 0 0 1 1.32-4.24 2.5 2.5 0 0 1 4.44-1.14Z"/>
            <path d="M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96-.46 2.5 2.5 0 0 0 2.96-3.08 3 3 0 0 0 .34-5.58 2.5 2.5 0 0 0-1.32-4.24 2.5 2.5 0 0 0-4.44-1.14Z"/>
          </svg>
        </div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: isExpanded ? 14 : 13, fontWeight: 700, color: 'var(--text)', letterSpacing: '.01em', display: 'flex', alignItems: 'center', gap: 6 }}>
            Digi AI
            <span style={{ fontSize: 9, fontWeight: 700, fontFamily: 'var(--font-mono)', padding: '2px 7px', borderRadius: 4, background: 'color-mix(in oklch, var(--ai-500) 14%, transparent)', color: 'var(--ai-500)', letterSpacing: '.08em', textTransform: 'uppercase' }}>CRM</span>
          </div>
          <div style={{ fontSize: 10, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)', marginTop: 1 }}>{ctxLabel}</div>
        </div>
        <div style={{ display: 'flex', gap: 2, background: 'var(--bg-sunken)', borderRadius: 8, padding: 3, flexShrink: 0 }}>
          <button style={mBtn(mode === 'chat')} onClick={() => setMode('chat')}>Chat</button>
          <button style={mBtn(mode === 'timeline')} onClick={() => setMode('timeline')}>Timeline</button>
        </div>
        <div style={{ display: 'flex', gap: 4, marginLeft: 2 }}>
          {iconBtn(isExpanded ? 'Reduzir' : 'Expandir', () => setExpanded(e => !e),
            isExpanded
              ? <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polyline points="4 14 10 14 10 20"/><polyline points="20 10 14 10 14 4"/><line x1="10" y1="14" x2="3" y2="21"/><line x1="21" y1="3" x2="14" y2="10"/></svg>
              : <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg>
          )}
          {isExpanded && iconBtn('Fechar', () => { setOpen(false); setExpanded(false); },
            <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M18 6 6 18M6 6l12 12"/></svg>
          )}
        </div>
      </div>
    </div>
  );

  const DigiChat = ({ isExpanded }) => (
    <>
      <div ref={bodyRef} className="scrollbar" style={{ flex: 1, overflowY: 'auto', padding: isExpanded ? '18px 20px 8px' : '14px 14px 6px', minHeight: 0 }}>
        {insights.length > 0 && (
          <div style={{ marginBottom: 10 }}>
            <div style={{ fontSize: 10, fontWeight: 700, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '.06em', marginBottom: 8 }}>
              {insights.length} insight{insights.length > 1 ? 's' : ''} activo{insights.length > 1 ? 's' : ''}
            </div>
            {insights.map(ins => (
              <_DigiInsightCard
                key={ins.id}
                id={ins.id}
                priority={ins.priority}
                category={ins.category}
                title={ins.title}
                summary={ins.summary}
                onDismiss={async () => {
                  const prev = insights;
                  setInsights(p => p.filter(i => i.id !== ins.id));
                  const r = await fetch(`/api/marketing/digi/insights/${ins.id}/dismiss`, { method: 'POST' }).catch(() => null);
                  if (!r || !r.ok) setInsights(prev); // rollback
                }}
                onAccept={async () => {
                  const prev = insights;
                  setInsights(p => p.filter(i => i.id !== ins.id));
                  const r = await fetch(`/api/marketing/digi/insights/${ins.id}/accept`, { method: 'POST' }).catch(() => null);
                  if (!r || !r.ok) setInsights(prev);
                }}
              />
            ))}
          </div>
        )}
        {msgs.map((m, i) => <_DigiMsgBubble key={m.id} msg={m}
          showHeader={m.role !== 'user' && (i === 0 || msgs[i-1].role === 'user')}
          onWaSent={(id) => setMsgs(prev => prev.map(x => x.id === id ? { ...x, waAction: { ...x.waAction, sent: true } } : x))}
        />)}
        {loading && (
          <div style={{ display: 'flex', alignItems: 'center', gap: 7, marginBottom: 12 }}>
            <div style={{ width: 20, height: 20, borderRadius: '50%', background: 'var(--ai-500)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
              <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="1.8">
                <path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96-.46 2.5 2.5 0 0 1-2.96-3.08 3 3 0 0 1-.34-5.58 2.5 2.5 0 0 1 1.32-4.24 2.5 2.5 0 0 1 4.44-1.14Z"/>
                <path d="M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96-.46 2.5 2.5 0 0 0 2.96-3.08 3 3 0 0 0 .34-5.58 2.5 2.5 0 0 0-1.32-4.24 2.5 2.5 0 0 0-4.44-1.14Z"/>
              </svg>
            </div>
            <div style={{ display: 'flex', gap: 4 }}>
              {[0, 1, 2].map(i => (
                <div key={i} style={{ width: 6, height: 6, borderRadius: '50%', background: 'var(--ai-500)', opacity: 0.5, animation: `digi-crm-dot ${0.7 + i * 0.18}s ease-in-out infinite alternate` }} />
              ))}
            </div>
          </div>
        )}
      </div>
      <div style={{ padding: isExpanded ? '6px 20px 0' : '6px 14px 0', display: 'flex', gap: 5, flexWrap: 'wrap', flexShrink: 0 }}>
        {quickActs.map(a => (
          <button key={a} onClick={() => handleSend(a)} style={{ fontSize: 11, padding: '4px 10px', borderRadius: 999, border: '1px solid var(--border)', background: 'var(--bg-elev)', color: 'var(--text-muted)', cursor: 'pointer', transition: 'all .15s' }}
            onMouseEnter={e => { e.currentTarget.style.borderColor = 'var(--ai-500)'; e.currentTarget.style.color = 'var(--ai-500)'; }}
            onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border)'; e.currentTarget.style.color = 'var(--text-muted)'; }}>
            {a}
          </button>
        ))}
      </div>
      <div style={{ padding: isExpanded ? '8px 20px 16px' : '8px 14px 14px', display: 'flex', gap: 8, alignItems: 'flex-end', flexShrink: 0 }}>
        <textarea value={inp} onChange={e => setInp(e.target.value)} onKeyDown={handleKey}
          placeholder="Pergunta ou instrução... (Enter envia)" rows={2}
          style={{ flex: 1, resize: 'none', padding: '8px 12px', borderRadius: 10, border: '1px solid var(--border)', background: 'var(--bg)', color: 'var(--text)', fontSize: 12, fontFamily: 'var(--font-body)', lineHeight: 1.5, outline: 'none', transition: 'border-color .15s' }}
          onFocus={e => e.target.style.borderColor = 'var(--ai-500)'}
          onBlur={e => e.target.style.borderColor = 'var(--border)'}
        />
        <button onClick={() => handleSend()} disabled={!inp.trim() || loading} style={{
          width: 38, height: 38, borderRadius: 10, border: 'none', cursor: inp.trim() && !loading ? 'pointer' : 'default',
          background: inp.trim() && !loading ? 'var(--ai-500)' : 'var(--bg-sunken)',
          color: inp.trim() && !loading ? '#fff' : 'var(--text-dim)',
          display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, transition: 'all .15s',
        }}>
          <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="m22 2-7 20-4-9-9-4 20-7z"/></svg>
        </button>
      </div>
    </>
  );

  const DigiTimeline = () => {
    let grouped = null;
    if (timelineEvs && timelineEvs.length > 0) {
      const today = new Date(); today.setHours(0,0,0,0);
      const yest  = new Date(today); yest.setDate(today.getDate() - 1);
      const days  = {};
      for (const ev of timelineEvs) {
        const d = new Date(ev.created_at); d.setHours(0,0,0,0);
        let label;
        if (d.getTime() === today.getTime())      label = 'Hoje';
        else if (d.getTime() === yest.getTime())  label = 'Ontem';
        else label = new Date(ev.created_at).toLocaleDateString('pt-PT', { weekday: 'long', day: 'numeric', month: 'short' });
        if (!days[label]) days[label] = [];
        days[label].push({
          channel: ev.channel,
          time: new Date(ev.created_at).toLocaleTimeString('pt-PT', { hour: '2-digit', minute: '2-digit' }),
          summary: ev.summary,
          typeLabel: ev.event_type,
        });
      }
      grouped = days;
    }

    return (
      <div ref={bodyRef} className="scrollbar" style={{ flex: 1, overflowY: 'auto', padding: expanded ? '18px 20px' : '14px', minHeight: 0 }}>
        <div style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', marginBottom: 14, textTransform: 'uppercase', letterSpacing: '.06em' }}>
          Actividade Digi · últimos 7 dias · todos os canais
        </div>
        {!timelineEvs && (
          <div style={{ fontSize: 11, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', textAlign: 'center', paddingTop: 24 }}>A carregar...</div>
        )}
        {timelineEvs && (!grouped || Object.keys(grouped).length === 0) && (
          <div style={{ fontSize: 11, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', textAlign: 'center', paddingTop: 24 }}>
            Sem actividade registada nos últimos 7 dias.
          </div>
        )}
        {grouped && Object.entries(grouped).map(([day, evs]) => (
          <_DigiTimelineDay key={day} day={day} events={evs} />
        ))}
      </div>
    );
  };

  return (
    <>
      {/* FAB */}
      {!expanded && (
        <div ref={fabRef} style={{ position: 'fixed', bottom: 28, right: 28, zIndex: 200 }}>
          {INSIGHT_COUNT > 0 && !open && (
            <div style={{
              position: 'absolute', top: -5, right: -5, zIndex: 201,
              width: 19, height: 19, borderRadius: '50%',
              background: '#f59e0b', color: '#fff', border: '2px solid var(--bg)',
              fontSize: 10, fontWeight: 700, display: 'flex', alignItems: 'center', justifyContent: 'center',
              animation: 'digi-crm-pulse 2.4s ease-in-out infinite',
            }}>{INSIGHT_COUNT}</div>
          )}
          <button className="digi-fab" onClick={() => setOpen(o => !o)} title="Digi AI" style={{
            width: 52, height: 52, borderRadius: '50%', border: 'none', cursor: 'pointer',
            background: open ? '#1e293b' : 'var(--ai-500)', color: '#fff',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            boxShadow: open ? 'none' : '0 4px 18px color-mix(in oklch, var(--ai-500) 38%, rgba(0,0,0,.22))',
            transition: 'all .2s',
          }}>
            {open
              ? <svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2.5"><path d="M18 6 6 18M6 6l12 12"/></svg>
              : <svg width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="1.8">
                  <path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96-.46 2.5 2.5 0 0 1-2.96-3.08 3 3 0 0 1-.34-5.58 2.5 2.5 0 0 1 1.32-4.24 2.5 2.5 0 0 1 4.44-1.14Z"/>
                  <path d="M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96-.46 2.5 2.5 0 0 0 2.96-3.08 3 3 0 0 0 .34-5.58 2.5 2.5 0 0 0-1.32-4.24 2.5 2.5 0 0 0-4.44-1.14Z"/>
                </svg>
            }
          </button>
        </div>
      )}

      {/* Compact panel */}
      {open && !expanded && (
        <div ref={panelRef} className="digi-panel" style={{
          position: 'fixed', bottom: 92, right: 28, width: 420, height: 640, zIndex: 199,
          background: 'var(--bg-elev)', border: '1px solid var(--border)', borderRadius: 16,
          boxShadow: '0 20px 60px color-mix(in oklch, var(--ai-500) 14%, rgba(0,0,0,.2))',
          display: 'flex', flexDirection: 'column', overflow: 'hidden',
        }}>
          {DigiHeader({ isExpanded: false })}
          {mode === 'chat' ? DigiChat({ isExpanded: false }) : DigiTimeline()}
        </div>
      )}

      {/* Expanded modal */}
      {open && expanded && (
        <>
          <div className="digi-modal-backdrop" onClick={() => setExpanded(false)} style={{
            position: 'fixed', inset: 0, background: 'rgba(0,0,0,.54)', zIndex: 298,
          }} />
          <div className="digi-modal-panel" style={{
            position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%,-50%)',
            width: 'min(1040px, 92vw)', height: '82vh', zIndex: 299,
            background: 'var(--bg-elev)', border: '1px solid var(--border)', borderRadius: 18,
            boxShadow: '0 32px 80px rgba(0,0,0,.32)',
            display: 'flex', flexDirection: 'column', overflow: 'hidden',
          }}>
            {DigiHeader({ isExpanded: true })}
            {mode === 'chat' ? DigiChat({ isExpanded: true }) : DigiTimeline()}
          </div>
        </>
      )}

      {/* CSS */}
      <style>{`
        @keyframes digi-crm-pulse { 0%,100%{transform:scale(1)} 50%{transform:scale(1.18)} }
        @keyframes digi-crm-dot { from{opacity:.3;transform:translateY(0)} to{opacity:1;transform:translateY(-3px)} }
        @keyframes digi-panel-in { from { opacity:0; transform:translateY(12px) scale(.96); } to { opacity:1; transform:translateY(0) scale(1); } }
        @keyframes digi-modal-in { from { opacity:0; transform:translate(-50%,-50%) scale(.94); } to { opacity:1; transform:translate(-50%,-50%) scale(1); } }
        @keyframes digi-backdrop-in { from { opacity:0; } to { opacity:1; } }
        @keyframes digi-msg-in { from { opacity:0; transform:translateY(6px); } to { opacity:1; transform:translateY(0); } }
        @keyframes digi-fab-in { from { opacity:0; transform:scale(.5); } to { opacity:1; transform:scale(1); } }
        .digi-fab { animation: digi-fab-in .28s cubic-bezier(.34,1.56,.64,1); }
        .digi-fab:hover { transform: scale(1.08); box-shadow: 0 6px 24px color-mix(in oklch, var(--ai-500) 50%, rgba(0,0,0,.28)) !important; }
        .digi-panel { animation: digi-panel-in .22s cubic-bezier(.2,.9,.3,1); transform-origin: bottom right; }
        .digi-modal-backdrop { animation: digi-backdrop-in .18s ease-out; }
        .digi-modal-panel { animation: digi-modal-in .24s cubic-bezier(.2,.9,.3,1); }
        .digi-msg { animation: digi-msg-in .22s cubic-bezier(.2,.9,.3,1); }
      `}</style>
    </>
  );
}

window.MktCRMScreen = MktCRMScreen;
window.ExternoLeadProfile = ExternoLeadProfile;
window.TabListas = TabListas;
