/* Workflow de activação, no mesmo interface do portal. Não envia email nem activa anúncios. */

function wfNovoId() {
  return 'n' + Math.random().toString(36).slice(2, 10);
}

function wfClone(grafo) {
  return JSON.parse(JSON.stringify(grafo || { nos: [], ligacoes: [] }));
}

function wfSaidas(grafo) {
  const nos = new Map((grafo?.nos || []).map(n => [n.id, n]));
  const saidas = {};
  for (const lig of grafo?.ligacoes || []) {
    if (!saidas[lig.de]) saidas[lig.de] = {};
    saidas[lig.de][lig.quando] = lig;
  }
  return { nos, saidas };
}

function wfCriar(tipo, peca) {
  if (tipo === 'espera') return { id: wfNovoId(), tipo, titulo: 'Aguardar 1 dia útil', config: { dias: 1 } };
  if (tipo === 'email') return { id: wfNovoId(), tipo, titulo: peca?.titulo || 'Enviar email', config: { peca_id: peca?.id || '' } };
  if (tipo === 'whatsapp') return { id: wfNovoId(), tipo, titulo: peca?.titulo || 'Enviar WhatsApp', config: { peca_id: peca?.id || '' } };
  if (tipo === 'meta_ads') return { id: wfNovoId(), tipo, titulo: 'Conjunto de anúncios', config: { alcance: 'campanha' } };
  if (tipo === 'ramo') return { id: wfNovoId(), tipo, titulo: 'Condição', config: { criterio: 'resposta' } };
  if (tipo === 'fim') return { id: wfNovoId(), tipo, titulo: 'Fim', config: {} };
  return { id: wfNovoId(), tipo: 'espera', titulo: 'Aguardar 1 dia útil', config: { dias: 1 } };
}

function wfGruposAccoes(pecas, { entrada = false } = {}) {
  const lista = pecas || [];
  const emails = lista.filter(p => p.canal === 'email');
  const was = lista.filter(p => p.canal === 'whatsapp');
  const grupos = [];
  if (entrada) {
    grupos.push({
      id: 'entrada',
      titulo: 'Entrada',
      itens: [
        { tipo: 'ja_esta', label: 'Já está na audiência', icone: 'trigger' },
        { tipo: 'passa_a_estar', label: 'Passa a estar na audiência', icone: 'trigger' },
      ],
    });
  }
  grupos.push(
    { id: 'tempo', titulo: 'Tempo', itens: [{ tipo: 'espera', label: 'Aguardar', icone: 'espera' }] },
    { id: 'decisao', titulo: 'Decisão', itens: [{ tipo: 'ramo', label: 'Condição', icone: 'ramo' }] },
  );
  const comunicacao = [];
  emails.forEach(p => comunicacao.push({ tipo: 'email', label: p.titulo || 'Enviar email', icone: 'email', peca: p }));
  was.forEach(p => comunicacao.push({ tipo: 'whatsapp', label: p.titulo || 'Enviar WhatsApp', icone: 'whatsapp', peca: p }));
  if (lista.some(p => p.canal === 'meta_ads')) {
    comunicacao.push({ tipo: 'meta_ads', label: 'Conjunto de anúncios', icone: 'meta_ads' });
  }
  if (comunicacao.length) grupos.push({ id: 'comunicacao', titulo: 'Acção', itens: comunicacao });
  return grupos.filter(g => g.itens.length);
}

function wfInserir(grafo, aresta, tipo, peca) {
  const g = wfClone(grafo);
  const no = wfCriar(tipo, peca);
  g.nos.push(no);
  const destino = aresta.para;
  const volta = !destino || destino === aresta.de || destino === no.id || wfAlcanca(g, destino, aresta.de);
  let lig = null;
  g.ligacoes = g.ligacoes.filter(l => {
    if (l.quando === 'ir') return true;
    if (l.de === aresta.de && l.quando === aresta.quando) {
      if (!lig) lig = l;
      return false;
    }
    return true;
  });
  if (lig) {
    lig.para = no.id;
    g.ligacoes.push(lig);
  } else {
    g.ligacoes.push({ id: wfNovoId(), de: aresta.de, para: no.id, quando: aresta.quando });
  }
  if (tipo === 'ramo') {
    const fim = wfCriar('fim');
    fim.config = { motivo: 'resposta' };
    fim.titulo = 'Sai do workflow';
    g.nos.push(fim);
    g.ligacoes.push({ id: wfNovoId(), de: no.id, para: fim.id, quando: 'sim' });
    if (!volta) g.ligacoes.push({ id: wfNovoId(), de: no.id, para: destino, quando: 'nao' });
  } else if (tipo !== 'fim' && !volta) {
    g.ligacoes.push({ id: wfNovoId(), de: no.id, para: destino, quando: 'sempre' });
  }
  return wfPodar(g);
}

function wfAlcanca(grafo, origem, destino) {
  if (!origem || !destino || origem === destino) return origem === destino && !!origem;
  const { saidas } = wfSaidas(grafo);
  const stack = [origem];
  const seen = new Set();
  while (stack.length) {
    const id = stack.pop();
    if (!id || seen.has(id)) continue;
    if (id === destino) return true;
    seen.add(id);
    for (const lig of Object.values(saidas[id] || {})) {
      if (lig.quando === 'ir') continue;
      stack.push(lig.para);
    }
  }
  return false;
}

function wfPodar(grafo) {
  const nos = new Map((grafo.nos || []).map(n => [n.id, n]));
  const porDe = {};
  for (const lig of grafo.ligacoes || []) {
    if (lig.quando === 'ir') continue;
    if (!porDe[lig.de]) porDe[lig.de] = {};
    const actual = porDe[lig.de][lig.quando];
    if (!actual) porDe[lig.de][lig.quando] = lig;
  }
  const ordem = ['sempre', 'sim', 'nao'];
  const entrada = (grafo.nos || []).find(n => n.tipo === 'entrada');
  const keep = new Set();
  const reservado = new Set(entrada ? [entrada.id] : []);
  const keepLig = [];
  const stack = entrada ? [entrada.id] : [];
  while (stack.length) {
    const id = stack.pop();
    if (!id || keep.has(id)) continue;
    keep.add(id);
    const outs = porDe[id] || {};
    for (let i = ordem.length - 1; i >= 0; i--) {
      const lig = outs[ordem[i]];
      if (!lig) continue;
      const alvo = nos.get(lig.para);
      if (!alvo || lig.para === id) continue;
      if (reservado.has(lig.para) && alvo.tipo !== 'fim') continue;
      reservado.add(lig.para);
      keepLig.push(lig);
      if (!keep.has(lig.para)) stack.push(lig.para);
    }
  }
  for (const lig of grafo.ligacoes || []) {
    if (lig.quando !== 'ir') continue;
    if (keep.has(lig.de) && keep.has(lig.para) && lig.de !== lig.para) keepLig.push(lig);
  }
  grafo.nos = (grafo.nos || []).filter(n => keep.has(n.id));
  grafo.ligacoes = keepLig;
  return grafo;
}

function wfRecebido(grafo) {
  return wfPodar(wfClone(grafo || wfVazio()));
}

function wfParaInput(iso) {
  if (!iso) return '';
  const d = new Date(iso);
  if (Number.isNaN(d.getTime())) return '';
  const pad = (n) => String(n).padStart(2, '0');
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
}

function wfDataEntrada(grafo, primeiroEm) {
  if (primeiroEm) return String(primeiroEm);
  const entrada = (grafo?.nos || []).find(n => n.tipo === 'entrada');
  return String(entrada?.config?.quando || '');
}

function wfRemover(grafo, id) {
  const g = wfClone(grafo);
  const no = g.nos.find(n => n.id === id);
  if (!no || no.tipo === 'entrada') return g;
  const saidas = g.ligacoes.filter(l => l.de === id && l.quando !== 'ir');
  const continua = saidas.find(l => l.quando === 'sempre') || saidas.find(l => l.quando === 'nao');
  const cair = new Set([id]);
  if (no.tipo === 'ramo') {
    const sim = saidas.find(l => l.quando === 'sim');
    const alvo = sim && g.nos.find(n => n.id === sim.para);
    const soEste = sim && g.ligacoes.filter(l => l.para === sim.para).length === 1;
    if (alvo?.tipo === 'fim' && soEste) cair.add(alvo.id);
  }
  if (continua && !cair.has(continua.para)) {
    g.ligacoes = g.ligacoes
      .filter(l => !cair.has(l.de))
      .map(l => (l.para === id ? { ...l, para: continua.para } : l))
      .filter(l => !cair.has(l.para) && l.de !== l.para);
  } else {
    g.ligacoes = g.ligacoes.filter(l => !cair.has(l.de) && !cair.has(l.para));
  }
  g.nos = g.nos.filter(n => !cair.has(n.id));
  return wfPodar(g);
}

function wfTrigger(modo) {
  return {
    id: wfNovoId(),
    tipo: 'trigger',
    titulo: modo === 'ja_esta' ? 'Já está na audiência' : 'Passa a estar na audiência',
    config: { modo },
  };
}

function wfInserirTrigger(grafo, modo) {
  const g = wfClone(grafo);
  if (g.nos.some(n => n.tipo === 'trigger' && n.config?.modo === modo)) return g;
  const entrada = g.nos.find(n => n.tipo === 'entrada');
  if (!entrada) return g;
  const no = wfTrigger(modo);
  g.nos.push(no);
  let cursor = entrada.id;
  let saida = g.ligacoes.find(l => l.de === cursor && l.quando === 'sempre');
  while (saida) {
    const alvo = g.nos.find(n => n.id === saida.para);
    if (!alvo || alvo.tipo !== 'trigger') break;
    cursor = alvo.id;
    saida = g.ligacoes.find(l => l.de === cursor && l.quando === 'sempre');
  }
  const destino = saida ? saida.para : null;
  if (saida) saida.para = no.id;
  else g.ligacoes.push({ id: wfNovoId(), de: cursor, para: no.id, quando: 'sempre' });
  if (destino) g.ligacoes.push({ id: wfNovoId(), de: no.id, para: destino, quando: 'sempre' });
  return g;
}

function wfVazio() {
  const fimId = wfNovoId();
  return {
    nos: [
      { id: 'entrada', tipo: 'entrada', titulo: 'Inscrição', config: {} },
      { id: fimId, tipo: 'fim', titulo: 'Fim', config: {} },
    ],
    ligacoes: [{ id: wfNovoId(), de: 'entrada', para: fimId, quando: 'sempre' }],
  };
}

function wfArestaVisivel(nos, saidas, deId, paraId) {
  let id = deId;
  let edge = saidas[id]?.sempre || null;
  const seen = new Set();
  while (edge && !seen.has(id)) {
    seen.add(id);
    if (edge.para === paraId) return edge;
    const meio = nos.get(edge.para);
    if (!meio || meio.tipo !== 'trigger') break;
    id = edge.para;
    edge = saidas[id]?.sempre || null;
  }
  return edge;
}

function wfArestaPrincipal(grafo) {
  const { nos, saidas } = wfSaidas(grafo);
  const entrada = (grafo?.nos || []).find(n => n.tipo === 'entrada');
  if (!entrada) return null;
  let id = entrada.id;
  const vistos = new Set();
  let aresta = null;
  while (id && !vistos.has(id)) {
    vistos.add(id);
    const sempre = saidas[id]?.sempre;
    const seguinte = sempre || saidas[id]?.nao;
    if (!seguinte) break;
    aresta = seguinte;
    const alvo = nos.get(seguinte.para);
    if (!alvo || alvo.tipo === 'fim') break;
    id = alvo.id;
  }
  return aresta;
}

function wfInserirNaCadeia(grafo, tipo, peca) {
  if (tipo === 'ja_esta' || tipo === 'passa_a_estar') return wfInserirTrigger(grafo, tipo);
  const aresta = wfArestaPrincipal(grafo);
  if (!aresta) return grafo;
  return wfInserir(grafo, { de: aresta.de, para: aresta.para, quando: aresta.quando || 'sempre' }, tipo, peca);
}

function wfDiasPassos(grafo) {
  const { nos, saidas } = wfSaidas(grafo);
  const entrada = (grafo?.nos || []).find(n => n.tipo === 'entrada');
  const passos = [];
  const vistos = new Set();
  let id = entrada?.id;
  let diasAcum = 0;
  while (id && !vistos.has(id) && vistos.size < 40) {
    vistos.add(id);
    const no = nos.get(id);
    if (!no) break;
    if (no.tipo === 'espera') diasAcum += Math.max(1, Number(no.config?.dias) || 1);
    if (no.tipo === 'email' && no.config?.peca_id) {
      passos.push({
        peca_id: no.config.peca_id,
        dias_depois: passos.length === 0 ? 0 : Math.max(1, diasAcum || 1),
      });
      diasAcum = 0;
    }
    if (no.tipo === 'ramo') {
      id = saidas[id]?.nao?.para;
      continue;
    }
    if (no.tipo === 'fim') break;
    id = saidas[id]?.sempre?.para;
  }
  return passos;
}

function wfLinhaTempo(grafo) {
  const { nos, saidas } = wfSaidas(grafo);
  const entrada = (grafo?.nos || []).find(n => n.tipo === 'entrada');
  const emails = [];
  const esperaDeEmail = [];
  const fimRespostaDeEmail = [];
  let id = entrada?.id;
  const vistos = new Set();
  let emailIndex = -1;
  let esperaPendente = null;
  while (id && !vistos.has(id) && vistos.size < 40) {
    vistos.add(id);
    const no = nos.get(id);
    if (!no) break;
    if (no.tipo === 'espera') esperaPendente = no.id;
    if (no.tipo === 'ramo') {
      const sim = saidas[id]?.sim?.para;
      const fim = sim && nos.get(sim);
      if (fim?.tipo === 'fim') fimRespostaDeEmail[emailIndex + 1] = fim.id;
      id = saidas[id]?.nao?.para;
      continue;
    }
    if (no.tipo === 'email') {
      emailIndex += 1;
      emails[emailIndex] = no.id;
      esperaDeEmail[emailIndex] = esperaPendente;
      esperaPendente = null;
    }
    if (no.tipo === 'fim') break;
    id = saidas[id]?.sempre?.para;
  }
  return { emails, esperaDeEmail, fimRespostaDeEmail };
}

const WF_ESPERA_ZONAS = [
  { fg: '#475569', bg: '#F8FAFC', border: '#CBD5E1', anel: 'rgba(71,85,105,0.18)' },
  { fg: '#B45309', bg: '#FFF7ED', border: '#FDBA74', anel: 'rgba(180,83,9,0.18)' },
  { fg: '#3859D0', bg: '#F3F7FF', border: '#93A4E8', anel: 'rgba(56,89,208,0.18)' },
  { fg: '#0F766E', bg: '#F0FDFA', border: '#5EEAD4', anel: 'rgba(15,118,110,0.18)' },
  { fg: '#7C3AED', bg: '#F5F3FF', border: '#C4B5FD', anel: 'rgba(124,58,237,0.18)' },
];

function wfZonaEspera(passo) {
  const i = Number(passo);
  if (!Number.isInteger(i) || i < 0) return WF_ESPERA_ZONAS[0];
  return WF_ESPERA_ZONAS[Math.min(i, WF_ESPERA_ZONAS.length - 1)];
}

function wfQuando(iso) {
  const d = iso ? new Date(iso) : null;
  if (!d || Number.isNaN(d.getTime())) return '';
  return d.toLocaleString('pt-PT', { timeZone: 'Europe/Lisbon', weekday: 'long', day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' });
}

function wfBlocoEnvio(b) {
  const n = Number(b?.n) || 0;
  const dia = b?.dia || '';
  const de = b?.de || '';
  const ate = b?.ate || '';
  const horas = de && ate && de !== ate ? `${de}–${ate}` : (de || ate);
  const quando = [dia, horas].filter(Boolean).join(', ');
  return `${quando} · ${n.toLocaleString('pt-PT')}`;
}

function wfPorEnviar(n) {
  const v = Number(n) || 0;
  if (v === 1) return '1 por enviar';
  if (v > 1) return `${v.toLocaleString('pt-PT')} por enviar`;
  return 'Nenhum por enviar';
}

function wfOcupacao(grafo, posicao) {
  if (!posicao) return {};
  const linha = wfLinhaTempo(grafo);
  const out = {};
  const n = (arr, i) => Number(arr?.[i]) || 0;
  linha.emails.forEach((id, i) => {
    const enviados = n(posicao.enviados, i);
    const porEnviar = n(posicao.espera, i);
    const blocos = Array.isArray(posicao.blocos?.[i]) ? posicao.blocos[i] : [];
    if (!enviados && !porEnviar && !blocos.length) return;
    const quandoIso = porEnviar && Array.isArray(posicao.proximos_em) ? posicao.proximos_em[i] : null;
    out[id] = {
      rotulo: enviados === 1 ? '1 enviado' : (enviados ? `${enviados.toLocaleString('pt-PT')} enviados` : ''),
      porEnviar,
      blocos,
      quando: wfQuando(quandoIso),
      passado: posicao.agora_passo == null || i < posicao.agora_passo,
    };
  });
  linha.esperaDeEmail.forEach((id, i) => {
    if (!id) return;
    const espera = n(posicao.espera, i);
    if (!espera) return;
    const quandoIso = Array.isArray(posicao.proximos_em) ? posicao.proximos_em[i] : null;
    const blocos = Array.isArray(posicao.esperas_blocos?.[i]) ? posicao.esperas_blocos[i] : [];
    out[id] = {
      rotulo: espera === 1 ? '1 contacto à espera' : `${espera.toLocaleString('pt-PT')} contactos à espera`,
      agora: posicao.agora_passo === i,
      quando: blocos.length > 1 ? '' : wfQuando(quandoIso),
      blocos,
      passo: i,
    };
  });
  linha.fimRespostaDeEmail.forEach((id, i) => {
    if (!id) return;
    const respostas = n(posicao.respostas, i);
    if (!respostas) return;
    out[id] = {
      rotulo: respostas === 1 ? '1 respondeu' : `${respostas.toLocaleString('pt-PT')} responderam`,
      resposta: true,
    };
  });
  return out;
}

function wfActualizar(grafo, id, patch) {
  const g = wfClone(grafo);
  g.nos = g.nos.map(n => (n.id === id ? { ...n, ...patch, config: { ...(n.config || {}), ...(patch.config || {}) } } : n));
  return g;
}

const WF_ACCAO = {
  inscrito: 'Inscrito',
  distribuicao: 'Distribuição por territórios',
  entrou: 'Entrou no passo',
  email_pendente: 'Email por enviar pelo workflow já armado',
  whatsapp_por_armar: 'WhatsApp à espera de armar',
  meta_conjunto: 'Conjunto de anúncios assinalado na campanha',
  envio_lote: 'Email enviado',
  enviado: 'Email enviado',
  aberto: 'Abriu o email',
  clique: 'Clicou no email',
  devolvido: 'Email devolvido',
  cancelado: 'Cancelou a sequência',
  resposta: 'Respondeu',
  resposta_auto: 'Ausência automática',
  bloqueado: 'Envio bloqueado',
  spam: 'Marcado como spam',
  erro: 'Erro de envio',
  saida: 'Saiu do workflow',
  empresa_com_lead: 'Parou porque a empresa gerou uma lead',
  passado_comercial: 'Parou porque passou ao comercial',
  segmento: 'Parou porque o segmento mudou',
  entregue: 'Email entregue',
};

const WF_HIST_FILTROS = [
  { id: 'tudo', label: 'Tudo' },
  { id: 'engajamento', label: 'Aberturas e cliques', tipos: ['aberto', 'clique'] },
  { id: 'respostas', label: 'Respostas', tipos: ['resposta', 'resposta_auto'] },
  { id: 'problemas', label: 'Devolvidos', tipos: ['devolvido', 'bloqueado', 'spam', 'erro'] },
  { id: 'saidas', label: 'Saídas', tipos: ['cancelado', 'saida', 'empresa_com_lead', 'passado_comercial', 'segmento'] },
  { id: 'envios', label: 'Envios', tipos: ['envio_lote', 'enviado'] },
];

function WfCarregar() {
  return (
    <div style={{ padding: '48px 16px', background: '#fff', border: '1px solid #ECEFF5', borderRadius: 8, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 20 }}>
      <div style={{ display: 'flex', alignItems: 'flex-end', gap: 4, height: 48 }}>
        {[0, 1, 2, 3, 4, 5, 6].map(i => (
          <div key={i} style={{ width: 4, height: 32, background: '#3859D0', borderRadius: 4, animation: 'wfWave 1s ease-in-out infinite', animationDelay: `${i * 0.1}s`, transformOrigin: 'bottom' }} />
        ))}
      </div>
      <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.10em', color: '#3859D0', fontFamily: 'var(--font-mono, monospace)', textAlign: 'center' }}>A CARREGAR A SEQUÊNCIA</div>
      <style>{`@keyframes wfWave { 0%,100%{transform:scaleY(0.25)} 50%{transform:scaleY(1)} }`}</style>
    </div>
  );
}

function WfIcon({ tipo }) {
  const comum = { width: 16, height: 16, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 1.8 };
  if (tipo === 'entrada') return <svg {...comum}><circle cx="12" cy="8" r="3" /><path d="M5 19c1.5-3 3.8-4.5 7-4.5S17.5 16 19 19" /></svg>;
  if (tipo === 'email') return <svg {...comum}><rect x="3" y="5" width="18" height="14" rx="2" /><path d="M4 7l8 6 8-6" /></svg>;
  if (tipo === 'espera') return <svg {...comum}><circle cx="12" cy="12" r="8" /><path d="M12 8v5l3 2" /></svg>;
  if (tipo === 'whatsapp') return <svg {...comum}><path d="M6 18l1.2-3.2A7 7 0 1 1 9.2 18H6z" /></svg>;
  if (tipo === 'meta_ads') return <svg {...comum}><path d="M4 16l5-8 3 5 2-3 6 6" /></svg>;
  if (tipo === 'ramo') return <svg {...comum}><path d="M12 3v6M12 9l-6 6M12 9l6 6" /></svg>;
  if (tipo === 'trigger') return <svg {...comum}><path d="M5 12h10M12 8l4 4-4 4" /><circle cx="5" cy="12" r="1.4" fill="currentColor" /></svg>;
  return <svg {...comum}><rect x="6" y="6" width="12" height="12" rx="2" /></svg>;
}

function ActWorkflowEditor({ campId, nome, onClose, embutido, onEscolherPeca, primeiroEm, onPrimeiroEm, audiencias, nContactos, onAbrirAudiencias, onConfirmar, onArmar, acaoRef, onAccaoTick }) {
  const [dados, setDados] = React.useState(null);
  const [grafo, setGrafo] = React.useState(null);
  const [tab, setTab] = React.useState('accoes');
  const [vista, setVista] = React.useState(null);
  const [menu, setMenu] = React.useState(null);
  const [sel, setSel] = React.useState(null);
  const [erro, setErro] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const [nomeLocal, setNomeLocal] = React.useState(nome || 'Workflow');
  const [reinscricao, setReinscricao] = React.useState('depois');
  const [sdrEmail, setSdrEmail] = React.useState('');
  const [sdrModo, setSdrModo] = React.useState('campanha');
  const [pararEmpresa, setPararEmpresa] = React.useState(false);
  const [pessoas, setPessoas] = React.useState([]);
  const [ampliado, setAmpliado] = React.useState(false);
  const [passosAberto, setPassosAberto] = React.useState(false);
  const passosBotao = React.useRef(null);
  const [passosCaixa, setPassosCaixa] = React.useState(null);
  const [proposta, setProposta] = React.useState(null);
  const [aPropor, setAPropor] = React.useState(false);
  const [aConfirmar, setAConfirmar] = React.useState(false);
  const tocouData = React.useRef(false);
  const hidratouData = React.useRef(false);
  const onPrimeiroEmRef = React.useRef(onPrimeiroEm);
  onPrimeiroEmRef.current = onPrimeiroEm;
  React.useEffect(() => {
    if (!ampliado && !aConfirmar) return undefined;
    const onKey = (e) => {
      if (e.key !== 'Escape' || busy) return;
      if (aConfirmar) setAConfirmar(false);
      else setAmpliado(false);
    };
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, [ampliado, aConfirmar, busy]);

  const carregar = React.useCallback(async () => {
    const r = await fetch('/api/marketing/activacao/workflow?campanha_id=' + encodeURIComponent(campId));
    const d = await r.json();
    if (!r.ok) throw new Error(d.error || 'Não consegui abrir o workflow.');
    setDados(d);
    const recebido = wfRecebido(d.workflow?.grafo);
    setGrafo(recebido);
    const guardada = wfDataEntrada(recebido, '') || d.primeiro_em || '';
    if (!hidratouData.current && guardada && onPrimeiroEmRef.current) {
      hidratouData.current = true;
      onPrimeiroEmRef.current(wfParaInput(guardada) || guardada);
    }
    setNomeLocal(d.workflow?.nome || d.campanha?.titulo || nome || 'Workflow');
    setReinscricao(d.workflow?.reinscricao || 'depois');
    setSdrEmail(d.workflow?.sdr_email || '');
    setSdrModo(d.workflow?.sdr_modo === 'territorio' ? 'territorio' : 'campanha');
    setPararEmpresa(!!d.workflow?.parar_empresa_lead);
  }, [campId, nome]);

  React.useEffect(() => {
    fetch('/api/marketing/activacao/destinatarios-teste')
      .then(r => r.json())
      .then(d => setPessoas(Array.isArray(d.pessoas) ? d.pessoas : []))
      .catch(() => setPessoas([]));
  }, []);

  React.useEffect(() => {
    carregar().catch(e => setErro(e.message));
  }, [carregar]);

  React.useEffect(() => {
    if (dados?.workflow?.estado !== 'activo') return undefined;
    const t = setInterval(() => { carregar().catch(() => {}); }, 30000);
    return () => clearInterval(t);
  }, [dados?.workflow?.estado, carregar]);

  const corpo = () => {
    const bruto = wfDataEntrada(grafo, primeiroEm);
    const body = {
      campanha_id: campId,
      nome: nomeLocal,
      reinscricao,
      grafo,
      sdr_modo: sdrModo,
      sdr_email: sdrModo === 'territorio' ? '' : sdrEmail,
      parar_empresa_lead: pararEmpresa,
    };
    if (bruto) {
      const d = new Date(bruto);
      if (!Number.isNaN(d.getTime())) body.primeiro_em = d.toISOString();
    } else if (tocouData.current) {
      body.primeiro_em = null;
    }
    return body;
  };

  const gravar = async () => {
    setBusy(true);
    setErro(null);
    try {
      const r = await fetch('/api/marketing/activacao/workflow', {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(corpo()),
      });
      const d = await r.json();
      if (!r.ok) throw new Error(d.error || 'Não consegui gravar.');
      setDados(d);
      setGrafo(wfRecebido(d.workflow.grafo));
    } catch (e) {
      setErro(e.message);
    } finally {
      setBusy(false);
    }
  };

  const activar = async () => {
    if (sdrModo !== 'territorio' && !sdrEmail) {
      setTab('definicoes');
      setErro('Escolhe o SDR da campanha ou a distribuição por territórios.');
      return;
    }
    const quandoBruto = wfDataEntrada(grafo, primeiroEm);
    const quando = quandoBruto ? new Date(quandoBruto) : null;
    if (!quando || Number.isNaN(quando.getTime())) {
      setErro('Define a data e a hora do primeiro email.');
      return;
    }
    if (quando.getTime() <= Date.now()) {
      setErro('A data do primeiro email já passou. Escolhe uma hora futura.');
      return;
    }
    if (!(audiencias || []).length) {
      setErro('Escolhe pelo menos uma audiência no painel.');
      return;
    }
    setAConfirmar(true);
  };

  const executarActivacao = async () => {
    setBusy(true);
    setErro(null);
    const passos = wfDiasPassos(grafo);
    try {
      const r = await fetch('/api/marketing/activacao/workflow', {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(corpo()),
      });
      const d = await r.json();
      if (!r.ok) throw new Error(d.error || 'Não consegui gravar.');
      setDados(d);
      setGrafo(wfRecebido(d.workflow.grafo));
      const lig = await fetch('/api/marketing/activacao/workflow/ligar', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ campanha_id: campId }),
      });
      const dl = await lig.json();
      if (!lig.ok) throw new Error(dl.error || 'Não consegui activar.');
      setDados(dl);
      setGrafo(wfRecebido(dl.workflow.grafo));
      const bruto = wfDataEntrada(grafo, primeiroEm);
      const quandoIso = bruto ? new Date(bruto).toISOString() : null;
      if (onArmar) await onArmar(passos, quandoIso);
    } catch (e) {
      setErro(e.message);
    } finally {
      setBusy(false);
      setAConfirmar(false);
    }
  };

  const mudarEstado = async (path) => {
    setBusy(true);
    setErro(null);
    try {
      const r = await fetch(path, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ campanha_id: campId }),
      });
      const d = await r.json();
      if (!r.ok) throw new Error(d.error || 'Não consegui alterar o estado.');
      setDados(d);
      setGrafo(wfRecebido(d.workflow.grafo));
    } catch (e) {
      setErro(e.message);
    } finally {
      setBusy(false);
    }
  };

  const readOnly = dados?.workflow?.estado === 'activo';
  const pecas = dados?.pecas || [];
  const entrada = (grafo?.nos || []).find(n => n.tipo === 'entrada');
  const escolher = (id) => { if (id && onEscolherPeca) onEscolherPeca(id); };
  const temPassos = (grafo?.nos || []).some(n => n.tipo !== 'entrada' && n.tipo !== 'fim');
  if (acaoRef) {
    acaoRef.current = {
      gravar,
      activar,
      pausar: () => mudarEstado('/api/marketing/activacao/workflow/pausar'),
      busy,
      readOnly,
      estado: dados?.workflow?.estado || 'rascunho',
    };
  }
  React.useEffect(() => {
    if (onAccaoTick) onAccaoTick();
  }, [busy, dados?.workflow?.estado, readOnly, onAccaoTick]);
  const recomecar = () => {
    if (readOnly) return;
    if (!window.confirm('Recomeçar o workflow? O desenho actual sai do ecrã. Guarda para o gravar.')) return;
    setGrafo(wfVazio());
    setSel(null);
    setProposta(null);
  };
  const pedirDigi = async () => {
    if (readOnly || aPropor) return;
    setAPropor(true);
    setErro(null);
    try {
      const r = await fetch('/api/marketing/activacao/workflow/propor', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ campanha_id: campId }),
      });
      const d = await r.json().catch(() => ({}));
      if (!r.ok) throw new Error(d.error || 'Não consegui desenhar o workflow.');
      setProposta(d);
    } catch (e) {
      setErro(e.message);
    } finally {
      setAPropor(false);
    }
  };
  const usarProposta = () => {
    if (!proposta?.grafo) return;
    if (temPassos && !window.confirm('Este desenho substitui os passos que estão no ecrã. Guarda para o gravar.')) return;
    setGrafo(wfRecebido(proposta.grafo));
    setProposta(null);
    setSel(null);
  };
  const porObjecto = (tipo, peca) => {
    if (readOnly) return;
    setPassosAberto(false);
    setGrafo(g => wfInserirNaCadeia(g, tipo, peca));
  };
  React.useEffect(() => {
    if (!passosAberto || !passosBotao.current) { setPassosCaixa(null); return undefined; }
    const ler = () => {
      const r = passosBotao.current.getBoundingClientRect();
      setPassosCaixa({ top: r.bottom + 8, right: Math.max(8, window.innerWidth - r.right) });
    };
    ler();
    window.addEventListener('resize', ler);
    window.addEventListener('scroll', ler, true);
    return () => {
      window.removeEventListener('resize', ler);
      window.removeEventListener('scroll', ler, true);
    };
  }, [passosAberto]);

  const painel = (
    <div style={{ background: '#fff', border: '1px solid #ECEFF5', borderRadius: 10, padding: 12, display: 'flex', flexDirection: 'column', gap: 12, width: '100%', ...(ampliado ? { position: 'fixed', inset: 16, zIndex: 80, width: 'auto', height: 'auto', minHeight: 0, overflow: 'hidden', boxShadow: '0 24px 80px rgba(15,23,42,.28)' } : {}) }}>
      <div style={{ flexShrink: 0 }}>
        <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.06em', color: '#64748b', textTransform: 'uppercase', marginBottom: 4 }}>Workflow</div>
        <div style={{ display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', gap: 16, borderBottom: '1px solid #ECEFF5' }}>
          <div style={{ display: 'flex', gap: 18, minWidth: 0 }}>
            {[
              ['accoes', 'Sequência'],
              ['definicoes', 'Definições'],
              ['desempenho', 'Desempenho'],
              ['historico', 'Histórico'],
            ].map(([id, label]) => {
              const on = tab === id;
              return (
                <button key={id} type="button" onClick={() => { if (id === 'historico') setVista(null); setTab(id); }} style={{
                  height: 36, padding: 0, marginBottom: -1, border: 0, borderRadius: 0,
                  borderBottom: `2px solid ${on ? '#3859D0' : 'transparent'}`,
                  background: 'transparent', color: on ? '#3859D0' : '#64748b',
                  fontSize: 13, fontWeight: on ? 700 : 600, fontFamily: 'inherit', cursor: 'pointer',
                }}>{label}</button>
              );
            })}
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, paddingBottom: 8, flexShrink: 0 }}>
            {ampliado && onAbrirAudiencias && (
              <button type="button" onClick={onAbrirAudiencias} style={{ height: 32, padding: '0 4px', border: 0, background: 'transparent', color: '#64748b', fontSize: 12, fontWeight: 650, fontFamily: 'inherit', cursor: 'pointer' }}>Audiências ({(audiencias || []).length})</button>
            )}
            <button ref={passosBotao} type="button" onClick={() => { setTab('accoes'); setPassosAberto(v => !v); }} aria-label="Adicionar passo" aria-expanded={passosAberto} title="Adicionar passo" style={{ width: 28, height: 28, borderRadius: 6, border: `1px solid ${passosAberto ? '#3859D0' : '#E3E6EB'}`, background: passosAberto ? 'rgba(56,89,208,0.06)' : '#fff', color: '#475569', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', padding: 0, flexShrink: 0 }}>
              <svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true"><path d="M2 3.5h10M2 7h10M2 10.5h10" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round"/></svg>
            </button>
            <button type="button" onClick={() => setAmpliado(v => !v)} aria-label={ampliado ? 'Reduzir o workflow' : 'Maximizar o workflow'} title={ampliado ? 'Reduzir' : 'Maximizar'} style={{ width: 28, height: 28, borderRadius: 6, border: '1px solid #E3E6EB', background: '#fff', color: '#475569', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', padding: 0, flexShrink: 0 }}>
              {ampliado
                ? <svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true"><path d="M5 1v4H1M9 5h4V1M13 9h-4v4M5 13V9H1" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/></svg>
                : <svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true"><path d="M1 5V1h4M9 1h4v4M13 9v4H9M5 13H1V9" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/></svg>}
            </button>
            {!embutido && onClose && (
              <button type="button" onClick={onClose} style={{ height: 32, padding: '0 4px', border: 0, background: 'transparent', color: '#64748b', fontSize: 12, fontWeight: 650, fontFamily: 'inherit', cursor: 'pointer' }}>Voltar</button>
            )}
            {(!embutido || ampliado) && (
              <React.Fragment>
                <button type="button" onClick={gravar} disabled={busy || readOnly} style={{ height: 32, padding: '0 12px', borderRadius: 6, border: '1px solid #ECEFF5', background: '#fff', color: readOnly ? '#94A3B8' : '#112954', fontSize: 12, fontWeight: 650, fontFamily: 'inherit', cursor: readOnly ? 'default' : 'pointer' }}>{busy ? 'A gravar…' : 'Guardar'}</button>
                {dados?.workflow?.estado === 'activo'
                  ? <button type="button" onClick={() => mudarEstado('/api/marketing/activacao/workflow/pausar')} disabled={busy} style={{ height: 32, padding: '0 12px', borderRadius: 6, border: '1px solid #ECEFF5', background: '#fff', color: '#112954', fontSize: 12, fontWeight: 650, fontFamily: 'inherit', cursor: 'pointer' }}>Pausar</button>
                  : <button type="button" onClick={activar} disabled={busy} style={{ height: 32, padding: '0 12px', borderRadius: 6, border: 0, background: '#1F8A52', color: '#fff', fontSize: 12, fontWeight: 650, fontFamily: 'inherit', cursor: 'pointer' }}>{busy ? 'A activar…' : 'Activar'}</button>}
              </React.Fragment>
            )}
          </div>
        </div>
      </div>
      {passosAberto && passosCaixa && ReactDOM.createPortal(
        <React.Fragment>
          <div onClick={() => setPassosAberto(false)} style={{ position: 'fixed', inset: 0, zIndex: 260 }} />
          <div onClick={e => e.stopPropagation()} style={{ position: 'fixed', top: passosCaixa.top, right: passosCaixa.right, zIndex: 261, width: 360, maxHeight: 'min(520px, calc(100vh - 48px))', overflowY: 'auto', background: '#fff', border: '1px solid #ECEFF5', borderRadius: 10, boxShadow: '0 16px 48px rgba(17,41,84,0.16)', padding: 12 }}>
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, marginBottom: 4 }}>
              <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.06em', textTransform: 'uppercase', color: '#64748b' }}>Adicionar passo</div>
              <div style={{ display: 'flex', gap: 14 }}>
                <button type="button" onClick={() => { setPassosAberto(false); pedirDigi(); }} disabled={busy || readOnly || aPropor} style={{ height: 28, padding: 0, border: 0, background: 'transparent', color: readOnly ? '#94A3B8' : '#3859D0', fontSize: 12, fontWeight: 650, fontFamily: 'inherit', cursor: readOnly ? 'default' : 'pointer' }}>{aPropor ? 'A desenhar…' : 'A Digi AI desenha'}</button>
                <button type="button" onClick={() => { setPassosAberto(false); recomecar(); }} disabled={busy || readOnly} style={{ height: 28, padding: 0, border: 0, background: 'transparent', color: readOnly ? '#94A3B8' : '#64748b', fontSize: 12, fontWeight: 650, fontFamily: 'inherit', cursor: readOnly ? 'default' : 'pointer' }}>Recomeçar</button>
              </div>
            </div>
            <WfPaleta pecas={pecas} grafo={grafo} readOnly={readOnly} onEscolher={porObjecto} />
          </div>
        </React.Fragment>,
        document.body
      )}
      {erro && <div style={{ fontSize: 12, color: '#B91C1C', lineHeight: 1.4 }}>{erro}</div>}
      <div style={ampliado ? { flex: '1 1 auto', minHeight: 0, overflow: 'auto' } : undefined}>
        {!dados && !erro && <WfCarregar />}
        {tab === 'accoes' && entrada && (
          <div style={{ background: '#F8FAFC', borderRadius: 8, overflowX: 'auto' }}>
          <div style={{ display: 'flex', justifyContent: 'center', minWidth: 'max-content', padding: '8px 12px 4px' }}>
          {Number(dados?.posicao?.cancelados) > 0 && (
            <div style={{ fontSize: 12, color: '#64748b', textAlign: 'center', padding: '8px 12px 0' }}>
              {Number(dados.posicao.cancelados).toLocaleString('pt-PT')} cancelaram a sequência e já não seguem.
            </div>
          )}
          <WfTrilho
            grafo={grafo}
            startId={entrada.id}
            campId={campId}
            ocupacao={wfOcupacao(grafo, dados?.posicao)}
            audiencias={audiencias}
            pecas={pecas}
            sel={sel}
            readOnly={readOnly}
            menu={menu}
            onSelect={id => { setSel(id); const no = (grafo?.nos || []).find(n => n.id === id); if (no?.tipo === 'email') escolher(no.config?.peca_id); }}
            onMenu={setMenu}
            onInserir={(aresta, tipo, peca) => {
              setMenu(null);
              if (tipo === 'ja_esta' || tipo === 'passa_a_estar') {
                setGrafo(g => wfInserirTrigger(g, tipo));
                return;
              }
              setGrafo(g => wfInserir(g, { de: aresta.de, para: aresta.para, quando: aresta.quando }, tipo, peca));
            }}
            onRemover={id => { setErro(null); setGrafo(g => wfRemover(g, id)); setSel(null); }}
            onActualizar={(id, patch) => setGrafo(g => wfActualizar(g, id, patch))}
            primeiroEm={primeiroEm}
            onPrimeiroEm={valor => { tocouData.current = true; if (onPrimeiroEm) onPrimeiroEm(valor); }}
            onReporTrigger={modo => setGrafo(g => wfInserirTrigger(g, modo))}
            onIr={(de, para) => {
              setGrafo(g => {
                const next = wfClone(g);
                next.ligacoes = next.ligacoes.filter(l => !(l.de === de && l.quando === 'ir'));
                if (para) next.ligacoes.push({ id: wfNovoId(), de, para, quando: 'ir' });
                return next;
              });
            }}
          />
          </div>
          </div>
        )}
        {tab === 'definicoes' && (
          <WfDefinicoes
            dados={dados}
            audiencias={audiencias}
            reinscricao={reinscricao}
            setReinscricao={setReinscricao}
            sdrEmail={sdrEmail}
            setSdrEmail={setSdrEmail}
            sdrModo={sdrModo}
            setSdrModo={setSdrModo}
            pessoas={pessoas}
            readOnly={readOnly}
            pararEmpresa={pararEmpresa}
            setPararEmpresa={setPararEmpresa}
            campId={campId}
          />
        )}
        {tab === 'desempenho' && <WfDesempenho dados={dados} onAbrir={id => { setVista(id); setTab('historico'); }} />}
        {tab === 'historico' && <WfHistorico dados={dados} campId={campId} vista={vista} onVista={setVista} />}
      </div>
    </div>
  );
  const quandoActivarBruto = wfDataEntrada(grafo, primeiroEm);
  const quandoActivar = quandoActivarBruto ? new Date(quandoActivarBruto) : null;
  const quandoTxt = quandoActivar && !Number.isNaN(quandoActivar.getTime())
    ? quandoActivar.toLocaleString('pt-PT', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' })
    : '';
  const sdrPessoa = (pessoas || []).find(p => String(p.email || '').toLowerCase() === String(sdrEmail || '').toLowerCase());
  const sdrNome = dados?.workflow?.sdr_nome || sdrPessoa?.nome || '';
  const passosActivar = wfDiasPassos(grafo);
  const tituloPeca = new Map((pecas || []).map(p => [p.id, p.titulo]));
  const modosEntrada = new Set((grafo?.nos || []).filter(n => n.tipo === 'trigger').map(n => n.config?.modo).filter(Boolean));
  const nAud = Number(nContactos) || 0;
  const audTxt = (audiencias || []).join(', ');
  const linhasActivar = [
    ['Audiência', nAud ? `${audTxt} · ${nAud.toLocaleString('pt-PT')} contactos` : audTxt],
    ['Primeiro email', `${quandoTxt}${passosActivar[0] ? ` · ${tituloPeca.get(passosActivar[0].peca_id) || 'Email 1'}` : ''}`],
    ['Emails seguintes', passosActivar.slice(1).map(p => `${tituloPeca.get(p.peca_id) || 'Email'} · ${p.dias_depois} dias úteis depois`).join(' · ') || 'Sem emails seguintes no desenho'],
    ['Horário', 'Dias úteis, das 9h às 21h, hora de Lisboa'],
    ['Remetente', sdrModo === 'territorio'
      ? 'Cada contacto fica com o comercial do território da marca. O envio sai pela Brevo.'
      : `${sdrNome || 'SDR da campanha'}${sdrEmail ? ` · ${sdrEmail}` : ''}. O envio sai pela Brevo, com o layout gravado em cada email.`],
    ['Quem entra', [
      modosEntrada.has('ja_esta') ? 'quem já está na audiência' : '',
      modosEntrada.has('passa_a_estar') ? 'quem passar a estar depois' : '',
    ].filter(Boolean).join(' e ') || 'Ninguém, até haver um trigger'],
    ['Quem responde', 'Sai antes do email seguinte. Quem não responde continua.'],
    ['SDR', sdrModo === 'territorio'
      ? 'A inscrição fica activa e cada contacto segue o comercial do território.'
      : `A inscrição fica activa para ${sdrNome || 'o SDR da campanha'}.`],
  ];
  const modalActivar = aConfirmar && ReactDOM.createPortal(
    <div onClick={() => { if (!busy) setAConfirmar(false); }} style={{ position: 'fixed', inset: 0, background: 'rgba(17,41,84,0.45)', zIndex: 300, display: 'flex', justifyContent: 'center', alignItems: 'flex-start', padding: 24, overflow: 'auto' }}>
      <div onClick={e => e.stopPropagation()} role="dialog" aria-labelledby="wf-activar-titulo" style={{ width: 'min(560px, 100%)', marginTop: 24, background: '#fff', borderRadius: 12, padding: 20, boxShadow: '0 24px 80px rgba(15,23,42,.28)' }}>
        <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.06em', textTransform: 'uppercase', color: '#64748b' }}>{nomeLocal}</div>
        <h2 id="wf-activar-titulo" style={{ margin: '6px 0 0', fontSize: 18, fontWeight: 700, color: '#112954' }}>Activar esta sequência</h2>
        <p style={{ margin: '8px 0 0', fontSize: 13, lineHeight: 1.45, color: '#475569' }}>A partir da confirmação a campanha fica armada e os contactos passam a receber os emails abaixo.</p>
        <div style={{ marginTop: 16, display: 'flex', flexDirection: 'column', gap: 10 }}>
          {linhasActivar.map(([rotulo, valor]) => (
            <div key={rotulo} style={{ display: 'grid', gridTemplateColumns: '132px 1fr', gap: 12, alignItems: 'start' }}>
              <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.04em', textTransform: 'uppercase', color: '#94A3B8', paddingTop: 2 }}>{rotulo}</div>
              <div style={{ fontSize: 13, lineHeight: 1.4, color: '#112954' }}>{valor}</div>
            </div>
          ))}
        </div>
        <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 20 }}>
          <button type="button" onClick={() => setAConfirmar(false)} disabled={busy} style={{ height: 36, padding: '0 14px', borderRadius: 6, border: '1px solid #E3E6EB', background: '#fff', color: '#112954', fontSize: 13, fontWeight: 650, fontFamily: 'inherit', cursor: busy ? 'default' : 'pointer' }}>Cancelar</button>
          <button type="button" onClick={executarActivacao} disabled={busy} style={{ height: 36, padding: '0 14px', borderRadius: 6, border: 0, background: '#1F8A52', color: '#fff', fontSize: 13, fontWeight: 650, fontFamily: 'inherit', cursor: busy ? 'default' : 'pointer', opacity: busy ? 0.7 : 1 }}>{busy ? 'A activar…' : 'Activar'}</button>
        </div>
      </div>
    </div>,
    document.body
  );
  const modalProposta = proposta && (
    <div onClick={e => { e.stopPropagation(); setProposta(null); }} style={{ position: 'fixed', inset: 0, background: 'rgba(17,41,84,0.45)', zIndex: 80, overflowY: 'auto', padding: 24 }}>
      <div onClick={e => e.stopPropagation()} style={{ width: 'min(520px, 100%)', margin: '24px auto', background: '#fff', borderRadius: 12, padding: 16, boxShadow: '0 16px 48px rgba(17,41,84,0.18)' }}>
        <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.06em', color: '#64748b', textTransform: 'uppercase' }}>Proposta da Digi AI</div>
        <div style={{ fontSize: 16, fontWeight: 700, color: '#112954', marginTop: 6 }}>Quando a audiência cumpre, esta é a sequência.</div>
        {temPassos && <div style={{ fontSize: 12, color: '#92400E', lineHeight: 1.45, marginTop: 8 }}>Usar este desenho substitui os passos que estão no ecrã.</div>}
        <div style={{ marginTop: 12, display: 'flex', flexDirection: 'column', gap: 6 }}>
          {(proposta.resumo || []).map((linha, i) => (
            <div key={i} style={{ fontSize: 13, color: '#112954', lineHeight: 1.4 }}>{linha}</div>
          ))}
          {!(proposta.resumo || []).length && <div style={{ fontSize: 13, color: '#64748b' }}>A sequência está pronta a colocar no ecrã.</div>}
        </div>
        <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 16 }}>
          <button type="button" onClick={() => setProposta(null)} style={{ height: 32, padding: '0 12px', borderRadius: 6, border: '1px solid #ECEFF5', background: '#fff', color: '#112954', fontSize: 12, cursor: 'pointer' }}>Fechar</button>
          <button type="button" onClick={usarProposta} style={{ height: 32, padding: '0 12px', borderRadius: 6, border: 0, background: '#3859D0', color: '#fff', fontSize: 12, fontWeight: 650, cursor: 'pointer' }}>Usar este desenho</button>
        </div>
      </div>
    </div>
  );
  if (!ampliado) return <React.Fragment>{painel}{modalProposta}{modalActivar}</React.Fragment>;
  return ReactDOM.createPortal(
    <React.Fragment>
      {painel}
      {modalProposta}
      {modalActivar}
    </React.Fragment>,
    document.body
  );
}

function WfPaleta({ pecas, grafo, readOnly, onEscolher }) {
  const grupos = wfGruposAccoes(pecas, { entrada: true });
  const triggersOn = new Set(
    (grafo?.nos || []).filter(n => n.tipo === 'trigger').map(n => n.config?.modo).filter(Boolean)
  );
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 10, padding: '10px 12px 4px' }}>
      {grupos.map(grupo => (
        <div key={grupo.id}>
          <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.06em', textTransform: 'uppercase', color: '#94A3B8', marginBottom: 6 }}>{grupo.titulo}</div>
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
            {grupo.itens.map(item => {
              const key = item.peca?.id || item.tipo;
              const activo = (item.tipo === 'ja_esta' || item.tipo === 'passa_a_estar') && triggersOn.has(item.tipo);
              return (
                <button
                  key={key}
                  type="button"
                  disabled={readOnly || activo}
                  title={activo ? 'Já está activo na inscrição' : undefined}
                  onClick={() => onEscolher(item.tipo, item.peca)}
                  style={{
                    display: 'inline-flex', alignItems: 'center', gap: 8, minHeight: 36, maxWidth: 280,
                    padding: '6px 12px', borderRadius: 8,
                    border: `1px solid ${activo ? '#3859D0' : '#ECEFF5'}`,
                    background: activo ? 'rgba(56,89,208,0.06)' : '#fff',
                    color: readOnly || activo ? '#94A3B8' : '#112954',
                    fontSize: 12, fontWeight: 650, fontFamily: 'inherit',
                    cursor: readOnly || activo ? 'default' : 'pointer',
                    textAlign: 'left', lineHeight: 1.3,
                  }}
                >
                  <span style={{ color: '#3859D0', display: 'inline-flex', flexShrink: 0 }}><WfIcon tipo={item.icone || item.tipo} /></span>
                  <span style={{ overflow: 'hidden', textOverflow: 'ellipsis' }}>{item.label}</span>
                </button>
              );
            })}
          </div>
        </div>
      ))}
    </div>
  );
}

function WfMais({ aresta, readOnly, menu, onMenu, onInserir, pecas }) {
  const botao = React.useRef(null);
  const [caixa, setCaixa] = React.useState(null);
  const aberto = menu && menu.de === aresta.de && menu.quando === aresta.quando && menu.para === aresta.para;
  const grupos = wfGruposAccoes(pecas, { entrada: false });
  React.useEffect(() => {
    if (!aberto || !botao.current) { setCaixa(null); return undefined; }
    const ler = () => {
      const r = botao.current.getBoundingClientRect();
      const margem = 16;
      const largura = 260;
      const abaixo = window.innerHeight - r.bottom - 8 - margem;
      const acima = r.top - 8 - margem;
      const abrirCima = abaixo < 280 && acima > abaixo;
      const centro = r.left + r.width / 2;
      const left = Math.min(Math.max(centro, margem + largura / 2), window.innerWidth - margem - largura / 2);
      setCaixa({
        top: abrirCima ? null : r.bottom + 8,
        bottom: abrirCima ? window.innerHeight - r.top + 8 : null,
        left,
        maxH: Math.max(160, Math.min(420, abrirCima ? acima : abaixo)),
      });
    };
    ler();
    window.addEventListener('resize', ler);
    window.addEventListener('scroll', ler, true);
    return () => {
      window.removeEventListener('resize', ler);
      window.removeEventListener('scroll', ler, true);
    };
  }, [aberto]);
  return (
    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', position: 'relative' }}>
      <div style={{ width: 2, height: 14, background: '#E2E8F0' }} />
      <button ref={botao} type="button" disabled={readOnly} onClick={() => onMenu(aberto ? null : aresta)} style={{
        width: 22, height: 22, borderRadius: 11, border: '1px solid #ECEFF5', background: '#fff', color: '#3859D0',
        cursor: readOnly ? 'default' : 'pointer', fontSize: 16, lineHeight: 1,
      }}>+</button>
      <div style={{ width: 2, height: 14, background: '#E2E8F0' }} />
      {aberto && caixa && ReactDOM.createPortal(
        <React.Fragment>
          <div onClick={() => onMenu(null)} style={{ position: 'fixed', inset: 0, zIndex: 260 }} />
          <div onClick={e => e.stopPropagation()} style={{ position: 'fixed', top: caixa.top == null ? 'auto' : caixa.top, bottom: caixa.bottom == null ? 'auto' : caixa.bottom, left: caixa.left, transform: 'translateX(-50%)', zIndex: 261, background: '#fff', border: '1px solid #ECEFF5', borderRadius: 8, boxShadow: '0 12px 32px rgba(17,41,84,0.12)', width: 260, maxHeight: caixa.maxH, overflowY: 'auto', padding: 6 }}>
            {grupos.map(grupo => (
              <div key={grupo.id} style={{ marginBottom: 4 }}>
                <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.06em', textTransform: 'uppercase', color: '#94A3B8', padding: '6px 10px 2px' }}>{grupo.titulo}</div>
                {grupo.itens.map(item => (
                  <WfOpcao
                    key={item.peca?.id || item.tipo + item.label}
                    tipo={item.icone || item.tipo}
                    label={item.label}
                    onClick={() => { onMenu(null); onInserir(aresta, item.tipo, item.peca); }}
                  />
                ))}
              </div>
            ))}
          </div>
        </React.Fragment>,
        document.body
      )}
    </div>
  );
}

function WfOpcao({ tipo, label, onClick }) {
  return (
    <button type="button" onClick={onClick} style={{ display: 'flex', alignItems: 'center', gap: 8, width: '100%', textAlign: 'left', border: 0, background: 'transparent', padding: '8px 10px', cursor: 'pointer', color: '#112954', fontSize: 12, fontWeight: 650, fontFamily: 'inherit', borderRadius: 6 }}>
      <span style={{ color: '#3859D0', display: 'inline-flex', flexShrink: 0 }}><WfIcon tipo={tipo} /></span>
      <span style={{ lineHeight: 1.3 }}>{label}</span>
    </button>
  );
}

function WfEsperaLista({ campId, passo, dias, zona, onClose }) {
  const [q, setQ] = React.useState('');
  const [lista, setLista] = React.useState(null);
  const [erro, setErro] = React.useState(null);
  React.useEffect(() => {
    let vivo = true;
    setLista(null);
    setErro(null);
    fetch(`/api/marketing/activacao/workflow/espera?campanha_id=${encodeURIComponent(campId)}&passo=${encodeURIComponent(passo)}`)
      .then(r => r.json().then(d => ({ ok: r.ok, d })))
      .then(({ ok, d }) => {
        if (!vivo) return;
        if (!ok) throw new Error(d.error || 'Não consegui abrir a lista.');
        setLista(d);
      })
      .catch(e => { if (vivo) setErro(e.message); });
    return () => { vivo = false; };
  }, [campId, passo]);
  const termo = q.trim().toLowerCase();
  const linhas = (lista?.contactos || []).filter(c => {
    if (!termo) return true;
    return [c.nome, c.email, c.empresa].some(v => String(v || '').toLowerCase().includes(termo));
  });
  const tituloEspera = `Aguardar ${dias} ${dias === 1 ? 'dia útil' : 'dias úteis'}`;
  return ReactDOM.createPortal(
    <div onClick={onClose} style={{ position: 'fixed', inset: 0, background: 'rgba(17,41,84,0.45)', zIndex: 300, display: 'flex', justifyContent: 'center', alignItems: 'flex-start', padding: 24, overflow: 'auto' }}>
      <div onClick={e => e.stopPropagation()} role="dialog" aria-label={tituloEspera} style={{ width: 'min(640px, 100%)', marginTop: 24, background: '#fff', borderRadius: 12, overflow: 'hidden', boxShadow: '0 24px 80px rgba(15,23,42,.28)' }}>
        <div style={{ background: zona.bg, borderBottom: `3px solid ${zona.fg}`, padding: '16px 20px' }}>
          <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.06em', textTransform: 'uppercase', color: zona.fg }}>Lista de espera · email {passo + 1}</div>
          <div style={{ fontSize: 18, fontWeight: 700, color: '#112954', marginTop: 4 }}>{tituloEspera}</div>
          <div style={{ fontSize: 13, color: '#475569', marginTop: 4 }}>
            {lista ? `${lista.total.toLocaleString('pt-PT')} ${lista.total === 1 ? 'contacto' : 'contactos'}` : 'A carregar…'}
          </div>
        </div>
        <div style={{ padding: 16 }}>
          <input value={q} onChange={e => setQ(e.target.value)} placeholder="Procurar nome, email ou empresa" style={{ height: 36, width: '100%', boxSizing: 'border-box', padding: '0 10px', border: '1px solid #E3E6EB', borderRadius: 6, fontSize: 13, fontFamily: 'inherit', color: '#112954' }} />
          {erro && <div style={{ marginTop: 12, fontSize: 13, color: '#B42318' }}>{erro}</div>}
          <div className="scrollbar" style={{ marginTop: 12, border: '1px solid #ECEFF5', borderRadius: 8, maxHeight: 'min(480px, calc(100vh - 280px))', overflowY: 'auto' }}>
            {!lista && !erro && <div style={{ padding: 16, fontSize: 13, color: '#64748b' }}>A abrir os contactos…</div>}
            {lista && linhas.length === 0 && <div style={{ padding: 16, fontSize: 13, color: '#94A3B8' }}>{termo ? 'Sem resultados para esta procura.' : 'Ninguém está à espera neste passo.'}</div>}
            {linhas.map(c => (
              <div key={c.email} style={{ display: 'grid', gridTemplateColumns: '1fr 190px', gap: 12, padding: '12px', borderBottom: '1px solid #ECEFF5' }}>
                <div style={{ minWidth: 0 }}>
                  <div style={{ fontSize: 13, fontWeight: 650, color: '#112954' }}>{c.nome || c.email}</div>
                  <div style={{ fontSize: 12, color: '#64748b', marginTop: 2, overflowWrap: 'anywhere' }}>{[c.empresa, c.cargo].filter(Boolean).join(' · ')}</div>
                  {c.nome ? <div style={{ fontSize: 12, color: '#64748b', marginTop: 2, overflowWrap: 'anywhere' }}>{c.email}</div> : null}
                </div>
                <div style={{ fontSize: 12, fontWeight: 650, color: zona.fg, lineHeight: 1.4 }}>{wfQuando(c.proximo_em)}</div>
              </div>
            ))}
          </div>
          <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 16 }}>
            <button type="button" onClick={onClose} style={{ height: 36, padding: '0 14px', borderRadius: 6, border: '1px solid #E3E6EB', background: '#fff', color: '#112954', fontSize: 13, fontWeight: 650, fontFamily: 'inherit', cursor: 'pointer' }}>Fechar</button>
          </div>
        </div>
      </div>
    </div>,
    document.body
  );
}

function WfCartao({ no, pecas, sel, readOnly, onSelect, onRemover, onActualizar, onIr, grafo, primeiroEm, onPrimeiroEm, audiencias, onReporTrigger, ocupacao, campId }) {
  const [esperaAberta, setEsperaAberta] = React.useState(false);
  const aberto = sel === no.id;
  const fase = ocupacao?.[no.id];
  const ir = (grafo.ligacoes || []).find(l => l.de === no.id && l.quando === 'ir');
  const destino = ir && (grafo.nos || []).find(n => n.id === ir.para);
  const peca = pecas.find(p => p.id === no.config?.peca_id);
  const diasEspera = Math.max(1, Number(no.config?.dias) || 1);
  const zona = no.tipo === 'espera' && Number.isInteger(fase?.passo) ? wfZonaEspera(fase.passo) : null;
  const abrirEspera = zona && campId;
  return (
    <div style={{ width: 280, background: zona ? zona.bg : (fase?.agora ? '#F3F7FF' : (aberto ? 'rgba(56,89,208,0.06)' : '#fff')), border: `1px solid ${zona ? zona.border : (fase?.agora || aberto ? '#3859D0' : '#ECEFF5')}`, borderRadius: 8, boxShadow: zona && fase?.agora ? `0 0 0 3px ${zona.anel}` : (fase?.agora ? '0 0 0 3px rgba(56,89,208,0.15)' : 'none') }}>
      {fase?.agora && <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.06em', textTransform: 'uppercase', color: '#fff', background: zona ? zona.fg : '#3859D0', borderRadius: '7px 7px 0 0', padding: '4px 12px' }}>Agora</div>}
      <button type="button" onClick={() => { if (abrirEspera) { setEsperaAberta(true); return; } onSelect(aberto ? null : no.id); }} style={{
        width: '100%', textAlign: 'left', background: 'transparent', border: 0,
        borderRadius: 8, padding: '10px 12px', cursor: 'pointer', color: '#112954',
      }}>
        <span style={{ display: 'flex', gap: 8, alignItems: 'flex-start', color: zona ? zona.fg : '#3859D0' }}><WfIcon tipo={no.tipo} /><span style={{ fontSize: 12, fontWeight: 650, color: '#112954', lineHeight: 1.35 }}>{no.tipo === 'fim' && no.config?.motivo === 'resposta' ? 'Sai do workflow' : no.tipo === 'espera' ? `Aguardar ${diasEspera} ${diasEspera === 1 ? 'dia útil' : 'dias úteis'}` : no.tipo === 'ramo' ? 'Condição' : ((no.tipo === 'email' || no.tipo === 'whatsapp') && peca?.titulo ? peca.titulo : no.titulo)}</span></span>
        <span style={{ display: 'block', marginTop: 6, fontSize: 11, color: '#64748b', lineHeight: 1.4 }}>
          {no.tipo === 'entrada' && ((audiencias || []).length
            ? `Início da sequência. Audiências: ${(audiencias || []).join(', ')}. Os triggers decidem quem entra.`
            : 'Início da sequência. Escolhe as audiências no painel. Os triggers decidem quem entra.')}
          {no.tipo === 'trigger' && (no.config?.modo === 'passa_a_estar'
            ? 'Entra quando passar a cumprir a audiência, depois da activação.'
            : 'Entra quem já cumpre a audiência no momento da activação.')}
          {no.tipo === 'espera' && `${diasEspera} ${diasEspera === 1 ? 'dia útil' : 'dias úteis'}. O envio sai entre as 9h e as 21h.`}
          {no.tipo === 'espera' && fase?.rotulo && <span style={{ display: 'block', marginTop: 4, color: zona ? zona.fg : '#112954', fontWeight: 650 }}>{fase.rotulo}{fase.quando ? ` · próximo envio ${fase.quando}` : ''}</span>}
          {no.tipo === 'espera' && (fase?.blocos || []).length > 1 && fase.blocos.map((b, i) => (
            <span key={`${b.dia}-${b.de}-${i}`} style={{ display: 'block', marginTop: 2, color: zona ? zona.fg : '#112954' }}>{wfBlocoEnvio(b)}</span>
          ))}
          {abrirEspera && <span style={{ display: 'block', marginTop: 4, fontSize: 11, fontWeight: 650, color: zona.fg }}>Ver contactos</span>}
          {no.tipo === 'email' && (fase?.rotulo || (peca ? 'A enviar esta peça.' : 'Escolhe a peça no cartão.'))}
          {no.tipo === 'email' && fase && (
            <span style={{ display: 'block', marginTop: 4, color: fase.porEnviar ? '#112954' : '#64748b', fontWeight: fase.porEnviar ? 650 : 500 }}>
              {wfPorEnviar(fase.porEnviar)}{fase.porEnviar && fase.quando ? ` · próximo envio ${fase.quando}` : ''}
            </span>
          )}
          {no.tipo === 'email' && (fase?.blocos || []).map((b, i) => (
            <span key={`${b.dia}-${b.de}-${i}`} style={{ display: 'block', marginTop: 2, color: '#112954' }}>{wfBlocoEnvio(b)}</span>
          ))}
          {no.tipo === 'whatsapp' && (peca ? 'O envio espera pelo armar.' : 'Escolhe a peça. O envio espera pelo armar.')}
          {no.tipo === 'meta_ads' && 'O conjunto fica assinalado na campanha, sem envio por contacto.'}
          {no.tipo === 'ramo' && 'Quem responder sai do workflow. Quem não responder segue para o próximo email.'}
          {no.tipo === 'fim' && (no.config?.motivo === 'resposta' ? 'Saiu porque respondeu.' : 'Fim do workflow.')}
        </span>
      </button>
      {no.tipo === 'entrada' && (
        <div style={{ padding: '0 12px 12px' }}>
          <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.06em', color: '#64748b', textTransform: 'uppercase', marginBottom: 6 }}>Primeiro email</div>
          <input
            type="datetime-local"
            value={wfParaInput(wfDataEntrada(grafo, primeiroEm)) || wfDataEntrada(grafo, primeiroEm) || ''}
            disabled={readOnly}
            onChange={e => {
              const valor = e.target.value;
              if (onActualizar) onActualizar(no.id, { config: { quando: valor } });
              if (onPrimeiroEm) onPrimeiroEm(valor);
            }}
            style={{ fontSize: 12, fontFamily: 'inherit', color: '#112954', border: '1px solid #ECEFF5', borderRadius: 6, padding: '6px 8px', background: '#fff', width: '100%' }}
          />
          <div style={{ fontSize: 11, color: '#64748b', lineHeight: 1.4, marginTop: 6 }}>
            Dias úteis, das 9h às 21h, hora de Lisboa. Fora desse horário passa ao período útil seguinte.
          </div>
          <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.06em', color: '#64748b', textTransform: 'uppercase', margin: '12px 0 6px' }}>Quem entra</div>
          {[['ja_esta', 'Já está na audiência'], ['passa_a_estar', 'Passa a estar na audiência']].map(([modo, label]) => {
            const trig = (grafo?.nos || []).find(n => n.tipo === 'trigger' && n.config?.modo === modo);
            return (
              <label key={modo} style={{ display: 'flex', gap: 8, alignItems: 'flex-start', fontSize: 12, color: '#112954', marginBottom: 6, cursor: readOnly ? 'default' : 'pointer' }}>
                <input
                  type="checkbox"
                  checked={!!trig}
                  disabled={readOnly}
                  onChange={() => {
                    if (trig) onRemover(trig.id);
                    else if (onReporTrigger) onReporTrigger(modo);
                  }}
                  style={{ marginTop: 2 }}
                />
                <span>{label}</span>
              </label>
            );
          })}
        </div>
      )}
      {no.tipo !== 'entrada' && !(no.tipo === 'fim' && no.config?.motivo === 'resposta') && !readOnly && (
        <div style={{ padding: '0 12px 10px' }}>
          <button type="button" onClick={() => onRemover(no.id)} style={{ border: 0, background: 'transparent', color: '#B91C1C', cursor: 'pointer', fontSize: 12, padding: 0 }}>Apagar</button>
        </div>
      )}
      {no.tipo === 'email' && (
        <div style={{ padding: '0 12px 10px' }}>
          <button type="button" onClick={() => onSelect(no.id)} style={{ border: 0, background: 'transparent', color: '#3859D0', cursor: 'pointer', fontSize: 12, padding: 0 }}>Ver pré-visualização</button>
        </div>
      )}
      {destino && (
        <div style={{ marginTop: 8, border: '1px dashed #ECEFF5', borderRadius: 8, padding: '8px 10px', fontSize: 11, color: '#64748b', background: '#fff' }}>
          Ir para acção · {destino.titulo}
        </div>
      )}
      {aberto && !readOnly && ['espera', 'email', 'whatsapp', 'meta_ads'].includes(no.tipo) && (
        <div style={{ marginTop: 8, background: '#fff', border: '1px solid #ECEFF5', borderRadius: 8, padding: 10 }}>
          {no.tipo === 'espera' && (
            <label style={{ fontSize: 12, color: '#112954' }}>Dias úteis
              <input type="number" min="1" value={no.config?.dias || 1} onChange={e => onActualizar(no.id, { titulo: `Aguardar ${Math.max(1, Number(e.target.value) || 1)} ${Math.max(1, Number(e.target.value) || 1) === 1 ? 'dia útil' : 'dias úteis'}`, config: { dias: Math.max(1, Number(e.target.value) || 1) } })} style={{ display: 'block', marginTop: 4, width: '100%', height: 32, borderRadius: 6, border: '1px solid #ECEFF5', background: '#fff', color: '#112954', fontSize: 12, fontFamily: 'inherit', padding: '0 8px' }} />
            </label>
          )}
          {(no.tipo === 'email' || no.tipo === 'whatsapp') && (
            <select value={no.config?.peca_id || ''} onChange={e => {
              const p = pecas.find(x => x.id === e.target.value);
              onActualizar(no.id, { titulo: p?.titulo || no.titulo, config: { peca_id: e.target.value } });
            }} style={{ width: '100%', height: 32, borderRadius: 6, border: '1px solid #ECEFF5', background: '#fff', color: '#112954', fontSize: 12, fontFamily: 'inherit' }}>
              <option value="">Escolhe a peça</option>
              {pecas.filter(p => p.canal === (no.tipo === 'email' ? 'email' : 'whatsapp')).map(p => <option key={p.id} value={p.id}>{p.titulo}</option>)}
            </select>
          )}
          {no.tipo !== 'fim' && no.tipo !== 'trigger' && no.tipo !== 'ramo' && (
            <label style={{ display: 'block', marginTop: 8, fontSize: 12, color: '#112954' }}>Ir para acção
              <select value={ir?.para || ''} onChange={e => onIr(no.id, e.target.value)} style={{ display: 'block', marginTop: 4, width: '100%', height: 32, borderRadius: 6, border: '1px solid #ECEFF5', background: '#fff', color: '#112954', fontSize: 12, fontFamily: 'inherit' }}>
                <option value="">Sem salto</option>
                {(grafo.nos || []).filter(n => n.id !== no.id).map(n => <option key={n.id} value={n.id}>{n.titulo}</option>)}
              </select>
            </label>
          )}
        </div>
      )}
      {esperaAberta && abrirEspera && (
        <WfEsperaLista campId={campId} passo={fase.passo} dias={diasEspera} zona={zona} onClose={() => setEsperaAberta(false)} />
      )}
    </div>
  );
}

function WfTrilho(props) {
  const { grafo, startId } = props;
  const depth = props.depth || 0;
  if (depth > 24) return null;
  const { nos, saidas } = wfSaidas(grafo);
  const vistos = props.vistos || new Set();
  const blocos = [];
  let id = startId;
  let ramo = null;
  while (id) {
    const no = nos.get(id);
    if (!no) break;
    if (vistos.has(id) && no.tipo !== 'fim') break;
    vistos.add(id);
    if (no.tipo === 'trigger') {
      id = saidas[id]?.sempre?.para;
      continue;
    }
    blocos.push(no);
    if (no.tipo === 'ramo' || no.tipo === 'fim') {
      if (no.tipo === 'ramo') ramo = no;
      break;
    }
    id = saidas[id]?.sempre?.para;
  }
  return (
    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', padding: depth ? '0 0 12px' : '8px 0 16px' }}>
      {blocos.map((no, i) => {
        const anterior = blocos[i - 1];
        const aresta = anterior ? wfArestaVisivel(nos, saidas, anterior.id, no.id) : null;
        return (
          <React.Fragment key={no.id}>
            {aresta && <WfMais aresta={aresta} {...props} />}
            <WfCartao no={no} {...props} />
          </React.Fragment>
        );
      })}
      {ramo && (
        <WfDecisao {...props} ramo={ramo} saidas={saidas} vistos={vistos} />
      )}
    </div>
  );
}

function WfDecisao({ ramo, saidas, vistos, ...props }) {
  const nos = new Map((props.grafo?.nos || []).map(n => [n.id, n]));
  const ramos = [
    ['sim', 'Respondeu', 'Sai e não recebe os emails seguintes.', '#1F8A52'],
    ['nao', 'Não respondeu', 'Continua para o passo seguinte.', '#B45309'],
  ].map(([quando, label, nota, cor]) => ({ quando, label, nota, cor, lig: saidas[ramo.id]?.[quando] }))
    .filter(r => r.lig);
  if (!ramos.length) return null;
  return (
    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
      <div style={{ width: 2, height: 16, background: '#94A3B8' }} />
      <div style={{ display: 'flex', alignItems: 'flex-start' }}>
        {ramos.map((ramoSaida, i) => {
          const destino = nos.get(ramoSaida.lig.para);
          const saidaDirecta = ramoSaida.quando === 'sim' && destino?.tipo === 'fim' && destino.config?.motivo === 'resposta';
          const repetido = !saidaDirecta && destino && vistos && vistos.has(destino.id) && destino.tipo !== 'fim';
          return (
            <div key={ramoSaida.quando} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', width: 320 }}>
              <div style={{ position: 'relative', width: '100%', height: 18 }}>
                <div style={{
                  position: 'absolute', top: 0, height: 2, background: '#94A3B8',
                  left: i === 0 ? '50%' : 0,
                  right: i === ramos.length - 1 ? '50%' : 0,
                }} />
                <div style={{ position: 'absolute', top: 0, left: '50%', width: 2, height: 18, marginLeft: -1, background: '#94A3B8' }} />
              </div>
              <span style={{ fontSize: 12, fontWeight: 700, color: ramoSaida.cor, margin: '4px 0 2px' }}>{ramoSaida.label}</span>
              <span style={{ fontSize: 11, color: '#64748b', marginBottom: 4, textAlign: 'center', maxWidth: 220, lineHeight: 1.35 }}>{ramoSaida.nota}</span>
              {saidaDirecta ? (
                <div style={{ marginTop: 8, width: 220, background: props.ocupacao?.[destino.id]?.resposta ? '#F0FDF4' : '#fff', border: `1px solid ${props.ocupacao?.[destino.id]?.resposta ? '#1F8A52' : '#ECEFF5'}`, borderRadius: 8, padding: '10px 12px' }}>
                  <div style={{ fontSize: 12, fontWeight: 650, color: '#112954' }}>Sai do workflow</div>
                  <div style={{ fontSize: 11, color: '#64748b', marginTop: 4, lineHeight: 1.4 }}>A resposta chega à caixa do remetente e a sequência termina.</div>
                  {props.ocupacao?.[destino.id]?.rotulo && <div style={{ fontSize: 12, fontWeight: 650, color: '#1F8A52', marginTop: 6 }}>{props.ocupacao[destino.id].rotulo}</div>}
                </div>
              ) : repetido ? (
                <div style={{ marginTop: 8, width: 220, fontSize: 11, color: '#64748b', textAlign: 'center', lineHeight: 1.4 }}>Este passo já está na sequência.</div>
              ) : (
                <React.Fragment>
                  <WfMais aresta={ramoSaida.lig} {...props} />
                  <WfTrilho {...props} vistos={vistos} startId={ramoSaida.lig.para} depth={(props.depth || 0) + 1} />
                </React.Fragment>
              )}
            </div>
          );
        })}
      </div>
    </div>
  );
}

function WfSdrLista({ pessoas, value, onChange, disabled }) {
  const [empresa, setEmpresa] = React.useState('all');
  const [depto, setDepto] = React.useState('all');
  const [q, setQ] = React.useState('');
  const empresas = React.useMemo(() => [...new Set((pessoas || []).map(p => p.empresa).filter(Boolean))].sort(), [pessoas]);
  const deptos = React.useMemo(() => {
    const src = empresa === 'all' ? (pessoas || []) : (pessoas || []).filter(p => p.empresa === empresa);
    return [...new Set(src.map(p => p.departamento).filter(Boolean))].sort();
  }, [pessoas, empresa]);
  const filtered = (pessoas || []).filter(p => {
    if (empresa !== 'all' && p.empresa !== empresa) return false;
    if (depto !== 'all' && p.departamento !== depto) return false;
    if (q && !(p.nome + ' ' + p.email).toLowerCase().includes(q.toLowerCase())) return false;
    return true;
  });
  const campo = { fontSize: 12, padding: '5px 8px', border: '1px solid #ECEFF5', borderRadius: 6, background: '#fff', color: '#112954', fontFamily: 'inherit', width: '100%', boxSizing: 'border-box' };
  const escolhido = (pessoas || []).find(p => p.email === value);
  return (
    <div>
      <div style={{ fontSize: 12, color: '#112954', marginBottom: 8 }}>
        {escolhido ? escolhido.nome : 'SDR por escolher'}
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6, marginBottom: 6 }}>
        <select value={empresa} disabled={disabled} onChange={e => { setEmpresa(e.target.value); setDepto('all'); }} style={campo}>
          <option value="all">Todas as empresas</option>
          {empresas.map(x => <option key={x} value={x}>{x}</option>)}
        </select>
        <select value={depto} disabled={disabled} onChange={e => setDepto(e.target.value)} style={campo}>
          <option value="all">Todos os departamentos</option>
          {deptos.map(x => <option key={x} value={x}>{x}</option>)}
        </select>
      </div>
      <input value={q} disabled={disabled} onChange={e => setQ(e.target.value)} placeholder="Procurar nome ou email…" style={{ ...campo, marginBottom: 8 }} />
      <div className="scrollbar" style={{ border: '1px solid #ECEFF5', borderRadius: 6, maxHeight: 240, overflowY: 'auto', background: '#fff' }}>
        {filtered.length === 0 && <div style={{ padding: 10, fontSize: 12, color: '#64748b' }}>{(pessoas || []).length ? 'Sem resultados.' : 'A carregar utilizadores…'}</div>}
        {filtered.map(p => {
          const on = value === p.email;
          return (
            <label key={p.email} style={{ display: 'flex', gap: 8, alignItems: 'flex-start', padding: '7px 8px', borderBottom: '1px solid #ECEFF5', cursor: disabled ? 'default' : 'pointer', background: on ? 'rgba(56,89,208,0.06)' : '#fff' }}>
              <input type="radio" name="wf-sdr-campanha" checked={on} disabled={disabled} onChange={() => onChange(p.email)} style={{ marginTop: 2 }} />
              <span style={{ minWidth: 0 }}>
                <span style={{ display: 'block', fontSize: 12, fontWeight: 600, color: '#112954' }}>{p.nome}</span>
                <span style={{ display: 'block', fontSize: 10, color: '#64748b', overflowWrap: 'anywhere' }}>{p.email}{p.empresa ? ` · ${p.empresa}` : ''}{p.departamento && p.departamento !== '—' ? ` · ${p.departamento}` : ''}</span>
              </span>
            </label>
          );
        })}
      </div>
    </div>
  );
}

function WfDefinicoes({ dados, audiencias, reinscricao, setReinscricao, sdrEmail, setSdrEmail, sdrModo, setSdrModo, pessoas, readOnly, pararEmpresa, setPararEmpresa, campId }) {
  const [aGravarEmpresa, setAGravarEmpresa] = React.useState(false);
  const [erroEmpresa, setErroEmpresa] = React.useState(null);
  const gravarEmpresa = async () => {
    setAGravarEmpresa(true);
    setErroEmpresa(null);
    try {
      const r = await fetch('/api/marketing/activacao/workflow/parar-empresa', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ campanha_id: campId, parar_empresa_lead: !!pararEmpresa }),
      });
      const d = await r.json();
      if (!r.ok) throw new Error(d.error || 'Não consegui gravar.');
      setPararEmpresa(!!d.workflow?.parar_empresa_lead);
    } catch (e) {
      setErroEmpresa(e.message);
    } finally {
      setAGravarEmpresa(false);
    }
  };
  const estado = dados?.workflow?.estado || 'rascunho';
  const estadoTxt = estado === 'activo' ? 'Inscrição ligada' : estado === 'pausado' ? 'Em pausa' : 'Rascunho';
  const visto = new Set();
  const lista = (pessoas || []).filter(p => {
    if (!p.email || visto.has(p.email)) return false;
    visto.add(p.email);
    return true;
  });
  const sec = { border: '1px solid #ECEFF5', borderRadius: 8, padding: 14, marginBottom: 12, color: '#112954' };
  const titulo = { fontSize: 13, fontWeight: 700, marginBottom: 8 };
  const nota = { fontSize: 12, lineHeight: 1.45, color: '#64748b', margin: '6px 0 0' };
  return (
    <div>
      <div style={sec}>
        <div style={titulo}>Quem envia o email</div>
        <label style={{ display: 'flex', gap: 8, alignItems: 'flex-start', fontSize: 13, marginBottom: 8, cursor: readOnly ? 'default' : 'pointer' }}>
          <input type="radio" name="wf-sdr-modo" checked={sdrModo !== 'territorio'} disabled={readOnly} onChange={() => setSdrModo('campanha')} style={{ marginTop: 2 }} />
          <span>
            <span style={{ display: 'block', fontWeight: 650 }}>SDR desta campanha</span>
            <span style={{ display: 'block', ...nota, marginTop: 2 }}>Toda a audiência fica com esta pessoa. O email sai da caixa dela. Enquanto a inscrição está activa, a empresa sai da carteira do comercial e fica na lista de SDR Prospect.</span>
          </span>
        </label>
        {sdrModo !== 'territorio' && (
          <div style={{ margin: '0 0 12px 24px' }}>
            <WfSdrLista pessoas={lista} value={sdrEmail} disabled={readOnly} onChange={setSdrEmail} />
            <p style={nota}>Guarda o workflow para ficar registado.</p>
          </div>
        )}
        <label style={{ display: 'flex', gap: 8, alignItems: 'flex-start', fontSize: 13, cursor: readOnly ? 'default' : 'pointer' }}>
          <input type="radio" name="wf-sdr-modo" checked={sdrModo === 'territorio'} disabled={readOnly} onChange={() => { setSdrModo('territorio'); setSdrEmail(''); }} style={{ marginTop: 2 }} />
          <span>
            <span style={{ display: 'block', fontWeight: 650 }}>Distribuição por territórios</span>
            <span style={{ display: 'block', ...nota, marginTop: 2 }}>Cada contacto fica com o comercial do território da marca, como está na prospeção. Entra quem tiver uma só pessoa com email. O comercial permanente da empresa não muda.</span>
          </span>
        </label>
      </div>
      <div style={sec}>
        <div style={titulo}>Quem volta a entrar</div>
        <label style={{ display: 'flex', gap: 8, alignItems: 'flex-start', fontSize: 13, marginBottom: 8, cursor: readOnly ? 'default' : 'pointer' }}>
          <input type="radio" name="wf-reinscricao" checked={reinscricao === 'depois'} disabled={readOnly} onChange={() => setReinscricao('depois')} style={{ marginTop: 2 }} />
          <span>Voltar a inscrever no dia seguinte a concluir</span>
        </label>
        <label style={{ display: 'flex', gap: 8, alignItems: 'flex-start', fontSize: 13, cursor: readOnly ? 'default' : 'pointer' }}>
          <input type="radio" name="wf-reinscricao" checked={reinscricao === 'nunca'} disabled={readOnly} onChange={() => setReinscricao('nunca')} style={{ marginTop: 2 }} />
          <span>Uma só vez</span>
        </label>
      </div>
      <div style={sec}>
        <div style={titulo}>Quando uma empresa responde</div>
        <label style={{ display: 'flex', gap: 8, alignItems: 'flex-start', fontSize: 13, marginBottom: 8, cursor: 'pointer' }}>
          <input type="radio" name="wf-parar-empresa" checked={!pararEmpresa} onChange={() => setPararEmpresa(false)} style={{ marginTop: 2 }} />
          <span>Continuar a escrever aos outros contactos da empresa</span>
        </label>
        <label style={{ display: 'flex', gap: 8, alignItems: 'flex-start', fontSize: 13, cursor: 'pointer' }}>
          <input type="radio" name="wf-parar-empresa" checked={!!pararEmpresa} onChange={() => setPararEmpresa(true)} style={{ marginTop: 2 }} />
          <span>
            <span style={{ display: 'block' }}>Parar os outros contactos da empresa quando nasce uma lead</span>
            <span style={{ display: 'block', ...nota, marginTop: 2 }}>Uma resposta humana é lida e só conta como lead se pedir amostras, reunião, o relatório ou disser que quer avançar. Aí param essa pessoa e os colegas da mesma empresa na prospeção ou do mesmo domínio de email. Uma ausência automática ou um cancelamento não param a empresa. Gmail, Hotmail, Outlook e Sapo não agrupam ninguém. Passar ao Comercial pára essa pessoa e os colegas neste workflow, esteja esta opção ligada ou desligada. Sem Interesse e A Aguardar não param a sequência.</span>
          </span>
        </label>
        <div style={{ marginTop: 10, display: 'flex', alignItems: 'center', gap: 10 }}>
          <button type="button" onClick={gravarEmpresa} disabled={aGravarEmpresa} style={{ height: 32, padding: '0 12px', borderRadius: 6, border: '1px solid #ECEFF5', background: '#fff', color: '#112954', fontSize: 12, fontWeight: 650, fontFamily: 'inherit', cursor: 'pointer' }}>{aGravarEmpresa ? 'A gravar…' : 'Guardar esta opção'}</button>
          {erroEmpresa && <span style={{ fontSize: 12, color: '#B42318' }}>{erroEmpresa}</span>}
        </div>
      </div>
      <div style={sec}>
        <div style={titulo}>Estado</div>
        <div style={{ fontSize: 13, marginBottom: 8 }}>{estadoTxt}</div>
        <p style={{ ...nota, marginTop: 0 }}>
          {(audiencias || []).length
            ? `${audiencias.length} audiência${audiencias.length === 1 ? '' : 's'} no painel: ${audiencias.join(', ')}.`
            : 'Ainda sem audiências. Escolhe-as no painel da esquerda.'}
        </p>
        <p style={nota}>O envio é em dias úteis, entre as 9h e as 21h, hora de Lisboa. Para activar usa o botão Activar no painel.</p>
      </div>
    </div>
  );
}

function wfPct(n, d) {
  const a = Number(n) || 0;
  const b = Number(d) || 0;
  if (!b) return '—';
  return `${(100 * a / b).toLocaleString('pt-PT', { minimumFractionDigits: 1, maximumFractionDigits: 1 })}%`;
}

function wfNotaEmpresas(pessoas, empresas) {
  const p = Number(pessoas) || 0;
  const e = Number(empresas) || 0;
  if (!p) return 'Nenhuma nesta campanha';
  if (p > e) return `${e.toLocaleString('pt-PT')} ${e === 1 ? 'empresa' : 'empresas'}, colegas incluídos`;
  return `${e.toLocaleString('pt-PT')} ${e === 1 ? 'empresa' : 'empresas'}`;
}

function wfNotaOportunidades(por) {
  const rotulo = {
    aberta: q => (q === 1 ? 'aberta' : 'abertas'),
    ganha: q => (q === 1 ? 'ganha' : 'ganhas'),
    perdida: q => (q === 1 ? 'perdida' : 'perdidas'),
  };
  const partes = ['aberta', 'ganha', 'perdida']
    .filter(k => Number(por?.[k]) > 0)
    .map(k => `${Number(por[k]).toLocaleString('pt-PT')} ${rotulo[k](Number(por[k]))}`);
  if (!partes.length) return 'Ainda sem oportunidade no Gestor';
  return partes.join(' · ');
}

function wfNotaLeads(porPedido, n) {
  const rotulo = {
    amostras: q => (q === 1 ? 'pedido de amostras' : 'pedidos de amostras'),
    reuniao: q => (q === 1 ? 'reunião' : 'reuniões'),
    informacao: q => (q === 1 ? 'pedido de informação' : 'pedidos de informação'),
  };
  const partes = (Array.isArray(porPedido) ? porPedido : [])
    .filter(p => Number(p.n) > 0 && p.pedido)
    .map(p => {
      const q = Number(p.n);
      const nome = rotulo[p.pedido] ? rotulo[p.pedido](q) : p.pedido;
      return `${q.toLocaleString('pt-PT')} ${nome}`;
    });
  if (!n) return 'Ainda sem interesse genuíno';
  if (!partes.length) return 'Interesse genuíno na campanha';
  return partes.join(' · ');
}

function WfNumero({ n, abrir, onAbrir }) {
  const texto = (Number(n) || 0).toLocaleString('pt-PT');
  if (!abrir || !onAbrir || !(Number(n) > 0)) {
    return <span style={{ fontSize: 'inherit', fontWeight: 'inherit', color: 'inherit' }}>{texto}</span>;
  }
  return (
    <button type="button" onClick={() => onAbrir(abrir)} style={{ border: 0, background: 'transparent', padding: 0, fontSize: 'inherit', fontWeight: 'inherit', fontFamily: 'inherit', color: '#3859D0', cursor: 'pointer', textDecoration: 'underline', textUnderlineOffset: 3 }}>{texto}</button>
  );
}

function WfFunil({ passos, base, onAbrir }) {
  const topo = Math.max(Number(base) || 0, 1);
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
      {passos.map(passo => {
        const n = Number(passo.n) || 0;
        const largura = n > 0 ? Math.max((100 * n) / topo, 1.2) : 0;
        return (
          <div key={passo.nome} style={{ display: 'grid', gridTemplateColumns: 'minmax(140px, 190px) 1fr auto', gap: 12, alignItems: 'center' }}>
            <div>
              <div style={{ fontSize: 13, fontWeight: 650, color: '#112954' }}>{passo.nome}</div>
              <div style={{ fontSize: 11, color: '#64748b', lineHeight: 1.35, marginTop: 2 }}>{passo.nota}</div>
            </div>
            <div style={{ height: 12, background: '#EEF2F7', borderRadius: 99, overflow: 'hidden' }}>
              <div style={{ width: `${largura}%`, height: '100%', background: passo.cor || '#3859D0', borderRadius: 99 }} />
            </div>
            <div style={{ minWidth: 48, fontSize: 16, fontWeight: 700, color: '#112954', textAlign: 'right' }}>
              <WfNumero n={n} abrir={passo.abrir} onAbrir={onAbrir} />
            </div>
          </div>
        );
      })}
    </div>
  );
}

function WfLinhas({ titulo, linhas, onAbrir }) {
  return (
    <div>
      <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.06em', textTransform: 'uppercase', color: '#64748b', marginBottom: 8 }}>{titulo}</div>
      <div style={{ border: '1px solid #ECEFF5', borderRadius: 8, background: '#fff' }}>
        {linhas.map((linha, i) => (
          <div key={linha.nome} style={{ display: 'grid', gridTemplateColumns: '1fr auto', gap: 12, alignItems: 'center', padding: '10px 12px', borderTop: i ? '1px solid #ECEFF5' : '0' }}>
            <div>
              <div style={{ fontSize: 13, fontWeight: 650, color: '#112954' }}>{linha.nome}</div>
              <div style={{ fontSize: 11, color: '#64748b', lineHeight: 1.35, marginTop: 2 }}>{linha.nota}</div>
            </div>
            <div style={{ fontSize: 18, fontWeight: 700, color: '#112954' }}>
              <WfNumero n={linha.n} abrir={linha.abrir} onAbrir={onAbrir} />
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

function WfDesempenho({ dados, onAbrir }) {
  const c = dados?.contagens || {};
  const m = dados?.metricas || {};
  const p = m.pessoas || {};
  const sdrNome = dados?.workflow?.sdr_nome || '';
  const sdrEmail = dados?.workflow?.sdr_email || '';
  const porTerritorio = dados?.workflow?.sdr_modo === 'territorio';
  const quem = porTerritorio
    ? 'Distribuição por territórios'
    : (sdrNome || sdrEmail ? `Quem recebe: ${sdrNome || sdrEmail}` : 'Quem recebe: SDR por escolher nas Definições');
  const enviados = Number(m.enviados) || 0;
  const entregues = p.entregue != null ? Number(p.entregue) : (Number(m.entregue) || 0);
  const abertos = p.aberto != null ? Number(p.aberto) : (Number(m.aberto) || 0);
  const cliques = p.clique != null ? Number(p.clique) : (Number(m.clique) || 0);
  const devolvidos = p.devolvido != null ? Number(p.devolvido) : (Number(m.devolvido) || 0);
  const adiados = p.adiado != null ? Number(p.adiado) : (Number(m.adiado) || 0);
  const cancelados = Number(m.cancelados) || 0;
  const humanas = Number(m.respostas_humanas) || 0;
  const auto = Number(m.respostas_auto) || 0;
  const leads = Number(m.leads) || 0;
  const passado = Number(m.passado_comercial) || 0;
  const passadoEmpresas = Number(m.passado_empresas) || 0;
  const empresaLead = Number(m.empresa_com_lead) || 0;
  const empresaLeadEmpresas = Number(m.empresa_com_lead_empresas) || 0;
  const oportunidades = Number(m.oportunidades) || 0;
  const funil = [
    { nome: 'Enviados', n: enviados, nota: 'Emails que saíram' },
    { nome: 'Entregues', n: entregues, nota: `${wfPct(entregues, enviados)} dos enviados` },
    { nome: 'Aberturas', n: abertos, nota: `${wfPct(abertos, entregues)} dos entregues` },
    { nome: 'Cliques', n: cliques, nota: abertos ? `${wfPct(cliques, abertos)} de quem abriu` : 'Ainda sem aberturas' },
    { nome: 'Respostas humanas', n: humanas, nota: `${wfPct(humanas, entregues)} dos entregues`, abrir: 'humanas' },
    { nome: 'Leads', n: leads, nota: wfNotaLeads(m.leads_por_pedido, leads), cor: '#1F8A52', abrir: 'leads' },
    { nome: 'Oportunidades', n: oportunidades, nota: wfNotaOportunidades(m.oportunidades_por_estado), cor: '#1F8A52', abrir: 'oportunidades' },
  ];
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
      <div style={{ fontSize: 12, color: '#64748b', lineHeight: 1.45 }}>{quem} · {(Number(c.activo) || 0).toLocaleString('pt-PT')} activos na sequência</div>
      <div style={{ background: '#F8FAFC', border: '1px solid #ECEFF5', borderRadius: 8, padding: '14px 14px 16px' }}>
        <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.06em', textTransform: 'uppercase', color: '#64748b', marginBottom: 12 }}>Funil</div>
        <WfFunil passos={funil} base={enviados} onAbrir={onAbrir} />
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: 16 }}>
        <WfLinhas onAbrir={onAbrir} titulo="Saíram da sequência" linhas={[
          { nome: 'Cancelaram', n: cancelados, nota: `${wfPct(cancelados, entregues)} dos entregues` },
          { nome: 'Passaram ao comercial', n: passado, nota: wfNotaEmpresas(passado, passadoEmpresas), abrir: 'passado' },
          { nome: 'Pararam porque a empresa gerou uma lead', n: empresaLead, nota: wfNotaEmpresas(empresaLead, empresaLeadEmpresas) },
        ]} />
        <WfLinhas titulo="Entrega" linhas={[
          { nome: 'Devolvidos', n: devolvidos, nota: `${wfPct(devolvidos, enviados)} dos enviados · rejeitados ou inválidos` },
          { nome: 'Atrasos temporários', n: adiados, nota: 'Ainda podem ser entregues' },
          { nome: 'Ausências automáticas', n: auto, nota: m.caixa_ligada === false ? 'Caixa do SDR por ligar' : 'Continuam na sequência' },
        ]} />
      </div>
    </div>
  );
}

const WF_VISTAS = {
  humanas: 'Respostas humanas',
  leads: 'Leads',
  passado: 'Passaram ao comercial',
  oportunidades: 'Oportunidades',
};
const WF_PEDIDO_TXT = { amostras: 'Pedido de amostras', reuniao: 'Reunião', informacao: 'Pedido de informação' };
const WF_OPP_TXT = { aberta: 'Oportunidade aberta', ganha: 'Oportunidade ganha', perdida: 'Oportunidade perdida' };

function wfAcerta(q, partes) {
  const n = String(q || '').trim().toLowerCase();
  if (!n) return true;
  return partes.some(p => String(p || '').toLowerCase().includes(n));
}

function wfLinhaVista(vista, row, i) {
  const base = {
    id: `${vista}-${row.email || row.empresa || i}`,
    em: row.em || null,
    nome: row.nome || null,
    empresa: row.empresa || null,
    email: row.email || null,
  };
  if (vista === 'oportunidades') {
    return { ...base, titulo: WF_OPP_TXT[row.estado] || 'Oportunidade', detalhe: row.fase ? `Fase ${row.fase} no Gestor` : null };
  }
  if (vista === 'leads') {
    return { ...base, titulo: WF_PEDIDO_TXT[row.pedido] || 'Lead', detalhe: row.assunto || null };
  }
  if (vista === 'passado') return { ...base, titulo: 'Passou ao comercial', detalhe: null };
  return { ...base, titulo: 'Resposta humana', detalhe: row.assunto || null };
}

function wfEstadoInscrito(c) {
  if (!c) return '';
  if (c.motivo === 'cancelado') return 'Cancelou a sequência';
  if (c.motivo === 'resposta') return 'Saiu depois de responder';
  if (c.motivo === 'empresa_com_lead') return 'Parou porque a empresa gerou uma lead';
  if (c.motivo === 'passado_comercial') return 'Parou porque passou ao comercial';
  if (c.motivo === 'segmento') return 'Parou porque o segmento mudou';
  if (c.estado === 'concluido') return 'Concluído';
  if (c.estado === 'activo') {
    const n = Number(c.passo);
    if (Number.isInteger(n) && n > 0) return `Activo · email ${n} enviado`;
    return 'Activo · à espera do primeiro email';
  }
  return c.estado || 'No workflow';
}

function WfHistorico({ dados, campId, vista, onVista }) {
  const [filtro, setFiltro] = React.useState('tudo');
  const [q, setQ] = React.useState('');
  const [ficha, setFicha] = React.useState(null);
  const [aAbrir, setAAbrir] = React.useState(false);
  const [erroFicha, setErroFicha] = React.useState(null);
  const [emailVisto, setEmailVisto] = React.useState(null);
  const [contactos, setContactos] = React.useState([]);
  const [totalContactos, setTotalContactos] = React.useState(0);
  const [aProcurar, setAProcurar] = React.useState(false);
  const [erroProcura, setErroProcura] = React.useState(null);
  React.useEffect(() => {
    const termo = q.trim();
    if (termo.length < 2 || !campId) {
      setContactos([]);
      setTotalContactos(0);
      setAProcurar(false);
      setErroProcura(null);
      return undefined;
    }
    let vivo = true;
    setAProcurar(true);
    const t = setTimeout(async () => {
      try {
        const r = await fetch(`/api/marketing/activacao/workflow/contactos?campanha_id=${encodeURIComponent(campId)}&q=${encodeURIComponent(termo)}`);
        const d = await r.json();
        if (!vivo) return;
        if (!r.ok) throw new Error(d.error || 'Não consegui procurar nos contactos.');
        setContactos(Array.isArray(d.contactos) ? d.contactos : []);
        setTotalContactos(Number(d.total) || 0);
        setErroProcura(null);
      } catch (e) {
        if (!vivo) return;
        setContactos([]);
        setTotalContactos(0);
        setErroProcura(e.message);
      } finally {
        if (vivo) setAProcurar(false);
      }
    }, 250);
    return () => { vivo = false; clearTimeout(t); };
  }, [q, campId]);
  const linhas = Array.isArray(dados?.historico) && dados.historico.length
    ? dados.historico
    : (dados?.historial || []).map(h => ({
      id: h.id,
      em: h.em,
      tipo: h.accao,
      rotulo: h.accao,
      email: h.email || null,
      nome: h.nome || null,
      empresa: h.empresa || null,
      detalhe: h.detalhe?.sdr_nome || null,
    }));
  const activo = WF_HIST_FILTROS.find(f => f.id === filtro) || WF_HIST_FILTROS[0];
  const vistas = linhas.filter(h => (!activo.tipos || activo.tipos.includes(h.tipo)) && wfAcerta(q, [h.nome, h.email, h.empresa, h.detalhe, h.assunto, h.rotulo]));
  const daVista = vista
    ? (dados?.listas?.[vista] || []).map((row, i) => wfLinhaVista(vista, row, i)).filter(h => wfAcerta(q, [h.nome, h.email, h.empresa, h.detalhe, h.titulo]))
    : null;
  const mostradas = daVista || vistas;
  const fmt = (iso) => {
    if (!iso) return '';
    return new Date(iso).toLocaleString('pt-PT', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' });
  };
  const pessoa = (h) => {
    const nome = String(h.nome || '').trim();
    const empresa = String(h.empresa || '').trim();
    const email = String(h.email || '').trim();
    if (nome && empresa) return `${nome} · ${empresa}`;
    if (nome) return nome;
    if (empresa && email) return `${empresa} · ${email}`;
    if (empresa) return empresa;
    return email || '';
  };
  const abrirContacto = async (email) => {
    const addr = String(email || '').trim().toLowerCase();
    if (!addr || !campId) return;
    setAAbrir(true);
    setErroFicha(null);
    setFicha(null);
    try {
      const r = await fetch(`/api/marketing/activacao/workflow/contacto?campanha_id=${encodeURIComponent(campId)}&email=${encodeURIComponent(addr)}`);
      const d = await r.json();
      if (!r.ok) throw new Error(d.error || 'Não consegui abrir o contacto.');
      setFicha(d);
    } catch (e) {
      setErroFicha(e.message);
    } finally {
      setAAbrir(false);
    }
  };
  const fecharFicha = () => { setFicha(null); setErroFicha(null); setEmailVisto(null); };
  const verEmail = async (passo) => {
    const addr = String(ficha?.contacto?.email || '').trim().toLowerCase();
    if (!addr || !campId || !Number.isInteger(Number(passo))) return;
    setEmailVisto({ aCarregar: true, erro: null, dados: null });
    try {
      const r = await fetch(`/api/marketing/activacao/workflow/contacto/email?campanha_id=${encodeURIComponent(campId)}&email=${encodeURIComponent(addr)}&passo=${encodeURIComponent(passo)}`);
      const d = await r.json();
      if (!r.ok) throw new Error(d.error || 'Não consegui abrir o email.');
      setEmailVisto({ aCarregar: false, erro: null, dados: d });
    } catch (e) {
      setEmailVisto({ aCarregar: false, erro: e.message, dados: null });
    }
  };
  const estadoTxt = (c) => {
    if (!c) return '';
    if (c.motivo === 'cancelado') return 'Cancelou a sequência';
    if (c.motivo === 'resposta') return 'Saiu depois de responder';
    if (c.motivo === 'empresa_com_lead') return 'Parou porque a empresa gerou uma lead';
    if (c.motivo === 'passado_comercial') return 'Parou porque passou ao comercial';
    if (c.motivo === 'segmento') return 'Parou porque o segmento mudou';
    if (c.estado === 'concluido') return 'Concluído';
    if (c.estado === 'activo') return `Activo${c.passo != null ? ` · passo ${Number(c.passo) + 1}` : ''}`;
    return c.estado || '';
  };
  const modalFicha = (ficha || erroFicha || aAbrir) && ReactDOM.createPortal(
    <div onClick={fecharFicha} style={{ position: 'fixed', inset: 0, background: 'rgba(17,41,84,0.45)', zIndex: 300, display: 'flex', justifyContent: 'center', alignItems: 'flex-start', padding: 24, overflow: 'auto' }}>
      <div onClick={e => e.stopPropagation()} role="dialog" aria-label="Ficha do contacto" style={{ width: 'min(560px, 100%)', marginTop: 24, background: '#fff', borderRadius: 12, padding: 20, boxShadow: '0 24px 80px rgba(15,23,42,.28)' }}>
        {aAbrir && !ficha && <div style={{ fontSize: 13, color: '#64748b' }}>A abrir o contacto…</div>}
        {erroFicha && <div style={{ fontSize: 13, color: '#B42318' }}>{erroFicha}</div>}
        {ficha && (
          <React.Fragment>
            <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.06em', textTransform: 'uppercase', color: '#64748b' }}>Contacto na campanha</div>
            <div style={{ fontSize: 18, fontWeight: 700, color: '#112954', marginTop: 6 }}>{ficha.contacto?.nome || ficha.contacto?.email}</div>
            <div style={{ fontSize: 13, color: '#475569', marginTop: 4, lineHeight: 1.45 }}>
              {[ficha.contacto?.cargo, ficha.contacto?.empresa].filter(Boolean).join(' · ') || '—'}
            </div>
            <div style={{ fontSize: 12, color: '#64748b', marginTop: 4, overflowWrap: 'anywhere' }}>{ficha.contacto?.email}</div>
            <div style={{ marginTop: 14, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
              <div style={{ background: '#F8FAFC', border: '1px solid #ECEFF5', borderRadius: 8, padding: '10px 12px' }}>
                <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.06em', textTransform: 'uppercase', color: '#94A3B8' }}>Estado</div>
                <div style={{ fontSize: 13, fontWeight: 650, color: '#112954', marginTop: 4 }}>{estadoTxt(ficha.contacto)}</div>
              </div>
              <div style={{ background: '#F8FAFC', border: '1px solid #ECEFF5', borderRadius: 8, padding: '10px 12px' }}>
                <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.06em', textTransform: 'uppercase', color: '#94A3B8' }}>SDR</div>
                <div style={{ fontSize: 13, fontWeight: 650, color: '#112954', marginTop: 4 }}>{ficha.contacto?.sdr_nome || ficha.contacto?.sdr_email || '—'}</div>
              </div>
            </div>
            {(ficha.respostas || []).length > 0 && (
              <div style={{ marginTop: 16 }}>
                <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.06em', textTransform: 'uppercase', color: '#64748b' }}>Respostas</div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginTop: 8 }}>
                  {ficha.respostas.map((r, i) => (
                    <div key={i} style={{ border: '1px solid #ECEFF5', borderRadius: 8, padding: '10px 12px', background: r.automatica ? '#F8FAFC' : '#fff' }}>
                      <div style={{ fontSize: 11, color: '#64748b' }}>{fmt(r.em)} · {r.automatica ? 'Ausência automática' : 'Resposta humana'}</div>
                      {r.assunto ? <div style={{ fontSize: 13, fontWeight: 650, color: '#112954', marginTop: 4 }}>{r.assunto}</div> : null}
                      {r.preview ? <div style={{ fontSize: 13, color: '#475569', lineHeight: 1.45, marginTop: 4, whiteSpace: 'pre-wrap' }}>{r.preview}</div> : null}
                    </div>
                  ))}
                </div>
              </div>
            )}
            <div style={{ marginTop: 16 }}>
              <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.06em', textTransform: 'uppercase', color: '#64748b' }}>Linha do tempo</div>
              <div style={{ marginTop: 8, border: '1px solid #ECEFF5', borderRadius: 8, maxHeight: 280, overflowY: 'auto' }}>
                {(ficha.timeline || []).length === 0 && (
                  <div style={{ padding: 12, fontSize: 12, color: '#94A3B8' }}>Ainda sem movimentos neste contacto.</div>
                )}
                {(ficha.timeline || []).map((t, i) => (
                  <div key={i} style={{ display: 'grid', gridTemplateColumns: '130px 1fr', gap: 10, padding: '10px 12px', borderBottom: '1px solid #ECEFF5', fontSize: 12 }}>
                    <span style={{ color: '#64748b' }}>{fmt(t.em)}</span>
                    <div>
                      <div style={{ fontWeight: 650, color: '#112954' }}>{WF_ACCAO[t.tipo] || t.rotulo || t.tipo}</div>
                      {t.assunto || t.detalhe ? <div style={{ color: '#64748b', marginTop: 2, lineHeight: 1.35 }}>{t.assunto || t.detalhe}</div> : null}
                      {t.preview && t.assunto ? <div style={{ color: '#475569', marginTop: 4, lineHeight: 1.4 }}>{t.preview}</div> : null}
                      {Number.isInteger(t.passo) && (
                        <button type="button" onClick={() => verEmail(t.passo)} style={{ marginTop: 6, padding: 0, border: 0, background: 'transparent', color: '#3859D0', fontSize: 12, fontWeight: 650, fontFamily: 'inherit', cursor: 'pointer' }}>Ver o email</button>
                      )}
                    </div>
                  </div>
                ))}
              </div>
            </div>
          </React.Fragment>
        )}
        <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 16 }}>
          <button type="button" onClick={fecharFicha} style={{ height: 36, padding: '0 14px', borderRadius: 6, border: '1px solid #E3E6EB', background: '#fff', color: '#112954', fontSize: 13, fontWeight: 650, fontFamily: 'inherit', cursor: 'pointer' }}>Fechar</button>
        </div>
      </div>
    </div>,
    document.body
  );
  const modalEmail = emailVisto && ReactDOM.createPortal(
    <div onClick={() => setEmailVisto(null)} style={{ position: 'fixed', inset: 0, background: 'rgba(17,41,84,0.45)', zIndex: 340, display: 'flex', justifyContent: 'center', alignItems: 'flex-start', padding: 24, overflow: 'auto' }}>
      <div onClick={e => e.stopPropagation()} role="dialog" aria-label="Email que a pessoa recebeu" style={{ width: 'min(760px, 100%)', marginTop: 24, background: '#fff', borderRadius: 12, boxShadow: '0 24px 80px rgba(15,23,42,.28)', display: 'flex', flexDirection: 'column', maxHeight: 'calc(100vh - 48px)', overflow: 'hidden' }}>
        <div style={{ padding: '16px 16px 12px', borderBottom: '1px solid #ECEFF5', flexShrink: 0 }}>
          <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.06em', textTransform: 'uppercase', color: '#64748b' }}>Email que recebeu</div>
          {emailVisto.aCarregar && <div style={{ marginTop: 8, fontSize: 13, color: '#64748b' }}>A abrir o email…</div>}
          {emailVisto.erro && <div style={{ marginTop: 8, fontSize: 13, color: '#B42318', lineHeight: 1.45 }}>{emailVisto.erro}</div>}
          {emailVisto.dados && (
            <React.Fragment>
              <div style={{ fontSize: 16, fontWeight: 700, color: '#112954', marginTop: 6, lineHeight: 1.35 }}>{emailVisto.dados.assunto || 'Email'}</div>
              <div style={{ fontSize: 12, color: '#64748b', marginTop: 6, lineHeight: 1.45 }}>
                De {emailVisto.dados.de || '—'}
                <span style={{ color: '#CBD5E1' }}> · </span>
                Para {emailVisto.dados.para || ficha?.contacto?.email}
              </div>
              {emailVisto.dados.preheader ? <div style={{ fontSize: 13, color: '#475569', marginTop: 6, lineHeight: 1.45 }}>{emailVisto.dados.preheader}</div> : null}
            </React.Fragment>
          )}
        </div>
        {emailVisto.dados?.html && (
          <div style={{ overflow: 'auto', flex: '1 1 auto', minHeight: 0 }}>
            {window.CtdBrevoPreview
              ? <window.CtdBrevoPreview html={emailVisto.dados.html} empty="Sem HTML neste envio." />
              : <iframe title="Email recebido" sandbox="allow-same-origin" srcDoc={emailVisto.dados.html} style={{ width: '100%', minHeight: 480, border: 0, background: '#f4f4f4' }} />}
          </div>
        )}
        <div style={{ display: 'flex', justifyContent: 'flex-end', padding: 12, borderTop: '1px solid #ECEFF5', flexShrink: 0 }}>
          <button type="button" onClick={() => setEmailVisto(null)} style={{ height: 36, padding: '0 14px', borderRadius: 6, border: '1px solid #E3E6EB', background: '#fff', color: '#112954', fontSize: 13, fontWeight: 650, fontFamily: 'inherit', cursor: 'pointer' }}>Fechar</button>
        </div>
      </div>
    </div>,
    document.body
  );
  const chip = (id, label, on, click) => (
    <button
      key={id}
      type="button"
      onClick={click}
      style={{
        height: 28, padding: '0 10px', borderRadius: 6,
        border: `1px solid ${on ? '#3859D0' : '#E3E6EB'}`,
        background: on ? 'rgba(56,89,208,0.06)' : '#fff',
        color: on ? '#3859D0' : '#64748b',
        fontSize: 12, fontWeight: 650, fontFamily: 'inherit', cursor: 'pointer',
      }}
    >{label}</button>
  );
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
      <input
        value={q}
        onChange={e => setQ(e.target.value)}
        placeholder="Procurar nome, email ou empresa"
        style={{ height: 36, padding: '0 10px', border: '1px solid #E3E6EB', borderRadius: 6, fontSize: 13, fontFamily: 'inherit', color: '#112954', width: '100%', boxSizing: 'border-box' }}
      />
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
        {vista
          ? (
            <React.Fragment>
              {chip(vista, WF_VISTAS[vista] || 'Lista', true, () => {})}
              {chip('tudo', 'Todo o histórico', false, () => onVista && onVista(null))}
            </React.Fragment>
          )
          : WF_HIST_FILTROS.map(f => chip(f.id, f.label, filtro === f.id, () => setFiltro(f.id)))}
      </div>
      <div style={{ border: '1px solid #ECEFF5', borderRadius: 8, maxHeight: 'min(560px, calc(100vh - 260px))', overflowY: 'auto' }}>
        {erroProcura && (
          <div style={{ padding: '12px 16px', color: '#B42318', fontSize: 13, lineHeight: 1.45 }}>{erroProcura}</div>
        )}
        {contactos.map(c => {
          const nome = String(c.nome || '').trim();
          const empresa = String(c.empresa || '').trim();
          const cargo = String(c.cargo || '').trim();
          const quem = [empresa, cargo].filter(Boolean).join(' · ');
          return (
            <button
              key={`contacto-${c.email}`}
              type="button"
              onClick={() => abrirContacto(c.email)}
              style={{ display: 'grid', gridTemplateColumns: '150px 1fr', gap: 12, width: '100%', padding: '12px', border: 0, borderBottom: '1px solid #ECEFF5', background: 'transparent', fontSize: 12, color: '#112954', fontFamily: 'inherit', cursor: 'pointer', textAlign: 'left' }}
            >
              <span style={{ color: '#64748b', lineHeight: 1.4 }}>{fmt(c.entrou_em)}</span>
              <div style={{ minWidth: 0 }}>
                <div style={{ fontWeight: 650, lineHeight: 1.35 }}>{nome || c.email}</div>
                {quem ? <div style={{ marginTop: 2, color: '#112954', lineHeight: 1.35 }}>{quem}</div> : null}
                {nome ? <div style={{ marginTop: 2, color: '#64748b', overflowWrap: 'anywhere' }}>{c.email}</div> : null}
                <div style={{ marginTop: 2, color: '#64748b', lineHeight: 1.35 }}>{wfEstadoInscrito(c)}</div>
                <div style={{ marginTop: 4, fontSize: 11, fontWeight: 650, color: '#3859D0' }}>Abrir contacto</div>
              </div>
            </button>
          );
        })}
        {q.trim().length >= 2 && totalContactos > contactos.length && (
          <div style={{ padding: '8px 16px', color: '#64748b', fontSize: 12, lineHeight: 1.4 }}>
            {contactos.length.toLocaleString('pt-PT')} de {totalContactos.toLocaleString('pt-PT')} contactos. Afina a procura para ver os restantes.
          </div>
        )}
        {mostradas.length === 0 && contactos.length === 0 && (
          <div style={{ padding: 16, color: '#94A3B8', fontSize: 13, lineHeight: 1.45 }}>
            {aProcurar
              ? 'A procurar nos contactos do workflow…'
              : (q.trim()
                ? 'Sem resultados para esta procura.'
                : (vista
                  ? 'Ainda não há contactos nesta lista.'
                  : 'Ainda sem movimentos nesta vista. Depois de activar, aparecem aqui envios, aberturas, cliques, respostas, devoluções e saídas.'))}
          </div>
        )}
        {mostradas.map(h => {
          const titulo = h.titulo || WF_ACCAO[h.tipo] || WF_ACCAO[h.rotulo] || h.rotulo || h.tipo;
          const quem = pessoa(h);
          const extra = h.tipo === 'envio_lote' && h.fim && h.em
            ? `${fmt(h.em)} → ${fmt(h.fim)}`
            : null;
          const clicavel = !!h.email;
          const corpo = (
            <React.Fragment>
              <span style={{ color: '#64748b', lineHeight: 1.4 }}>{fmt(h.em)}</span>
              <div style={{ minWidth: 0, textAlign: 'left' }}>
                <div style={{ fontWeight: 650, lineHeight: 1.35 }}>{titulo}{h.n ? ` · ${h.n}` : ''}</div>
                {quem ? <div style={{ marginTop: 2, color: '#112954', lineHeight: 1.35 }}>{quem}</div> : null}
                {h.email && h.nome ? <div style={{ marginTop: 2, color: '#64748b', overflowWrap: 'anywhere' }}>{h.email}</div> : null}
                {(h.detalhe || extra) ? <div style={{ marginTop: 2, color: '#64748b', lineHeight: 1.35 }}>{h.detalhe || extra}</div> : null}
                {clicavel ? <div style={{ marginTop: 4, fontSize: 11, fontWeight: 650, color: '#3859D0' }}>Abrir contacto</div> : null}
              </div>
            </React.Fragment>
          );
          if (clicavel) {
            return (
              <button
                key={h.id}
                type="button"
                onClick={() => abrirContacto(h.email)}
                style={{ display: 'grid', gridTemplateColumns: '150px 1fr', gap: 12, width: '100%', padding: '12px', border: 0, borderBottom: '1px solid #ECEFF5', background: 'transparent', fontSize: 12, color: '#112954', fontFamily: 'inherit', cursor: 'pointer' }}
              >{corpo}</button>
            );
          }
          return (
            <div key={h.id} style={{ display: 'grid', gridTemplateColumns: '150px 1fr', gap: 12, padding: '12px', borderBottom: '1px solid #ECEFF5', fontSize: 12, color: '#112954' }}>
              {corpo}
            </div>
          );
        })}
      </div>
      {modalFicha}
      {modalEmail}
    </div>
  );
}

window.ActWorkflowEditor = ActWorkflowEditor;
