/* 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:    () => fetch(`${BASE}/stats`).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) => fetch(`${BASE}/entidades/${id}/360`).then(r => r.json()),

  // ── Profile PA1/PA2/PA7 — KPIs + Apreciação + 360 + Timeline por pilar ─
  entKpis:      (id) => fetch(`${BASE}/entidades/${id}/kpis`).then(r => r.json()),
  entApreciacao:(id) => fetch(`${BASE}/entidades/${id}/apreciacao`).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: () => fetch(`${BASE}/audiences`).then(r => r.json()),
  audiencia: (id) => fetch(`${BASE}/audiences/${id}`).then(r => r.json()),
  criarAudiencia: (body) => fetch(`${BASE}/audiences`, { method: 'POST', 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) => fetch(`${BASE}/segments/ai`, {
    method: 'POST', headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ texto, created_by }),
  }).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()),
  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)
function rgpdDotColor(val) {
  if (val === 'opt_in') return '#22c55e';
  if (val === 'opt_out') return '#ef4444';
  return '#94a3b8';
}
function rgpdLabel(val) {
  if (val === 'opt_in') return 'opt-in';
  if (val === 'opt_out') return 'opt-out';
  return 'sem resposta';
}

// 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 }) => {
  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('entidades');
  // CRM é transversal — sem filtro por marca (dados vêm todos do Gestor).
  const [meta, setMeta] = React.useState(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 [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 }
  // Refresh ping para TabAudiencias após criar
  const [audRefresh, setAudRefresh] = React.useState(0);
  // 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 camp   = h.match(/[?&]campanha=([^&]+)/);
      const intent = h.match(/[?&]intent=([^&]+)/);
      const canal  = h.match(/[?&]canal=([^&]+)/);
      setViewingEntity(e ? decodeURIComponent(e[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) => {
    let h = window.location.hash;
    h = h.replace(/[?&]entidade=[^&]*/, '').replace(/[?&]contacto=[^&]*/, '').replace(/[?&]op=[^&]*/, '');
    if (value) {
      h = h + (h.includes('?') ? '&' : (h ? '?' : '#')) + key + '=' + encodeURIComponent(value);
    }
    window.location.hash = h;
  };
  const openProfile  = (fmId) => setHashParam('entidade', fmId);
  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} onBack={closeProfile} onOpenContacto={openContacto} onOpenOp={openOp} userEmail={window.currentUser?.email} userName={firstName} />;
  }

  return (
    <div style={{ height: '100%', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>

      {/* ─── 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)' }}>
          MARKETING · CRM
        </div>

        {/* Título + subtítulo inline + CTAs */}
        <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 }}>
              CRM
            </h1>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 6, flexWrap: 'wrap' }}>
              <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 */}
            {(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>
            )}
            {tab === 'audiencias' && (
              <button className="btn btn-ai"
                style={{ height: 30, padding: '0 14px', fontSize: 12 }}
                onClick={() => setAudienceModal({ initialDef: {} })}
                title="Criar nova audiência com filtros avançados">+ Nova Audiência</button>
            )}
            {tab === 'listas' && (
              <button className="btn btn-ai"
                style={{ height: 30, padding: '0 14px', fontSize: 12 }}
                onClick={() => setTab('listas')}
                title="Criar nova lista de leads de campanha">+ Nova Lista</button>
            )}
            {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) */}
        <div style={{ height: 1, background: 'var(--border)', margin: '16px -40px 0' }} />

        {/* Sub-tabs: BD Gestor / Audiências / Leads Campanhas / Templates WA / Definições
            BD Gestor tem toggle interno Entidades | Contactos */}
        <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>
      </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 }}>

        {/* Hero copy dinâmico (padrão Briefings DigiAIBanner) */}
        <div style={{ flexShrink: 0, padding: '10px 0 0' }}>
          <div style={{ fontSize: 22, fontWeight: 700, color: 'var(--text)', fontFamily: 'var(--font-display)', marginBottom: 6, letterSpacing: '-0.02em' }}>
            {heroCopy.titulo}
          </div>
          <div style={{ fontSize: 13, color: 'var(--text-muted)', lineHeight: 1.75 }}>
            {heroCopy.sub}
          </div>
        </div>

        {/* Tab content */}
        {tab === 'entidades' && <TabEntidades meta={meta} stats={stats} firstName={firstName} onOpenProfile={openProfile} onOpenPreview={openPreview} />}
        {tab === 'contactos' && <TabContactos meta={meta} stats={stats} firstName={firstName} onOpenProfile={openProfile} onOpenContacto={openContacto} onOpenPreview={openPreview} onCreateAudience={(preFill) => setAudienceModal({ initialDef: preFill || {} })} />}
        {tab === 'audiencias' && <TabAudiencias meta={meta} firstName={firstName} refreshPing={audRefresh}
          onCreateNew={() => setAudienceModal({ initialDef: {} })}
          onDuplicate={(aud) => setAudienceModal({ initialDef: aud.definicao || {}, initialName: `${aud.nome} (cópia)` })} />}
        {tab === 'listas' && <TabListas />}
        {tab === 'wa-templates' && <TabWaTemplates />}
        {tab === 'gestao' && <TabGestao />}

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

      {/* ─── FOOTER STATS (padrão Briefings) ─────────────────────────────── */}
      <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}
          userEmail={window.currentUser?.email}
          onSaved={() => setAudRefresh(v => v + 1)}
          onClose={() => setAudienceModal(null)}
          onSaveAndSend={() => {
            setAudienceModal(null);
            setAudRefresh(v => v + 1);
            setTab('activacao');
          }}
        />
      )}
    </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 }) {
  const [search, setSearch] = React.useState('');
  const ref = React.useRef(null);
  const inputRef = React.useRef(null);

  React.useEffect(() => {
    setTimeout(() => inputRef.current?.focus(), 50);
    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);
    return () => { document.removeEventListener('mousedown', onDown); document.removeEventListener('keydown', onKey); };
  }, [onClose]);

  const rect = anchor?.getBoundingClientRect();
  const top = rect ? rect.bottom + 6 : 0;
  const left = rect ? rect.left : 0;

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

  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 (
    <div ref={ref} role="dialog" aria-label={title}
      style={{
        position: 'fixed', top, left, minWidth: 280, maxWidth: 360,
        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: 300, 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>
  );
}

// ═══════════════════════════════════════════════════════════════════════════
// FilterChip · trigger visual dum filtro (pill compacto)
// ═══════════════════════════════════════════════════════════════════════════
function CRMFilterChip({ label, value, count, isPreview, aguarda, active, onClick, onClear }) {
  const previewColor = '#7B61FF';  // roxo (design system) — preview não é warning
  const previewBg = 'color-mix(in oklch, #7B61FF 8%, transparent)';
  const activeBg = 'color-mix(in oklch, var(--ai-500) 12%, transparent)';

  return (
    <button
      onClick={onClick}
      aria-label={`Filtro ${label}${value ? `: ${value}` : ''}${isPreview ? ' — preview' : ''}`}
      style={{
        display: 'inline-flex', alignItems: 'center', gap: 6,
        padding: '6px 10px', borderRadius: 6,
        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)',
        fontSize: 12, fontWeight: active || isPreview ? 600 : 500,
        cursor: 'pointer', whiteSpace: 'nowrap',
        transition: 'all 150ms ease',
        outline: 'none',
      }}
      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'}
    >
      <span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', letterSpacing: '0.04em', textTransform: 'uppercase', color: isPreview ? previewColor : 'var(--text-dim)' }}>{label}</span>
      <span>{value || 'Todos'}</span>
      {count != null && <span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)' }}>·{count}</span>}
      {isPreview && (
        <span style={{ fontSize: 9, fontFamily: 'var(--font-mono)', fontWeight: 700, padding: '1px 5px', borderRadius: 3, background: previewColor + '20', color: previewColor, textTransform: 'uppercase', letterSpacing: '0.05em' }}>
          preview
        </span>
      )}
      {active && onClear && (
        <span
          role="button" aria-label="Limpar filtro"
          onClick={e => { e.stopPropagation(); onClear(); }}
          style={{ marginLeft: 2, cursor: 'pointer', color: 'var(--ai-500)', fontSize: 14, lineHeight: 1, padding: '0 2px' }}
        >×</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 }

  // Grupos colapsáveis (persistidos)
  const [groups, setGroups] = React.useState(() => {
    try { return JSON.parse(localStorage.getItem('crm_adv_groups') || '{}'); }
    catch { return {}; }
  });
  const toggleGroup = (id) => setGroups(g => {
    const n = { ...g, [id]: !g[id] };
    try { localStorage.setItem('crm_adv_groups', JSON.stringify(n)); } catch {}
    return n;
  });
  const isCollapsed = (id) => groups[id] === true;

  React.useEffect(() => {
    CRMAPI.tags().then(setTags).catch(() => {});
    CRMAPI.segmentos().then(setSegs).catch(() => {});
    CRMAPI.listas().then(setListas).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 }; });

  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)'  },
  };

  // Definição dos grupos com origem explícita
  const groupDefs = [
    {
      id: 'comercial',
      title: 'Comercial',
      origem: 'gestor',
      hint: 'Localização, marca activa e estado no Gestor',
      chips: [
        {
          key: 'pais', label: 'País', 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: 'equipa', label: 'Marca / Equipa', 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 OP', 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', 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 OP', 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'),
        },
      ],
    },
    {
      id: 'quem',
      title: 'Quem',
      origem: 'qualificacao',
      origemExtra: 'marketing',
      hint: 'Perfil (Qualificação Gestor) + classificação marketing',
      chips: [
        {
          // Tipo de Indústria — SEG1 de todos os segmentos reais do Gestor (crm_profile_values atributo='1')
          key: 'profile_seg1', label: 'Tipo de Indústria', 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'),
        },
        {
          // Tem Impressão Digital — SEG2 (atributo='2') do SignGraphics
          key: 'profile_seg2', label: 'Impressão Digital', 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: '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: 'cargo_contacto', label: 'Cargo do Contacto', 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'),
        },
      ],
    },
    {
      id: 'comport',
      title: 'Comportamento',
      origem: 'marketing',
      hint: 'Interacções e histórico de contacto',
      chips: [
        {
          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'),
        },
        // Score, Emails, Ads, Site — removidos (Fase 2: Touchpoint Tracker)
      ],
    },
    {
      id: 'consent',
      title: 'Consentimentos RGPD',
      origem: 'gestor',
      hint: 'Só contactar quem consentiu — obrigatório por lei',
      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 opt-out (default)' },
          { value: 'opt_out', label: 'Opt-out' },
          { value: 'desconhecido', label: 'Desconhecido' },
        ],
        onSelect: v => updateAdv({ [`consent_${c.canal}`]: v || undefined }),
        onClear: () => clearField(`consent_${c.canal}`),
      })),
    },
    // Integrações externas (Primavera, SAT, Dono) — removidas (Fase 2)
  ];

  // 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' };

  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)',
    }}>
      {/* 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 onClick={() => toggleGroup(g.id)} style={groupHeaderStyle}
                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)', fontFamily: 'var(--font-display)', letterSpacing: '-0.01em' }}>{g.title}</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>
                )}
                <span style={{ fontSize: 11, color: 'var(--text-dim)', marginLeft: 4, fontStyle: 'italic' }}>{g.hint}</span>
              </div>

              {!collapsed && (
                <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, paddingLeft: 22, paddingTop: 8, 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} 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} style={{ display: 'inline-block' }}>
                        <CRMFilterChip
                          label={chip.label} 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}
                          />
                        )}
                      </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, onOpenProfile, onOpenPreview }) {
  const [q, setQ] = React.useState('');
  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({});   // fm_id → { lifecycle, signals }
  // 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(setAvailListas).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.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, pais, equipa, stages, comOp, wonSemOp, lostJanela, adv }));
    } catch {}
  }, [q, pais, equipa, stages, comOp, wonSemOp, lostJanela, adv]);

  const load = React.useCallback(() => {
    setLoading(true);
    const params = { page, pageSize: 50 };
    if (debouncedQ) params.q = debouncedQ;
    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;
    CRMAPI.entidades(params)
      .then(d => { setData(d); setLoading(false); })
      .catch(() => setLoading(false));
  }, [debouncedQ, pais, equipa, stages, comOp, wonSemOp, lostJanela, page, adv]);

  React.useEffect(() => { setPage(1); setSelected(new Set()); }, [debouncedQ, 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 || advCount > 0;
  const clearFilters = () => { setQ(''); 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>
      {/* ─── 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} />

      {/* ─── 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={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', color: 'var(--text)' }}>{e.nome || '—'}</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, onOpenProfile, onOpenContacto, onOpenPreview, openSegPing, onCreateAudience }) {
  const [q, setQ] = React.useState('');
  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 [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 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(setAvailListas).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.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.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, pais, equipa, stages, temTel, temEmail, comOp, wonSemOp, lostJanela, adv }));
    } catch {}
  }, [q, pais, equipa, stages, temTel, temEmail, comOp, wonSemOp, lostJanela, adv]);

  const clearFilters = () => { setPais([]); setEquipa([]); setStages([]); setTemTel(''); setTemEmail(''); setComOp(false); setWonSemOp(false); setLostJanela(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 (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 (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, pais, equipa, stages, temTel, temEmail, comOp, wonSemOp, lostJanela, 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;

  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>
      {/* ─── 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)}</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 === '2' ? 'rgba(239,68,68,.1)' : c.opt_rgpd === 'opt_in' ? 'rgba(34,197,94,.1)' : 'var(--bg-sunken)',
                        color: c.opt_rgpd === '2' ? '#ef4444' : c.opt_rgpd === 'opt_in' ? '#15803d' : 'var(--text-dim)',
                      }}>
                      <span style={{ width: 5, height: 5, borderRadius: '50%', flexShrink: 0,
                        background: c.opt_rgpd === '2' ? '#ef4444' : c.opt_rgpd === 'opt_in' ? '#22c55e' : '#94a3b8' }} />
                      {c.opt_rgpd === '2' ? 'bloqueado' : c.opt_rgpd === 'opt_in' ? 'opt-in' : 'neutro'}
                    </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;
      }
      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 && <div style={{ fontSize: 12, color: 'var(--text-dim)' }}>A calcular...</div>}
        {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-card)', 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-card)', 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-card)', color: 'var(--text)', fontSize: 12 }}
          >
            <option value="">Seleccionar campanha...</option>
            {campanhas.map(c => <option key={c.id} value={c.id}>{c.nome}</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-card)', 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 AUDIENCE_TEMPLATES = [
  {
    id: 'customers_12m',
    label: 'Customers activos 12m',
    icon: '★',
    descricao: 'Clientes com WON nos últimos 12 meses (usar para up-sell)',
    def: { com_op_activa: true },  // ajustar quando tivermos won_janela filter
  },
  {
    id: 'reactivar_24m',
    label: 'Reactivação 24m',
    icon: '↻',
    descricao: 'Compraram alguma vez mas sem OP recente (últimos 24m)',
    def: {},  // simplifica — user afina depois
  },
  {
    id: 'mql_alto',
    label: 'MQL alto valor',
    icon: '↑',
    descricao: 'OPs em fases avançadas (DEMO, AG DECISAO, AG FINANCEIRO)',
    def: { stages: ['DEMO', 'AG DECISAO', 'AG FINANCEIRO'] },
  },
  {
    id: 'cold_leads',
    label: 'Cold leads 90d',
    icon: '❄',
    descricao: 'Entidades sem interacção há mais de 90 dias',
    def: { nunca_contactado: true },
  },
  {
    id: 'wa_pt',
    label: 'Portugal · WA opt-in',
    icon: '💬',
    descricao: 'Contactos portugueses com consent WA — base para campanhas',
    def: { pais: ['PORTUGAL'], consent_wa: 'opt_in' },
  },
];

// 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(' · ');
}

// ═══════════════════════════════════════════════════════════════════════════
// AudienceBuilderModal — modal fullscreen para criar audiência com todos os filtros
// ═══════════════════════════════════════════════════════════════════════════
function AudienceBuilderModal({ meta, initialDef, initialName, campaignContext, onClose, onSaved, onSaveAndSend, 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','em_lista','tem_notas_mkt','contactado_janela_dias','nunca_contactado','sources','preview']);
    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 [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('');
  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 [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(() => {
    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.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;
    return d;
  }, [q, pais, equipa, stages, comOp, wonSemOp, adv]);

  // Preview automático (debounce 400ms)
  React.useEffect(() => {
    setLoadingPreview(true);
    const t = setTimeout(() => {
      CRMAPI.preview(definicao)
        .then(p => { setPreview(p); setLoadingPreview(false); })
        .catch(() => setLoadingPreview(false));
    }, 400);
    return () => 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(() => {});
  }, []);

  // 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(() => {
    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.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.profile_seg1?.length) n++;
    if (adv.cargo?.length) n++;
    return n;
  }, [q, pais, equipa, stages, comOp, wonSemOp, adv]);

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

  const handleSaveAndActivar = async () => {
    if (!nome.trim()) return;
    setSaving(true);
    try {
      const aud = await CRMAPI.criarAudiencia({ nome, descricao, definicao, created_by: userEmail });
      setSavedAud(aud);
      if (onSaved) onSaved(aud);
      if (onSaveAndSend) onSaveAndSend(aud);
      else onClose();
    } catch (e) {
      alert('Erro ao guardar: ' + e.message);
    } finally {
      setSaving(false);
    }
  };

  const handleSaveAndPush = async () => {
    if (!nome.trim() || !campSel) return;
    setSaving(true);
    try {
      const aud = await CRMAPI.criarAudiencia({ nome, descricao, definicao, created_by: userEmail });
      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;
      }
      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 });
      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) => {
    const parsed = parseInitial(tpl.def);
    setPais(parsed.pais); setEquipa(parsed.equipa); setStages(parsed.stages);
    setComOp(parsed.comOp); setWonSemOp(parsed.wonSemOp); 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;
    const sugestao = suggestAudienceName(definicao, meta);
    if (sugestao !== 'Nova audiência') setNome(sugestao);
  }, [JSON.stringify(definicao), nomeSugerido]);

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

  const handleSuggestName = () => {
    setNome(suggestAudienceName(definicao, meta));
    setNomeSugerido(true);
  };

  // Warnings para preview (Fix 2.2)
  const warnings = React.useMemo(() => {
    if (!preview) return [];
    const 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 pctSemTel = 1 - (preview.com_telefone || 0) / preview.contactos;
      if (pctSemTel > 0.3) w.push({ level: 'info', txt: `${(pctSemTel * 100)|0}% sem telefone — só campanhas email.` });
    }
    return w;
  }, [preview]);

  const handleAI = async () => {
    if (!aiTexto.trim()) return;
    setAiLoading(true); setAiResult(null);
    try {
      const r = await CRMAPI.aiSegment(aiTexto, userEmail);
      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]);
        // adv fields
        const advPatch = {};
        for (const k of ['tags','tags_mode','sector','dimensao','maturidade_digital','consent_wa','consent_email','consent_tel','em_lista','tem_notas_mkt','contactado_janela_dias','nunca_contactado','sources']) {
          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 }}>CRM · Segmentar</div>
            <h2 style={{ margin: 0, fontSize: 20, fontWeight: 700, fontFamily: 'var(--font-display)', color: 'var(--text)', letterSpacing: '-0.015em' }}>
              {initialName ? `Duplicar: ${initialName}` : 'Nova audiência'}
            </h2>
          </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>
                  {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 */}
        <div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) 380px', flex: 1, overflow: 'hidden' }}>
          {/* Coluna esquerda — Filtros */}
          <div className="scrollbar" style={{ overflowY: 'auto', padding: '20px 24px', borderRight: '1px solid var(--border)' }}>
            {/* AI segmentar */}
            <div style={{ marginBottom: 18, 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 color-mix(in oklch, var(--ai-500) 20%, var(--border))' }}>
              <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--ai-500)', letterSpacing: '0.08em', textTransform: 'uppercase', marginBottom: 6, fontWeight: 700 }}>
                ✨ Descreve o segmento
              </div>
              <div style={{ display: 'flex', gap: 8 }}>
                <input
                  value={aiTexto} onChange={e => setAiTexto(e.target.value)}
                  onKeyDown={e => e.key === 'Enter' && handleAI()}
                  placeholder="Ex: clientes VIP em Portugal com OP em decisão sem contactar há 30d"
                  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()}
                  style={{ padding: '7px 14px', borderRadius: 6, border: 'none', background: 'var(--ai-500)', color: '#fff', fontSize: 12, fontWeight: 600, cursor: 'pointer', opacity: aiLoading || !aiTexto.trim() ? 0.5 : 1 }}>
                  {aiLoading ? '...' : 'Sugerir'}
                </button>
              </div>
              {aiResult && (
                <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>
              )}
            </div>

            {/* 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 }}>
              <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={{ fontSize: 12, color: 'var(--text-muted)' }}>A calcular...</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', 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 telefone</div>
                        <div style={{ color: 'var(--success)', fontWeight: 700 }}>{(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 }}>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' : 'Guardar audiência'}
              </button>
              {!savedAud && (
                <button onClick={handleSaveAndActivar} disabled={saving || !nome.trim()}
                  style={{
                    width: '100%', padding: '9px', borderRadius: 6, border: '1px solid var(--ai-500)',
                    background: 'transparent', color: 'var(--ai-500)', fontSize: 12, fontWeight: 600,
                    cursor: saving || !nome.trim() ? 'not-allowed' : 'pointer',
                    opacity: !nome.trim() ? 0.5 : 1,
                  }}>
                  Guardar &amp; Activar
                </button>
              )}
            </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}>{c.titulo || c.commercial_name || c.nome}</option>)}
                  </select>
                  <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>
                </>
              )}

              {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.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>
    </>
  );
}

// ── Tab Audiencias ─────────────────────────────────────────────────────────
function TabAudiencias({ meta, refreshPing, onCreateNew, onDuplicate }) {
  const [audiencias, setAudiencias] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [selected, setSelected] = React.useState(null);
  const [detail, setDetail] = React.useState(null);
  const [loadingDetail, setLoadingDetail] = React.useState(false);
  const [pushes, setPushes] = React.useState([]);
  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);
  const [eliminando, setEliminando] = React.useState(null);

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

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

  const openAudiencia = (id) => {
    setSelected(id); setDetail(null); setLoadingDetail(true); setPushes([]); setPushResult(null); setConfirmPush(null);
    Promise.all([CRMAPI.audiencia(id), CRMAPI.pushesAudiencia(id)])
      .then(([d, ps]) => { setDetail(d); setPushes(ps || []); setLoadingDetail(false); })
      .catch(() => setLoadingDetail(false));
  };

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

  const handleEliminar = async (id) => {
    if (!confirm('Eliminar esta audiencia?')) return;
    setEliminando(id);
    await CRMAPI.eliminarAudiencia(id).catch(() => {});
    setEliminando(null);
    if (selected === id) setSelected(null);
    load();
  };

  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 totAlcancados = (audiencias || []).reduce((sum, a) => sum + (a.contagem_ultima || 0), 0);
  const medPorAud = audiencias?.length ? Math.round(totAlcancados / audiencias.length) : 0;
  const ultima = (audiencias || [])[0];

  const kpiCards = [
    { label: 'AUDIÊNCIAS', value: (audiencias?.length || 0).toLocaleString('pt-PT'), sub: 'guardadas', accent: 'var(--ai-500, #3859D0)', fill: Math.min(1, (audiencias?.length || 0) / 10) },
    { label: 'ALCANCE TOTAL', value: totAlcancados.toLocaleString('pt-PT'), sub: 'contactos únicos', accent: 'var(--success, #22c55e)', fill: Math.min(1, totAlcancados / 20000) },
    { label: 'MÉDIA / AUDIÊNCIA', value: medPorAud.toLocaleString('pt-PT'), sub: 'contactos', accent: '#0ea5e9', fill: Math.min(1, medPorAud / 5000) },
    { label: 'ÚLTIMA CRIADA', value: ultima ? humanTimeAgo(ultima.created_at) : '—', sub: ultima?.nome?.slice(0, 22) || 'nenhuma', accent: 'var(--warning, #d97706)', fill: 0 },
  ];

  return (
    <div>
      <KPIStrip cards={kpiCards} />

      <div style={{ display: 'grid', gridTemplateColumns: selected ? '1fr 420px' : '1fr', gap: 20, alignItems: 'start' }}>
      {/* Lista */}
      <div>
        {loading && <div style={{ color: 'var(--text-dim)', fontSize: 13, padding: 20 }}>A carregar...</div>}
        {!loading && (!audiencias || audiencias.length === 0) && (
          <div style={{ padding: '48px 20px', textAlign: 'center', background: 'var(--bg-sunken)', borderRadius: 10, border: '1px dashed var(--border)' }}>
            <div style={{ fontSize: 32, color: 'var(--text-dim)', marginBottom: 10, fontFamily: 'var(--font-display)', fontWeight: 300 }}>◇</div>
            <div style={{ fontSize: 15, fontWeight: 600, color: 'var(--text)', marginBottom: 6 }}>Ainda não tens audiências guardadas</div>
            <div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 16, maxWidth: 380, margin: '0 auto 16px' }}>
              Cria segmentos reutilizáveis para envio de campanhas WA/Email — combina até 18 filtros (país, tags, consentimentos, engagement, etc.)
            </div>
            {onCreateNew && (
              <button onClick={onCreateNew} className="btn-ai"
                style={{ padding: '10px 22px', fontSize: 13, fontWeight: 600 }}>
                + Nova audiência
              </button>
            )}
          </div>
        )}
        {audiencias && audiencias.length > 0 && (
          <>
          <div style={{ border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden' }}>
            <table style={{ width: '100%', borderCollapse: 'collapse' }}>
              <thead>
                <tr>{['Nome', 'Contactos', 'Uso', 'Actualizado', ''].map(h => <th key={h} style={thS}>{h}</th>)}</tr>
              </thead>
              <tbody>
                {audiencias.map(a => (
                  <tr
                    key={a.id}
                    onClick={() => openAudiencia(a.id)}
                    style={{ cursor: 'pointer', background: selected === a.id ? 'color-mix(in oklch, var(--ai-500) 6%, transparent)' : 'transparent' }}
                    onMouseEnter={ev => { if (selected !== a.id) ev.currentTarget.style.background = 'var(--bg-sunken)'; }}
                    onMouseLeave={ev => { if (selected !== a.id) ev.currentTarget.style.background = 'transparent'; }}
                  >
                    <td style={{ ...tdS, fontWeight: 600 }}>
                      {a.nome}
                      {a.descricao && <div style={{ fontSize: 11, color: 'var(--text-muted)', fontWeight: 400, marginTop: 2 }}>{a.descricao}</div>}
                    </td>
                    <td style={{ ...tdS, textAlign: 'center', fontFamily: 'var(--font-mono)' }}>{(a.contagem_ultima ?? 0).toLocaleString('pt-PT')}</td>
                    <td style={{ ...tdS, fontSize: 11 }}>
                      {a.n_pushes > 0 ? (
                        <div>
                          <div style={{ color: 'var(--ai-500)', fontWeight: 600, fontFamily: 'var(--font-mono)' }}>
                            {a.n_pushes} campanha{a.n_pushes > 1 ? 's' : ''}
                          </div>
                          {a.ultima_utilizacao && (
                            <div style={{ color: 'var(--text-dim)', fontSize: 10, marginTop: 1 }}>
                              última {fmtDias(Math.floor((Date.now() - new Date(a.ultima_utilizacao).getTime()) / 86400000))}
                            </div>
                          )}
                        </div>
                      ) : <span style={{ color: 'var(--text-dim)' }}>—</span>}
                    </td>
                    <td style={{ ...tdS, color: 'var(--text-muted)', fontSize: 11 }}>
                      {a.refreshed_at ? new Date(a.refreshed_at).toLocaleString('pt-PT', { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' }) : new Date(a.created_at).toLocaleString('pt-PT', { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' })}
                    </td>
                    <td style={{ ...tdS, textAlign: 'right' }}>
                      <div style={{ display: 'flex', gap: 4, justifyContent: 'flex-end' }}>
                        {onDuplicate && (
                          <button
                            onClick={ev => { ev.stopPropagation(); onDuplicate(a); }}
                            style={{ background: 'none', border: '1px solid var(--border)', borderRadius: 4, cursor: 'pointer', color: 'var(--text-muted)', fontSize: 11, padding: '2px 8px' }}
                            title="Duplicar audiência com estes filtros"
                          >Duplicar</button>
                        )}
                        <button
                          onClick={ev => { ev.stopPropagation(); handleEliminar(a.id); }}
                          disabled={eliminando === a.id}
                          style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--danger)', fontSize: 11, padding: '2px 6px' }}
                        >Eliminar</button>
                      </div>
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
          </>
        )}
      </div>

      {/* Detalhe */}
      {selected && (
        <div style={{ background: 'var(--bg-sunken)', borderRadius: 10, border: '1px solid var(--border)', padding: '16px 20px' }}>
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 14 }}>
            <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--text)' }}>
              {detail ? detail.nome : 'A carregar...'}
            </div>
            <button onClick={() => setSelected(null)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-muted)', fontSize: 18 }}>×</button>
          </div>

          {loadingDetail && <div style={{ color: 'var(--text-dim)', fontSize: 12 }}>A carregar...</div>}

          {detail && !loadingDetail && (
            <>
              {/* Stats */}
              <div style={{ display: 'flex', gap: 16, marginBottom: 14 }}>
                <div style={{ textAlign: 'center' }}>
                  <div style={{ fontSize: 20, fontWeight: 700, color: 'var(--text)' }}>{(detail.contagem_ultima||0).toLocaleString('pt-PT')}</div>
                  <div style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>ENTIDADES</div>
                </div>
                <div style={{ textAlign: 'center' }}>
                  <div style={{ fontSize: 20, fontWeight: 700, color: 'var(--text)' }}>{(detail.contactos?.length||0).toLocaleString('pt-PT')}</div>
                  <div style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>CONTACTOS</div>
                </div>
              </div>

              {/* Amostra */}
              {detail.contactos?.length > 0 && (
                <div style={{ marginBottom: 14 }}>
                  <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 6 }}>Amostra</div>
                  {detail.contactos.slice(0, 8).map((c, i) => (
                    <div key={i} style={{ fontSize: 11, color: 'var(--text-muted)', padding: '3px 0', borderBottom: '1px solid var(--border)' }}>
                      <span style={{ color: 'var(--text)', fontWeight: 500 }}>{c.nome}</span>
                      {c.entidade_nome && <span style={{ marginLeft: 6 }}>· {c.entidade_nome}</span>}
                      {c.pais && <span style={{ marginLeft: 6 }}>· {c.pais}</span>}
                    </div>
                  ))}
                  {detail.contactos.length > 8 && (
                    <div style={{ fontSize: 11, color: 'var(--text-dim)', marginTop: 4 }}>+{detail.contactos.length - 8} contactos</div>
                  )}
                </div>
              )}

              {/* Usar em campanha */}
              <div style={{ marginBottom: 14, 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: 6, flexWrap: 'wrap' }}>
                  <select
                    value={campSel} onChange={e => setCampSel(e.target.value)}
                    style={{ flex: 1, minWidth: 150, padding: '5px 8px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg-card)', color: 'var(--text)', fontSize: 12 }}
                  >
                    <option value="">Seleccionar...</option>
                    {campanhas.map(c => <option key={c.id} value={c.id}>{c.nome}</option>)}
                  </select>
                  <button className="btn-ai" onClick={() => handlePush(false)} disabled={pushing || !campSel} style={{ fontSize: 12, padding: '5px 12px' }}>
                    {pushing ? 'A processar...' : 'Enviar'}
                  </button>
                </div>
                {confirmPush && (
                  <div style={{ marginTop: 8, padding: '8px 12px', background: 'color-mix(in oklch, var(--warning) 10%, transparent)', border: '1px solid color-mix(in oklch, var(--warning) 30%, transparent)', borderRadius: 6, fontSize: 12 }}>
                    <div style={{ marginBottom: 6 }}>{confirmPush.msg}</div>
                    <div style={{ display: 'flex', gap: 6 }}>
                      <button className="btn-ai" onClick={() => handlePush(true)} style={{ fontSize: 11, padding: '3px 10px' }}>Confirmar</button>
                      <button className="btn" onClick={() => setConfirmPush(null)} style={{ fontSize: 11, padding: '3px 10px' }}>Cancelar</button>
                    </div>
                  </div>
                )}
                {pushResult && (
                  <div style={{ marginTop: 6, fontSize: 12, color: pushResult.erro ? 'var(--danger)' : 'var(--success)' }}>
                    {pushResult.erro ? `Erro: ${pushResult.erro}` : `${pushResult.total} enviados · ${pushResult.excluidos_antidup} excluidos`}
                  </div>
                )}
              </div>

              {/* Histórico de pushes */}
              {pushes.length > 0 && (
                <div>
                  <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 6 }}>Utilizacoes anteriores</div>
                  {pushes.map((p, i) => (
                    <div key={i} style={{ fontSize: 11, color: 'var(--text-muted)', padding: '4px 0', borderBottom: '1px solid var(--border)' }}>
                      <span style={{ color: 'var(--text)' }}>{p.campanha_nome || p.campanha_id?.slice(0,8)}</span>
                      <span style={{ marginLeft: 8 }}>{p.contagem_estimada} contactos</span>
                      {p.executado_at && <span style={{ marginLeft: 8 }}>{new Date(p.executado_at).toLocaleDateString('pt-PT')}</span>}
                    </div>
                  ))}
                </div>
              )}
            </>
          )}
        </div>
      )}
      </div>
    </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, 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),
      CRMAPI.entTags(fmId),
      CRMAPI.entSegs(fmId),
      CRMAPI.entKpis(fmId).catch(() => null),
      CRMAPI.entApreciacao(fmId).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]);

  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)}
               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>
      )}

      {/* Última 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.corpo || ultimaNota.note || '—'}
          </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) },
    { icon: '↗', label: 'Ver empresa', disabled: !data.entidade, onClick: () => onOpenEntity && data.entidade && onOpenEntity(data.entidade.fm_id) },
  ];

  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)}>
            <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-card)', 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(setListas).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-card)', 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 color = colors[classification] || '#94a3b8';
  const sz = small ? 9 : 10;
  return score != null ? (
    <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',
    }}>{score}/100 {classification}</span>
  ) : <span style={{ fontSize: sz, color: 'var(--text-dim)' }}>—</span>;
}

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 }) {
  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 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();
  };

  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 } = data;
  const phone = x.telefone;
  const nome = x.nome || phone;
  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 tabs = ['overview', 'conversa', 'followups', 'notas'];
  const tabLabel = { overview: 'Overview', conversa: `Conversa WA (${msgs.length})`, followups: `Follow-ups (${fups.length})`, notas: `Notas (${notas.length})` };

  return (
    <div>
      {/* Header */}
      <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>
            {x.origem_canal && <CanalChip canal={x.origem_canal} />}
            {s && <ScoreBadge score={s.lead_score} classification={s.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>}
          </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 }}>
          {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>}
        </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 || s?.empresa_declarada || na;
        const cargo = s?.decisor_role || na;
        const timing = s?.timing_bucket ? { '0-3m': 'Próximos 0-3 meses', '3-6m': '3-6 meses', '6-12m': '6-12 meses', '+12m': 'Mais de 12 meses' }[s.timing_bucket] || s.timing_bucket : na;
        return (
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 20 }}>
            {/* Coluna esquerda — OPORTUNIDADE QUALIFICADA (formato Rui) */}
            <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', cargo)}
                  {row('Telefone', x.telefone)}
                  {row('Email', x.email || (s?.lead_email) || na)}
                  {row('Canal origem', x.origem_canal ? x.origem_canal.toUpperCase() : na)}
                  {row('Campanha', x.origem_campanha || na)}
                  {row('País', x.pais || na)}
                  {row('Match Gestor', x.matched_entidade_fm_id ? `Ligado (${x.matched_entidade_fm_id})` : 'Sem match')}
                  {s && <>
                    {secLabel('Negócio / Produção')}
                    {row('Volume m²/mês', s.volume_m2_mes ? `${s.volume_m2_mes} m²/mês` : na)}
                    {row('Produção externa', s.outsourcing_m2_mes ? `${s.outsourcing_m2_mes} m²/mês` : na)}
                    {row('Equipamento actual', s.equipment_current || na)}
                    {secLabel('Problema / Impacto / Necessidade')}
                    {row('Dor identificada', s.pain_identified || na, true)}
                    {secLabel('Interesse / Financeiro / Timing / Decisão')}
                    {row('Interesse solução', s.aceita_demo ? 'Aceita demonstração' : 'Demo não confirmada')}
                    {row('Timing decisão', timing)}
                    {row('Budget range', s.budget_range || na)}
                    {row('Decisor', s.decisor_role || na)}
                    {secLabel('Próximo Passo')}
                    {row('Aceita demo', s.aceita_demo ? 'Sim — confirmar data' : 'Ainda não')}
                    {row('Captado em', x.captado_em ? new Date(x.captado_em).toLocaleDateString('pt-PT') : na)}
                    {row('Última actividade', s.ultima_actividade ? new Date(s.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) */}
              {(() => {
                const enrich = s?.company_enrichment;
                if (!enrich) return null;
                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>
                );
              })()}
              {s && (
                <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 {s.lead_score || 0}/100 —{' '}
                        <span style={{ color: { HOT: '#ef4444', QUALIFICADO: '#22c55e', NURTURE: '#f59e0b', NAO_QUALIFICADO: '#94a3b8' }[s.classification] || '#94a3b8' }}>
                          {s.classification || 'NÃO QUALIFICADO'}
                        </span>
                      </th></tr>
                    </thead>
                    <tbody>
                      {s.score_criteria && Object.entries(s.score_criteria).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>
              )}
              {s?.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 }}>{s.recomendacao_comercial}</div>
                </div>
              )}
            </div>
          </div>
        );
      })()}

      {/* Tab: Conversa WA */}
      {activeTab === 'conversa' && (
        <div className="scrollbar" style={{ maxHeight: 600, overflowY: 'auto' }}>
          {msgs.length === 0 && s?.messages && Array.isArray(s.messages) && s.messages.length > 0 ? (
            s.messages.map((m, i) => (
              <div key={i} style={{ marginBottom: 8, display: 'flex', justifyContent: m.role === 'user' ? 'flex-start' : 'flex-end' }}>
                <div style={{
                  maxWidth: '75%', padding: '8px 12px', borderRadius: 10, fontSize: 12, lineHeight: 1.4,
                  background: m.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)' }}>
                    {m.role === 'user' ? nome : 'Digi AI'}
                  </div>
                  {m.content}
                </div>
              </div>
            ))
          ) : msgs.map((m, i) => (
            <div key={i} style={{ marginBottom: 8, display: 'flex', justifyContent: m.role === 'user' ? 'flex-start' : 'flex-end' }}>
              <div style={{
                maxWidth: '75%', padding: '8px 12px', borderRadius: 10, fontSize: 12, lineHeight: 1.4,
                background: m.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)' }}>
                  {m.role === 'user' ? nome : 'Digi AI'} · {m.created_at ? new Date(m.created_at).toLocaleTimeString('pt-PT', { hour: '2-digit', minute: '2-digit' }) : ''}
                </div>
                {m.content}
              </div>
            </div>
          ))}
          {msgs.length === 0 && (!s?.messages || !Array.isArray(s.messages)) && (
            <div style={{ padding: 40, textAlign: 'center', color: 'var(--text-dim)', fontSize: 12 }}>Sem conversa disponível.</div>
          )}
        </div>
      )}

      {/* Tab: Follow-ups */}
      {activeTab === 'followups' && (
        <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>
      )}

      {/* 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-card)', 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>
      )}
    </div>
  );
}

// TAB LISTAS (redesenhado — campanhas + manuais + click-through profile)
// ═══════════════════════════════════════════════════════════════════════════
function TabListas() {
  const [listas, setListas] = React.useState([]);
  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 [novaNome, setNovaNome] = React.useState('');
  const [novaDesc, setNovaDesc] = React.useState('');
  const fileInputRef = React.useRef(null);

  const load = () => CRMAPI.listas().then(setListas).catch(() => {});
  React.useEffect(() => { load(); }, []);

  const openLista = (l) => {
    setSelectedLista(l);
    setView('lista');
    setListaDetail(null);
    CRMAPI.lista(l.id).then(setListaDetail).catch(() => {});
  };

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

  const criar = async () => {
    if (!novaNome.trim()) return;
    await CRMAPI.criarLista({ nome: novaNome, descricao: novaDesc });
    setNovaNome(''); setNovaDesc(''); load();
  };

  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 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 campanha = listas.filter(l => l.tipo === 'campanha');
  const outras   = listas.filter(l => l.tipo !== 'campanha');

  const totLeads = campanha.reduce((s, l) => s + parseInt(l.n_items || 0), 0);
  const kpiCards = [
    { label: 'CAMPANHAS', value: campanha.length, sub: 'activas', accent: 'var(--ai-500, #3859D0)', fill: Math.min(1, campanha.length / 10) },
    { label: 'TOTAL LEADS', value: totLeads, sub: 'em campanhas', accent: '#22c55e', fill: Math.min(1, totLeads / 100) },
    { label: 'LISTAS MANUAIS', value: outras.length, sub: 'curadas / CSV', accent: '#f59e0b', fill: Math.min(1, outras.length / 10) },
  ];

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

  // View: Detalhe lista
  if (view === 'lista' && selectedLista) {
    const items = listaDetail?.items || [];
    return (
      <div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 20 }}>
          <button onClick={() => setView('index')} className="btn btn-xs">Listas</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)' }}>{items.length} leads</span>
          <input type="file" accept=".csv" ref={fileInputRef} onChange={importCSV} style={{ display: 'none' }} />
          {selectedLista.tipo !== 'campanha' && (
            <button className="btn btn-xs" onClick={() => fileInputRef.current?.click()}>Importar CSV</button>
          )}
        </div>

        {items.length === 0 && <div style={{ padding: 40, textAlign: 'center', color: 'var(--text-dim)', fontSize: 12 }}>Sem leads nesta lista.</div>}
        {items.length > 0 && (
          <div style={{ border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden' }}>
            <table style={{ width: '100%', borderCollapse: 'collapse' }}>
              <thead>
                <tr>{['Nome', 'Telefone', 'Canal', 'Score', 'Timing', 'Captado em', ''].map(h => <th key={h} style={thS}>{h}</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;
                  // Aceita externo_id OU visitor_id como identificador da ficha
                  const extId = it.contacto_externo_id || it.externo_id || it.visitor_id;
                  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, fontFamily: 'var(--font-mono)', fontSize: 11 }}>{tel || '—'}</td>
                      <td style={tdS}>{it.origem_canal ? <CanalChip canal={it.origem_canal} /> : '—'}</td>
                      <td style={tdS}><ScoreBadge score={it.lead_score} classification={it.classification} small /></td>
                      <td style={{ ...tdS, fontSize: 11, color: 'var(--text-muted)' }}>{it.timing_bucket || '—'}</td>
                      <td style={{ ...tdS, fontSize: 11, color: 'var(--text-muted)' }}>{it.captado_em ? new Date(it.captado_em).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
  return (
    <div>
      <KPIStrip cards={kpiCards} />

      {/* Campanhas */}
      {campanha.length > 0 && (
        <div style={{ marginBottom: 28 }}>
          <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: 12 }}>
            Listas de Campanha
          </div>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(220px, 1fr))', gap: 12 }}>
            {campanha.map(l => (
              <div key={l.id} onClick={() => openLista(l)}
                style={{ padding: '14px 16px', background: 'var(--bg-card)', borderRadius: 10, border: '1px solid var(--border)', cursor: 'pointer', transition: 'border-color 0.15s' }}
                onMouseEnter={ev => ev.currentTarget.style.borderColor = 'var(--ai-500)'}
                onMouseLeave={ev => ev.currentTarget.style.borderColor = 'var(--border)'}>
                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 6 }}>
                  <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)' }}>{l.nome}</div>
                  <button onClick={e => eliminar(l.id, e)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-dim)', fontSize: 14, padding: 0 }}>×</button>
                </div>
                <div style={{ display: 'flex', gap: 6, marginBottom: 8, flexWrap: 'wrap' }}>
                  {l.canal && <CanalChip canal={l.canal.toLowerCase()} />}
                  {l.marca && <span style={{ fontSize: 9, fontFamily: 'var(--font-mono)', padding: '1px 5px', borderRadius: 4, background: 'var(--bg-sunken)', color: 'var(--text-muted)', border: '1px solid var(--border)' }}>{l.marca}</span>}
                </div>
                <div style={{ fontSize: 20, fontWeight: 700, fontFamily: 'var(--font-mono)', color: 'var(--ai-500)' }}>{l.n_items || 0}</div>
                <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 2 }}>leads</div>
              </div>
            ))}
          </div>
        </div>
      )}

      {/* Listas manuais / CSV */}
      <div style={{ marginBottom: 12 }}>
        <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: 10 }}>
          Listas Manuais
        </div>
        <div style={{ padding: '10px 12px', background: 'var(--bg-sunken)', borderRadius: 8, border: '1px solid var(--border)', marginBottom: 12, display: 'flex', gap: 6 }}>
          <input value={novaNome} onChange={e => setNovaNome(e.target.value)} placeholder="Nome da nova lista..."
            style={{ flex: 1, padding: '5px 10px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg-card)', color: 'var(--text)', fontSize: 12 }} />
          <input value={novaDesc} onChange={e => setNovaDesc(e.target.value)} placeholder="Descrição (opcional)"
            style={{ flex: 1, padding: '5px 10px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg-card)', color: 'var(--text)', fontSize: 12 }} />
          <button className="btn-ai" onClick={criar} disabled={!novaNome.trim()} style={{ fontSize: 12 }}>Criar</button>
        </div>

        {outras.length === 0 && <div style={{ padding: 24, textAlign: 'center', color: 'var(--text-dim)', fontSize: 12 }}>Sem listas manuais.</div>}
        {outras.length > 0 && (
          <div style={{ border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden' }}>
            <table style={{ width: '100%', borderCollapse: 'collapse' }}>
              <thead><tr>{['Nome', 'Tipo', 'Items', 'Actualizado', ''].map(h => <th key={h} style={thS}>{h}</th>)}</tr></thead>
              <tbody>
                {outras.map(l => (
                  <tr key={l.id} onClick={() => openLista(l)}
                    style={{ cursor: 'pointer' }}
                    onMouseEnter={ev => ev.currentTarget.style.background = 'var(--bg-sunken)'}
                    onMouseLeave={ev => ev.currentTarget.style.background = 'transparent'}>
                    <td style={{ ...tdS, fontWeight: 600 }}>{l.nome}{l.descricao && <div style={{ fontSize: 11, color: 'var(--text-muted)', fontWeight: 400 }}>{l.descricao}</div>}</td>
                    <td style={{ ...tdS, fontSize: 10, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)', textTransform: 'uppercase' }}>{l.tipo}</td>
                    <td style={{ ...tdS, textAlign: 'center', fontFamily: 'var(--font-mono)' }}>{l.n_items}</td>
                    <td style={{ ...tdS, fontSize: 11, color: 'var(--text-muted)' }}>{l.updated_at ? new Date(l.updated_at).toLocaleDateString('pt-PT') : '—'}</td>
                    <td style={{ ...tdS, textAlign: 'right' }}><button onClick={e => eliminar(l.id, e)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--danger)', fontSize: 11 }}>Eliminar</button></td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        )}
      </div>
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════════════════
// ═══════════════════════════════════════════════════════════════════════════
// TAB TEMPLATES WA (#25/#27) — gestão de templates Meta por campanha
// ═══════════════════════════════════════════════════════════════════════════
function TabWaTemplates() {
  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' };

  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 g = {};
    for (const t of templates) {
      if (!g[t.campaign_name]) g[t.campaign_name] = [];
      g[t.campaign_name].push(t);
    }
    return g;
  }, [templates]);

  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 INTEGRACOES = [
    { id: 'gestor',    label: 'Gestor CRM',       desc: 'FileMaker Data API — sync entidades, contactos, OPs, sources', status: 'active', since: 'Jan 2026' },
    { id: 'brevo',     label: 'Brevo',             desc: 'Email + WhatsApp templates — envio de campanhas', status: 'active', since: 'Mar 2026' },
    { id: 'meta',      label: 'Meta Ads',          desc: 'Graph API — campanhas Lead Gen, CTWA, criativos', status: 'active', since: 'Ago 2026' },
    { id: 'primavera', label: 'Primavera ERP',     desc: 'Histórico de compras, facturas, estado financeiro de clientes', status: 'pending', since: 'Fase B' },
    { id: 'sat',       label: 'SAT FileMaker',     desc: 'Tickets de suporte, histórico técnico, equipamentos instalados', status: 'pending', since: 'Fase C' },
    { id: 'google_ads',label: 'Google Ads',        desc: 'Campanhas Search + Display — integração Google Ads API', status: 'pending', since: 'Fase C' },
  ];
  const STATUS_C = { active: { label:'Activo', color:'#15803d', bg:'rgba(22,163,74,.1)' }, pending: { label:'Pendente', color:'#94a3b8', bg:'var(--bg-sunken)' } };
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
      {INTEGRACOES.map(ig => {
        const sc = STATUS_C[ig.status];
        return (
          <div key={ig.id} 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 }}>{ig.since}</div>
          </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-card)', 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}</div>
            </div>
          ))}
          {results.length === 0 && q && <div style={{ padding: 20, textAlign: 'center', color: 'var(--text-dim)', fontSize: 12 }}>Sem resultados</div>}
        </div>
      </div>
    </>
  );
}

window.MktCRMScreen = MktCRMScreen;
