const TabEstrategiaFull = ({ campanha, userEmail, generating: parentGenerating, genStep: parentGenStep, onAction }) => {
  const [data, setData] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [error, setError] = React.useState('');
  const [editingCountry, setEditingCountry] = React.useState(null);
  const [editDraft, setEditDraft] = React.useState({});
  const [saving, setSaving] = React.useState(false);
  const stepStartRef = React.useRef(null);
  const [stepPct, setStepPct] = React.useState(0);
  // Elapsed time counter — obrigatório antes de qualquer early return (Rules of Hooks)
  const [elapsedSec, setElapsedSec] = React.useState(0);
  const elapsedRef = React.useRef(null);
  const bCol = _brandColor(campanha?.brand_slug);

  // Usar estado do parent para loading (unifica header CTA + botão interno)
  const isGenerating = !!(parentGenerating?.estrategia);
  const activeStep   = (parentGenStep?.key === 'estrategia' ? parentGenStep?.step : 0) || 0;
  const progressMsg  = parentGenStep?.key === 'estrategia' ? (parentGenStep?.message || '') : '';

  // Timer de progresso por step
  React.useEffect(() => {
    if (!isGenerating || activeStep <= 0) { setStepPct(0); return; }
    stepStartRef.current = Date.now();
    setStepPct(0);
  }, [activeStep]);

  React.useEffect(() => {
    if (!isGenerating || activeStep <= 0) return;
    const def = ESTRATEGIA_STEPS.find(s => s.step === activeStep);
    if (!def?.duration) return;
    const iv = setInterval(() => {
      const elapsed = Date.now() - (stepStartRef.current || Date.now());
      setStepPct(Math.min((elapsed / def.duration) * 90, 90));
    }, 80);
    return () => clearInterval(iv);
  }, [activeStep, isGenerating]);

  const totalSteps = ESTRATEGIA_STEPS.length;
  const doneSteps  = ESTRATEGIA_STEPS.filter(s => s.step < activeStep).length;
  const overallPct = isGenerating ? Math.round(((doneSteps + stepPct / 100) / totalSteps) * 100) : 0;

  const fetchStrategy = React.useCallback(() => {
    setLoading(true);
    campApiCall(`/api/marketing/campanhas/${campanha.id}/estrategia`)
      .then(d => { setData(d); setLoading(false); })
      .catch(e => { setError(e.message); setLoading(false); });
  }, [campanha?.id]);

  React.useEffect(() => { if (campanha?.id) fetchStrategy(); }, [campanha?.id, fetchStrategy]);

  React.useEffect(() => {
    if (isGenerating) {
      setElapsedSec(0);
      elapsedRef.current = setInterval(() => setElapsedSec(s => s + 1), 1000);
    } else {
      clearInterval(elapsedRef.current);
      setElapsedSec(0);
    }
    return () => clearInterval(elapsedRef.current);
  }, [isGenerating]);

  const approve = async () => {
    try {
      await campApiCall(`/api/marketing/campanhas/${campanha.id}/estrategia/approve`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ approver_email: userEmail, approver_name: userEmail?.split('@')[0] }),
      });
      fetchStrategy();
    } catch (e) { setError(e.message); }
  };

  if (loading) return <div style={{ padding: 40, textAlign: 'center', fontSize: 13, color: 'var(--fg-3)' }}>A carregar estratégia...</div>;

  const fmtElapsed = (s) => s < 60 ? `${s}s` : `${Math.floor(s/60)}m${String(s%60).padStart(2,'0')}s`;

  // ── Loading state de geração (controlado pelo parent via props) ──
  if (isGenerating) {
    const EsDot = ({ done, active }) => {
      if (done) return (
        <div style={{ width: 16, height: 16, borderRadius: '50%', background: 'rgba(21,128,61,.12)', border: '1.5px solid rgba(21,128,61,.3)', flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
          <svg width="8" height="8" viewBox="0 0 10 10" fill="none"><polyline points="1.5,5 4,7.5 8.5,2.5" stroke="#15803d" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/></svg>
        </div>
      );
      if (active) return (
        <div style={{ width: 16, height: 16, borderRadius: '50%', border: '2px solid #5B43C5', flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', animation: 'espin 1.4s linear infinite' }}>
          <div style={{ width: 6, height: 6, borderRadius: '50%', background: '#5B43C5' }} />
        </div>
      );
      return <div style={{ width: 16, height: 16, borderRadius: '50%', border: '1.5px solid #e2e8f0', flexShrink: 0 }} />;
    };
    return (
      <div style={{ background: 'var(--bg-elev,#fff)', border: '1px solid var(--border,#e2e8f0)', borderRadius: 10, padding: '20px 24px', display: 'flex', flexDirection: 'column', gap: 16 }}>
        <style>{`
          @keyframes cpulse { 0%,100%{opacity:1} 50%{opacity:.25} }
          @keyframes espin  { 0%{box-shadow:0 -5px 0 #5B43C5} 25%{box-shadow:5px 0 0 #5B43C5} 50%{box-shadow:0 5px 0 #5B43C5} 75%{box-shadow:-5px 0 0 #5B43C5} 100%{box-shadow:0 -5px 0 #5B43C5} }
        `}</style>
        {/* Aviso: não fechar */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '8px 12px', background: '#fffbeb', border: '1px solid #fde68a', borderRadius: 6 }}>
          <span style={{ fontSize: 13 }}>⚠</span>
          <span style={{ fontSize: 12, color: '#92400e', fontWeight: 500 }}>Não feches esta janela nem saias deste ecrã — a geração está em curso.</span>
        </div>
        {/* Barra de progresso + tempo */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
          <div style={{ flex: 1, height: 5, borderRadius: 99, background: 'var(--bg-app,#f5f6f8)', overflow: 'hidden' }}>
            <div style={{ height: '100%', borderRadius: 99, background: 'linear-gradient(90deg,#5B43C5,#7c6de0)', width: `${overallPct}%`, transition: 'width 0.15s linear' }} />
          </div>
          <span style={{ fontSize: 11, fontWeight: 700, color: '#5B43C5', fontFamily: 'monospace', flexShrink: 0, minWidth: 32 }}>{overallPct}%</span>
          <span style={{ fontSize: 11, fontWeight: 600, color: '#94a3b8', fontFamily: 'monospace', flexShrink: 0, minWidth: 36 }}>{fmtElapsed(elapsedSec)}</span>
        </div>
        {/* Tabela de steps */}
        <table style={{ width: '100%', borderCollapse: 'collapse', tableLayout: 'fixed' }}>
          <colgroup><col style={{ width: 28 }} /><col style={{ width: 220 }} /><col /></colgroup>
          <thead>
            <tr>
              <th style={{ padding: '4px 8px 4px 0', fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em', color: '#94a3b8', fontFamily: 'monospace', textAlign: 'left' }} />
              <th style={{ padding: '4px 8px', fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em', color: '#94a3b8', fontFamily: 'monospace', textAlign: 'left' }}>SECÇÃO</th>
              <th style={{ padding: '4px 8px', fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em', color: '#94a3b8', fontFamily: 'monospace', textAlign: 'left' }}>DETALHE</th>
            </tr>
          </thead>
          <tbody>
            {ESTRATEGIA_STEPS.map((s) => {
              const done   = activeStep > s.step;
              const active = activeStep === s.step;
              return (
                <tr key={s.step}>
                  <td style={{ padding: '8px 8px 8px 0', verticalAlign: 'middle' }}><EsDot done={done} active={active} /></td>
                  <td style={{ padding: '8px', fontSize: 13, fontWeight: active ? 600 : 400, color: done ? '#15803d' : active ? '#1d2e38' : '#94a3b8', fontFamily: 'var(--font-body,Inter,sans-serif)', borderBottom: '1px solid #f1f5f9' }}>{s.label}</td>
                  <td style={{ padding: '8px', fontSize: 12, color: active ? '#64748b' : '#cbd5e1', fontFamily: 'var(--font-body,Inter,sans-serif)', borderBottom: '1px solid #f1f5f9' }}>{s.detail}</td>
                </tr>
              );
            })}
          </tbody>
        </table>
        {error && <div style={{ padding: '8px 12px', background: 'rgba(220,38,38,.06)', border: '1px solid rgba(220,38,38,.2)', borderRadius: 6, fontSize: 12, color: '#dc2626' }}>{error}</div>}
      </div>
    );
  }

  const estrategia = data?.estrategia_json;
  const approved   = !!data?.estrategia_approved_at;

  // ── Estado vazio ──
  if (!estrategia) {
    return (
      <div style={{ background: 'var(--bg-surface,#fff)', borderRadius: 'var(--radius-xl,14px)', boxShadow: 'var(--shadow-card)', padding: '32px 28px', textAlign: 'center' }}>
        <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: 'var(--fg-3)', fontFamily: 'var(--font-mono)', marginBottom: 8 }}>Estratégia · fase 1</div>
        <div style={{ fontSize: 20, fontWeight: 700, color: 'var(--dd-primary-900,#112954)', fontFamily: 'var(--font-sans,Montserrat,sans-serif)', marginBottom: 10 }}>Ainda sem estratégia definida</div>
        <p style={{ fontSize: 13, color: 'var(--fg-2)', lineHeight: 1.6, maxWidth: 560, margin: '0 auto 20px', fontFamily: 'var(--font-text,Inter,sans-serif)' }}>
          Gerada 100% a partir do briefing aprovado. Adapta canais, tom, mensagem-chave e personas às preferências reais de cada mercado, enriquecendo com a KB <code style={{ fontSize: 11, background: 'var(--bg-sunken)', padding: '1px 5px', borderRadius: 3, fontFamily: 'var(--font-mono)' }}>market_preferences</code>. Respeita os guardrails do bloco 5 e alimenta todas as fases seguintes.
        </p>
        <button onClick={() => onAction && onAction('generateEstrategia')} disabled={isGenerating} className="btn btn-ai" style={{ padding: '10px 24px', fontSize: 13 }}>
          {isGenerating ? (progressMsg || 'A gerar...') : 'Gerar Estratégia →'}
        </button>
        {error && <div style={{ marginTop: 14, fontSize: 12, color: '#dc2626' }}>{error}</div>}
      </div>
    );
  }

  // ── Edição inline por mercado ──
  const startEdit = (m) => {
    setEditingCountry(m.country);
    setEditDraft({ tom: m.tom || '', personas_priorizadas: m.personas_priorizadas ? JSON.parse(JSON.stringify(m.personas_priorizadas)) : [] });
  };
  const saveEdit = async () => {
    setSaving(true);
    try {
      await campApiCall(`/api/marketing/campanhas/${campanha.id}/estrategia`, {
        method: 'PATCH', body: JSON.stringify({ country: editingCountry, ...editDraft }),
      });
      setEditingCountry(null);
      fetchStrategy();
    } catch (e) { setError(e.message); }
    setSaving(false);
  };

  // ── Renderização estratégia por mercado ──
  const markets = estrategia.markets || [];

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
      {/* Header status + actions */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 14px', background: 'var(--bg-surface,#fff)', borderRadius: 'var(--radius-md,8px)', boxShadow: 'var(--shadow-card)' }}>
        <span style={{ fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em', padding: '3px 8px', borderRadius: 'var(--radius-xs,4px)', background: approved ? 'var(--green-100,#E1F7E6)' : 'var(--dd-blue-100,#EBEFF9)', color: approved ? 'var(--green-700,#1F8A52)' : 'var(--dd-primary-600,#3859D0)' }}>
          {approved ? 'Aprovada' : 'Gerada — aguarda aprovação'}
        </span>
        <span style={{ fontSize: 11, color: 'var(--fg-3)' }}>
          {markets.length} mercado{markets.length !== 1 ? 's' : ''} · gerado {data.estrategia_generated_at ? new Date(data.estrategia_generated_at).toLocaleString('pt-PT') : '—'}
        </span>
        {approved && data.estrategia_approved_at && (
          <span style={{ fontSize: 11, color: 'var(--green-700, #1F8A52)' }}>
            · aprovada {new Date(data.estrategia_approved_at).toLocaleString('pt-PT')}{data.estrategia_approved_by ? ` por ${data.estrategia_approved_by.split('@')[0]}` : ''}
          </span>
        )}
        <div style={{ marginLeft: 'auto', display: 'flex', gap: 6, alignItems: 'center' }}>
          <button
            onClick={() => onAction && onAction('generateEstrategia')}
            disabled={isGenerating}
            className="btn"
            style={{ height: 28, padding: '0 12px', fontSize: 12 }}
            title="Regenerar estratégia — invalida a aprovação"
          >Regenerar</button>
          {!approved && (
            <button onClick={approve} className="btn btn-ai" style={{ height: 28, padding: '0 14px', fontSize: 12 }}>
              Aprovar Estratégia
            </button>
          )}
        </div>
      </div>

      {error && <div style={{ padding: 12, background: 'rgba(220,38,38,.06)', border: '1px solid rgba(220,38,38,.2)', borderRadius: 8, fontSize: 12, color: '#dc2626' }}>{error}</div>}

      {/* Síntese executiva */}
      {estrategia.sintese_campanha && (() => {
        const sc = estrategia.sintese_campanha;
        const anc = sc.ancoras || {};
        return (
          <div style={{ background: 'var(--bg-surface,#fff)', borderRadius: 'var(--radius-xl,14px)', boxShadow: 'var(--shadow-card)', padding: '20px 24px', borderTop: `3px solid ${bCol}` }}>
            <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: bCol, fontFamily: 'var(--font-mono)', marginBottom: 14 }}>Síntese Executiva da Campanha</div>
            {/* Fio condutor */}
            {sc.fio_condutor && (
              <div style={{ background: bCol + '08', border: `1px solid ${bCol}30`, borderRadius: 8, padding: '12px 16px', marginBottom: 16 }}>
                <div style={{ fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em', color: bCol, fontFamily: 'var(--font-mono)', marginBottom: 4 }}>Fio Condutor · presente em todo o conteúdo</div>
                <div style={{ fontSize: 15, fontWeight: 700, color: 'var(--fg-1)', fontFamily: 'var(--font-sans,Montserrat,sans-serif)', lineHeight: 1.4, fontStyle: 'italic' }}>"{ct(sc.fio_condutor)}"</div>
              </div>
            )}
            {/* Objectivo com pontos numerados + Abordagem */}
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginBottom: 16 }}>
              {sc.objectivo_concreto && (() => {
                const paras = splitPoints(sc.objectivo_concreto);
                return (
                  <div>
                    <div style={{ fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em', color: 'var(--fg-3)', fontFamily: 'var(--font-mono)', marginBottom: 6 }}>Objectivo</div>
                    {paras.map(({ num, txt }, i) => (
                      <div key={i} style={{ display: 'flex', gap: 8, marginBottom: i < paras.length - 1 ? 6 : 0, alignItems: 'flex-start' }}>
                        {num && <span style={{ fontSize: 10, fontWeight: 700, color: bCol, fontFamily: 'var(--font-mono)', flexShrink: 0, minWidth: 16, paddingTop: 2 }}>{num}.</span>}
                        <div style={{ fontSize: 12, color: 'var(--fg-1)', lineHeight: 1.6 }}>{txt}</div>
                      </div>
                    ))}
                  </div>
                );
              })()}
              {sc.abordagem && (() => {
                const paras = splitPoints(sc.abordagem);
                return (
                  <div>
                    <div style={{ fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em', color: 'var(--fg-3)', fontFamily: 'var(--font-mono)', marginBottom: 6 }}>Como chegamos lá</div>
                    {paras.map(({ num, txt }, i) => (
                      <div key={i} style={{ display: 'flex', gap: 8, marginBottom: i < paras.length - 1 ? 6 : 0, alignItems: 'flex-start' }}>
                        {num && <span style={{ fontSize: 10, fontWeight: 700, color: bCol, fontFamily: 'var(--font-mono)', flexShrink: 0, minWidth: 16, paddingTop: 2 }}>{num}.</span>}
                        <div style={{ fontSize: 12, color: 'var(--fg-1)', lineHeight: 1.6 }}>{txt}</div>
                      </div>
                    ))}
                  </div>
                );
              })()}
            </div>
            {/* Âncoras */}
            {(anc.dor_dominante || anc.usp_principal || anc.persona_principal) && (
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 8, marginBottom: sc.diferenca_por_mercado ? 12 : 0 }}>
                {[
                  { label: 'Dor Dominante', val: anc.dor_dominante, col: '#dc2626' },
                  { label: 'USP Principal', val: anc.usp_principal, col: '#059669' },
                  { label: 'Persona Principal', val: anc.persona_principal, col: '#8b5cf6' },
                ].filter(x => x.val).map(({ label, val, col }) => (
                  <div key={label} style={{ padding: '8px 10px', background: col + '08', borderRadius: 6, borderLeft: `3px solid ${col}` }}>
                    <div style={{ fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.07em', color: col, fontFamily: 'var(--font-mono)', marginBottom: 3 }}>{label}</div>
                    <div style={{ fontSize: 11, color: 'var(--fg-1)', lineHeight: 1.4 }}>{ct(val)}</div>
                  </div>
                ))}
              </div>
            )}
            {sc.diferenca_por_mercado && (
              <div style={{ marginTop: 10, fontSize: 11, color: 'var(--fg-3)', fontStyle: 'italic', lineHeight: 1.5, paddingTop: 10, borderTop: '1px solid var(--border-1,#ECEFF5)' }}>
                <span style={{ fontWeight: 700, fontStyle: 'normal' }}>Mercados: </span>{ct(sc.diferenca_por_mercado)}
              </div>
            )}
          </div>
        );
      })()}

      {/* Oferta comercial */}
      {(() => {
        let offer = campanha?.commercial_offer || campanha?.briefing?.commercial_offer;
        if (!offer) return null;
        if (typeof offer === 'string') { try { offer = JSON.parse(offer); } catch { return null; } }
        if (!offer?.type || offer.type === 'sem_oferta_especifica') return null;
        const MECH_LABEL = { digirent: 'Digirent', printplan: 'PrintPlan', voucher: 'Voucher on Demand', direct_discount: 'Desconto Directo', trade_in: 'Trade-In' };
        const TIER_LABEL = { standard: 'Standard', premium: 'Premium', large: 'Large', enterprise: 'Enterprise', tailor_made: 'Tailor-Made' };
        const CTA_LABEL  = { demo_request: 'Pedido de demonstração', sample_request: 'Pedido de amostra', quote_request: 'Pedido de orçamento', call_back: 'Callback comercial' };
        return (
          <div style={{ background: 'var(--bg-surface,#fff)', borderRadius: 'var(--radius-md,8px)', boxShadow: 'var(--shadow-card)', padding: '14px 18px', borderLeft: `3px solid ${bCol}` }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: offer.details ? 8 : 0 }}>
              <span style={{ fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.07em', color: '#fff', background: bCol, borderRadius: 4, padding: '2px 7px', fontFamily: 'var(--font-mono)', flexShrink: 0 }}>Oferta Comercial</span>
              <span style={{ fontSize: 13, fontWeight: 700, color: 'var(--fg-1)', fontFamily: 'var(--font-text,Inter,sans-serif)' }}>{MECH_LABEL[offer.type] || offer.type}</span>
              {offer.tier && <span style={{ fontSize: 10, color: 'var(--fg-3)', fontFamily: 'var(--font-mono)', padding: '1px 6px', borderRadius: 3, background: 'var(--border-1,#ECEFF5)' }}>{TIER_LABEL[offer.tier] || offer.tier}</span>}
              {offer.primary_cta && <span style={{ fontSize: 11, color: 'var(--fg-2)', marginLeft: 4 }}>· CTA: {CTA_LABEL[offer.primary_cta] || offer.primary_cta}</span>}
              {offer.negotiable !== undefined && <span style={{ fontSize: 10, color: 'var(--fg-3)', marginLeft: 'auto', fontFamily: 'var(--font-mono)' }}>{offer.negotiable ? 'Negociável' : 'Valor fixo'}</span>}
            </div>
            {offer.details && <div style={{ fontSize: 12, color: 'var(--fg-2)', lineHeight: 1.6 }}>{ct(offer.details)}</div>}
          </div>
        );
      })()}

      {/* Notas gerais */}
      {estrategia.notas_gerais && (() => {
        const raw = ct(estrategia.notas_gerais);
        // Split por pontos numerados (1) (2) (3) ou quebras de linha
        const parts = raw.split(/\s*\((\d+)\)\s*/).reduce((acc, seg, i, arr) => {
          if (/^\d+$/.test(seg)) return acc; // é o número, skip
          const prev = arr[i - 1];
          const num = /^\d+$/.test(prev) ? prev : null;
          const txt = seg.trim();
          if (txt) acc.push({ num, txt });
          return acc;
        }, []);
        const paras = parts.length > 1 ? parts : raw.split(/\n+/).filter(Boolean).map(t => ({ num: null, txt: t }));
        return (
          <div style={{ background: 'var(--bg-surface,#fff)', borderLeft: `3px solid ${bCol}`, borderRadius: 'var(--radius-md,8px)', padding: '16px 20px', boxShadow: 'var(--shadow-card)' }}>
            <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.08em', textTransform: 'uppercase', color: bCol, fontFamily: 'var(--font-mono)', marginBottom: 12 }}>Notas cross-market</div>
            {paras.map(({ num, txt }, i) => (
              <div key={i} style={{ display: 'flex', gap: 10, marginBottom: i < paras.length - 1 ? 10 : 0, alignItems: 'flex-start' }}>
                {num && <span style={{ fontSize: 10, fontWeight: 700, color: bCol, fontFamily: 'var(--font-mono)', flexShrink: 0, minWidth: 18, paddingTop: 2 }}>{num}.</span>}
                <p style={{ margin: 0, fontSize: 13, color: 'var(--fg-1)', lineHeight: 1.7, fontFamily: 'var(--font-text,Inter,sans-serif)' }}>{txt}</p>
              </div>
            ))}
          </div>
        );
      })()}

      {/* Cards por mercado */}
      {markets.map((m, i) => {
        const confCol = m.confidence === 'high' ? 'var(--green-700,#1F8A52)' : m.confidence === 'medium' ? 'var(--pumpkin-700,#E96D17)' : 'var(--red-700,#B53B3B)';
        return (
          <div key={i} style={{ background: 'var(--bg-surface,#fff)', borderRadius: 'var(--radius-xl,14px)', boxShadow: 'var(--shadow-card)', padding: '20px 24px' }}>
            {/* Header país */}
            <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 16, paddingBottom: 12, borderBottom: '1px solid var(--border-1,#ECEFF5)' }}>
              <span style={{ fontSize: 11, fontWeight: 700, color: '#fff', background: bCol, borderRadius: 'var(--radius-xs,4px)', padding: '3px 8px', fontFamily: 'var(--font-mono)', letterSpacing: '0.04em' }}>{m.country}</span>
              <span style={{ fontSize: 16, fontWeight: 700, color: 'var(--dd-primary-900,#112954)', fontFamily: 'var(--font-sans,Montserrat,sans-serif)' }}>{m.language_variant || m.country}</span>
              <span style={{ fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.06em', color: confCol, padding: '2px 8px', border: `1px solid ${confCol}`, borderRadius: 'var(--radius-pill)' }}>
                {m.confidence} confidence
              </span>
              <button onClick={() => editingCountry === m.country ? setEditingCountry(null) : startEdit(m)}
                className="btn" style={{ marginLeft: 'auto', fontSize: 11, height: 26, padding: '0 10px' }}>
                {editingCountry === m.country ? 'Cancelar' : '✎ Editar'}
              </button>
            </div>

            {/* Formulário de edição inline */}
            {editingCountry === m.country && (
              <div style={{ marginBottom: 16, padding: '14px 16px', background: 'var(--bg-sunken,#f8fafc)', borderRadius: 8, border: '1px solid var(--border-1,#ECEFF5)' }}>
                <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.08em', textTransform: 'uppercase', color: 'var(--fg-3)', fontFamily: 'var(--font-mono)', marginBottom: 12 }}>Editar {m.country}</div>
                {/* Tom */}
                <div style={{ marginBottom: 12 }}>
                  <label style={{ fontSize: 10, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.06em', color: 'var(--fg-3)', fontFamily: 'var(--font-mono)', display: 'block', marginBottom: 4 }}>Tom</label>
                  <textarea value={editDraft.tom} onChange={e => setEditDraft(d => ({ ...d, tom: e.target.value }))}
                    rows={2} style={{ width: '100%', padding: '7px 10px', borderRadius: 6, border: '1px solid var(--border-1,#ECEFF5)', fontSize: 13, fontFamily: 'inherit', resize: 'vertical', boxSizing: 'border-box' }} />
                </div>
                {/* Personas */}
                <div style={{ marginBottom: 12 }}>
                  <label style={{ fontSize: 10, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.06em', color: 'var(--fg-3)', fontFamily: 'var(--font-mono)', display: 'block', marginBottom: 6 }}>Personas priorizadas</label>
                  {(editDraft.personas_priorizadas || []).map((p, j) => (
                    <div key={j} style={{ display: 'flex', gap: 6, marginBottom: 6, alignItems: 'center' }}>
                      <select value={p.peso} onChange={e => { const ps = [...editDraft.personas_priorizadas]; ps[j] = { ...p, peso: e.target.value }; setEditDraft(d => ({ ...d, personas_priorizadas: ps })); }}
                        style={{ fontSize: 11, padding: '4px 6px', borderRadius: 4, border: '1px solid var(--border-1)', background: 'var(--bg)', flexShrink: 0 }}>
                        <option value="primary">Primary</option>
                        <option value="secondary">Secondary</option>
                      </select>
                      <input value={p.nome} onChange={e => { const ps = [...editDraft.personas_priorizadas]; ps[j] = { ...p, nome: e.target.value }; setEditDraft(d => ({ ...d, personas_priorizadas: ps })); }}
                        placeholder="Nome da persona" style={{ flex: 1, fontSize: 12, padding: '4px 8px', borderRadius: 4, border: '1px solid var(--border-1)', fontFamily: 'inherit' }} />
                      <button onClick={() => { const ps = editDraft.personas_priorizadas.filter((_, k) => k !== j); setEditDraft(d => ({ ...d, personas_priorizadas: ps })); }}
                        style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#dc2626', fontSize: 14, padding: '0 4px' }}>×</button>
                    </div>
                  ))}
                  <button onClick={() => setEditDraft(d => ({ ...d, personas_priorizadas: [...(d.personas_priorizadas || []), { nome: '', peso: 'secondary', razao: '' }] }))}
                    className="btn" style={{ fontSize: 11, height: 26, padding: '0 10px', marginTop: 4 }}>+ Persona</button>
                </div>
                <div style={{ display: 'flex', gap: 6, justifyContent: 'flex-end' }}>
                  <button onClick={() => setEditingCountry(null)} className="btn" style={{ fontSize: 11 }}>Cancelar</button>
                  <button onClick={saveEdit} disabled={saving} className="btn btn-ai" style={{ fontSize: 11 }}>
                    {saving ? 'A guardar...' : '✓ Guardar'}
                  </button>
                </div>
              </div>
            )}

            {/* Personas (read-only) */}
            {editingCountry !== m.country && (m.personas_priorizadas || []).length > 0 && (
              <div style={{ marginBottom: 16 }}>
                <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.08em', textTransform: 'uppercase', color: 'var(--fg-3)', fontFamily: 'var(--font-mono)', marginBottom: 10 }}>Personas priorizadas</div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
                  {m.personas_priorizadas.map((p, j) => {
                    const isPrimary = p.peso === 'primary';
                    const cleanDash = (t) => t ? t.replace(/\u2014|\u2013/g, ',').replace(/ , /g, ', ').replace(/,{2,}/g, ',') : t;
                    return (
                      <div key={j} style={{ display: 'flex', gap: 0, alignItems: 'stretch', background: isPrimary ? bCol + '06' : 'var(--bg-sunken,#f8fafc)', borderRadius: 8, borderLeft: `3px solid ${isPrimary ? bCol : '#e2e8f0'}`, overflow: 'hidden' }}>
                        <div style={{ width: 72, flexShrink: 0, display: 'flex', alignItems: 'flex-start', justifyContent: 'center', paddingTop: 11, paddingBottom: 10 }}>
                          <span style={{ fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.06em', padding: '2px 6px', borderRadius: 4, background: isPrimary ? bCol : '#e2e8f0', color: isPrimary ? '#fff' : 'var(--fg-3)', fontFamily: 'var(--font-mono)', whiteSpace: 'nowrap' }}>{p.peso}</span>
                        </div>
                        <div style={{ flex: 1, minWidth: 0, padding: '10px 12px 10px 0' }}>
                          <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--fg-1)', fontFamily: 'var(--font-text,Inter,sans-serif)', lineHeight: 1.4, marginBottom: p.razao ? 5 : 0 }}>{cleanDash(p.nome)}</div>
                          {p.razao && <div style={{ fontSize: 12, color: 'var(--fg-2)', lineHeight: 1.65, fontFamily: 'var(--font-text,Inter,sans-serif)' }}>{cleanDash(p.razao)}</div>}
                        </div>
                      </div>
                    );
                  })}
                </div>
              </div>
            )}

            {/* Channel fit KB */}
            {(m.channel_fit?.length > 0 || m.sugestoes_alternativas?.length > 0) && (() => {
              const hasLow    = (m.channel_fit || []).some(ch => ch.fit === 'low');
              const hasSugest = (m.sugestoes_alternativas || []).length > 0;
              const fitCol = { high: '#059669', medium: '#d97706', low: '#dc2626', sem_dados: '#94a3b8' };
              const fitBg  = { high: '#f0fdf4', medium: '#fffbeb', low: '#fef2f2', sem_dados: '#f8fafc' };
              return (
                <div style={{ marginBottom: 16, borderRadius: 8, border: '1px solid var(--border-1,#ECEFF5)', background: 'var(--bg-sunken,#f8fafc)', padding: '12px 14px' }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 10 }}>
                    <span style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.07em', textTransform: 'uppercase', color: 'var(--fg-3)', fontFamily: 'var(--font-mono)' }}>
                      Fit de Canal · KB {m.kb_confidence ? `· ${m.kb_confidence}` : ''}
                    </span>
                    {hasLow && <span style={{ fontSize: 10, fontWeight: 700, color: '#dc2626', padding: '1px 6px', borderRadius: 4, background: '#fef2f2', border: '1px solid #fecaca' }}>⚠ Canal com baixa adopção</span>}
                  </div>

                  {/* Todos os canais aprovados */}
                  <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginBottom: hasSugest ? 10 : 0 }}>
                    {(m.channel_fit || []).map((ch, k) => (
                      <div key={k} style={{ display: 'flex', alignItems: 'flex-start', gap: 10 }}>
                        <div style={{ flex: 1 }}>
                          <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
                            <span style={{ fontSize: 11, fontWeight: 700, padding: '1px 7px', borderRadius: 4, background: fitBg[ch.fit], color: fitCol[ch.fit], fontFamily: 'var(--font-mono)', border: `1px solid ${fitCol[ch.fit]}30` }}>{ch.canal}</span>
                            <span style={{ fontSize: 11, fontWeight: 600, color: fitCol[ch.fit] }}>{ch.pct != null ? `${ch.pct}%` : 'sem dados'} adopção</span>
                            <span style={{ fontSize: 10, color: 'var(--fg-3)', fontStyle: 'italic' }}>· {ch.label}</span>
                          </div>
                          <div style={{ height: 4, background: '#e2e8f0', borderRadius: 2, overflow: 'hidden', maxWidth: 200 }}>
                            <div style={{ height: '100%', width: `${ch.pct || 0}%`, background: fitCol[ch.fit], borderRadius: 2, transition: 'width .3s' }} />
                          </div>
                          {ch.notes && <div style={{ fontSize: 11, color: '#64748b', marginTop: 4, lineHeight: 1.5 }}>{ct(ch.notes)}</div>}
                        </div>
                      </div>
                    ))}
                  </div>

                  {/* Sugestões alternativas */}
                  {hasSugest && (
                    <div style={{ borderTop: `1px solid ${hasLow ? '#fecaca' : '#fde68a'}`, paddingTop: 8, marginTop: 4 }}>
                      <div style={{ fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.07em', color: '#059669', fontFamily: 'var(--font-mono)', marginBottom: 6 }}>Canais com maior adopção neste mercado</div>
                      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
                        {(m.sugestoes_alternativas || []).map((s, k) => (
                          <div key={k} style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '5px 10px', borderRadius: 6, background: '#f0fdf4', border: '1px solid #bbf7d0' }}>
                            <span style={{ fontSize: 11, fontWeight: 700, color: '#059669', fontFamily: 'var(--font-mono)' }}>{s.canal}</span>
                            <span style={{ fontSize: 11, color: '#059669', fontWeight: 600 }}>{s.pct}%</span>
                          </div>
                        ))}
                      </div>
                    </div>
                  )}
                </div>
              );
            })()}

            {/* Channel mix */}
            {(m.channel_mix || []).length > 0 && (
              <div style={{ marginBottom: 16 }}>
                <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.08em', textTransform: 'uppercase', color: 'var(--fg-3)', fontFamily: 'var(--font-mono)', marginBottom: 8 }}>Channel mix · todos cobrem as 3 fases</div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
                  {m.channel_mix.map((ch, j) => {
                    const cCol = CANAL_COLORS[ch.canal] || bCol;
                    const f3 = ch.como_cobre_3_fases || {};
                    return (
                      <div key={j} style={{ padding: '10px 12px', background: 'var(--bg-sunken,#f8fafc)', borderRadius: 'var(--radius-md,8px)', borderLeft: `3px solid ${cCol}` }}>
                        {/* Header canal */}
                        <div style={{ display: 'flex', gap: 8, alignItems: 'center', marginBottom: f3.awareness || ch.papel_no_mix ? 8 : 0 }}>
                          <span style={{ fontSize: 11, fontWeight: 700, padding: '2px 8px', borderRadius: 'var(--radius-xs,4px)', background: cCol + '18', color: cCol, fontFamily: 'var(--font-mono)' }}>{ch.canal}</span>
                          {ch.budget_pct != null && <span style={{ fontSize: 12, fontWeight: 700, color: 'var(--fg-1)', fontFamily: 'var(--font-mono)' }}>{ch.budget_pct}%</span>}
                          {Array.isArray(ch.formatos) && ch.formatos.length > 0 && <span style={{ fontSize: 11, color: 'var(--fg-3)' }}>· {ch.formatos.join(', ')}</span>}
                          {ch.papel_no_mix && <span style={{ fontSize: 11, color: 'var(--fg-2)', fontStyle: 'italic', flex: 1, textAlign: 'right' }}>{ct(ch.papel_no_mix)}</span>}
                        </div>
                        {/* 3 fases */}
                        {(f3.awareness || f3.consideration || f3.decision) && (
                          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 6 }}>
                            {[['awareness','A','#3859D0'], ['consideration','C','#8b5cf6'], ['decision','D','#059669']].map(([key, abbr, col]) => f3[key] && (
                              <div key={key} style={{ padding: '6px 8px', background: col + '08', borderRadius: 6, borderTop: `2px solid ${col}` }}>
                                <div style={{ fontSize: 8, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.07em', color: col, fontFamily: 'var(--font-mono)', marginBottom: 3 }}>{key}</div>
                                <div style={{ fontSize: 11, color: 'var(--fg-2)', lineHeight: 1.4 }}>{ct(f3[key])}</div>
                              </div>
                            ))}
                          </div>
                        )}
                        {ch.razao && <div style={{ fontSize: 10, color: 'var(--fg-3)', fontStyle: 'italic', marginTop: 4 }}>{ct(ch.razao)}</div>}
                      </div>
                    );
                  })}
                </div>
              </div>
            )}

            {/* Tom (read-only quando não editando) */}
            {editingCountry !== m.country && m.tom && (
              <div style={{ marginBottom: 16 }}>
                <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.08em', textTransform: 'uppercase', color: 'var(--fg-3)', fontFamily: 'var(--font-mono)', marginBottom: 6 }}>Tom</div>
                <div style={{ fontSize: 13, color: 'var(--fg-1)', fontStyle: 'italic', fontFamily: 'var(--font-sans,Montserrat,sans-serif)', lineHeight: 1.55 }}>{ct(m.tom)}</div>
              </div>
            )}

            {/* Mensagem unificada — 3 fases presentes em todo o conteúdo */}
            {(m.mensagem_unificada || m.funil) && (() => {
              const mu = m.mensagem_unificada || m.funil;
              const decisionKey = mu.decision || mu.conversion;
              const phaseColors = { awareness: '#3859D0', consideration: '#8b5cf6', decision: '#059669', conversion: '#059669' };
              return (
                <div style={{ marginBottom: 16 }}>
                  <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.08em', textTransform: 'uppercase', color: 'var(--fg-3)', fontFamily: 'var(--font-mono)', marginBottom: 8 }}>
                    Mensagem unificada · presente em todo o conteúdo
                  </div>
                  <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: 10 }}>
                    {[
                      { key: 'awareness',    label: 'Awareness',     col: '#3859D0', val: mu.awareness },
                      { key: 'consideration',label: 'Consideration', col: '#8b5cf6', val: mu.consideration },
                      { key: 'decision',     label: 'Decision',      col: '#059669', val: decisionKey },
                    ].filter(p => p.val).map(({ key, label, col, val }) => (
                      <div key={key} style={{ padding: '10px 12px', background: col + '08', borderRadius: 'var(--radius-md,8px)', borderTop: `2px solid ${col}` }}>
                        <div style={{ fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em', color: col, fontFamily: 'var(--font-mono)', marginBottom: 5 }}>{label}</div>
                        <div style={{ fontSize: 12, color: 'var(--fg-1)', lineHeight: 1.5, fontFamily: 'var(--font-text,Inter,sans-serif)' }}>{ct(val)}</div>
                      </div>
                    ))}
                  </div>
                </div>
              );
            })()}

            {/* KPIs */}
            {Array.isArray(m.kpis) && m.kpis.length > 0 && (
              <div style={{ marginBottom: 16 }}>
                <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.08em', textTransform: 'uppercase', color: 'var(--fg-3)', fontFamily: 'var(--font-mono)', marginBottom: 8 }}>KPIs</div>
                <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
                  {m.kpis.map((k, j) => (
                    <span key={j} style={{ fontSize: 11, padding: '3px 10px', borderRadius: 'var(--radius-pill)', background: 'var(--dd-blue-100,#EBEFF9)', color: 'var(--dd-primary-900,#112954)', fontFamily: 'var(--font-text,Inter,sans-serif)' }}>
                      {k.metrica}: <strong>{k.target}</strong>{k.canal ? ` (${k.canal})` : ''}
                    </span>
                  ))}
                </div>
              </div>
            )}

            {/* Market evidence */}
            {m.market_evidence && (
              <div style={{ padding: '10px 14px', background: 'var(--dd-blue-50,#F9FAFF)', borderLeft: `3px solid ${bCol}`, borderRadius: '0 8px 8px 0' }}>
                <div style={{ fontSize: 9, fontWeight: 700, letterSpacing: '0.08em', textTransform: 'uppercase', color: bCol, fontFamily: 'var(--font-mono)', marginBottom: 4 }}>Baseado na KB</div>
                <div style={{ fontSize: 12, color: 'var(--fg-2)', lineHeight: 1.55, fontFamily: 'var(--font-text,Inter,sans-serif)' }}>{ct(m.market_evidence)}</div>
              </div>
            )}
          </div>
        );
      })}
    </div>
  );
};

// ── TabBriefingFull — resumo visual do briefing aprovado ──────────────────────
const TabBriefingFull = ({ campanha }) => {
  const [heroImage,  setHeroImage]  = React.useState(null);
  const [summaries,  setSummaries]  = React.useState(null);
  const [loading,    setLoading]    = React.useState(false);
  const bCol = _brandColor(campanha?.brand_slug || campanha?.briefing?.brand_slug);

  // Usar campanha.briefing directamente (já incluído no GET /campanhas/:id)
  // Mapear estrutura flat para block1/block2/... que o componente espera
  const briefing = React.useMemo(() => {
    if (!campanha?.briefing) return null;
    const f = campanha.briefing;
    return {
      ...f,
      block1: { commercial_name: f.commercial_name, elevator_pitch: f.elevator_pitch, usps: f.usps, geo_markets: f.geo_markets, applications: f.applications, certifications: f.certifications },
      block2: { decision_maker: f.decision_maker, end_user: f.end_user, pain_points: f.pain_points, motivators: f.motivators, objections: f.objections, purchase_trigger: f.purchase_trigger },
      block3: { positioning_narrative: f.positioning_narrative, differentiation_args: f.differentiation_args, competitors: f.competitors, market_trends: f.market_trends },
      block4: { objective: f.objective, channels: f.channels, tone: f.tone, key_message: f.key_message, timeline_start: f.timeline_start, timeline_end: f.timeline_end, commercial_offer: f.commercial_offer },
      block5: { prohibited_claims: f.prohibited_claims, off_brand_messages: f.off_brand_messages },
      brand_name: f.brand_name || campanha?.brand_name,
      brand_slug: f.brand_slug || campanha?.brand_slug,
    };
  }, [campanha?.briefing, campanha?.brand_name, campanha?.brand_slug]);

  // Buscar hero image e summaries em background (não bloqueiam render)
  React.useEffect(() => {
    if (!campanha?.id) return;
    campApiCall(`/api/marketing/campanhas/${campanha.id}/product-image`).then(d => setHeroImage(d?.image?.url || null)).catch(() => {});
    campApiCall(`/api/marketing/campanhas/${campanha.id}/briefing-summary`).then(d => setSummaries(d || {})).catch(() => {});
  }, [campanha?.id]);

  const str  = (v) => v || null;
  const list = (v) => Array.isArray(v) && v.length ? v : null;
  const joinNarr = (items, prefix) => {
    if (!items || !items.length) return null;
    const clean = items.map(i => typeof i === 'object' ? (i.name || i.label || '') : i).filter(Boolean);
    if (!clean.length) return null;
    if (clean.length === 1) return `${prefix} ${clean[0]}.`;
    return `${prefix} ${clean.slice(0, -1).join(', ')} e ${clean[clean.length - 1]}.`;
  };

  const BSection = ({ title, children }) => (
    <div style={{ background: '#fff', border: '1px solid var(--border, #e2e8f0)', borderRadius: 10, padding: '20px 24px', display: 'flex', flexDirection: 'column', gap: 14 }}>
      <div style={{ fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em', color: 'var(--text-dim, #94a3b8)', fontFamily: 'var(--font-mono, monospace)', paddingBottom: 10, borderBottom: '1px solid var(--border, #e2e8f0)' }}>{title}</div>
      {children}
    </div>
  );

  const BRow = ({ label, value }) => value ? (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
      <div style={{ fontSize: 10, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.07em', color: 'var(--text-dim, #94a3b8)', fontFamily: 'var(--font-mono, monospace)' }}>{label}</div>
      <div style={{ fontSize: 13.5, color: 'var(--text, #283252)', lineHeight: 1.65, fontFamily: 'var(--font-body, Inter, sans-serif)' }}>{value}</div>
    </div>
  ) : null;

  if (!campanha) return <div style={{ fontSize: 13, color: 'var(--text-muted)', padding: 24 }}>A carregar…</div>;
  if (!briefing) return <div style={{ fontSize: 13, color: 'var(--text-muted)', padding: 24 }}>Briefing não encontrado.</div>;

  const lSt = { fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em', color: 'var(--text-dim, #94a3b8)', fontFamily: 'var(--font-mono, monospace)', marginBottom: 3 };
  const vSt = { fontSize: 13, color: 'var(--text, #1d2e38)', lineHeight: 1.55, fontFamily: 'var(--font-body, Inter, sans-serif)' };
  const OBJ  = { awareness: 'Awareness', lead_gen: 'Geração de leads', conversion: 'Conversão', retention: 'Retenção / Upsell' };
  const MECH = { digirent: 'Digirent', printplan: 'PrintPlan', voucher: 'Voucher on Demand', direct_discount: 'Desconto Directo', trade_in: 'Trade-In' };
  const TIER = { standard: 'Standard', premium: 'Premium', tailor_made: 'Tailor-Made', tailor_made_rappel: 'TM Rappel', tailor_made_renda: 'TM Renda' };
  const CHAN_L = { meta_ads:'Meta Ads', linkedin_ads:'LinkedIn', google_ads_search:'Google Search', google_ads_display:'Google Display', email:'Email', whatsapp:'WhatsApp', website:'Website', muppi_led:'LED', linkedin:'LinkedIn', google_ads:'Google Ads', instagram:'Instagram', facebook:'Facebook' };
  const MECANICA_COLOR = { digirent:'#7C3AED', printplan:'#D97706', voucher:'#0EA5E9', direct_discount:'#DC2626', trade_in:'#059669' };

  const b1 = briefing.block1 || {};
  const b2 = briefing.block2 || {};
  const b4 = briefing.block4 || {};
  const b5 = briefing.block5 || {};

  const hasRestrictions = [b5.prohibited_claims, b5.off_brand_messages, b5.competitors_not_name, b5.out_of_scope_markets, b5.other_restrictions].some(v => Array.isArray(v) && v.length);

  const productName = b1.commercial_name || campanha?.titulo || '';
  const pitch       = b1.elevator_pitch || '';
  const usps        = (b1.usps || []).filter(Boolean).slice(0, 3);
  const decisor     = b2.decision_maker || '';
  const pain        = (b2.pain_points || []).filter(Boolean)[0] || '';
  const channels    = (b4.channels || []).map(ch => CHAN_L[ch] || ch).filter(Boolean);
  const period      = [b4.timeline_start, b4.timeline_end].filter(Boolean).join(' → ');
  const keyMsg      = b4.key_message || '';
  const offer       = b4.commercial_offer
    ? (typeof b4.commercial_offer === 'string' ? JSON.parse(b4.commercial_offer) : b4.commercial_offer)
    : null;

  const restrictions = [
    ...(b5.prohibited_claims    || []).filter(Boolean),
    ...(b5.off_brand_messages   || []).filter(Boolean),
    ...(b5.competitors_not_name || []).filter(Boolean),
  ];

  const Chip = ({ children, color }) => (
    <span style={{
      fontSize: 11, fontWeight: 500, padding: '3px 10px', borderRadius: 99,
      background: (color || bCol) + '12', color: color || bCol,
      fontFamily: 'var(--font-body, Inter, sans-serif)', whiteSpace: 'nowrap',
    }}>{children}</span>
  );

  const mSt = { fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em', color: 'var(--text-dim, #94a3b8)', fontFamily: 'var(--font-mono)', marginBottom: 2 };

  const capSt = { fontSize: 10, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.06em', color: 'var(--fg-3, #94A4C4)', fontFamily: 'var(--font-text, Inter, sans-serif)', marginBottom: 4 };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>

      {/* ── CARD PRODUTO ── */}
      <div style={{ background: 'var(--bg-surface, #fff)', borderRadius: 'var(--radius-xl, 14px)', boxShadow: 'var(--shadow-card)', overflow: 'hidden', display: 'flex' }}>

        {/* Coluna imagem — estreita */}
        <div style={{ width: 180, flexShrink: 0, background: 'var(--dd-blue-100, #EBEFF9)', display: 'flex', alignItems: 'center', justifyContent: 'center', borderRight: '1px solid var(--border-1, #ECEFF5)', padding: 16 }}>
          {heroImage
            ? <img src={heroImage} alt={productName} style={{ maxWidth: '100%', maxHeight: 140, objectFit: 'contain', display: 'block' }} />
            : <span style={{ fontSize: 10, color: 'var(--fg-3)', fontFamily: 'var(--font-mono)' }}>SEM IMAGEM</span>
          }
        </div>

        {/* Coluna texto */}
        <div style={{ flex: 1, padding: '20px 24px', display: 'flex', flexDirection: 'column', gap: 12, justifyContent: 'center' }}>

          {/* Nome + pitch truncado */}
          <div style={{ borderLeft: `3px solid ${bCol}`, paddingLeft: 12 }}>
            <div style={{ fontSize: 16, fontWeight: 700, color: 'var(--dd-primary-900, #112954)', fontFamily: 'var(--font-sans, Montserrat, sans-serif)', lineHeight: 1.25, marginBottom: 4 }}>
              {productName}
            </div>
            {pitch && (
              <div style={{ fontSize: 12, color: 'var(--fg-2, #404968)', fontFamily: 'var(--font-text, Inter, sans-serif)', lineHeight: 1.5, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>
                {pitch}
              </div>
            )}
          </div>

          {/* Chips */}
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
            {channels.slice(0, 5).map((ch, i) => <Chip key={i}>{ch}</Chip>)}
            {period && <Chip color="var(--fg-3)">{period}</Chip>}
            {offer && <Chip color={MECANICA_COLOR[offer.type] || 'var(--fg-3)'}>{MECH[offer.type] || offer.type}{offer.tier ? ` · ${TIER[offer.tier] || offer.tier}` : ''}</Chip>}
          </div>
        </div>
      </div>

      {/* ── OFERTA COMERCIAL ── */}
      {offer && offer.type && offer.type !== 'sem_oferta_especifica' && (() => {
        const offerColor = MECANICA_COLOR[offer.type] || bCol;
        const tierLabels = { standard: 'Standard', premium: 'Premium', large: 'Large', enterprise: 'Enterprise', tailor_made: 'Tailor-Made' };
        const ctaLabels  = { demo_request: 'Pedido de demonstração', sample_request: 'Pedido de amostra', quote_request: 'Pedido de orçamento', call_back: 'Callback comercial' };
        return (
          <div style={{ background: 'var(--bg-surface,#fff)', borderRadius: 'var(--radius-xl,14px)', boxShadow: 'var(--shadow-card)', padding: '16px 20px', borderLeft: '4px solid #3859D0' }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12, paddingBottom: 10, borderBottom: '1px solid var(--border-1,#ECEFF5)' }}>
              <span style={{ fontSize: 10, fontWeight: 700, color: '#fff', background: '#3859D0', borderRadius: 'var(--radius-xs,4px)', padding: '2px 8px', fontFamily: 'var(--font-mono)', letterSpacing: '0.04em', flexShrink: 0 }}>OFERTA COMERCIAL</span>
              <span style={{ fontSize: 13, fontWeight: 700, color: '#3859D0', fontFamily: 'var(--font-display, Montserrat, sans-serif)' }}>{MECH[offer.type] || offer.type}</span>
              {offer.tier && <span style={{ fontSize: 10, fontWeight: 600, color: 'var(--fg-3,#94A4C4)', fontFamily: 'var(--font-mono)', padding: '2px 7px', borderRadius: 4, background: 'var(--border-1,#ECEFF5)' }}>{tierLabels[offer.tier] || offer.tier}</span>}
            </div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
              {offer.details && (
                <div style={{ fontSize: 13, color: 'var(--fg-2,#404968)', fontFamily: 'var(--font-text,Inter,sans-serif)', lineHeight: 1.6 }}>{offer.details}</div>
              )}
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 16, marginTop: 4 }}>
                {offer.primary_cta && (
                  <div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
                    <span style={{ fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.07em', color: 'var(--fg-3,#94A4C4)', fontFamily: 'var(--font-mono)' }}>CTA Principal</span>
                    <span style={{ fontSize: 12, color: 'var(--fg-1,#283252)', fontFamily: 'var(--font-text,Inter,sans-serif)', fontWeight: 500 }}>{ctaLabels[offer.primary_cta] || offer.primary_cta}</span>
                  </div>
                )}
                <div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
                  <span style={{ fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.07em', color: 'var(--fg-3,#94A4C4)', fontFamily: 'var(--font-mono)' }}>Negociável</span>
                  <span style={{ fontSize: 12, color: 'var(--fg-1,#283252)', fontFamily: 'var(--font-text,Inter,sans-serif)', fontWeight: 500 }}>{offer.negotiable ? 'Sim — condições finais fecham com o comercial' : 'Não'}</span>
                </div>
              </div>
            </div>
          </div>
        );
      })()}

      {/* ── BLOCOS B1–B5 — resumos AI ── */}
      {summaries && (() => {
        // Parser **bold** → <strong> com cor da marca
        const renderText = (text, isWarning) => {
          if (!text) return null;
          const boldColor = isWarning ? 'var(--fg-danger,#B53B3B)' : 'var(--dd-primary-900,#112954)';
          const parts = text.split(/\*\*([^*]+)\*\*/g);
          return parts.map((part, i) =>
            i % 2 === 1
              ? <strong key={i} style={{ fontWeight: 700, color: boldColor }}>{part}</strong>
              : part
          );
        };

        const tSt = { fontSize: 13, color: 'var(--fg-2,#404968)', fontFamily: 'var(--font-text,Inter,sans-serif)', lineHeight: 1.7, margin: 0 };

        const BCard = ({ num, title, text, isWarning }) => !text ? null : (
          <div style={{ background: 'var(--bg-surface,#fff)', borderRadius: 'var(--radius-xl,14px)', boxShadow: 'var(--shadow-card)', padding: '16px 20px' }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10, paddingBottom: 10, borderBottom: '1px solid var(--border-1,#ECEFF5)' }}>
              <span style={{ fontSize: 10, fontWeight: 700, color: '#fff', background: isWarning ? 'var(--fg-danger,#B53B3B)' : bCol, borderRadius: 'var(--radius-xs,4px)', padding: '2px 7px', fontFamily: 'var(--font-mono)', letterSpacing: '0.04em', flexShrink: 0 }}>0{num}</span>
              <span style={{ fontSize: 11, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em', color: 'var(--fg-1,#283252)', fontFamily: 'var(--font-text,Inter,sans-serif)' }}>{title}</span>
            </div>
            <p style={tSt}>{renderText(text, isWarning)}</p>
          </div>
        );

        // B6 — Score do briefing
        const apr = summaries.apreciation || {};
        const breakdown = apr.breakdown || {};
        const scoreTotal = apr.scoreTotal;
        const hasScore = scoreTotal !== null && scoreTotal !== undefined;

        const ScoreBar = ({ score }) => {
          const col = score >= 85 ? 'var(--green-700,#1F8A52)' : score >= 70 ? 'var(--pumpkin-700,#E96D17)' : 'var(--red-700,#B53B3B)';
          return (
            <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
              <div style={{ flex: 1, height: 4, background: 'var(--border-1,#ECEFF5)', borderRadius: 99, overflow: 'hidden' }}>
                <div style={{ width: `${score}%`, height: '100%', background: col, borderRadius: 99, transition: 'width .4s ease' }} />
              </div>
              <span style={{ fontSize: 11, fontWeight: 700, color: col, fontFamily: 'var(--font-mono)', minWidth: 32 }}>{score}</span>
            </div>
          );
        };

        const BLOCK_LABELS = { b1: 'Produto', b2: 'Cliente', b3: 'Mercado', b4: 'Campanha', b5: 'Restrições' };

        return (
          <>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
              <BCard num={1} title="Produto"      text={summaries.b1} />
              <BCard num={2} title="Cliente-alvo" text={summaries.b2} />
            </div>
            <BCard num={3} title="Mercado"    text={summaries.b3} />
            <BCard num={4} title="Campanha"   text={summaries.b4} />
            <BCard num={5} title="Restrições" text={summaries.b5} isWarning />

            {/* B6 — Avaliação Digi AI */}
            {(hasScore || summaries.b6) && (
              <div style={{ background: 'var(--bg-surface,#fff)', borderRadius: 'var(--radius-xl,14px)', boxShadow: 'var(--shadow-card)', padding: '16px 20px' }}>
                {/* Header */}
                <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10, paddingBottom: 10, borderBottom: '1px solid var(--border-1,#ECEFF5)' }}>
                  <span style={{ fontSize: 10, fontWeight: 700, color: '#fff', background: 'var(--purple-700,#5B43C5)', borderRadius: 'var(--radius-xs,4px)', padding: '2px 7px', fontFamily: 'var(--font-mono)', letterSpacing: '0.04em' }}>06</span>
                  <span style={{ fontSize: 11, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em', color: 'var(--fg-1,#283252)', fontFamily: 'var(--font-text,Inter,sans-serif)' }}>Avaliação Digi AI</span>
                  {hasScore && (
                    <span style={{ marginLeft: 'auto', fontSize: 18, fontWeight: 700, color: scoreTotal >= 85 ? 'var(--green-700,#1F8A52)' : scoreTotal >= 70 ? 'var(--pumpkin-700,#E96D17)' : 'var(--red-700,#B53B3B)', fontFamily: 'var(--font-mono)' }}>
                      {scoreTotal}<span style={{ fontSize: 11, fontWeight: 500, color: 'var(--fg-3)' }}>/100</span>
                    </span>
                  )}
                </div>
                {/* Resumo texto */}
                {summaries.b6 && (
                  <p style={{ ...tSt, marginBottom: 12 }}>{renderText(summaries.b6)}</p>
                )}
              </div>
            )}
          </>
        );
      })()}
    </div>
  );
};

// ── PhaseLoadingState — loading state consistente com steps + progress + tempo ─
// Usado durante gerações AI (Estratégia, Conceito, Segmentação). Cada fase passa
// os seus próprios steps + activeStep controlado pelo parent via SSE ou timing.
const PhaseLoadingState = ({ steps, activeStep, elapsedSec, error, brandColor = '#5B43C5' }) => {
  const [stepPct, setStepPct] = React.useState(0);
  const stepStartRef = React.useRef(null);

  React.useEffect(() => {
    if (activeStep <= 0) { setStepPct(0); return; }
    stepStartRef.current = Date.now();
    setStepPct(0);
  }, [activeStep]);

  React.useEffect(() => {
    if (activeStep <= 0) return;
    const def = steps.find(s => s.step === activeStep);
    if (!def?.duration) return;
    const iv = setInterval(() => {
      const elapsed = Date.now() - (stepStartRef.current || Date.now());
      setStepPct(Math.min((elapsed / def.duration) * 90, 90));
    }, 80);
    return () => clearInterval(iv);
  }, [activeStep, steps]);

  const totalSteps = steps.length;
  const doneSteps  = steps.filter(s => s.step < activeStep).length;
  const overallPct = Math.round(((doneSteps + stepPct / 100) / totalSteps) * 100);
  const fmtElapsed = s => s < 60 ? `${s}s` : `${Math.floor(s/60)}m${String(s%60).padStart(2,'0')}s`;

  const Dot = ({ done, active }) => {
    if (done) return (
      <div style={{ width: 16, height: 16, borderRadius: '50%', background: 'rgba(21,128,61,.12)', border: '1.5px solid rgba(21,128,61,.3)', flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        <svg width="8" height="8" viewBox="0 0 10 10" fill="none"><polyline points="1.5,5 4,7.5 8.5,2.5" stroke="#15803d" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/></svg>
      </div>
    );
    if (active) return (
      <div style={{ width: 16, height: 16, borderRadius: '50%', border: `2px solid ${brandColor}`, flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', animation: 'espin 1.4s linear infinite' }}>
        <div style={{ width: 6, height: 6, borderRadius: '50%', background: brandColor }} />
      </div>
    );
    return <div style={{ width: 16, height: 16, borderRadius: '50%', border: '1.5px solid var(--border, #e2e8f0)', flexShrink: 0 }} />;
  };

  return (
    <div style={{ background: 'var(--bg-elev,#fff)', border: '1px solid var(--border, #e2e8f0)', borderRadius: 10, padding: '20px 24px', display: 'flex', flexDirection: 'column', gap: 16 }}>
      <style>{`
        @keyframes espin { 0%{box-shadow:0 -5px 0 ${brandColor}} 25%{box-shadow:5px 0 0 ${brandColor}} 50%{box-shadow:0 5px 0 ${brandColor}} 75%{box-shadow:-5px 0 0 ${brandColor}} 100%{box-shadow:0 -5px 0 ${brandColor}} }
      `}</style>
      {/* Aviso: não fechar */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '8px 12px', background: '#fffbeb', border: '1px solid #fde68a', borderRadius: 6 }}>
        <span style={{ fontSize: 13 }}>⚠</span>
        <span style={{ fontSize: 12, color: '#92400e', fontWeight: 500 }}>Não feches esta janela nem saias deste ecrã — a geração está em curso.</span>
      </div>
      {/* Barra de progresso + tempo */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
        <div style={{ flex: 1, height: 5, borderRadius: 99, background: 'var(--bg-app,#f5f6f8)', overflow: 'hidden' }}>
          <div style={{ height: '100%', borderRadius: 99, background: `linear-gradient(90deg,${brandColor},${brandColor}cc)`, width: `${overallPct}%`, transition: 'width 0.15s linear' }} />
        </div>
        <span style={{ fontSize: 11, fontWeight: 700, color: brandColor, fontFamily: 'var(--font-mono)', flexShrink: 0, minWidth: 32 }}>{overallPct}%</span>
        <span style={{ fontSize: 11, fontWeight: 600, color: '#94a3b8', fontFamily: 'var(--font-mono)', flexShrink: 0, minWidth: 36 }}>{fmtElapsed(elapsedSec || 0)}</span>
      </div>
      {/* Tabela de steps */}
      <table style={{ width: '100%', borderCollapse: 'collapse', tableLayout: 'fixed' }}>
        <colgroup><col style={{ width: 28 }} /><col style={{ width: 240 }} /><col /></colgroup>
        <thead>
          <tr>
            <th />
            <th style={{ padding: '4px 8px', fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em', color: '#94a3b8', fontFamily: 'var(--font-mono)', textAlign: 'left' }}>SECÇÃO</th>
            <th style={{ padding: '4px 8px', fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em', color: '#94a3b8', fontFamily: 'var(--font-mono)', textAlign: 'left' }}>DETALHE</th>
          </tr>
        </thead>
        <tbody>
          {steps.map(s => {
            const done   = activeStep > s.step;
            const active = activeStep === s.step;
            return (
              <tr key={s.step}>
                <td style={{ padding: '8px 8px 8px 0', verticalAlign: 'middle' }}><Dot done={done} active={active} /></td>
                <td style={{ padding: '8px', fontSize: 13, fontWeight: active ? 600 : 400, color: done ? '#15803d' : active ? 'var(--text)' : '#94a3b8', fontFamily: 'var(--font-body, Inter, sans-serif)', borderBottom: '1px solid var(--border, #f1f5f9)' }}>{s.label}</td>
                <td style={{ padding: '8px', fontSize: 12, color: active ? '#64748b' : '#cbd5e1', fontFamily: 'var(--font-body, Inter, sans-serif)', borderBottom: '1px solid var(--border, #f1f5f9)' }}>{s.detail}</td>
              </tr>
            );
          })}
        </tbody>
      </table>
      {error && <div style={{ padding: '8px 12px', background: 'rgba(220,38,38,.06)', border: '1px solid rgba(220,38,38,.2)', borderRadius: 6, fontSize: 12, color: '#dc2626' }}>{error}</div>}
    </div>
  );
};

// Steps para cada fase — usados por PhaseLoadingState
const CONCEITO_STEPS = [
  { step: 1, label: 'Estratégia & Briefing',      detail: 'Leitura da estratégia aprovada + 5 blocos do briefing · guardrails do bloco 5',                                  duration: 3000 },
  { step: 2, label: 'USPs × Dores × Personas',    detail: 'Mapeamento 1:1 dos USPs às dores dominantes e personas priorizadas por mercado',                                 duration: 4000 },
  { step: 3, label: 'Big Idea & Ângulos por USP', detail: 'Claude · big idea unificada · 1 ângulo por USP (cada um integra as 3 fases do funil na mesma peça)',              duration: 22000 },
  { step: 4, label: 'Abordagem por Canal',        detail: 'Direcção criativa por canal — email (sequência) · website (blog + página produto) · Ads (formatos) · WhatsApp',   duration: 10000 },
  { step: 5, label: 'Hooks & Cronograma',         detail: 'Hooks por canal · cronograma orgânico (comm_plan) — que passa depois para Planeamento',                          duration: 8000 },
  { step: 6, label: 'Variantes por Mercado',      detail: 'Reinterpretação idiomática por país (não tradução literal) · tom cultural · ângulos locais',                     duration: 8000 },
  { step: 7, label: 'Guardar Conceito',           detail: 'Persistência do proposta_json · actualização de status',                                                          duration: 1500 },
];

// Steps para Orçamento — determinístico + análise Meta Ads opcional
const ORCAMENTO_STEPS_WITH_META = [
  { step: 1, label: 'Briefing & Estratégia',       detail: 'Targets briefing (leads · CPL · CTR) · budget_weight por mercado · USPs · timeline',             duration: 2000  },
  { step: 2, label: 'Histórico Meta Ads',          detail: 'listCampaigns + insights 180 dias · filtro por produto · CPL ponderado por spend',               duration: 25000 },
  { step: 3, label: 'Fórmula 3 Âncoras',          detail: 'CPL blended (briefing + histórico + KB B2B PT/ES) · margem por confiança · factor CTR · Haiku',  duration: 8000  },
  { step: 4, label: 'Distribuição por Canal',      detail: 'Paid media only · budget_weight estratégia por mercado · split canal por pct KB',                duration: 2000  },
  { step: 5, label: 'Guardar Orçamento',           detail: 'campanha_orcamento por country × canal · supersede rows anteriores',                             duration: 1500  },
  { step: 6, label: 'Meta Ads Analyst — Dados',    detail: 'Skills: ad-creative · data-analyst · forecast · meta-ads-b2b · histórico filtrado',             duration: 8000  },
  { step: 7, label: 'Meta Ads Analyst — Análise',  detail: 'Claude Sonnet 4.6 — diagnóstico · orçamento · recomendações · previsão · campanhas analisadas', duration: 25000 },
];
const ORCAMENTO_STEPS_NO_META = [
  { step: 1, label: 'Briefing & Estratégia',       detail: 'Targets briefing (leads · CPL · CTR) · budget_weight por mercado · USPs · timeline',             duration: 2000 },
  { step: 2, label: 'Distribuição por Canal',      detail: 'Paid media only · budget_weight estratégia por mercado · split canal por pct KB',                duration: 2000 },
  { step: 3, label: 'Guardar Orçamento',           detail: 'campanha_orcamento por country × canal · supersede rows anteriores',                             duration: 1500 },
];

// ── PhaseEmptyState — empty state consistente para todas as fases ─────────────
// Segue o mesmo padrão da Estratégia (empty state): card branco centrado com
// eyebrow label (fase X), title, descrição e CTA primário. Usado nos tabs
// Orçamento · Segmentação · Planeamento · Funil.
const PhaseEmptyState = ({ label, title, description, ctaLabel, onCta, disabled, disabledReason, error, loading, secondaryCta }) => (
  <div style={{ background: 'var(--bg-surface,#fff)', borderRadius: 'var(--radius-xl,14px)', boxShadow: 'var(--shadow-card, 0 1px 3px rgba(17,41,84,.08), 0 4px 16px rgba(17,41,84,.06))', padding: '32px 28px', textAlign: 'center' }}>
    <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: 'var(--fg-3, var(--text-dim))', fontFamily: 'var(--font-mono)', marginBottom: 8 }}>
      {label}
    </div>
    <div style={{ fontSize: 20, fontWeight: 700, color: 'var(--dd-primary-900, var(--text, #112954))', fontFamily: 'var(--font-display, Montserrat, sans-serif)', marginBottom: 10 }}>
      {title}
    </div>
    <p style={{ fontSize: 13, color: 'var(--fg-2, var(--text-muted))', lineHeight: 1.6, maxWidth: 560, margin: '0 auto 20px', fontFamily: 'var(--font-text, Inter, sans-serif)' }}>
      {description}
    </p>
    <div style={{ display: 'flex', gap: 8, justifyContent: 'center', flexWrap: 'wrap' }}>
      {onCta && (
        <button onClick={onCta} disabled={disabled || loading} className="btn btn-ai" style={{ padding: '10px 24px', fontSize: 13 }}>
          {loading ? 'A gerar...' : ctaLabel}
        </button>
      )}
      {secondaryCta && (
        <button onClick={secondaryCta.onClick} className="btn" style={{ padding: '10px 20px', fontSize: 13 }}>
          {secondaryCta.label}
        </button>
      )}
    </div>
    {disabled && disabledReason && (
      <div style={{ marginTop: 14, fontSize: 12, color: 'var(--text-muted)', fontStyle: 'italic' }}>{disabledReason}</div>
    )}
    {error && <div style={{ marginTop: 14, fontSize: 12, color: '#dc2626' }}>{error}</div>}
  </div>
);

// ── TabPlaneamento — timeline por canal + estrutura de anúncios Meta ─────────
const TabPlaneamento = ({ campanha, copy, prompts, userEmail, onAction }) => {
  const bCol = _brandColor(campanha?.brand_slug);
  const [rows,      setRows]      = React.useState([]);
  const [loading,   setLoading]   = React.useState(true);
  const [generating,setGenerating]= React.useState(false);
  const [approving, setApproving] = React.useState(false);
  const [saving,    setSaving]    = React.useState({});
  const [toast,     setToast]     = React.useState('');
  const [genStep,   setGenStep]   = React.useState(0);
  const [genElapsed,setGenElapsed]= React.useState(0);
  const [ganttZoom, setGanttZoom] = React.useState('days');
  const [tooltip,   setTooltip]   = React.useState(null); // { x, y, gr } // 'days' | 'weeks' | 'months'
  const planTimersRef  = React.useRef([]);
  const planElapsedRef = React.useRef(null);

  const showToast = msg => { setToast(msg); setTimeout(() => setToast(''), 2500); };

  // Canais activos para steps dinâmicos
  const planChannels = new Set((campanha?.estrategia_json?.markets || []).flatMap(m => (m.channel_fit||[]).map(ch => ch.canal)));
  const hasPaidAds = [...planChannels].some(c => ['meta_ads','linkedin_ads','google_ads_search','google_ads_display'].includes(c));
  const hasEmail   = planChannels.has('email');
  const hasSocial  = [...planChannels].some(c => ['instagram','facebook','linkedin'].includes(c));
  const hasBlog    = planChannels.has('website');

  const PLAN_STEPS = [
    { label: 'Briefing & Estratégia',          detail: 'Timeline · canais activos · mercados · objectivo · comm_plan do conceito',                   duration: 2000 },
    { label: 'Âncora temporal',                detail: `Data efectiva = max(${campanha?.briefing?.timeline_start || 'hoje'}, hoje + 5 dias)`,          duration: 1500 },
    { label: 'Comunicação interna',            detail: 'Briefing equipa comercial 2 dias antes do primeiro conteúdo externo (email + WA + portal)',    duration: 2000 },
    ...(hasPaidAds ? [{ label: 'Paid Ads — datas',       detail: 'Meta/LinkedIn/Google · período = briefing timeline · múltiplas campanhas simultâneas permitidas', duration: 3000 }] : []),
    ...(hasEmail   ? [{ label: 'Email — anti-saturação', detail: 'Máx 1 email/semana por marca · intercalação cross-marca (Mimaki/Decal/BIOND) · conflito = semana seguinte', duration: 4000 }] : []),
    ...(hasSocial  ? [{ label: 'Social orgânico',        detail: 'Mínimo 2 dias entre posts no mesmo perfil · produtos diferentes podem intercalar',   duration: 4000 }] : []),
    ...(hasBlog    ? [{ label: 'Blog & Website',          detail: 'Data de publicação · website por marca+mercado+língua',                              duration: 2000 }] : []),
    { label: 'Conflitos cross-campanha',       detail: 'Verifica existing_schedule de todas as marcas · marca itens needs_review se irresolvível',    duration: 5000 },
    { label: 'Guardar Planeamento',            detail: 'campanha_plano_datas por canal × mercado · slots vazios visíveis no Gantt',                   duration: 1500 },
  ].map((s, i) => ({ ...s, step: i + 1 }));

  const load = () => {
    setLoading(true);
    campApiCall(`/api/marketing/campanhas/${campanha.id}/planeamento`)
      .then(d => setRows(d.rows || []))
      .finally(() => setLoading(false));
  };
  React.useEffect(() => { if (campanha?.id) load(); }, [campanha?.id]);
  React.useEffect(() => () => { clearInterval(planElapsedRef.current); planTimersRef.current.forEach(clearTimeout); }, []);

  const generate = async () => {
    setGenerating(true);
    setGenStep(1); setGenElapsed(0);
    planElapsedRef.current = setInterval(() => setGenElapsed(s => s + 1), 1000);
    const totalDuration = PLAN_STEPS.reduce((s, x) => s + x.duration, 0);
    let cum = 0;
    planTimersRef.current = PLAN_STEPS.map(s => {
      cum += s.duration; return setTimeout(() => setGenStep(s.step + 1 <= PLAN_STEPS.length ? s.step + 1 : PLAN_STEPS.length), cum);
    });
    const cleanup = () => { clearInterval(planElapsedRef.current); planTimersRef.current.forEach(clearTimeout); setGenStep(0); setGenElapsed(0); };
    try {
      const [d] = await Promise.all([
        campApiCall(`/api/marketing/campanhas/${campanha.id}/planeamento/generate`, { method: 'POST' }),
        new Promise(r => setTimeout(r, totalDuration)),
      ]);
      cleanup();
      setRows(d.rows || []);
      showToast('Planeamento gerado');
      if (onAction) onAction('refreshCampanha'); // recarregar comm_plan actualizado
    } catch(e) { cleanup(); showToast('Erro: ' + e.message); }
    setGenerating(false);
  };

  const userName = window.currentUser?.name || window.currentUser?.displayName || userEmail?.split('@')[0] || '';

  const approve = async () => {
    setApproving(true);
    try {
      await campApiCall(`/api/marketing/campanhas/${campanha.id}/planeamento/approve`, {
        method: 'POST', body: JSON.stringify({ user_email: userEmail, user_name: userName })
      });
      setRows(rs => rs.map(r => ({ ...r, approved_at: new Date().toISOString(), approved_by: userName || userEmail })));
      showToast('Planeamento aprovado');
      if (onAction) onAction('refreshCampanha');
    } catch(e) { showToast('Erro: ' + e.message); }
    setApproving(false);
  };

  const updateRow = async (row, field, val) => {
    const updated = { ...row, [field]: val };
    // Validação local: end_date não pode ser anterior a start_date
    if ((field === 'start_date' || field === 'end_date') && updated.start_date && updated.end_date) {
      if (new Date(updated.end_date) < new Date(updated.start_date)) {
        showToast('Data fim tem de ser posterior à data início');
        return;
      }
    }
    setRows(rs => rs.map(r => r.id === row.id ? updated : r));
    setSaving(s => ({ ...s, [row.id]: true }));
    try {
      await campApiCall(`/api/marketing/campanhas/${campanha.id}/planeamento/${row.id}`, {
        method: 'PUT',
        body: JSON.stringify({ start_date: updated.start_date, end_date: updated.end_date, budget_fase: updated.budget_fase, notas: updated.notas })
      });
      setSaving(s => ({ ...s, [row.id]: false }));
    } catch(e) { showToast('Erro ao guardar: ' + (e.message || '')); setSaving(s => ({ ...s, [row.id]: false })); }
  };

  // Calcular duração de cada canal em semanas — nunca negativa
  const durationWeeks = (r) => {
    if (!r.start_date || !r.end_date) return null;
    const diff = (new Date(r.end_date) - new Date(r.start_date)) / (7 * 864e5);
    return diff < 0 ? 0 : Math.round(diff);
  };

  const thS = { fontSize: 10, fontWeight: 700, fontFamily: 'var(--font-mono)', letterSpacing: '0.06em', textTransform: 'uppercase', color: 'var(--text-dim)', padding: '8px 14px', background: 'var(--bg-sunken)', borderBottom: '1px solid var(--border)', whiteSpace: 'nowrap' };
  const tdS = { fontSize: 12, padding: '9px 14px', borderBottom: '1px solid var(--border)', verticalAlign: 'middle' };
  const inpS = { fontSize: 12, padding: '4px 8px', borderRadius: 5, border: '1px solid var(--border)', background: 'var(--bg)', color: 'var(--text)', fontFamily: 'var(--font-mono)' };

  const hasAnuncios = !!(campanha?.proposta_json?.comm_plan?.length || campanha?.canais_setup);

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
      {/* Status strip + actions — padrão consistente */}
      {rows.length > 0 && (() => {
        const allApproved = rows.every(r => r.approved_at);
        return (
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 14px', background: 'var(--bg-surface,#fff)', borderRadius: 'var(--radius-md,8px)', boxShadow: 'var(--shadow-card)' }}>
            <span style={{ fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em', padding: '3px 8px', borderRadius: 'var(--radius-xs,4px)', background: allApproved ? 'var(--green-100,#E1F7E6)' : 'var(--dd-blue-100,#EBEFF9)', color: allApproved ? 'var(--green-700,#1F8A52)' : 'var(--dd-primary-600,#3859D0)' }}>
              {allApproved ? 'Aprovado' : 'Gerado — aguarda aprovação'}
            </span>
            <span style={{ fontSize: 11, color: 'var(--fg-3, var(--text-muted))' }}>
              {rows.length} canais planeados
              {campanha?.planeamento_generated_at && ` · gerado ${new Date(campanha.planeamento_generated_at).toLocaleString('pt-PT')}`}
            </span>
            {allApproved && campanha?.planeamento_approved_at && (
              <span style={{ fontSize: 11, color: 'var(--green-700, #1F8A52)' }}>
                · aprovada {new Date(campanha.planeamento_approved_at).toLocaleString('pt-PT')}{campanha.planeamento_approved_by ? ` por ${String(campanha.planeamento_approved_by).split('@')[0]}` : ''}
              </span>
            )}
            <div style={{ marginLeft: 'auto', display: 'flex', gap: 6, alignItems: 'center' }}>
              <button className="btn" onClick={generate} disabled={generating} style={{ height: 28, padding: '0 12px', fontSize: 12 }}>
                {generating ? 'A gerar...' : 'Regenerar'}
              </button>
              {!allApproved && (
                <button className="btn btn-ai" onClick={approve} disabled={approving} style={{ height: 28, padding: '0 14px', fontSize: 12 }}>
                  {approving ? 'A aprovar...' : 'Aprovar Planeamento'}
                </button>
              )}
            </div>
          </div>
        );
      })()}

      {/* ── Loading state com steps das regras anti-saturação ── */}
      {generating && (
        <PhaseLoadingState
          steps={PLAN_STEPS}
          activeStep={genStep}
          elapsedSec={genElapsed}
          brandColor="#7c3aed"
        />
      )}

      {/* ── Timeline integrada — todos os eventos da campanha por data ── */}
      {loading && !generating && <div style={{ padding: 32, color: 'var(--text-muted)', fontSize: 13 }}>A carregar...</div>}

      {!loading && !generating && rows.length === 0 && (
        <PhaseEmptyState
          label="Planeamento · fase 5"
          title="Ainda sem planeamento definido"
          description="Timeline integrada de todos os eventos da campanha: Meta Ads, Email, Social Orgânico, Blog, WA. Datas calculadas com base no briefing respeitando regras de anti-saturação — mínimo 2 dias entre posts orgânicos no mesmo perfil, máximo 1 email/semana por marca, intercalação entre marcas do ecossistema."
          ctaLabel="Gerar Planeamento →"
          onCta={generate}
          loading={generating}
          disabled={!campanha?.estrategia_json}
          disabledReason={!campanha?.estrategia_json ? 'A estratégia precisa de ser gerada primeiro.' : null}
        />
      )}

      {!loading && !generating && rows.length > 0 && (() => {
        const commPlan = campanha?.proposta_json?.comm_plan || [];

        // ── GANTT ─────────────────────────────────────────────────────────────
        const today = new Date(); today.setHours(0,0,0,0);
        const toD = (s) => { const d = new Date(s); d.setHours(0,0,0,0); return d; };
        const fmtD = (d) => d.toLocaleDateString('pt-PT', { day:'2-digit', month:'short' });

        // Construir linhas do Gantt — uma por canal/actividade
        const GANTT_COLOR = {
          meta_ads:'#3859D0', linkedin_ads:'#0a66c2', google_ads_search:'#ea4335',
          google_ads_display:'#34a853', email:'#7c3aed', email_externo:'#7c3aed',
          whatsapp:'#16a34a', instagram:'#e1306c', facebook:'#1877f2', linkedin:'#0a66c2',
          blog:'#0891b2', website:'#0891b2', youtube:'#ff0000',
          email_interno:'#92400e', muppi_led:'#d97706', default:'#6b7280',
        };

        // Linhas de planeamento (canais com período)
        const ganttRows = rows
          .filter(r => r.start_date)
          .map(r => ({
            id: r.id, label: CANAL_LABEL[r.canal] || r.canal,
            canal: r.canal, color: GANTT_COLOR[r.canal] || GANTT_COLOR.default,
            start: toD(r.start_date), end: r.end_date ? toD(r.end_date) : toD(r.start_date),
            notas: r.notas, budget: r.budget_fase, tipo: 'periodo', row: r,
            needs_review: false,
          }));

        // Eventos pontuais do comm_plan (publicações, envios)
        // Simplificar labels para caber numa linha no Gantt
        const shortLabel = (item) => {
          const ct = item.content_type || '';
          const titulo = item.titulo || '';
          const body = item.body || '';
          if (ct === 'briefing_interno') return 'Briefing Comercial';
          if (ct === 'campanha_paid') return titulo; // naming convention já curto
          if (ct === 'email_html') {
            if (/kick.?off/i.test(titulo))  return 'Kick-off · Campanha';
            if (/reminder/i.test(titulo))   return 'Reminder · ROI';
            if (/last.?call/i.test(titulo)) return 'Urgência Fim Stock';
            return titulo.split(/[—–]/)[0].trim().slice(0, 28);
          }
          if (ct === 'social_post' || ct === 'ad') {
            const adM = titulo.match(/AD-(\d+)/);
            const adNum = adM ? `AD-${adM[1].padStart(2,'0')}` : '';
            const words = body.replace(/[,.:;]/g,'').split(/\s+/).slice(0,3).join(' ');
            return adNum ? `${adNum} · ${words}` : words.slice(0,30);
          }
          if (ct === 'wa_message') {
            const obj = item.objectivo_wa || '';
            return `WA ${item.seq || ''} · ${obj}`.trim();
          }
          if (ct === 'artigo_blog') {
            const words = (body || titulo).replace(/[,.:;]/g,'').split(/\s+/).slice(0,3).join(' ');
            return `Blog · ${words}`;
          }
          return titulo.split(/[—–]/)[0].trim().slice(0, 30);
        };

        const commRows = commPlan.map((item, i) => {
          const ds = item.planned_date || item.data_envio || item.data_publicacao;
          if (!ds) return null;
          const d = toD(ds.slice(0,10));
          const de = item.planned_end_date ? toD(item.planned_end_date.slice(0,10)) : d;
          const tipo = item.item_type || 'evento';
          const canal = item.canal || 'social';
          return {
            id: 'c_'+i,
            label: shortLabel(item),
            fullLabel: item.titulo || CANAL_LABEL[canal] || canal,
            fullBody: item.body || null,
            canal, color: GANTT_COLOR[canal] || GANTT_COLOR.default,
            start: d, end: de,
            notas: item.body?.slice(0,80),
            tipo,
            content_type: item.content_type || null,
            is_interno: item.content_type === 'briefing_interno',
            needs_review: item.needs_review || false,
            audiencia: item.audiencia || null,
            audiencia_count: item.audiencia_count || null,
            objectivo_wa: item.objectivo_wa || null,
          };
        }).filter(Boolean);

        // Canal "site/website" = sempre blog posts no planeamento (landing pages são produção)
        // Excluir content_types que pertencem à fase de produção, não ao planeamento de publicação
        const EXCLUDE_CONTENT_TYPES = new Set(['landing_page', 'landing_page_html', 'lp']);
        const filteredCommRows = commRows.filter(r => !EXCLUDE_CONTENT_TYPES.has(r.content_type));

        // Agrupar por categoria
        const CATEGORIES = [
          {
            id: 'interno', label: 'Comunicação Interna', color: '#92400e',
            match: (r) => r.canal?.includes('interno') || r.is_interno,
          },
          {
            id: 'paid', label: 'Paid Media', color: '#3859D0',
            match: (r) => ['meta_ads','linkedin_ads','google_ads_search','google_ads_display','muppi_led'].includes(r.canal)
              && r.content_type !== 'social_post',
          },
          {
            id: 'email', label: 'Email', color: '#7c3aed',
            match: (r) => ['email','email_externo','email_html'].includes(r.canal) || r.canal === 'email',
          },
          {
            id: 'social', label: 'Social Media', color: '#e1306c',
            match: (r) => ['instagram','facebook','linkedin','social'].includes(r.canal)
              || r.content_type === 'social_post',
          },
          {
            id: 'whatsapp', label: 'WhatsApp', color: '#16a34a',
            match: (r) => r.canal === 'whatsapp' || r.content_type === 'wa_message',
          },
          {
            id: 'website', label: 'Blog', color: '#0891b2',
            match: (r) => ['site','website','blog','artigo_blog','youtube'].includes(r.canal) || ['artigo_blog','blog_post','artigo'].includes(r.content_type),
          },
        ];

        // Só comm_plan items — ganttRows (simples "Meta Ads", "Email") são redundantes com as categorias
        const allCandidates = [...filteredCommRows];
        // Agrupar e ordenar por categoria
        const grouped = CATEGORIES.map(cat => ({
          ...cat,
          rows: allCandidates.filter(r => cat.match(r)).sort((a,b) => a.start - b.start),
        })).filter(c => c.rows.length > 0);

        const allRows = grouped.flatMap(c => [{ ...c, isCategory: true }, ...c.rows]);
        if (!allRows.length) return <div style={{ padding: 32, color: 'var(--text-muted)', fontSize: 13 }}>Sem eventos planeados.</div>;

        // Janela temporal: 1 Jan 2026 → 31 Dez 2026 (calendário completo)
        const gStart = new Date(2026, 0, 1);
        const gEnd   = new Date(2026, 11, 31);
        const totalDays = Math.ceil((gEnd - gStart) / 864e5);

        // Pixels por dia conforme zoom
        const PX_PER_DAY = { days: 40, weeks: 14, months: 5 };
        const pxPerDay = PX_PER_DAY[ganttZoom] || 14;
        const totalWidth = Math.max(totalDays * pxPerDay, 600);

        // Posição em px no Gantt (não %)
        const px = (d) => Math.max(0, Math.min(totalWidth, ((d - gStart) / 864e5) * pxPerDay));
        const todayPx = px(today);

        // Meses — sempre visíveis como nível superior (independente do zoom)
        const monthMarkers = [];
        const mc = new Date(gStart.getFullYear(), gStart.getMonth(), 1);
        while (mc <= gEnd) {
          monthMarkers.push({
            key: mc.toISOString().slice(0,7),
            label: mc.toLocaleDateString('pt-PT', { month: 'long' }).charAt(0).toUpperCase() + mc.toLocaleDateString('pt-PT', { month: 'long' }).slice(1),
            px: px(mc),
            width: Math.max(0, px(new Date(mc.getFullYear(), mc.getMonth()+1, 1)) - px(mc)),
          });
          mc.setMonth(mc.getMonth() + 1);
        }

        // Helper: número da semana ISO do ano
        const getISOWeek = (d) => {
          const dt = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
          dt.setUTCDate(dt.getUTCDate() + 4 - (dt.getUTCDay() || 7));
          const y = new Date(Date.UTC(dt.getUTCFullYear(), 0, 1));
          return Math.ceil((((dt - y) / 864e5) + 1) / 7);
        };

        // Marcadores de detalhe (semanas ou dias) — nível inferior
        const headerMarkers = [];
        const cur = new Date(gStart);
        if (ganttZoom === 'days') {
          while (cur <= gEnd) {
            const isMonday = cur.getDay() === 1;
            const wk = isMonday ? getISOWeek(cur) : null;
            headerMarkers.push({
              key: cur.toISOString().slice(0,10),
              label: String(cur.getDate()),
              weekNum: wk, // mostra nº semana às segundas-feiras
              px: px(cur),
              isWeekend: cur.getDay()===0||cur.getDay()===6,
            });
            cur.setDate(cur.getDate() + 1);
          }
        } else if (ganttZoom === 'months') {
          // Zoom meses: mostrar semanas como nível inferior
          while (cur <= gEnd) {
            const mon = new Date(cur); mon.setDate(cur.getDate() - ((cur.getDay()+6)%7));
            if (!headerMarkers.length || headerMarkers[headerMarkers.length-1].key !== mon.toISOString().slice(0,10)) {
              const wk = getISOWeek(mon);
              headerMarkers.push({ key: mon.toISOString().slice(0,10), label: `S${wk}`, px: px(mon) });
            }
            cur.setDate(cur.getDate() + 7);
          }
        } else { // weeks (default)
          while (cur <= gEnd) {
            const mon = new Date(cur); mon.setDate(cur.getDate() - ((cur.getDay()+6)%7));
            if (!headerMarkers.length || headerMarkers[headerMarkers.length-1].key !== mon.toISOString().slice(0,10)) {
              const wk = getISOWeek(mon);
              headerMarkers.push({ key: mon.toISOString().slice(0,10), label: `S${wk} · ${fmtD(mon)}`, px: px(mon) });
            }
            cur.setDate(cur.getDate() + 7);
          }
        }

        const headerWeeks = headerMarkers; // alias
        const pct = (d) => px(d); // alias

        const ROW_H = 36;
        const LABEL_W = 280;

        // Scroll ref callback — só executa quando o zoom muda, não em re-renders do tooltip
        const scrollToToday = (el) => {
          if (el && el._ganttZoom !== ganttZoom) {
            el.scrollLeft = Math.max(0, todayPx - 200);
            el._ganttZoom = ganttZoom; // marcar como scrolled para este zoom
          }
        };

        return (
          <div style={{ background: 'var(--bg-card,#fff)', border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden' }}>
            {/* Toolbar zoom */}
            <div style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '6px 12px', borderBottom: '1px solid var(--border)', background: 'var(--bg-sunken)', justifyContent: 'flex-end' }}>
              <span style={{ fontSize: 10, color: 'var(--text-dim)', marginRight: 4 }}>Zoom:</span>
              {[['days','Dias'],['weeks','Semanas'],['months','Meses']].map(([z, l]) => (
                <button key={z} onClick={() => setGanttZoom(z)}
                  style={{ fontSize: 10, padding: '2px 10px', borderRadius: 4, cursor: 'pointer', border: `1px solid ${ganttZoom===z ? 'var(--ai-500,#3859D0)' : 'var(--border)'}`, background: ganttZoom===z ? 'var(--ai-500,#3859D0)' : 'var(--bg)', color: ganttZoom===z ? '#fff' : 'var(--text-muted)', fontWeight: ganttZoom===z ? 700 : 400 }}>
                  {l}
                </button>
              ))}
            </div>
            {/* Gantt — scroll lateral, labels sticky */}
            <div style={{ overflowX: 'auto' }} ref={scrollToToday}>
              <div style={{ width: LABEL_W + totalWidth, minWidth: LABEL_W + 400 }}>
                {/* Header dois níveis: meses (topo) + semanas/dias (baixo) */}
                <div style={{ position: 'sticky', top: 0, zIndex: 20 }}>
                  {/* Nível 1 — Meses */}
                  <div style={{ display: 'flex', borderBottom: '1px solid var(--border)', background: '#112954' }}>
                    <div style={{ width: LABEL_W, flexShrink: 0, borderRight: '1px solid rgba(255,255,255,.15)', position: 'sticky', left: 0, background: '#112954', zIndex: 21, height: 22, display:'flex', alignItems:'center', padding:'0 12px' }}>
                      <span style={{ fontSize:9, fontWeight:700, fontFamily:'var(--font-mono)', textTransform:'uppercase', letterSpacing:'.06em', color:'rgba(255,255,255,.5)' }}>Canal / Actividade</span>
                    </div>
                    <div style={{ flex:1, position:'relative', height:22, overflow:'visible' }}>
                      {monthMarkers.map(m => (
                        <div key={m.key} style={{ position:'absolute', left:m.px, top:0, bottom:0, borderLeft:'1px solid rgba(255,255,255,.2)', paddingLeft:6, display:'flex', alignItems:'center', overflow:'hidden', width:m.width }}>
                          <span style={{ fontSize:10, fontWeight:700, fontFamily:'var(--font-mono)', color:'#fff', whiteSpace:'nowrap', letterSpacing:'.04em' }}>{m.label}</span>
                        </div>
                      ))}
                      <div style={{ position:'absolute', left:todayPx, top:0, bottom:0, width:pxPerDay, background:'rgba(239,68,68,.15)', zIndex:10 }} />
                    </div>
                  </div>
                  {/* Nível 2 — Semanas / Dias */}
                  <div style={{ display:'flex', borderBottom:'2px solid var(--border)', background:'var(--bg-sunken)' }}>
                    <div style={{ width:LABEL_W, flexShrink:0, borderRight:'1px solid var(--border)', position:'sticky', left:0, background:'var(--bg-sunken)', zIndex:21, height:20 }} />
                    <div style={{ flex:1, position:'relative', height:20, overflow:'visible' }}>
                      {/* Fins de semana no header */}
                      {ganttZoom === 'days' && headerMarkers.filter(w => w.isWeekend).map(w => (
                        <div key={'weh_'+w.key} style={{ position:'absolute', left:w.px, top:0, bottom:0, width:pxPerDay, background:'rgba(0,0,0,.025)' }} />
                      ))}
                      {headerMarkers.map(w => (
                        <div key={w.key} style={{ position:'absolute', left:w.px, top:0, bottom:0, borderLeft:'1px solid var(--border)', paddingLeft:3, display:'flex', flexDirection:'column', justifyContent:'center' }}>
                          {w.weekNum != null && (
                            <span style={{ fontSize:7, fontFamily:'var(--font-mono)', color:'var(--ai-500,#3859D0)', fontWeight:700, lineHeight:1, whiteSpace:'nowrap' }}>S{w.weekNum}</span>
                          )}
                          <span style={{ fontSize:8, fontFamily:'var(--font-mono)', color:'var(--text-dim)', whiteSpace:'nowrap', opacity: w.isWeekend ? 0.5 : 1, lineHeight:1 }}>{w.label}</span>
                        </div>
                      ))}
                      <div style={{ position:'absolute', left:todayPx, top:0, bottom:0, width:pxPerDay, background:'rgba(239,68,68,.12)', zIndex:10 }} />
                    </div>
                  </div>
                </div>{/* fim sticky header */}
                {/* Rows — categorias + actividades */}
                {allRows.map((gr, ri) => {
                if (gr.isCategory) return (
                  <div key={'cat_'+gr.id} style={{ display:'flex', borderBottom:'1px solid var(--border)', background:'var(--bg-sunken)', height:26 }}>
                    <div style={{ width:LABEL_W, flexShrink:0, padding:'0 14px', display:'flex', alignItems:'center', gap:6, borderRight:'1px solid var(--border)', position:'sticky', left:0, background:'var(--bg-sunken)', zIndex:10 }}>
                      <span style={{ width:3, height:12, borderRadius:2, background:gr.color, flexShrink:0 }} />
                      <span style={{ fontSize:10, fontWeight:700, fontFamily:'var(--font-mono)', textTransform:'uppercase', letterSpacing:'.06em', color:gr.color }}>{gr.label}</span>
                    </div>
                    <div style={{ flex:1, position:'relative' }}>
                      {headerMarkers.map(w => <div key={w.key} style={{ position:'absolute', left:w.px, top:0, bottom:0, borderLeft:'1px solid var(--border)', opacity:.2 }} />)}
                      <div style={{ position:'absolute', left:todayPx, top:0, bottom:0, width:pxPerDay, background:'rgba(239,68,68,.08)' }} />
                    </div>
                  </div>
                );
                return (
                <div key={gr.id} style={{ display:'flex', borderBottom:'1px solid var(--border)', minHeight:ROW_H, background:'var(--bg-card,#fff)' }}>
                  {/* Label — sticky à esquerda */}
                  <div style={{ width:LABEL_W, flexShrink:0, padding:'0 12px 0 22px', display:'flex', alignItems:'center', gap:6, borderRight:'1px solid var(--border)', position:'sticky', left:0, background:'var(--bg-card,#fff)', zIndex:10 }}>
                    <span style={{ width:6, height:6, borderRadius:'50%', background:gr.is_interno?'#92400e':gr.color, flexShrink:0 }} />
                    <div style={{ fontSize:11, fontWeight:600, color:gr.is_interno?'#92400e':'var(--text)', whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis' }}>{gr.label}</div>
                  </div>
                  {/* Barra Gantt */}
                  <div style={{ flex:1, position:'relative', minHeight:ROW_H }}>
                    {/* Grid lines */}
                    {headerMarkers.map(w => <div key={w.key} style={{ position:'absolute', left:w.px, top:0, bottom:0, borderLeft:'1px solid var(--border)', opacity:0.2 }} />)}
                    {/* Fins de semana — coluna de pxPerDay (só zoom dias) */}
                    {ganttZoom === 'days' && headerMarkers.filter(w => w.isWeekend).map(w => (
                      <div key={'we_'+w.key} style={{ position:'absolute', left:w.px, top:0, bottom:0, width:pxPerDay, background:'rgba(0,0,0,.025)', zIndex:1 }} />
                    ))}
                    {/* Hoje — coluna de pxPerDay */}
                    <div style={{ position:'absolute', left:todayPx, top:0, bottom:0, width:pxPerDay, background:'rgba(239,68,68,.1)', zIndex:5 }} />
                    {(() => {
                      const lPx = px(gr.start);
                      // One-shot: barra de 1 dia. Período: duração real.
                      const oneDayPx = pxPerDay;
                      const rPx = gr.tipo==='periodo'
                        ? px(new Date(gr.end.getTime()+864e5))
                        : lPx + oneDayPx;
                      const wPx = Math.max(rPx - lPx, 2);
                      const isShort = gr.tipo !== 'periodo'; // one-shot = barra mais fina
                      return (
                        <div
                          onMouseEnter={e => setTooltip({ x: e.clientX, y: e.clientY, gr })}
                          onMouseMove={e => setTooltip(t => t ? { ...t, x: e.clientX, y: e.clientY } : null)}
                          onMouseLeave={() => setTooltip(null)}
                          style={{ position:'absolute', left:lPx, width:wPx,
                            top:0, bottom:0,
                            background: gr.needs_review?'#f59e0b':gr.is_interno?'#92400e':gr.color,
                            display:'flex', alignItems:'center', paddingLeft: wPx > 20 ? 8 : 0,
                            overflow:'hidden', cursor:'pointer', zIndex:6,
                            opacity: isShort ? 0.75 : 0.9,
                          }}>
                          {wPx > 35 && <span style={{ fontSize:9, color:'#fff', fontWeight:700, whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis', maxWidth:'100%', letterSpacing:'.02em' }}>
                            {gr.needs_review ? 'Rever' : gr.label.slice(0, Math.floor(wPx/7))}
                          </span>}
                        </div>
                      );
                    })()}
                    {gr.row && saving[gr.row.id] && <div style={{ position:'absolute', right:6, top:'50%', transform:'translateY(-50%)', fontSize:10, color:'#16a34a', fontWeight:700 }}>✓</div>}
                  </div>
                </div>
                );
                })}
              </div>{/* fim inner div totalWidth */}
            </div>{/* fim scroll wrapper */}

            {/* Legenda */}
            <div style={{ padding: '8px 14px', borderTop: '1px solid var(--border)', display: 'flex', gap: 16, flexWrap: 'wrap', background: 'var(--bg-sunken)' }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 10, color: 'var(--text-muted)' }}>
                <div style={{ width: 12, height: 3, background: '#ef4444', borderRadius: 1 }} /> Hoje
              </div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 10, color: 'var(--text-muted)' }}>
                <div style={{ width: 12, height: 10, background: '#3859D0', borderRadius: 2 }} /> Período
              </div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 10, color: 'var(--text-muted)' }}>
                <div style={{ width: 10, height: 10, background: '#7c3aed', borderRadius: '50%' }} /> Evento pontual
              </div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 10, color: 'var(--text-muted)' }}>
                <div style={{ width: 12, height: 10, background: '#f59e0b', borderRadius: 2 }} /> Requer revisão
              </div>
            </div>
          </div>
        );
      })()}

      {/* Tooltip hover — popup flutuante com info completa */}
      {tooltip && (() => {
        const { gr } = tooltip;
        const fmtD = (d) => d instanceof Date && !isNaN(d) ? d.toLocaleDateString('pt-PT', { day:'2-digit', month:'short' }) : '—';
        const CONTENT_LABEL = {
          briefing_interno: 'Comunicação Interna', campanha_paid: 'Paid Media',
          email_html: 'Email', social_post: 'Social Orgânico', ad: 'Anúncio',
          artigo_blog: 'Blog', default: CANAL_LABEL[gr.canal] || gr.canal,
        };
        const ctLabel = CONTENT_LABEL[gr.content_type] || CONTENT_LABEL.default;
        const status = gr.needs_review ? 'Requer revisão' : gr.approved ? 'Aprovado' : 'Planeado';
        const statusColor = gr.needs_review ? '#d97706' : gr.approved ? '#16a34a' : '#3859D0';
        // Posição: seguir cursor, evitar sair do viewport
        const vpW = window.innerWidth; const vpH = window.innerHeight;
        const popW = 320; const popH = 220;
        const left = tooltip.x + 16 + popW > vpW ? tooltip.x - popW - 8 : tooltip.x + 16;
        const top  = tooltip.y + 16 + popH > vpH ? tooltip.y - popH - 8 : tooltip.y + 16;
        return (
          <div style={{ position:'fixed', left, top, width:popW, background:'#fff', borderRadius:10, boxShadow:'0 8px 32px rgba(17,41,84,.18)', border:'1px solid var(--border)', zIndex:99999, pointerEvents:'none', overflow:'hidden' }}>
            {/* Header colorido */}
            <div style={{ background: gr.is_interno?'#92400e':gr.color, padding:'10px 14px' }}>
              <div style={{ fontSize:9, fontWeight:700, fontFamily:'var(--font-mono)', textTransform:'uppercase', letterSpacing:'.08em', color:'rgba(255,255,255,.7)', marginBottom:3 }}>{ctLabel}</div>
              <div style={{ fontSize:12, fontWeight:700, color:'#fff', lineHeight:1.3 }}>{gr.fullLabel || gr.label}</div>
            </div>
            {/* Corpo */}
            <div style={{ padding:'10px 14px', display:'flex', flexDirection:'column', gap:8 }}>
              {/* Datas */}
              <div style={{ display:'flex', alignItems:'center', gap:8 }}>
                <div style={{ fontSize:10, fontFamily:'var(--font-mono)', color:'var(--text-dim)', textTransform:'uppercase', letterSpacing:'.06em' }}>Data</div>
                <div style={{ fontSize:11, fontWeight:600, color:'var(--text)' }}>
                  {fmtD(gr.start)}{gr.tipo==='periodo' && gr.end>gr.start ? ` → ${fmtD(gr.end)}` : ''}
                </div>
              </div>
              {/* Conteúdo / tema */}
              {(gr.fullBody || gr.notas) && (
                <div>
                  <div style={{ fontSize:10, fontFamily:'var(--font-mono)', color:'var(--text-dim)', textTransform:'uppercase', letterSpacing:'.06em', marginBottom:3 }}>Conteúdo</div>
                  <div style={{ fontSize:11, color:'var(--text)', lineHeight:1.5, display:'-webkit-box', WebkitLineClamp:3, WebkitBoxOrient:'vertical', overflow:'hidden' }}>
                    {gr.fullBody || gr.notas}
                  </div>
                </div>
              )}
              {/* Status */}
              <div style={{ display:'flex', alignItems:'center', gap:6, paddingTop:4, borderTop:'1px solid var(--border)' }}>
                <span style={{ width:6, height:6, borderRadius:'50%', background:statusColor, flexShrink:0 }} />
                <span style={{ fontSize:10, fontWeight:600, color:statusColor }}>{status}</span>
                {gr.budget && <span style={{ marginLeft:'auto', fontSize:10, fontFamily:'var(--font-mono)', color:'var(--text-muted)' }}>€{gr.budget}</span>}
              </div>
            </div>
          </div>
        );
      })()}

      {toast && <div style={{ position: 'fixed', bottom: 24, right: 24, background: 'var(--text)', color: '#fff', padding: '10px 18px', borderRadius: 8, fontSize: 13, zIndex: 9999 }}>{toast}</div>}
    </div>
  );
};

// ── TabTarget — Targets por canal (Ads + CRM) sem toggle ─────────────────────
const _AUDIENCE_SIZE_COLOR = { 'Pequeno (<10K)': '#b91c1c', 'Médio (10K-100K)': '#a16207', 'Grande (>100K)': '#15803d', 'Pequeno (<500)': '#b91c1c', 'Médio (500-5000)': '#a16207', 'Grande (>5000)': '#15803d' };
const _ADS_CHANNELS_SET = new Set(['meta_ads','linkedin_ads','google_ads_search','google_ads_display','muppi_led']);
const _CRM_CHANNELS_SET = new Set(['email','whatsapp']);

const TabTarget = ({ campanha, userEmail, onAction }) => {
  const bCol = _brandColor(campanha?.brand_slug);
  const [rows,      setRows]      = React.useState([]);
  const [audiences, setAudiences] = React.useState([]);
  const [loading,   setLoading]   = React.useState(true);
  const [generating,setGenerating]= React.useState(false);
  const [toast,     setToast]     = React.useState('');
  const [genError,  setGenError]  = React.useState(null);
  const [editing,      setEditing]      = React.useState(null);
  const [creatingAud,  setCreatingAud]  = React.useState(null); // row.id a criar audiência
  const [newAudNome,   setNewAudNome]   = React.useState('');
  const [savingAud,    setSavingAud]    = React.useState(false);
  const [genStep,   setGenStep]   = React.useState(0);
  const [genElapsed,setGenElapsed]= React.useState(0);
  const genTimersRef     = React.useRef([]);
  const genElapsedRef    = React.useRef(null);
  // Modal de filtros manuais
  const [showFilters, setShowFilters] = React.useState(false);
  const [crmMeta,     setCrmMeta]     = React.useState(null); // dados do /crm/meta
  const [userFilters, setUserFilters] = React.useState({
    // Meta Ads
    meta_interesses: [], meta_comportamentos: [], meta_idade_min: '', meta_idade_max: '',
    // Email CRM
    email_profile_seg1: [], email_sources_grupo: [],
    // WhatsApp CRM
    wa_cargo: [], wa_lost_janela_min: '', wa_lost_janela_max: '',
  });

  const strategyChannels = new Set((campanha?.estrategia_json?.markets || []).flatMap(m => (m.channel_fit||[]).map(ch => ch.canal)));
  const hasAds = [...strategyChannels].some(c => _ADS_CHANNELS_SET.has(c));
  const hasCrm = [...strategyChannels].some(c => _CRM_CHANNELS_SET.has(c));
  const adsCanais = [...strategyChannels].filter(c => _ADS_CHANNELS_SET.has(c));
  const crmCanais = [...strategyChannels].filter(c => _CRM_CHANNELS_SET.has(c));

  // Steps dinâmicos conforme canais aprovados no briefing — step numbers auto-sequenciais
  const hasEmail = crmCanais.includes('email');
  const hasWA    = crmCanais.includes('whatsapp');
  const TARGET_STEPS = [
    {
      label: 'Briefing · Estratégia · Conceito',
      detail: `Canais activos: ${[...adsCanais,...crmCanais].map(c=>CANAL_LABEL[c]||c).join(', ')} · Objectivo · Personas · USPs · Restrições`,
      duration: 2000,
    },
    ...(hasAds ? [{
      label: `Interesses Meta Ads`,
      detail: `meta-ads-b2b skill · pesquisa IDs reais por keyword · ${adsCanais.map(c=>CANAL_LABEL[c]||c).join(', ')}`,
      duration: 8000,
    }] : []),
    ...(hasAds ? [{
      label: 'Target Ads — Geração',
      detail: `Claude Sonnet · interesses confirmados Meta · 1 ad set por país · guardrails briefing`,
      duration: 15000,
    }] : []),
    ...(hasEmail ? [{
      label: 'Target Email — BD Gestor',
      detail: `Massivo · profile_seg1 (Impressão Digital) · sem equipa/stages · 10k+ contactos`,
      duration: 15000,
    }] : []),
    ...(hasWA ? [{
      label: 'Target WA — BD Gestor',
      detail: `Reactivação (OPs perdidas produto) + Aceleração (AG Decisão) · cargo decisor`,
      duration: 15000,
    }] : []),
    {
      label: 'Contagens Reais + Guardar',
      detail: `BD Gestor: n_entidades + n_contactos reais · campanha_segmentacao_plano × mercado`,
      duration: 1500,
    },
  ].map((s, i) => ({ ...s, step: i + 1 }));

  const showToast = (msg) => { setToast(msg); setTimeout(() => setToast(''), 2500); };

  const openFilters = () => {
    setShowFilters(true);
    if (!crmMeta) campApiCall('/api/marketing/crm/meta').then(d => setCrmMeta(d)).catch(() => {});
  };

  const load = () => {
    setLoading(true);
    Promise.all([
      campApiCall(`/api/marketing/campanhas/${campanha.id}/segmentacao`),
      campApiCall('/api/marketing/crm-audiences-list').catch(() => ({ rows: [] }))
    ]).then(([segD, audD]) => {
      setRows(segD.rows || []);
      setAudiences(audD.rows || []);
    }).finally(() => setLoading(false));
  };
  React.useEffect(() => { if (campanha?.id) load(); }, [campanha?.id]);

  const generate = async () => {
    setGenerating(true);
    setGenError(null);
    setGenStep(1);
    setGenElapsed(0);
    genElapsedRef.current = setInterval(() => setGenElapsed(s => s + 1), 1000);
    let cum = 0;
    genTimersRef.current = TARGET_STEPS.map(s => {
      cum += s.duration;
      return setTimeout(() => setGenStep(s.step + 1 <= TARGET_STEPS.length ? s.step + 1 : TARGET_STEPS.length), cum);
    });
    const cleanup = () => {
      clearInterval(genElapsedRef.current);
      genTimersRef.current.forEach(clearTimeout);
      genTimersRef.current = [];
      setGenStep(0); setGenElapsed(0);
    };
    try {
      if (hasAds) {
        const d = await campApiCall(`/api/marketing/campanhas/${campanha.id}/publicos-ads/generate`, { method: 'POST', body: JSON.stringify({ user_email: userEmail, user_filters: userFilters }) });
        if (d.error) throw new Error(d.error);
      }
      if (hasCrm) {
        const d = await campApiCall(`/api/marketing/campanhas/${campanha.id}/segmentos-crm/generate`, { method: 'POST', body: JSON.stringify({ user_email: userEmail, user_filters: userFilters }) });
        if (d.error) throw new Error(d.error);
      }
      cleanup();
      load();
      showToast('Targets gerados');
      if (onAction) onAction('refreshCampanha');
    } catch(e) {
      cleanup();
      setGenError(e.message || 'Erro desconhecido');
      showToast('Erro: ' + (e.message || 'Erro desconhecido'));
    }
    setGenerating(false);
  };
  React.useEffect(() => () => { clearInterval(genElapsedRef.current); genTimersRef.current.forEach(clearTimeout); }, []);

  const userName = window.currentUser?.name || window.currentUser?.displayName || userEmail?.split('@')[0] || '';

  const approve = async () => {
    const missing = rows.filter(r => _CRM_CHANNELS_SET.has(r.canal) && !r.crm_audience_nome);
    if (missing.length > 0) {
      showToast(`Associa audiência CRM antes de confirmar: ${missing.map(r => r.audiencia_nome || r.country).join(', ')}`);
      return;
    }
    await campApiCall(`/api/marketing/campanhas/${campanha.id}/segmentacao/approve`, {
      method: 'POST', body: JSON.stringify({ user_email: userEmail, user_name: userName })
    });
    load();
    showToast('Targets confirmados');
    if (onAction) onAction('refreshCampanha');
  };

  const saveEdit = async (row, updates) => {
    try {
      await campApiCall(`/api/marketing/campanhas/${campanha.id}/segmentacao/${row.id}`, {
        method: 'PUT', body: JSON.stringify({ ...updates, user_email: userEmail })
      });
      setRows(rs => rs.map(r => r.id === row.id ? { ...r, ...updates } : r));
      setEditing(null);
      showToast('Guardado');
    } catch { showToast('Erro ao guardar'); }
  };

  // Agrupar rows por canal — Ads primeiro, depois CRM
  const CANAL_ORDER = ['meta_ads','linkedin_ads','google_ads_search','google_ads_display','muppi_led','email','whatsapp'];
  const rowsByCanal = {};
  for (const r of rows) {
    if (!rowsByCanal[r.canal]) rowsByCanal[r.canal] = [];
    rowsByCanal[r.canal].push(r);
  }
  const orderedCanais = Object.keys(rowsByCanal).sort((a, b) => {
    const ia = CANAL_ORDER.indexOf(a); const ib = CANAL_ORDER.indexOf(b);
    return (ia === -1 ? 99 : ia) - (ib === -1 ? 99 : ib);
  });
  const allConfirmed = rows.length > 0 && rows.every(r => r.approved_at);
  const STATUSES_AFTER_TARGET = ['segmentacao_aprovada','planeamento_pendente','planeamento_gerado','planeamento_aprovado','funil_pendente','funil_gerado','funil_aprovado','pending_executive','em_aprovacao','aprovado'];
  const isConfirmed = STATUSES_AFTER_TARGET.includes(campanha?.status) || allConfirmed;

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>

      {/* Status strip */}
      {rows.length > 0 && !generating && (
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 14px', background: 'var(--bg-surface,#fff)', borderRadius: 'var(--radius-md,8px)', boxShadow: 'var(--shadow-card)' }}>
          <span style={{ fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em', padding: '3px 8px', borderRadius: 'var(--radius-xs,4px)',
            background: isConfirmed ? 'var(--green-100,#E1F7E6)' : 'var(--dd-blue-100,#EBEFF9)',
            color:      isConfirmed ? 'var(--green-700,#1F8A52)' : 'var(--dd-primary-600,#3859D0)' }}>
            {isConfirmed ? 'Confirmado' : 'Gerado — aguarda confirmação'}
          </span>
          <span style={{ fontSize: 11, color: 'var(--fg-3,var(--text-muted))' }}>
            {orderedCanais.length} canal{orderedCanais.length !== 1 ? 'ais' : ''} · {[...new Set(rows.map(r=>r.country))].join(' + ')}
            {campanha?.segmentacao_generated_at && ` · gerado ${new Date(campanha.segmentacao_generated_at).toLocaleString('pt-PT')}`}
          </span>
          {isConfirmed && campanha?.segmentacao_approved_at && (
            <span style={{ fontSize: 11, color: 'var(--green-700, #1F8A52)' }}>
              · aprovada {new Date(campanha.segmentacao_approved_at).toLocaleString('pt-PT')}{campanha.segmentacao_approved_by ? ` por ${String(campanha.segmentacao_approved_by).split('@')[0]}` : ''}
            </span>
          )}
          <div style={{ marginLeft: 'auto', display: 'flex', gap: 6 }}>
            <button className="btn" onClick={openFilters} style={{ height: 28, padding: '0 12px', fontSize: 12 }}>Filtros</button>
            <button className="btn" onClick={generate} disabled={generating} style={{ height: 28, padding: '0 12px', fontSize: 12 }}>Regenerar</button>
            {!isConfirmed && (
              <button className="btn btn-ai" onClick={approve} style={{ height: 28, padding: '0 14px', fontSize: 12 }}>Confirmar Targets</button>
            )}
          </div>
        </div>
      )}

      {/* Cards resumo de audiência — canal × país × total */}
      {!loading && !generating && rows.length > 0 && (() => {
        // Agregar: por canal × país, somar contactos (CRM) ou usar audience_size (Ads)
        const summaryMap = {};
        for (const r of rows) {
          const key = `${r.canal}__${r.country}`;
          const aj = typeof r.audiencia_json === 'string' ? JSON.parse(r.audiencia_json||'{}') : (r.audiencia_json||{});
          if (!summaryMap[key]) summaryMap[key] = { canal: r.canal, country: r.country, n_contactos: 0, n_entidades: 0, audience_size: null, isAds: _ADS_CHANNELS_SET.has(r.canal) };
          summaryMap[key].n_contactos += (aj.n_contactos || 0);
          summaryMap[key].n_entidades += (aj.n_entidades || 0);
          // Para Ads: usar audience_size real da Meta API
          const rawAudSize = aj.audience_size || aj.audience_estimate;
          if (!summaryMap[key].audience_size && rawAudSize && String(rawAudSize).trim() !== '') {
            summaryMap[key].audience_size = String(rawAudSize);
          }
        }
        const summaries = Object.values(summaryMap).sort((a,b) => {
          const CANAL_ORDER = ['meta_ads','linkedin_ads','google_ads_search','google_ads_display','muppi_led','email','whatsapp'];
          return (CANAL_ORDER.indexOf(a.canal)+99||99) - (CANAL_ORDER.indexOf(b.canal)+99||99);
        });
        const CANAL_COLOR = { meta_ads:'#3859D0', linkedin_ads:'#0a66c2', google_ads_search:'#ea4335', google_ads_display:'#34a853', email:'#7c3aed', whatsapp:'#16a34a', muppi_led:'#0891b2' };
        const CANAL_ORDER = ['meta_ads','linkedin_ads','google_ads_search','google_ads_display','muppi_led','email','whatsapp'];
        // Agrupar por canal
        const byCanal = {};
        summaries.forEach(s => {
          if (!byCanal[s.canal]) byCanal[s.canal] = [];
          byCanal[s.canal].push(s);
        });
        const orderedCanais = Object.keys(byCanal).sort((a,b) => (CANAL_ORDER.indexOf(a)+99) - (CANAL_ORDER.indexOf(b)+99));
        return (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
            {orderedCanais.map(canal => {
              const color = CANAL_COLOR[canal] || '#3859D0';
              const canalCards = byCanal[canal].sort((a,b) => a.country.localeCompare(b.country));
              return (
                <div key={canal}>
                  <div style={{ fontSize: 10, fontWeight: 700, fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '.08em', color, marginBottom: 8 }}>
                    {CANAL_LABEL[canal] || canal}
                  </div>
                  <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
                    {canalCards.map(s => {
                      const mainNum = s.isAds
                        ? (s.audience_size || '—')
                        : (s.n_contactos > 0 ? s.n_contactos.toLocaleString('pt-PT') : s.n_entidades > 0 ? s.n_entidades.toLocaleString('pt-PT') : '—');
                      const subLabel = s.isAds ? 'alcance estimado' : (s.n_contactos > 0 ? 'contactos' : 'empresas');
                      return (
                        <div key={`${s.canal}__${s.country}`} style={{ flex: '1 1 120px', background: 'var(--bg-card,#fff)', border: `1px solid var(--border)`, borderTop: `3px solid ${color}`, borderRadius: 8, padding: '12px 14px', minWidth: 110 }}>
                          <div style={{ fontSize: 11, fontWeight: 700, color: 'var(--text-dim)', marginBottom: 8 }}>{s.country}</div>
                          <div style={{ fontSize: 24, fontWeight: 800, fontFamily: 'var(--font-display)', color: 'var(--text)', lineHeight: 1 }}>{mainNum}</div>
                          <div style={{ fontSize: 10, color: 'var(--text-muted)', marginTop: 4 }}>{subLabel}</div>
                        </div>
                      );
                    })}
                  </div>
                </div>
              );
            })}
          </div>
        );
      })()}

      {/* Loading state */}
      {generating && <PhaseLoadingState steps={TARGET_STEPS} activeStep={genStep} elapsedSec={genElapsed} brandColor="#7c3aed" />}
      {loading && !generating && <div style={{ padding: 32, color: 'var(--text-muted)', fontSize: 13 }}>A carregar...</div>}

      {/* Erro */}
      {genError && !generating && (
        <div style={{ padding: '14px 18px', background: 'rgba(220,38,38,.06)', border: '1px solid rgba(220,38,38,.25)', borderRadius: 10, display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12 }}>
          <div style={{ fontSize: 13, color: '#991B1B' }}>Erro: {genError}</div>
          <button className="btn" onClick={generate} style={{ height: 28, fontSize: 12, color: '#991B1B' }}>Tentar novamente</button>
        </div>
      )}

      {/* Empty state */}
      {!loading && !generating && rows.length === 0 && !genError && (
        <PhaseEmptyState
          label="Target · fase 4"
          title="Ainda sem targets definidos"
          description={`Análise de audiências e segmentos com base no briefing, estratégia e conceito.${hasAds ? ' Ads: interesses, cargos, lookalikes, geo por canal (Meta / LinkedIn / Google).' : ''}${hasCrm ? ' CRM: filtros Gestor para envio Email / WhatsApp (sector, cargo, região, equipamento).' : ''}`}
          ctaLabel="Gerar Targets →"
          onCta={generate}
          loading={generating}
          disabled={!campanha?.estrategia_json || (!hasAds && !hasCrm)}
          disabledReason={!campanha?.estrategia_json ? 'A estratégia precisa de ser gerada primeiro.' : 'Nenhum canal Ads ou CRM na estratégia.'}
        />
      )}

      {/* Targets por canal — mesmo padrão visual do Orçamento */}
      {!loading && !generating && orderedCanais.map(canal => {
        const canalRows = rowsByCanal[canal];
        const isCrm = _CRM_CHANNELS_SET.has(canal);
        const isAds = _ADS_CHANNELS_SET.has(canal);
        return (
          <div key={canal} style={{ background: 'var(--bg-card,#fff)', border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden' }}>
            {/* Canal header */}
            <div style={{ padding: '10px 16px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', gap: 10 }}>
              <div style={{ fontSize: 12, fontWeight: 700, color: 'var(--text)', fontFamily: 'var(--font-display)', textTransform: 'uppercase', letterSpacing: '.04em' }}>{CANAL_LABEL[canal] || canal}</div>
              <span style={{ fontSize: 10, padding: '1px 7px', borderRadius: 3, fontFamily: 'var(--font-mono)', fontWeight: 600,
                background: isAds ? 'rgba(56,89,208,.1)' : 'rgba(124,58,237,.1)',
                color: isAds ? '#3859D0' : '#7c3aed' }}>
                {isAds ? 'Ads' : 'CRM'}
              </span>
            </div>

            {/* Rows por mercado */}
            {canalRows.map(row => {
              const aj = typeof row.audiencia_json === 'string' ? JSON.parse(row.audiencia_json || '{}') : (row.audiencia_json || {});
              const td = aj.targeting_details || {};
              // Extrair audience_size real da Meta API
              const rawSize = aj.audience_size || '';
              const cleanSize = rawSize.replace(/^(Grande|Médio|Pequeno|Muy Grande|Muy Pequeño)\s*/i, '').replace(/^\(|\)$/g,'').trim();
              const sizeColor = 'var(--navy, #112954)';
              const isEditing = editing === row.id;
              return (
                <div key={row.id} style={{ padding: '12px 16px', borderBottom: '1px solid var(--border)', borderLeft: row.approved_at ? '3px solid var(--success,#16a34a)' : '3px solid transparent' }}>
                  {/* Mercado + nome Meta (naming convention) + tamanho estimado */}
                  <div style={{ display: 'flex', alignItems: 'flex-start', gap: 8, marginBottom: 10 }}>
                    {/* Country badge apenas para CRM — Ads já tem [PT]/[ES] no nome */}
                    {isCrm && <span style={{ fontSize: 10, fontWeight: 700, fontFamily: 'var(--font-mono)', background: 'var(--bg-sunken)', padding: '2px 7px', borderRadius: 3, color: 'var(--text-dim)', flexShrink: 0, marginTop: 2 }}>{row.country}</span>}
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div style={{ fontSize: 12, fontWeight: 700, color: 'var(--text)', fontFamily: 'var(--font-mono)' }}>{row.audiencia_nome || '—'}</div>
                      {isAds && cleanSize && (
                        <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginTop: 4 }}>
                          <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', textTransform: 'uppercase', color: 'var(--text-dim)', letterSpacing: '.06em' }}>Alcance estimado</div>
                          <div style={{ fontSize: 14, fontWeight: 800, fontFamily: 'var(--font-display)', color: '#3859D0' }}>{cleanSize}</div>
                        </div>
                      )}
                    </div>
                    {(!isConfirmed || isCrm) && (
                      <button className="btn" style={{ height: 22, padding: '0 8px', fontSize: 10, flexShrink: 0 }} onClick={() => setEditing(isEditing ? null : row.id)}>{isEditing ? 'Fechar' : 'Editar'}</button>
                    )}
                  </div>

                  {/* Targeting details — Ads: interesses+comportamentos | CRM: filtros Gestor + contagens reais */}
                  {isAds && td && (
                    <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
                      {/* Localização + Idade + Género — como Meta Ads Manager */}
                      <div style={{ display: 'flex', gap: 20, fontSize: 12 }}>
                        {td.geo?.length > 0 && <div><div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', textTransform: 'uppercase', color: 'var(--text-dim)', letterSpacing: '.06em' }}>Localização</div><div style={{ color: 'var(--text)' }}>{td.geo.join(', ')}</div></div>}
                        <div><div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', textTransform: 'uppercase', color: 'var(--text-dim)', letterSpacing: '.06em' }}>Idade</div><div style={{ color: 'var(--text)' }}>{td.idade_min||25} – {td.idade_max||65}+</div></div>
                        <div><div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', textTransform: 'uppercase', color: 'var(--text-dim)', letterSpacing: '.06em' }}>Género</div><div style={{ color: 'var(--text)' }}>Todos</div></div>
                      </div>
                      {/* Definição detalhada — como "Definição do público-alvo detalhada" no Meta */}
                      <div style={{ background: 'var(--bg-sunken)', borderRadius: 6, padding: '8px 10px', display: 'flex', flexDirection: 'column', gap: 5 }}>
                        <div style={{ fontSize: 10, fontWeight: 700, fontFamily: 'var(--font-mono)', textTransform: 'uppercase', color: 'var(--text-dim)', letterSpacing: '.06em' }}>Definição do público-alvo detalhada</div>
                        {td.interesses?.length > 0 && (
                          <div style={{ fontSize: 11, color: 'var(--text)', lineHeight: 1.5 }}>
                            <span style={{ color: 'var(--text-dim)' }}>Interesses: </span>{td.interesses.join(', ')}
                          </div>
                        )}
                        {td.comportamentos?.length > 0 && (
                          <div style={{ fontSize: 11, color: 'var(--text)', lineHeight: 1.5 }}>
                            <span style={{ color: 'var(--text-dim)' }}>E que também correspondem a — Comportamentos: </span>{td.comportamentos.join(', ')}
                          </div>
                        )}
                        {td.cargos?.length > 0 && (
                          <div style={{ fontSize: 11, color: 'var(--text)', lineHeight: 1.5 }}>
                            <span style={{ color: 'var(--text-dim)' }}>Cargos: </span>{td.cargos.join(', ')}
                          </div>
                        )}
                        {td.exclusoes?.length > 0 && (
                          <div style={{ fontSize: 11, color: '#991B1B', lineHeight: 1.5 }}>
                            <span style={{ color: '#991B1B' }}>Excluir: </span>{td.exclusoes.join(', ')}
                          </div>
                        )}
                      </div>
                    </div>
                  )}
                  {isCrm && (
                    <div style={{ background: 'var(--bg-sunken)', borderRadius: 6, padding: '8px 10px', display: 'flex', flexDirection: 'column', gap: 4 }}>
                      {/* Badge objectivo + Contagens reais do Gestor */}
                      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 8, flexWrap: 'wrap' }}>
                        {aj.objectivo && (() => {
                          const OBJ = { pipeline: ['Pipeline', '#3859D0'], reactivacao: ['Reactivação', '#d97706'], aceleracao: ['Aceleração', '#059669'] };
                          const [label, color] = OBJ[aj.objectivo] || ['—', 'var(--text-dim)'];
                          return <span style={{ fontSize: 10, fontWeight: 700, fontFamily: 'var(--font-mono)', padding: '2px 8px', borderRadius: 3, background: color+'18', color, textTransform: 'uppercase', letterSpacing: '.06em' }}>{label}</span>;
                        })()}
                        {aj.n_entidades != null && (
                          <>
                            <div>
                              <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', textTransform: 'uppercase', color: 'var(--text-dim)', letterSpacing: '.06em' }}>Empresas</div>
                              <div style={{ fontSize: 16, fontWeight: 800, fontFamily: 'var(--font-display)', color: 'var(--text)' }}>{(aj.n_entidades||0).toLocaleString('pt-PT')}</div>
                            </div>
                            <div>
                              <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', textTransform: 'uppercase', color: 'var(--text-dim)', letterSpacing: '.06em' }}>Contactos</div>
                              <div style={{ fontSize: 16, fontWeight: 800, fontFamily: 'var(--font-display)', color: 'var(--text)' }}>{(aj.n_contactos||0).toLocaleString('pt-PT')}</div>
                            </div>
                          </>
                        )}
                      </div>
                      {/* Filtros aplicados */}
                      {(() => {
                        const fg = td.filtros_gestor || {};
                        return (
                          <div style={{ display: 'flex', flexWrap: 'wrap', gap: '3px 12px', fontSize: 11, color: 'var(--text-muted)' }}>
                            {fg.pais?.length > 0 && <span><strong style={{ color: 'var(--text-dim)' }}>País:</strong> {fg.pais.join(', ')}</span>}
                            {fg.equipa?.length > 0 && <span><strong style={{ color: 'var(--text-dim)' }}>Equipa:</strong> {fg.equipa.join(', ')}</span>}
                            {fg.profile_seg1?.length > 0 && <span><strong style={{ color: 'var(--text-dim)' }}>Perfil:</strong> {fg.profile_seg1.join(', ')}</span>}
                            {fg.stages?.length > 0 && <span><strong style={{ color: 'var(--text-dim)' }}>Stages:</strong> {fg.stages.join(', ')}</span>}
                            {fg.lost_produto?.length > 0 && <span><strong style={{ color: '#d97706' }}>Produto perdido:</strong> {fg.lost_produto.slice(0,3).join(', ')}</span>}
                            {fg.lost_janela && <span style={{ color: '#d97706' }}>OPs perdidas {fg.lost_janela.min}-{fg.lost_janela.max}m</span>}
                            {fg.sources_grupo?.length > 0 && <span><strong style={{ color: 'var(--text-dim)' }}>Origem:</strong> {fg.sources_grupo.join(', ')}</span>}
                            {fg.com_op_activa && <span style={{ color: '#15803d' }}>Com OP activa</span>}
                            {fg.tem_email && <span>Email OK</span>}
                            {fg.tem_telefone && <span>Tel. OK</span>}
                          </div>
                        );
                      })()}
                    </div>
                  )}

                  {/* CRM audience link */}
                  {isCrm && (
                    <div style={{ marginTop: 8 }}>
                      <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: creatingAud === row.id ? 8 : 0 }}>
                        <span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase' }}>CRM:</span>
                        {row.crm_audience_nome
                          ? <span style={{ fontSize: 11, color: 'var(--text)', fontWeight: 600 }}>{row.crm_audience_nome} ({row.crm_audience_count?.toLocaleString('pt-PT')||'?'})</span>
                          : isEditing
                            ? <>
                                <select style={{ fontSize: 11, padding: '2px 6px', borderRadius: 4, border: '1px solid var(--border)', background: 'var(--bg)', color: 'var(--text)', flex: 1 }} defaultValue=""
                                  onChange={e => { const sel = audiences.find(a => a.id === e.target.value); saveEdit(row, { crm_audience_uuid: e.target.value||null, crm_audience_nome: sel?.nome||null, crm_audience_count: sel?.contagem_ultima||null, audiencia_nome: row.audiencia_nome, audiencia_json: aj }); }}>
                                  <option value="">— Sem audiência CRM —</option>
                                  {audiences.map(a => <option key={a.id} value={a.id}>{a.nome} ({a.contagem_ultima?.toLocaleString('pt-PT')||'?'})</option>)}
                                </select>
                                <button
                                  className="btn btn-ai"
                                  style={{ fontSize: 10, height: 24, padding: '0 8px', flexShrink: 0 }}
                                  onClick={() => { setCreatingAud(row.id); setNewAudNome(row.audiencia_nome || ''); }}
                                >+ Criar</button>
                              </>
                            : <span style={{ fontSize: 11, color: 'var(--text-muted)' }}>Não associada</span>
                        }
                      </div>
                      {/* Mini-form criação de audiência */}
                      {creatingAud === row.id && (
                        <div style={{ background: 'rgba(56,89,208,.05)', border: '1px solid rgba(56,89,208,.2)', borderRadius: 8, padding: '10px 12px', marginTop: 4 }}>
                          <div style={{ fontSize: 10, fontWeight: 700, color: '#3859D0', fontFamily: 'var(--font-mono)', textTransform: 'uppercase', marginBottom: 6 }}>Nova audiência CRM Marketing</div>
                          <input
                            value={newAudNome}
                            onChange={e => setNewAudNome(e.target.value)}
                            placeholder="Nome da audiência..."
                            style={{ width: '100%', fontSize: 12, padding: '6px 10px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg)', color: 'var(--text)', boxSizing: 'border-box', marginBottom: 8 }}
                          />
                          <div style={{ display: 'flex', gap: 6 }}>
                            <button className="btn btn-ai" disabled={savingAud || !newAudNome.trim()} style={{ fontSize: 11, height: 26, padding: '0 12px' }}
                              onClick={async () => {
                                setSavingAud(true);
                                try {
                                  // Criar audiência com a definição dos filtros do segmento
                                  const aud = await campApiCall('/api/marketing/crm/audiences', {
                                    method: 'POST',
                                    body: JSON.stringify({
                                      nome: newAudNome.trim(),
                                      descricao: `Criada a partir de campanha — ${campanha?.titulo || ''} · ${row.country}`,
                                      definicao: aj.targeting_details?.filtros_gestor || {},
                                      created_by: userEmail,
                                    })
                                  });
                                  // Associar ao segmento
                                  await saveEdit(row, { crm_audience_uuid: aud.id, crm_audience_nome: aud.nome, crm_audience_count: aud.contagem_ultima||null, audiencia_nome: row.audiencia_nome, audiencia_json: aj });
                                  // Actualizar lista de audiências
                                  const audList = await campApiCall('/api/marketing/crm-audiences-list').catch(() => ({ rows: [] }));
                                  if (audList.rows) setAudiences(audList.rows);
                                  setCreatingAud(null);
                                  setNewAudNome('');
                                  showToast(`Audiência "${aud.nome}" criada e associada`);
                                } catch (e) { showToast('Erro: ' + e.message); }
                                setSavingAud(false);
                              }}>
                              {savingAud ? 'A criar...' : 'Criar e associar'}
                            </button>
                            <button className="btn" style={{ fontSize: 11, height: 26, padding: '0 10px' }} onClick={() => { setCreatingAud(null); setNewAudNome(''); }}>Cancelar</button>
                          </div>
                        </div>
                      )}
                    </div>
                  )}

                  {aj.notas && <div style={{ fontSize: 11, color: 'var(--text-muted)', fontStyle: 'italic', marginTop: 4 }}>{aj.notas}</div>}
                </div>
              );
            })}
          </div>
        );
      })}

      {/* Modal de Filtros de Target */}
      {showFilters && (() => {
        const uf = userFilters;
        const setUf = (patch) => setUserFilters(prev => ({ ...prev, ...patch }));
        const toggleArr = (key, val) => setUf({ [key]: uf[key].includes(val) ? uf[key].filter(x => x !== val) : [...uf[key], val] });
        const nActive = [uf.meta_interesses.length, uf.meta_comportamentos.length, uf.meta_idade_min?1:0, uf.email_profile_seg1.length, uf.email_sources_grupo.length, uf.wa_cargo.length, uf.wa_lost_janela_min?1:0].reduce((a,b)=>a+b,0);
        const profileSegs = crmMeta?.profile_segs || [];
        const seg1Opts = [...new Set(profileSegs.flatMap(s => (s.valores||[]).filter(v=>v.atributo==='1').map(v=>v.valor)))];
        const cargosOpts = (crmMeta?.cargos || []).filter(c => !['Outros','OUTROS'].includes(c)).slice(0, 20);
        const sourcesOpts = crmMeta?.sources_grupos || ['MKT','FEIRAS','RH'];
        const META_INTERESTS = ['Digital printing','Print on demand','Textile industry','Textile printing','Screen printing','Small business','Entrepreneurship','Fashion design','Custom gifts and clothing','Manufacturing','Business networking','Graphic design'];
        const META_BEHAVIORS = ['Small business owners','Business decision makers'];

        const Label = ({ children }) => <div style={{ fontSize: 10, fontWeight: 700, fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '.06em', color: 'var(--text-dim)', marginBottom: 6 }}>{children}</div>;
        const Chips = ({ opts, sel, onToggle, color='#3859D0' }) => (
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 5, marginBottom: 12 }}>
            {opts.map(o => { const a = sel.includes(o); return (
              <button key={o} onClick={() => onToggle(o)} style={{ fontSize: 11, padding: '3px 10px', borderRadius: 20, cursor: 'pointer', border: `1px solid ${a?color:'var(--border)'}`, background: a?color:'var(--bg)', color: a?'#fff':'var(--text-muted)', fontWeight: a?600:400 }}>{o}</button>
            ); })}
          </div>
        );

        return (
          <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,.5)', zIndex: 9100, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24 }}>
            <div style={{ background: '#fff', borderRadius: 14, width: '100%', maxWidth: 700, maxHeight: '87vh', display: 'flex', flexDirection: 'column', boxShadow: '0 24px 60px rgba(0,0,0,.25)' }}>
              <div style={{ padding: '18px 24px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexShrink: 0 }}>
                <div>
                  <div style={{ fontSize: 15, fontWeight: 700, color: 'var(--text)', fontFamily: 'var(--font-display)' }}>Filtros de Target</div>
                  <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 2 }}>Deixar em branco = geração automática pelo agente.</div>
                </div>
                <button onClick={() => setShowFilters(false)} style={{ background: 'none', border: 'none', fontSize: 20, cursor: 'pointer', color: 'var(--text-muted)' }}>×</button>
              </div>

              <div style={{ flex: 1, overflow: 'auto', padding: '20px 24px' }}>

                {/* Meta Ads */}
                {hasAds && (
                  <div style={{ marginBottom: 24 }}>
                    <div style={{ fontSize: 12, fontWeight: 700, color: '#3859D0', fontFamily: 'var(--font-display)', textTransform: 'uppercase', letterSpacing: '.04em', marginBottom: 12, paddingBottom: 6, borderBottom: '2px solid rgba(56,89,208,.15)' }}>Meta Ads</div>
                    <Label>Interesses</Label>
                    <Chips opts={META_INTERESTS} sel={uf.meta_interesses} onToggle={v=>toggleArr('meta_interesses',v)} color="#3859D0" />
                    <Label>Comportamentos</Label>
                    <Chips opts={META_BEHAVIORS} sel={uf.meta_comportamentos} onToggle={v=>toggleArr('meta_comportamentos',v)} color="#3859D0" />
                    <Label>Idade</Label>
                    <div style={{ display: 'flex', gap: 10, alignItems: 'center', marginBottom: 12 }}>
                      <input type="number" placeholder="Min (25)" value={uf.meta_idade_min} onChange={e=>setUf({meta_idade_min:e.target.value})} style={{ width: 90, padding: '5px 8px', borderRadius: 6, border: '1px solid var(--border)', fontSize: 12 }} />
                      <span style={{ color: 'var(--text-dim)' }}>–</span>
                      <input type="number" placeholder="Max (65)" value={uf.meta_idade_max} onChange={e=>setUf({meta_idade_max:e.target.value})} style={{ width: 90, padding: '5px 8px', borderRadius: 6, border: '1px solid var(--border)', fontSize: 12 }} />
                    </div>
                  </div>
                )}

                {/* Email */}
                {crmCanais.includes('email') && (
                  <div style={{ marginBottom: 24 }}>
                    <div style={{ fontSize: 12, fontWeight: 700, color: '#7c3aed', fontFamily: 'var(--font-display)', textTransform: 'uppercase', letterSpacing: '.04em', marginBottom: 12, paddingBottom: 6, borderBottom: '2px solid rgba(124,58,237,.15)' }}>Email — BD Gestor</div>
                    {seg1Opts.length > 0 && (<>
                      <Label>Tipo de Indústria</Label>
                      <Chips opts={seg1Opts} sel={uf.email_profile_seg1} onToggle={v=>toggleArr('email_profile_seg1',v)} color="#7c3aed" />
                    </>)}
                    <Label>Origem do Lead</Label>
                    <Chips opts={sourcesOpts} sel={uf.email_sources_grupo} onToggle={v=>toggleArr('email_sources_grupo',v)} color="#7c3aed" />
                  </div>
                )}

                {/* WhatsApp */}
                {crmCanais.includes('whatsapp') && (
                  <div style={{ marginBottom: 8 }}>
                    <div style={{ fontSize: 12, fontWeight: 700, color: '#16a34a', fontFamily: 'var(--font-display)', textTransform: 'uppercase', letterSpacing: '.04em', marginBottom: 12, paddingBottom: 6, borderBottom: '2px solid rgba(22,163,74,.15)' }}>WhatsApp — BD Gestor</div>
                    {cargosOpts.length > 0 && (<>
                      <Label>Cargo Decisor / Influenciador</Label>
                      <Chips opts={cargosOpts} sel={uf.wa_cargo} onToggle={v=>toggleArr('wa_cargo',v)} color="#16a34a" />
                    </>)}
                    <Label>Reactivação — OPs perdidas há (meses)</Label>
                    <div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
                      <input type="number" placeholder="Min (6)" value={uf.wa_lost_janela_min} onChange={e=>setUf({wa_lost_janela_min:e.target.value})} style={{ width: 90, padding: '5px 8px', borderRadius: 6, border: '1px solid var(--border)', fontSize: 12 }} />
                      <span style={{ color: 'var(--text-dim)' }}>–</span>
                      <input type="number" placeholder="Max (24)" value={uf.wa_lost_janela_max} onChange={e=>setUf({wa_lost_janela_max:e.target.value})} style={{ width: 90, padding: '5px 8px', borderRadius: 6, border: '1px solid var(--border)', fontSize: 12 }} />
                      <span style={{ fontSize: 11, color: 'var(--text-muted)' }}>meses</span>
                    </div>
                  </div>
                )}
              </div>

              <div style={{ padding: '14px 24px', borderTop: '1px solid var(--border)', display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexShrink: 0 }}>
                <button onClick={() => setUserFilters({ meta_interesses:[], meta_comportamentos:[], meta_idade_min:'', meta_idade_max:'', email_profile_seg1:[], email_sources_grupo:[], wa_cargo:[], wa_lost_janela_min:'', wa_lost_janela_max:'' })} className="btn" style={{ fontSize: 12 }}>Limpar filtros</button>
                <div style={{ display: 'flex', gap: 8 }}>
                  <button onClick={() => setShowFilters(false)} className="btn" style={{ fontSize: 12 }}>Cancelar</button>
                  <button onClick={() => { setShowFilters(false); generate(); }} className="btn btn-ai" style={{ fontSize: 12 }}>
                    Gerar com estes filtros{nActive > 0 ? ` (${nActive})` : ''}
                  </button>
                </div>
              </div>
            </div>
          </div>
        );
      })()}

      {toast && <div style={{ position: 'fixed', bottom: 24, right: 24, background: 'var(--text)', color: '#fff', padding: '10px 18px', borderRadius: 8, fontSize: 13, zIndex: 9999 }}>{toast}</div>}
    </div>
  );
};

// ── TabOrcamento — budget split por canal gerado da estratégia · editável · aprovável ──
const TabOrcamento = ({ campanha, userEmail, onAction }) => {
  const bCol = _brandColor(campanha?.brand_slug);
  const [rows,    setRows]    = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [saving,  setSaving]  = React.useState(false);
  const [saved,   setSaved]   = React.useState({});
  const [toast,   setToast]   = React.useState('');
  const [budget,  setBudget]  = React.useState('');
  const [activeMkt, setActiveMkt] = React.useState(null);
  // Análise Meta Ads (histórico — gerado com orçamento)
  const [analyzing,    setAnalyzing]    = React.useState(false);
  const [analysis,     setAnalysis]     = React.useState(null);
  const [analysisErr,  setAnalysisErr]  = React.useState(null);
  // Edição inline do valor por conjunto (ad set)
  const [editingRow, setEditingRow] = React.useState(null); // id da row em edição
  const [editVal,    setEditVal]    = React.useState('');
  // Meta Ads Analyst Agent (SSE streaming)
  const [analystRunning, setAnalystRunning] = React.useState(false);
  const [analystStatus,  setAnalystStatus]  = React.useState('');
  const [analystResult,  setAnalystResult]  = React.useState(null);
  const [analystStream,  setAnalystStream]  = React.useState('');
  // Loading state com fases
  const [genStep,     setGenStep]     = React.useState(0);
  const [genElapsed,  setGenElapsed]  = React.useState(0);
  const genStepTimersRef  = React.useRef([]);
  const genElapsedTimerRef = React.useRef(null);

  const markets = [...new Set((campanha?.estrategia_json?.markets || []).map(m => m.country))];
  const strategyChannels = new Set((campanha?.estrategia_json?.markets || []).flatMap(m => (m.channel_fit || []).map(ch => ch.canal)));
  const hasMetaAds = strategyChannels.has('meta_ads');

  const load = () => {
    setLoading(true);
    campApiCall(`/api/marketing/campanhas/${campanha.id}/orcamento`)
      .then(d => {
        setRows(d.rows || []);
        if (d.rows?.length) setActiveMkt(d.rows[0].country);
        else setActiveMkt(markets[0] || null);
      })
      .finally(() => setLoading(false));
  };
  // Re-load rows sempre que campanha muda (mount + após refreshCampanha/aprovar)
  React.useEffect(() => { if (campanha?.id) load(); }, [campanha?.id, campanha?.updated_at]);

  // Restaurar análise e resultado do Analyst da BD sempre que campanha muda
  // (mount inicial + após refreshCampanha)
  React.useEffect(() => {
    if (!analystRunning) {
      if (campanha?.orcamento_meta_analysis_json) setAnalysis(campanha.orcamento_meta_analysis_json);
      if (campanha?.orcamento_analyst_json)       setAnalystResult(campanha.orcamento_analyst_json);
    }
  }, [campanha?.orcamento_analyst_json, campanha?.orcamento_meta_analysis_json]);

  const generate = async () => {
    setSaving(true);
    setGenStep(1);
    setGenElapsed(0);
    // Escolher steps conforme tem Meta Ads na estratégia
    const steps = hasMetaAds ? ORCAMENTO_STEPS_WITH_META : ORCAMENTO_STEPS_NO_META;
    // Timer elapsed
    genElapsedTimerRef.current = setInterval(() => setGenElapsed(s => s + 1), 1000);
    // Timers de progressão dos steps
    let cumulative = 0;
    genStepTimersRef.current = [];
    steps.forEach((s, i) => {
      cumulative += s.duration;
      genStepTimersRef.current.push(setTimeout(() => setGenStep(Math.min(s.step + 1, steps.length)), cumulative));
    });
    const cleanup = () => {
      clearInterval(genElapsedTimerRef.current);
      genStepTimersRef.current.forEach(t => clearTimeout(t));
      genStepTimersRef.current = [];
      setGenStep(0);
      setGenElapsed(0);
    };
    try {
      const d = await campApiCall(`/api/marketing/campanhas/${campanha.id}/orcamento/generate`, {
        method: 'POST',
        body: JSON.stringify({ budget_total_eur: budget ? parseFloat(budget) : null, user_email: userEmail })
      });
      setRows(d.rows || []);
      if (d.rows?.length) setActiveMkt(d.rows[0].country);
      if (d.meta_analysis) setAnalysis(d.meta_analysis);
      if (d.suggested_budget && !budget) setBudget(String(d.suggested_budget));

      // Se tem Meta Ads, encadear Analyst automaticamente (steps 6-7)
      if (hasMetaAds && d.rows?.length) {
        // Limpar timers pré-agendados (1-5) para não sobrescreverem o step 6-7 manual
        genStepTimersRef.current.forEach(t => clearTimeout(t));
        genStepTimersRef.current = [];
        setGenStep(6);
        setAnalystRunning(true);
        setAnalystResult(null);
        setAnalystStream('');
        setAnalystStatus('');
        await new Promise(resolve => {
          fetch(`/api/marketing/campanhas/${campanha.id}/orcamento/analyse`, {
            method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}'
          }).then(async resp => {
            const reader = resp.body.getReader();
            const dec = new TextDecoder();
            let buf = '';
            while (true) {
              const { done, value } = await reader.read();
              if (done) break;
              buf += dec.decode(value, { stream: true });
              const parts = buf.split('\n\n');
              buf = parts.pop();
              for (const part of parts) {
                if (!part.startsWith('data: ')) continue;
                try {
                  const msg = JSON.parse(part.slice(6));
                  if (msg.type === 'status' && msg.step === 3) setGenStep(7);
                  if (msg.type === 'chunk')  setAnalystStream(s => s + (msg.text || ''));
                  if (msg.type === 'done')   { setAnalystResult(msg.result); setAnalystStatus(''); }
                  if (msg.type === 'error')  setAnalystStatus('Erro: ' + msg.message);
                } catch {}
              }
            }
          }).catch(e => setAnalystStatus('Erro: ' + e.message))
            .finally(() => { setAnalystRunning(false); resolve(); });
        });
      }

      cleanup();
      setToast('Orçamento gerado');
      // Forçar refresh do parent para que campanha.orcamento_analyst_json fique actualizado
      if (onAction) onAction('refreshCampanha');
    } catch(e) { cleanup(); setToast('Erro: ' + e.message); }
    setSaving(false);
    setTimeout(() => setToast(''), 2800);
  };
  React.useEffect(() => () => {
    clearInterval(genElapsedTimerRef.current);
    genStepTimersRef.current.forEach(t => clearTimeout(t));
  }, []);

  const analyzeMetaHistory = async () => {
    setAnalyzing(true);
    setAnalysisErr(null);
    try {
      const d = await campApiCall(`/api/marketing/campanhas/${campanha.id}/orcamento/analyze-meta-history`, { method: 'POST' });
      if (d.error) throw new Error(d.error);
      setAnalysis(d);
    } catch(e) { setAnalysisErr(e.message || 'Erro desconhecido'); }
    setAnalyzing(false);
  };

  const applyMetaSuggestion = () => {
    if (!analysis?.suggestion) return;
    setBudget(String(analysis.suggestion.budget_total_meta_ads));
    setAnalysis(null);
    setToast('Budget aplicado — clica "Regenerar" para actualizar linhas');
    setTimeout(() => setToast(''), 3000);
  };

  const updateRow = async (row, field, val) => {
    const updated = { ...row, [field]: val };
    // Sync pct ↔ valor: proteger divisão por zero e NaN
    const nVal = parseFloat(val);
    const nBudget = parseFloat(budget);
    const totalEur = rows.filter(r => r.country === row.country).reduce((s,r) => s + (parseFloat(r.valor_eur)||0), 0);
    if (field === 'valor_eur' && totalEur > 0 && !isNaN(nVal)) updated.pct_total = Math.round((nVal/totalEur)*100);
    if (field === 'pct_total' && nBudget > 0 && !isNaN(nVal)) updated.valor_eur = Math.round((nVal/100)*nBudget*100)/100;
    setRows(rs => rs.map(r => r.id === row.id ? { ...updated, approved_at: null } : r));
    setSaved(s => ({...s, [row.id]: false}));
    try {
      const d = await campApiCall(`/api/marketing/campanhas/${campanha.id}/orcamento/${row.id}`, {
        method: 'PUT',
        body: JSON.stringify({ valor_eur: updated.valor_eur, pct_total: updated.pct_total, user_email: userEmail })
      });
      setSaved(s => ({...s, [row.id]: true}));
      setTimeout(() => setSaved(s => ({...s, [row.id]: false})), 2000);
      if (d?.statusReverted && onAction) onAction('refreshCampanha');
    } catch(e) { setToast('Erro ao guardar'); setTimeout(() => setToast(''), 2000); }
  };

  const saveEditRow = async (row) => {
    const newValorEur = parseFloat(editVal);
    if (isNaN(newValorEur) || newValorEur <= 0) { setEditingRow(null); return; }
    // Limpa approved_at localmente para allApproved reflectir imediatamente
    setRows(rs => rs.map(r => r.id === row.id ? { ...r, valor_eur: newValorEur, approved_at: null } : r));
    setEditingRow(null);
    try {
      const d = await campApiCall(`/api/marketing/campanhas/${campanha.id}/orcamento/${row.id}`, {
        method: 'PUT', body: JSON.stringify({ valor_eur: newValorEur, user_email: userEmail })
      });
      setToast('Budget actualizado — aprovação necessária');
      if (d?.statusReverted && onAction) onAction('refreshCampanha');
    } catch { setToast('Erro ao guardar'); }
    setTimeout(() => setToast(''), 3000);
  };

  const approve = () => {
    if (onAction) onAction('aprovarOrcamento');
  };

  const runAnalyst = () => {
    setAnalystRunning(true);
    setAnalystResult(null);
    setAnalystStream('');
    setAnalystStatus('A iniciar análise...');
    const es = new EventSource(`/api/marketing/campanhas/${campanha.id}/orcamento/analyse`);
    // EventSource não suporta POST — usar fetch com streaming manual
    es.close();
    setAnalystRunning(true);
    fetch(`/api/marketing/campanhas/${campanha.id}/orcamento/analyse`, {
      method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}'
    }).then(async resp => {
      const reader = resp.body.getReader();
      const dec = new TextDecoder();
      let buf = '';
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        buf += dec.decode(value, { stream: true });
        const parts = buf.split('\n\n');
        buf = parts.pop();
        for (const part of parts) {
          if (!part.startsWith('data: ')) continue;
          try {
            const msg = JSON.parse(part.slice(6));
            if (msg.type === 'status')  setAnalystStatus(msg.message || '');
            if (msg.type === 'chunk')   setAnalystStream(s => s + (msg.text || ''));
            if (msg.type === 'done')    { setAnalystResult(msg.result); setAnalystStatus(''); }
            if (msg.type === 'error')   { setAnalystStatus('Erro: ' + msg.message); }
          } catch {}
        }
      }
    }).catch(e => setAnalystStatus('Erro: ' + e.message))
      .finally(() => {
        setAnalystRunning(false);
        // Actualizar parent para que campanha.orcamento_analyst_json fique correcto
        if (onAction) onAction('refreshCampanha');
      });
  };

  const mktRows = rows.filter(r => r.country === activeMkt);
  const totalPct = mktRows.reduce((s,r) => s + (parseFloat(r.pct_total)||0), 0);
  const totalEur = mktRows.reduce((s,r) => s + (parseFloat(r.valor_eur)||0), 0);
  const pctOk = Math.round(totalPct) === 100;
  // Aprovação: lê do campanha.status (fiável, actualizado pelo parent via load())
  // Fallback para rows.every caso status ainda não tenha transitado
  const STATUSES_AFTER_ORCAMENTO = ['segmentacao_pendente','segmentacao_gerada','segmentacao_aprovada',
    'planeamento_pendente','planeamento_gerado','planeamento_aprovado',
    'funil_pendente','funil_gerado','funil_aprovado','pending_executive','em_aprovacao','aprovado'];
  const allApproved = STATUSES_AFTER_ORCAMENTO.includes(campanha?.status)
    || (rows.length > 0 && rows.every(r => r.approved_at));
  const budgetTotal = rows.reduce((s,r) => s + (parseFloat(r.valor_eur)||0), 0);
  const nCanais = [...new Set(rows.map(r => r.canal))].length;
  const nMercados = [...new Set(rows.map(r => r.country))].length;

  const thS = { fontSize: 10, fontWeight: 700, fontFamily: 'var(--font-mono)', letterSpacing: '0.06em', textTransform: 'uppercase', color: 'var(--text-dim)', padding: '8px 14px', background: 'var(--bg-sunken)', borderBottom: '1px solid var(--border)' };
  const tdS = { fontSize: 13, padding: '10px 14px', borderBottom: '1px solid var(--border)', verticalAlign: 'middle' };
  const inputS = { fontSize: 13, padding: '5px 8px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg)', color: 'var(--text)', width: '90px', textAlign: 'right', fontFamily: 'var(--font-mono)' };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
      {/* Status strip + actions — padrão consistente */}
      {rows.length > 0 && (
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 14px', background: 'var(--bg-surface,#fff)', borderRadius: 'var(--radius-md,8px)', boxShadow: 'var(--shadow-card)' }}>
          <span style={{ fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em', padding: '3px 8px', borderRadius: 'var(--radius-xs,4px)',
            background: allApproved ? 'var(--green-100,#E1F7E6)' : 'var(--dd-blue-100,#EBEFF9)',
            color:      allApproved ? 'var(--green-700,#1F8A52)' : 'var(--dd-primary-600,#3859D0)' }}>
            {allApproved ? 'Aprovado' : 'Gerado — aguarda aprovação'}
          </span>
          <span style={{ fontSize: 11, color: 'var(--fg-3, var(--text-muted))' }}>
            {allApproved
              ? `€${Math.round(budgetTotal).toLocaleString('pt-PT')} · ${nCanais} canal${nCanais !== 1 ? 'ais' : ''} · ${nMercados} mercado${nMercados !== 1 ? 's' : ''}`
              : `${nCanais} canal${nCanais !== 1 ? 'ais' : ''} · ${nMercados} mercado${nMercados !== 1 ? 's' : ''}`}
            {campanha?.orcamento_generated_at && ` · gerado ${new Date(campanha.orcamento_generated_at).toLocaleString('pt-PT')}`}
          </span>
          {allApproved && campanha?.orcamento_approved_at && (
            <span style={{ fontSize: 11, color: 'var(--green-700, #1F8A52)' }}>
              · aprovada {new Date(campanha.orcamento_approved_at).toLocaleString('pt-PT')}{campanha.orcamento_approved_by ? ` por ${String(campanha.orcamento_approved_by).split('@')[0]}` : ''}
            </span>
          )}
          <div style={{ marginLeft: 'auto', display: 'flex', gap: 6, alignItems: 'center' }}>
            <button className="btn" onClick={generate} disabled={saving} style={{ height: 28, padding: '0 12px', fontSize: 12 }}>
              {saving ? 'A gerar...' : 'Regenerar'}
            </button>
            {!allApproved && (
              <button className="btn btn-ai" onClick={approve} style={{ height: 28, padding: '0 14px', fontSize: 12 }}>
                Aprovar Orçamento
              </button>
            )}
          </div>
        </div>
      )}

      {/* Modal Análise Meta Ads */}

      {saving && (
        <PhaseLoadingState
          steps={hasMetaAds ? ORCAMENTO_STEPS_WITH_META : ORCAMENTO_STEPS_NO_META}
          activeStep={genStep}
          elapsedSec={genElapsed}
          brandColor="#0891b2"
        />
      )}
      {loading && !saving && <div style={{ padding: 32, color: 'var(--text-muted)', fontSize: 13 }}>A carregar...</div>}

      {!loading && !saving && rows.length === 0 && (
        <PhaseEmptyState
          label="Orçamento · fase 3"
          title="Ainda sem orçamento definido"
          description={hasMetaAds
            ? "Investimento em paid media da campanha (Meta Ads, LinkedIn Ads, Google Ads). Distribui o budget pelos canais Ads da estratégia usando pesos KB por mercado. A geração inclui automaticamente análise de campanhas Meta anteriores da marca para calibrar o valor. Aprovar avança para Target. (Email · WhatsApp · Website não têm investimento em ads — não aparecem aqui.)"
            : "Investimento em paid media da campanha (Meta Ads, LinkedIn Ads, Google Ads). Distribui o budget pelos canais Ads da estratégia usando pesos KB por mercado. O budget total pode ser calibrado manualmente. Aprovar avança para Target. (Email · WhatsApp · Website não têm investimento em ads — não aparecem aqui.)"}
          ctaLabel="Gerar Orçamento →"
          onCta={generate}
          loading={saving}
          disabled={!campanha?.estrategia_json}
          disabledReason={!campanha?.estrategia_json ? 'A estratégia precisa de ser gerada e aprovada primeiro.' : null}
        />
      )}

      {!loading && !saving && rows.length > 0 && (() => {
        // Estrutura: agrupar rows por canal (com todos os mercados juntos)
        const rowsByCanal = {};
        for (const r of rows) {
          if (!rowsByCanal[r.canal]) rowsByCanal[r.canal] = [];
          rowsByCanal[r.canal].push(r);
        }
        const orderedCanais = Object.keys(rowsByCanal);
        const usps = campanha?.briefing?.usps || [];
        const nUsps = usps.length || (campanha?.proposta_json?.messaging_angles?.length) || 4;
        const timelineStart = campanha?.briefing?.timeline_start;
        const timelineDays = (() => {
          const s = timelineStart;
          const e = campanha?.briefing?.timeline_end;
          if (!s || !e) return 30;
          return Math.max(1, Math.round((new Date(e) - new Date(s)) / 864e5));
        })();

        // Naming convention Meta Ads para ad sets: [PT] Q3.08.26 ProductName · Objetivo
        const objectiveLabel = { lead_gen: 'Lead Gen', conversion: 'Conversão', awareness: 'Awareness', retention: 'Engagement' };
        const objShort = objectiveLabel[campanha?.briefing?.objective] || campanha?.briefing?.objective || 'Conversão';
        const productShort = (campanha?.briefing?.commercial_name || campanha?.titulo || '').split(/[\s·–]/)[0];
        const buildAdSetName = (country) => {
          const d = timelineStart ? new Date(timelineStart) : new Date();
          const q = Math.ceil((d.getMonth() + 1) / 3);
          const mm = String(d.getMonth() + 1).padStart(2, '0');
          const yy = String(d.getFullYear()).slice(-2);
          return `[${country}] Q${q}.${mm}.${yy} ${productShort} · ${objShort}`;
        };
        // Análise Meta Ads guardada em `analysis`
        const meta = analysis || {};
        const sug = meta.suggestion || {};
        const hs = meta.history_summary || {};
        const bt = meta.briefing_targets || {};
        const budgetTotalCampanha = rows.reduce((s, r) => s + (parseFloat(r.valor_eur) || 0), 0);
        const dailyMedio = budgetTotalCampanha / timelineDays;

        return (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>

            {/* Hierarquia Meta Ads: Canal → Campanha → Conjunto por País → Anúncios por USP */}
            {orderedCanais.map(canal => {
              const canalRows = rowsByCanal[canal];
              const canalTotalEur = canalRows.reduce((s, r) => s + (parseFloat(r.valor_eur) || 0), 0);
              const campaignMarkets = canalRows.map(r => r.country).join('/');
              const qmmyy = (() => {
                const d = timelineStart ? new Date(timelineStart) : new Date();
                const q  = Math.ceil((d.getMonth()+1)/3);
                const mm = String(d.getMonth()+1).padStart(2,'0');
                const yy = String(d.getFullYear()).slice(-2);
                return `Q${q}.${mm}.${yy}`;
              })();
              const campaignName = `[${campaignMarkets}] ${qmmyy} ${productShort} · ${objShort}`;

              return (
                <div key={canal} style={{ background: 'var(--bg-card, #fff)', border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden' }}>

                  {/* Canal header */}
                  <div style={{ padding: '10px 16px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', gap: 10 }}>
                    <div style={{ fontSize: 12, fontWeight: 700, color: 'var(--text)', fontFamily: 'var(--font-display)', textTransform: 'uppercase', letterSpacing: '.04em' }}>{CANAL_LABEL[canal] || canal}</div>
                    <div style={{ fontSize: 11, color: 'var(--text-muted)', marginLeft: 'auto', fontFamily: 'var(--font-mono)' }}>
                      Total: <span style={{ color: 'var(--text)', fontWeight: 700 }}>€{Math.round(canalTotalEur).toLocaleString('pt-PT')}</span>
                    </div>
                  </div>

                  {/* Campanha row — nível 1 */}
                  <div style={{ padding: '8px 16px', borderBottom: '1px solid var(--border)', background: 'var(--bg-sunken)', display: 'flex', alignItems: 'center', gap: 8 }}>
                    <div style={{ width: 3, height: 14, background: 'var(--ai-500, #3859D0)', borderRadius: 2, flexShrink: 0 }} />
                    <div style={{ fontSize: 10, fontWeight: 700, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '.06em', marginRight: 6 }}>Campanha</div>
                    <div style={{ fontSize: 11, fontFamily: 'var(--font-mono)', color: 'var(--text)', fontWeight: 600 }}>{campaignName}</div>
                    <div style={{ marginLeft: 'auto', fontSize: 11, fontFamily: 'var(--font-mono)', color: 'var(--text-muted)' }}>€{Math.round(canalTotalEur).toLocaleString('pt-PT')} total</div>
                  </div>

                  {/* Conjuntos de Anúncios — nível 2, um por país */}
                  {canalRows.map(r => {
                    const mktTotal = parseFloat(r.valor_eur) || 0;
                    const mktDaily = Math.round(mktTotal / timelineDays);
                    const adSetName = `[${r.country}] ${qmmyy} ${productShort} · ${objShort}`;
                    return (
                      <div key={r.country}>
                        {/* Ad Set row */}
                        <div style={{ padding: '8px 16px 8px 28px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', gap: 8, background: 'var(--bg-card, #fff)' }}>
                          <div style={{ width: 2, height: 12, background: 'var(--border)', borderRadius: 1, flexShrink: 0 }} />
                          <div style={{ fontSize: 10, fontWeight: 700, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '.06em', marginRight: 6 }}>Conjunto</div>
                          <div style={{ fontSize: 11, fontFamily: 'var(--font-mono)', color: 'var(--text)', fontWeight: 600 }}>{adSetName}</div>
                          <div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 6 }}>
                            {editingRow === r.id ? (
                              <>
                                <span style={{ fontSize: 11, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>€</span>
                                <input
                                  autoFocus
                                  type="number"
                                  value={editVal}
                                  onChange={e => setEditVal(e.target.value)}
                                  onBlur={() => saveEditRow(r)}
                                  onKeyDown={e => { if (e.key === 'Enter') saveEditRow(r); if (e.key === 'Escape') setEditingRow(null); }}
                                  style={{ width: 80, fontFamily: 'var(--font-mono)', fontSize: 12, fontWeight: 700, padding: '2px 6px', borderRadius: 4, border: '1px solid var(--ai-500)', textAlign: 'right' }}
                                />
                                <span style={{ fontSize: 11, color: 'var(--text-muted)' }}>total</span>
                              </>
                            ) : (
                              <>
                                <span style={{ fontSize: 12, fontFamily: 'var(--font-mono)', color: 'var(--text)', fontWeight: 700 }}>
                                  {mktDaily > 0 ? `€${mktDaily}/dia` : `€${mktTotal.toFixed(2)} total`}
                                </span>
                                {mktDaily > 0 && (
                                  <span style={{ fontSize: 10, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>
                                    (€{mktTotal.toFixed(2)} total)
                                  </span>
                                )}
                                <button onClick={() => { setEditingRow(r.id); setEditVal(String(mktTotal)); }}
                                  style={{ fontSize: 10, padding: '2px 7px', borderRadius: 4, border: '1px solid var(--border)', background: 'var(--bg-sunken)', color: 'var(--text-dim)', cursor: 'pointer' }}>
                                  Editar
                                </button>
                              </>
                            )}
                          </div>
                        </div>

                        {/* Anúncios — nível 3, um por USP */}
                        {Array.from({ length: nUsps }).map((_, i) => {
                          const uspText = usps[i] || '';
                          const adName = `[${r.country}] ${qmmyy} - AD-${String(i+1).padStart(2,'0')} ${productShort}`;
                          return (
                            <div key={i} style={{ padding: '6px 16px 6px 44px', borderBottom: i < nUsps - 1 ? '1px solid var(--border)' : 'none', display: 'flex', alignItems: 'center', gap: 8 }}>
                              <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text)', fontWeight: 600, flexShrink: 0 }}>{adName}</div>
                              {uspText && (
                                <div style={{ fontSize: 10, color: 'var(--text-muted)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>· {uspText}</div>
                              )}
                            </div>
                          );
                        })}
                      </div>
                    );
                  })}
                </div>
              );
            })}

            {/* Sumário Executivo */}
            <div style={{ background: 'var(--bg-card, #fff)', border: '1px solid var(--border)', borderRadius: 10, padding: '14px 18px', display: 'flex', gap: 24, flexWrap: 'wrap' }}>
              <div>
                <div style={{ fontSize: 10, fontWeight: 700, fontFamily: 'var(--font-mono)', letterSpacing: '.08em', textTransform: 'uppercase', color: 'var(--text-dim)' }}>Budget Total</div>
                <div style={{ fontSize: 20, fontWeight: 800, color: 'var(--text)', fontFamily: 'var(--font-display)' }}>€{Math.round(budgetTotalCampanha).toLocaleString('pt-PT')}</div>
              </div>
              <div>
                <div style={{ fontSize: 10, fontWeight: 700, fontFamily: 'var(--font-mono)', letterSpacing: '.08em', textTransform: 'uppercase', color: 'var(--text-dim)' }}>Timeline</div>
                <div style={{ fontSize: 14, fontWeight: 600, color: 'var(--text)' }}>{timelineDays} dias</div>
              </div>
              <div>
                <div style={{ fontSize: 10, fontWeight: 700, fontFamily: 'var(--font-mono)', letterSpacing: '.08em', textTransform: 'uppercase', color: 'var(--text-dim)' }}>€/dia médio</div>
                <div style={{ fontSize: 14, fontWeight: 600, color: 'var(--text)' }}>€{Math.round(dailyMedio)}</div>
              </div>
              <div>
                <div style={{ fontSize: 10, fontWeight: 700, fontFamily: 'var(--font-mono)', letterSpacing: '.08em', textTransform: 'uppercase', color: 'var(--text-dim)' }}>Mercados</div>
                <div style={{ fontSize: 14, fontWeight: 600, color: 'var(--text)' }}>{markets.join(' + ')}</div>
              </div>
              {hs.campaigns_analyzed != null && (
                <div>
                  <div style={{ fontSize: 10, fontWeight: 700, fontFamily: 'var(--font-mono)', letterSpacing: '.08em', textTransform: 'uppercase', color: 'var(--text-dim)' }}>Análise Meta</div>
                  <div style={{ fontSize: 14, fontWeight: 600, color: 'var(--text)' }}>{hs.campaigns_analyzed} camp. · CPL €{hs.avg_cpl || '—'} · CTR {hs.avg_ctr || '—'}%</div>
                </div>
              )}
            </div>

            {/* Recomendação do Agente */}
            {sug.recomendacao_agente && (
              <div style={{ background: 'linear-gradient(135deg, rgba(56,89,208,.06), rgba(8,145,178,.06))', border: '1px solid rgba(56,89,208,.2)', borderRadius: 10, padding: '14px 18px' }}>
                <div style={{ fontSize: 10, fontWeight: 700, fontFamily: 'var(--font-mono)', letterSpacing: '.08em', textTransform: 'uppercase', color: 'var(--ai-500, #3859D0)', marginBottom: 8 }}>Recomendação do Agente</div>
                <div style={{ fontSize: 13, color: 'var(--text)', lineHeight: 1.6, whiteSpace: 'pre-wrap' }}>{sug.recomendacao_agente}</div>
              </div>
            )}

            {/* Cenário Scale */}
            {sug.cenario_scale && (
              <div style={{ background: 'var(--bg-card, #fff)', border: '1px solid rgba(234,179,8,.3)', borderLeft: '3px solid #d97706', borderRadius: 10, padding: '14px 18px' }}>
                <div style={{ fontSize: 10, fontWeight: 700, fontFamily: 'var(--font-mono)', letterSpacing: '.08em', textTransform: 'uppercase', color: '#92400e', marginBottom: 10 }}>Cenário Scale (opcional)</div>
                <div style={{ fontSize: 12, color: 'var(--text)', lineHeight: 1.7 }}>
                  <div><strong>Arranque (dias {sug.cenario_scale.fase_arranque.days}):</strong> €{sug.cenario_scale.fase_arranque.daily_por_mercado}/dia × {meta.n_markets} mercados = <span style={{ fontFamily: 'var(--font-mono)' }}>€{sug.cenario_scale.fase_arranque.total}</span></div>
                  <div><strong>Scale (dias {sug.cenario_scale.fase_scale.days}):</strong> €{sug.cenario_scale.fase_scale.daily_por_mercado}/dia × {meta.n_markets} mercados = <span style={{ fontFamily: 'var(--font-mono)' }}>€{sug.cenario_scale.fase_scale.total}</span></div>
                  <div style={{ marginTop: 6, paddingTop: 6, borderTop: '1px solid var(--border)', fontWeight: 700 }}>Total com scale: <span style={{ fontFamily: 'var(--font-mono)' }}>€{sug.cenario_scale.total_com_scale}</span> (vs €{Math.round(budgetTotalCampanha)} sem scale)</div>
                  <div style={{ marginTop: 6, fontSize: 11, color: '#92400e', fontStyle: 'italic' }}>{sug.cenario_scale.trigger}</div>
                </div>
              </div>
            )}

            {/* Justificação + Fórmula 3 âncoras + Referências */}
            {(sug.justificacao || sug.budget_formula || (meta.reference_campaigns && meta.reference_campaigns.length > 0)) && (
              <div style={{ background: 'var(--bg-card, #fff)', border: '1px solid var(--border)', borderRadius: 10, padding: '14px 18px' }}>
                <div style={{ fontSize: 10, fontWeight: 700, fontFamily: 'var(--font-mono)', letterSpacing: '.08em', textTransform: 'uppercase', color: 'var(--text-dim)', marginBottom: 10 }}>Justificação do Budget</div>

                {/* Fórmula 3 âncoras */}
                {sug.budget_formula && (
                  <div style={{ background: 'var(--bg-sunken)', borderRadius: 8, padding: '10px 12px', marginBottom: 10 }}>
                    <div style={{ fontSize: 10, fontWeight: 700, fontFamily: 'var(--font-mono)', textTransform: 'uppercase', color: 'var(--text-dim)', marginBottom: 8, letterSpacing: '.06em' }}>Fórmula 3 Âncoras · Confiança {sug.budget_formula.confidence?.toUpperCase()}</div>
                    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 8, marginBottom: 8 }}>
                      {[
                        { label: 'Briefing', valor: sug.budget_formula.ancora_briefing?.valor, peso: sug.budget_formula.ancora_briefing?.peso, color: '#3859D0' },
                        { label: 'Histórico ponderado', valor: sug.budget_formula.ancora_historico?.valor, peso: sug.budget_formula.ancora_historico?.peso, color: '#0891b2' },
                        { label: 'KB B2B PT/ES', valor: sug.budget_formula.ancora_kb?.valor, peso: sug.budget_formula.ancora_kb?.peso, color: '#059669' },
                      ].map(a => (
                        <div key={a.label} style={{ background: '#fff', borderRadius: 6, padding: '8px 10px', borderTop: `3px solid ${a.color}` }}>
                          <div style={{ fontSize: 9, fontWeight: 700, color: a.color, textTransform: 'uppercase', letterSpacing: '.06em', marginBottom: 2 }}>{a.label}</div>
                          <div style={{ fontSize: 14, fontWeight: 800, fontFamily: 'var(--font-mono)', color: 'var(--text)' }}>€{a.valor}</div>
                          <div style={{ fontSize: 10, color: 'var(--text-muted)' }}>peso {a.peso}%</div>
                        </div>
                      ))}
                    </div>
                    <div style={{ display: 'flex', gap: 16, fontSize: 11, color: 'var(--text-muted)', flexWrap: 'wrap' }}>
                      <span>CPL blended: <strong style={{ color: 'var(--text)', fontFamily: 'var(--font-mono)' }}>€{sug.budget_formula.cpl_blended}</strong></span>
                      <span>Margem segurança: <strong style={{ color: 'var(--text)', fontFamily: 'var(--font-mono)' }}>+{sug.budget_formula.margem}%</strong></span>
                      {sug.budget_formula.fator_ctr !== 1.0 && (
                        <span>Factor CTR: <strong style={{ color: 'var(--text)', fontFamily: 'var(--font-mono)' }}>{sug.budget_formula.fator_ctr}×</strong></span>
                      )}
                    </div>
                  </div>
                )}

                {sug.justificacao && (
                  <div style={{ fontSize: 11, color: 'var(--text-muted)', lineHeight: 1.6, marginBottom: 8, paddingLeft: 10, borderLeft: '3px solid var(--ai-500)' }}>{sug.justificacao}</div>
                )}
                {meta.reference_campaigns?.length > 0 && (
                  <>
                    <div style={{ fontSize: 10, fontWeight: 700, fontFamily: 'var(--font-mono)', letterSpacing: '.08em', textTransform: 'uppercase', color: 'var(--text-dim)', marginTop: 12, marginBottom: 6 }}>Campanhas de referência</div>
                    <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 11 }}>
                      <thead><tr style={{ borderBottom: '1px solid var(--border)' }}>
                        <th style={{ textAlign: 'left', padding: '5px 8px', color: 'var(--text-dim)' }}>Nome</th>
                        <th style={{ textAlign: 'right', padding: '5px 8px', color: 'var(--text-dim)' }}>Spend</th>
                        <th style={{ textAlign: 'right', padding: '5px 8px', color: 'var(--text-dim)' }}>Leads</th>
                        <th style={{ textAlign: 'right', padding: '5px 8px', color: 'var(--text-dim)' }}>CPL</th>
                        <th style={{ textAlign: 'right', padding: '5px 8px', color: 'var(--text-dim)' }}>CTR</th>
                      </tr></thead>
                      <tbody>
                        {meta.reference_campaigns.map((rc, i) => (
                          <tr key={i} style={{ borderBottom: '1px solid var(--border)' }}>
                            <td style={{ padding: '5px 8px', color: 'var(--text)' }}>{rc.name?.slice(0, 45)}</td>
                            <td style={{ padding: '5px 8px', textAlign: 'right', fontFamily: 'var(--font-mono)' }}>€{rc.spend}</td>
                            <td style={{ padding: '5px 8px', textAlign: 'right', fontFamily: 'var(--font-mono)' }}>{rc.leads || '—'}</td>
                            <td style={{ padding: '5px 8px', textAlign: 'right', fontFamily: 'var(--font-mono)', fontWeight: 600 }}>{rc.cpl ? `€${rc.cpl}` : '—'}</td>
                            <td style={{ padding: '5px 8px', textAlign: 'right', fontFamily: 'var(--font-mono)' }}>{rc.ctr ? `${rc.ctr}%` : '—'}</td>
                          </tr>
                        ))}
                      </tbody>
                    </table>
                  </>
                )}
              </div>
            )}
          </div>
        );
      })()}

      {/* Meta Ads Analyst Agent */}
      {!loading && !saving && rows.length > 0 && hasMetaAds && (
        <div style={{ background: 'var(--bg-card, #fff)', border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden' }}>
          <div style={{ padding: '12px 16px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', gap: 10 }}>
            <div style={{ fontSize: 12, fontWeight: 700, color: 'var(--text)', fontFamily: 'var(--font-display)', textTransform: 'uppercase', letterSpacing: '.04em' }}>Meta Ads Analyst</div>
            <div style={{ fontSize: 11, color: 'var(--text-muted)' }}>Diagnóstico · Recomendações · Previsão</div>
            {analystResult && !analystRunning && (
              <button className="btn" style={{ marginLeft: 'auto', fontSize: 11 }} onClick={runAnalyst}>Reanalisar</button>
            )}
          </div>

          {analystRunning && (
            <div style={{ padding: '16px 20px' }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 }}>
                <div style={{ width: 8, height: 8, borderRadius: '50%', background: 'var(--ai-500)', animation: 'pulse 1.2s infinite' }} />
                <span style={{ fontSize: 12, color: 'var(--text-muted)' }}>{analystStatus || 'A analisar...'}</span>
              </div>
              {analystStream && (
                <div style={{ fontSize: 12, color: 'var(--text)', lineHeight: 1.7, fontFamily: 'var(--font-mono)', whiteSpace: 'pre-wrap', opacity: .8 }}>{analystStream}</div>
              )}
            </div>
          )}

          {analystResult && !analystRunning && (() => {
            const r = analystResult;
            const sections = [
              { key: 'diagnostico',   label: 'Diagnóstico',    border: '#3859D0' },
              { key: 'orcamento',     label: 'Orçamento',      border: '#0891b2' },
              { key: 'recomendacoes', label: 'Recomendações',  border: '#059669' },
              { key: 'previsao',      label: 'Previsão',       border: '#d97706' },
            ];
            return (
              <div style={{ padding: '16px 20px', display: 'flex', flexDirection: 'column', gap: 14 }}>
                {sections.map(s => r[s.key] ? (
                  <div key={s.key} style={{ paddingLeft: 12, borderLeft: `3px solid ${s.border}` }}>
                    <div style={{ fontSize: 10, fontWeight: 700, fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '.06em', color: s.border, marginBottom: 4 }}>{s.label}</div>
                    <div style={{ fontSize: 12, color: 'var(--text)', lineHeight: 1.7, whiteSpace: 'pre-wrap' }}>{r[s.key]}</div>
                  </div>
                ) : null)}
                {r.meta_history && (
                  <div style={{ borderTop: '1px solid var(--border)', paddingTop: 12, marginTop: 4 }}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: r.meta_history.campaigns?.length > 0 ? 8 : 0 }}>
                      <span style={{ fontSize: 10, fontWeight: 700, fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '.06em', color: 'var(--text-dim)' }}>
                        Campanhas analisadas ({r.meta_history.campaigns_analyzed})
                      </span>
                      {r.meta_history.no_product_history && (
                        <span style={{ fontSize: 10, color: '#92400e', background: '#FEF3C7', padding: '1px 6px', borderRadius: 3 }}>sem histórico do produto — dados da conta</span>
                      )}
                      <span style={{ marginLeft: 'auto', fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-muted)' }}>
                        CPL €{r.meta_history.avg_cpl || '—'} · CTR {r.meta_history.avg_ctr || '—'}% · CPM €{r.meta_history.avg_cpm || '—'}
                      </span>
                    </div>
                    {r.meta_history.campaigns?.length > 0 && (
                      <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 10 }}>
                        <thead><tr style={{ borderBottom: '1px solid var(--border)' }}>
                          <th style={{ textAlign: 'left', padding: '4px 6px', color: 'var(--text-dim)', fontWeight: 600 }}>Campanha</th>
                          <th style={{ textAlign: 'right', padding: '4px 6px', color: 'var(--text-dim)', fontWeight: 600 }}>Spend</th>
                          <th style={{ textAlign: 'right', padding: '4px 6px', color: 'var(--text-dim)', fontWeight: 600 }}>CPL</th>
                          <th style={{ textAlign: 'right', padding: '4px 6px', color: 'var(--text-dim)', fontWeight: 600 }}>CTR</th>
                        </tr></thead>
                        <tbody>
                          {r.meta_history.campaigns.map((cp, i) => (
                            <tr key={i} style={{ borderBottom: '1px solid var(--border)' }}>
                              <td style={{ padding: '4px 6px', color: 'var(--text-muted)' }}>{(cp.name || '').slice(0, 50)}</td>
                              <td style={{ padding: '4px 6px', textAlign: 'right', fontFamily: 'var(--font-mono)' }}>€{cp.spend}</td>
                              <td style={{ padding: '4px 6px', textAlign: 'right', fontFamily: 'var(--font-mono)', fontWeight: 600 }}>{cp.cpl ? `€${cp.cpl}` : '—'}</td>
                              <td style={{ padding: '4px 6px', textAlign: 'right', fontFamily: 'var(--font-mono)' }}>{cp.ctr ? `${cp.ctr}%` : '—'}</td>
                            </tr>
                          ))}
                        </tbody>
                      </table>
                    )}
                  </div>
                )}
              </div>
            );
          })()}

        </div>
      )}

      {toast && <div style={{ position: 'fixed', bottom: 24, right: 24, background: 'var(--text)', color: '#fff', padding: '10px 18px', borderRadius: 8, fontSize: 13, zIndex: 9999 }}>{toast}</div>}
    </div>
  );
};

// ── TabFunilMulticanal — funil vertical 5 layers · design system portal ────────
const _LAYER_META = [
  { id: 'l1', num: 'LAYER 1', title: 'TRAFFIC SOURCES',        color: '#0F4C75', bg: 'rgba(15,76,117,.05)'   },
  { id: 'l2', num: 'LAYER 2', title: 'CONVERSION HUB',         color: '#3859D0', bg: 'rgba(56,89,208,.05)'   },
  { id: 'l3', num: 'LAYER 3', title: 'DIGI AI · QUALIFICAÇÃO', color: '#065F46', bg: 'rgba(6,95,70,.05)'     },
  { id: 'l4', num: 'LAYER 4', title: 'HANDOFF COMERCIAL',      color: '#92400E', bg: 'rgba(146,64,14,.05)'   },
  { id: 'l5', num: 'LAYER 5', title: 'NURTURING WA',           color: '#4C1D95', bg: 'rgba(76,29,149,.05)'   },
];

const _CH_ICONS = {
  meta_ads:           <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="2" y="2" width="9" height="9" rx="1"/><rect x="13" y="2" width="9" height="9" rx="1"/><rect x="2" y="13" width="9" height="9" rx="1"/><rect x="13" y="13" width="9" height="9" rx="1"/></svg>,
  linkedin_ads:       <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M6 9v9"/><circle cx="6" cy="5.5" r="1"/><path d="M10 18v-5a2 2 0 014 0v5"/><path d="M18 18v-4"/></svg>,
  email:              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m2 7 10 7 10-7"/></svg>,
  whatsapp:           <svg width="16" height="16" viewBox="0 0 24 24" fill="white"><path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413z"/><path d="M12 0C5.376 0 0 5.376 0 12c0 2.11.554 4.085 1.518 5.793L0 24l6.37-1.487A11.955 11.955 0 0 0 12 24C18.624 24 24 18.624 24 12S18.624 0 12 0z"/></svg>,
  google_ads_search:  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>,
  google_ads_display: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg>,
  muppi_led:          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="2" y="7" width="20" height="15" rx="2"/><path d="M16 3h-2l-2 4H8l-2-4H4"/></svg>,
  website:            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10A15.3 15.3 0 0112 2z"/></svg>,
};

const _NURTURE_STAGES = [
  { num: 'Stage 10', name: 'Entrada no Funil',  action: 'Confirmar perfil · identificar dor dominante · propor próximo passo' },
  { num: 'Stage 20', name: 'Lead Qualificada',  action: 'Aprofundar dor · dado concreto do cluster · propor demo' },
  { num: 'Stage 50', name: 'Pré-Demo',          action: 'Confirmar presença · pedir amostras para testar ao vivo' },
  { num: 'Stage 65', name: 'Follow-up 48h',     action: 'Reforçar o que viu · tratar objecção · propor próximo passo' },
  { num: 'Stage 80', name: 'Sem Resposta +5d',  action: 'Reactivar com nova perspectiva · financiamento como alternativa' },
  { num: 'Stage 90', name: 'Proposta Enviada',  action: 'Fechar · tratar objecção de preço · confirmar condições finais' },
];

const TabFunilMulticanal = ({ campanha, onAction }) => {
  const bCol = _brandColor(campanha?.brand_slug);

  const [activeMkt, setActiveMkt] = React.useState(() => (campanha?.estrategia_json?.markets || [])[0]?.country || null);
  const [sending,   setSending]   = React.useState(false);
  const [ctx,       setCtx]       = React.useState(null);  // funil-context (fonte única)
  const [loading,   setLoading]   = React.useState(false);

  const status     = campanha?.status || '';
  const isPending  = ['pending_executive','em_aprovacao'].includes(status);
  const isApproved = ['em_producao','publicado','concluida'].includes(status);
  const canSendToApproval = ['funil_pendente'].includes(status);
  const statusLabel = isApproved ? 'Campanha aprovada' : isPending ? 'Enviado para aprovação executiva' : 'Pronto para revisão';
  const statusBg    = isApproved ? 'var(--green-100,#E1F7E6)' : isPending ? '#FEF3C7' : 'var(--dd-blue-100,#EBEFF9)';
  const statusFg    = isApproved ? 'var(--green-700,#1F8A52)' : isPending ? '#92400E' : 'var(--dd-primary-600,#3859D0)';

  React.useEffect(() => {
    if (!campanha?.id) return;
    setLoading(true);
    campApiCall('/api/marketing/campanhas/' + campanha.id + '/funil-context')
      .then(d => { setCtx(d); if (d?.estrategia?.markets?.length) setActiveMkt(d.estrategia.markets[0].country); })
      .catch(() => null)
      .finally(() => setLoading(false));
  }, [campanha?.id]);

  // Dados derivados do ctx (fonte única) com fallback para campanha prop
  const brief    = ctx?.briefing   || campanha?.briefing || {};
  const markets  = ctx?.estrategia?.markets || campanha?.estrategia_json?.markets || [];
  const commPlan = ctx?.conceito?.comm_plan || campanha?.proposta_json?.comm_plan || [];
  const sdrAI    = ctx?.sdr_ai || null;
  const nurtStages = ctx?.nurturing_stages?.length ? ctx.nurturing_stages : _NURTURE_STAGES.map(s => ({ stage: s.num, nome: s.name, accao: s.action }));
  const estrategia = ctx?.estrategia || campanha?.estrategia_json;

  if (!estrategia || !markets.length) return (
    <PhaseEmptyState
      label="Funil Multicanal · fase 6"
      title="Ainda sem funil para visualizar"
      description="Síntese visual da campanha em 5 layers: Traffic Sources → Conversion Hub → Digi AI Qualificação → Handoff Comercial → Nurturing WA. Gera a estratégia primeiro para ter dados a mostrar."
      disabled
      disabledReason="A estratégia precisa de ser gerada primeiro."
    />
  );

  const mkt      = markets.find(m => m.country === activeMkt) || markets[0];
  const chFit    = mkt?.channel_fit || [];
  const personas = mkt?.personas_priorizadas || [];
  const msgU     = mkt?.mensagem_unificada || {};
  const kpis     = mkt?.kpis || [];
  const tom      = mkt?.tom || '';

  // Parse commercial offer (can be string or object in briefing)
  const offerRaw = brief.commercial_offer;
  const offer    = offerRaw
    ? (typeof offerRaw === 'string' ? (() => { try { return JSON.parse(offerRaw); } catch { return { descricao: offerRaw }; } })() : offerRaw)
    : {};
  const offerLabel = offer.tipo === 'digirent'   ? `DigiRent €${offer.valor_mensal || '—'}/mês`
                   : offer.tipo === 'printplan'   ? `PrintPlan €${offer.custo_por_m2 || '—'}/m²`
                   : offer.tipo === 'financiamento'? `Financiamento ${offer.prazo || ''}`
                   : offer.descricao || 'Oferta comercial';

  // Layer 1 = canais de tráfego pagos/email — blog/website é Conversion Hub (Layer 2)
  const LAYER1_CANAIS = new Set(['meta_ads','linkedin_ads','google_ads_search','google_ads_display','email','whatsapp','muppi_led']);
  const allChannels = [];
  const seen = new Set();
  markets.forEach(m => {
    (m.channel_fit || []).forEach(ch => {
      if (LAYER1_CANAIS.has(ch.canal) && !seen.has(ch.canal)) {
        seen.add(ch.canal); allChannels.push({ ...ch, country: m.country });
      }
    });
  });

  // Comm plan helpers
  const emailRows    = commPlan.filter(r => r.content_type === 'email_html' || r.canal === 'email').slice(0, 3);
  const blogRows     = commPlan.filter(r => r.content_type === 'blog_post'  || r.canal === 'website').slice(0, 2);
  const metaRows     = commPlan.filter(r => r.canal === 'meta_ads').slice(0, 2);

  // Segmentation CRM segments
  const crmSegs = ctx?.target?.crm?.length
    ? ctx.target.crm
    : [];
  const metaSeg = ctx?.target?.ads?.length ? ctx.target.ads : [];

  // Pain points (first 3)
  const pains = (Array.isArray(brief.pain_points) ? brief.pain_points : []).slice(0, 3);

  // USPs (first 3)
  const usps = (Array.isArray(brief.usps) ? brief.usps : []).slice(0, 3);

  // Persona split: primary (peso >= 60) / secondary
  const primaryPersonas   = personas.filter(p => (p.peso || 0) >= 60 || p.prioridade === 'primary');
  const secondaryPersonas = personas.filter(p => (p.peso || 0) < 60  && p.prioridade !== 'primary');

  // Language variants
  const langVariants = [...new Set(markets.map(m => m.language_variant || m.country || ''))].filter(Boolean);

  // ── Shared style helpers ──
  const layerHeader = (col) => ({
    background: col, padding: '10px 24px', display: 'flex', alignItems: 'center', gap: 10,
    fontSize: 11, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: '#fff',
    fontFamily: 'var(--font-display)',
  });
  const layerBody = (bg) => ({ padding: '18px 20px 20px', background: bg });
  const connector = {
    display: 'flex', alignItems: 'center', justifyContent: 'center',
    padding: '4px 0', background: 'transparent', position: 'relative',
  };
  const lNum = () => ({ background: 'rgba(255,255,255,.22)', padding: '2px 8px', borderRadius: 99, fontSize: 10, letterSpacing: '0.08em' });
  const card = (borderColor, extra) => ({
    background: 'var(--bg-card, #fff)', borderRadius: 10, border: '1px solid var(--border)',
    borderTop: `3px solid ${borderColor}`, padding: '14px 14px 12px',
    boxShadow: '0 1px 3px rgba(17,41,84,.06)', ...extra,
  });
  const metaLabel = (col) => ({ fontSize: 9, fontWeight: 700, letterSpacing: '0.08em', textTransform: 'uppercase', color: col, fontFamily: 'var(--font-mono)', marginBottom: 4 });
  const bigNum    = { fontSize: 20, fontWeight: 800, fontFamily: 'var(--font-display)', color: 'var(--navy,#112954)', lineHeight: 1 };
  const smallTag  = (col, bg) => ({ display: 'inline-block', fontSize: 9, fontWeight: 700, letterSpacing: '0.05em', textTransform: 'uppercase', padding: '2px 7px', borderRadius: 99, background: bg || `${col}14`, color: col, border: `1px solid ${col}30` });
  const dot = (col) => ({ width: 6, height: 6, borderRadius: '50%', background: col, flexShrink: 0, marginTop: 5 });

  const Connector = () => (
    <div style={connector}>
      <div style={{ position: 'absolute', left: '50%', top: 0, bottom: 0, width: 2, background: 'var(--border)', transform: 'translateX(-50%)' }} />
      <div style={{ position: 'relative', zIndex: 1, width: 28, height: 28, background: 'var(--navy,#112954)', borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
      </div>
    </div>
  );

  const ChIcon = ({ canal, size = 28, bg }) => (
    <div style={{ width: size, height: size, borderRadius: 7, background: bg || _LAYER_META[0].color, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
      {_CH_ICONS[canal] || <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2"><circle cx="12" cy="12" r="10"/></svg>}
    </div>
  );

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 0 }}>

      {/* ── Status strip ── */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 14px', background: 'var(--bg-surface,#fff)', borderRadius: 'var(--radius-md,8px)', boxShadow: 'var(--shadow-card)', marginBottom: 14 }}>
        <span style={{ fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em', padding: '3px 8px', borderRadius: 'var(--radius-xs,4px)', background: statusBg, color: statusFg }}>
          {statusLabel}
        </span>
        <span style={{ fontSize: 11, color: 'var(--fg-3,var(--text-muted))' }}>
          {markets.length} mercado{markets.length !== 1 ? 's' : ''} · {allChannels.length} canais · 5 layers
          {campanha?.funil_generated_at && ` · gerado ${new Date(campanha.funil_generated_at).toLocaleString('pt-PT')}`}
          {loading && !isApproved && !isPending && ' · a carregar dados...'}
        </span>
        {(isApproved || isPending) && campanha?.funil_approved_at && (
          <span style={{ fontSize: 11, color: isApproved ? 'var(--green-700,#1F8A52)' : '#92400E' }}>
            · aprovada {new Date(campanha.funil_approved_at).toLocaleString('pt-PT')}{campanha.funil_approved_by ? ` por ${String(campanha.funil_approved_by).split('@')[0]}` : ''}
          </span>
        )}
        <div style={{ marginLeft: 'auto', display: 'flex', gap: 6, alignItems: 'center' }}>
          {canSendToApproval && !isPending && (<>
            <button
              className="btn"
              style={{ height: 28, padding: '0 14px', fontSize: 12 }}
              disabled={sending}
              onClick={async () => {
                setSending(true);
                try { await onAction('enviarAprovacao'); } catch {}
                setSending(false);
              }}
            >
              Enviar para Aprovação Executiva
            </button>
            <button
              className="btn btn-ai"
              style={{ height: 28, padding: '0 14px', fontSize: 12 }}
              disabled={sending}
              onClick={async () => {
                setSending(true);
                try { await onAction('enviarProducao'); } catch {}
                setSending(false);
              }}
            >
              {sending ? 'A aprovar…' : 'Aprovar Internamente →'}
            </button>
          </>)}
          {isPending && (
            <span style={{ fontSize: 10, fontWeight: 700, padding: '3px 10px', borderRadius: 99, background: '#FEF3C7', color: '#92400E', fontFamily: 'var(--font-mono)' }}>
              Aguarda aprovação executiva
            </span>
          )}
          {isApproved && (
            <span style={{ fontSize: 10, fontWeight: 700, padding: '3px 10px', borderRadius: 99, background: 'var(--green-100,#E1F7E6)', color: 'var(--green-700,#1F8A52)', fontFamily: 'var(--font-mono)' }}>
              Campanha aprovada
            </span>
          )}
        </div>
      </div>

      {/* ════════════════════════════════════════
          LAYER 1 — TRAFFIC SOURCES
      ════════════════════════════════════════ */}
      <div>
        <div style={layerHeader(_LAYER_META[0].color)}>
          <span style={lNum()}>{_LAYER_META[0].num}</span>
          {_LAYER_META[0].title}
          <span style={{ marginLeft: 'auto', fontSize: 10, fontWeight: 500, opacity: .7 }}>{allChannels.length} canais activos</span>
        </div>
        <div style={layerBody(_LAYER_META[0].bg)}>
          <div style={{ display: 'grid', gridTemplateColumns: `repeat(${Math.min(allChannels.length || 3, 3)}, 1fr)`, gap: 12 }}>
            {allChannels.map((ch, i) => {
              const L1col = _LAYER_META[0].color;
              // Audience from segmentacao for this channel
              const audSeg  = metaSeg.find(s => s.canal === ch.canal || s.canal === 'meta_ads');
              const _audRaw = audSeg?.audiencia_estimada || audSeg?.audience_size;
              const audSize = (_audRaw && String(_audRaw).trim() !== '') ? String(_audRaw) : null;
              // Comm plan rows for this channel
              const planRows = ch.canal === 'email'    ? emailRows
                             : ch.canal === 'website' ? blogRows
                             : ch.canal === 'meta_ads' ? metaRows
                             : (ctx?.planeamento?.rows || []).filter(r => r.canal === ch.canal).slice(0, 2);
              return (
                <div key={i} style={card(L1col)}>
                  <div style={{ display: 'flex', alignItems: 'flex-start', gap: 10, marginBottom: 10 }}>
                    <ChIcon canal={ch.canal} size={32} bg={L1col} />
                    <div style={{ minWidth: 0 }}>
                      <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--navy,#112954)', lineHeight: 1.2, fontFamily: 'var(--font-display)' }}>
                        {CANAL_LABEL[ch.canal] || ch.canal}
                      </div>
                      <div style={{ fontSize: 10, color: 'var(--text-muted)', marginTop: 2 }}>{ch.country || mkt?.country || ''}</div>
                    </div>
                    {ch.pct != null && (
                      <div style={{ marginLeft: 'auto', flexShrink: 0, ...smallTag(L1col) }}>{ch.pct}%</div>
                    )}
                  </div>

                  {/* Type / audience */}
                  {ch.canal === 'meta_ads' && (
                    <div style={{ marginBottom: 8 }}>
                      <div style={metaLabel(L1col)}>Formato</div>
                      <div style={{ fontSize: 11, color: 'var(--text)', fontWeight: 600 }}>Lead Gen Form</div>
                      {audSize && (
                        <div style={{ marginTop: 4 }}>
                          <div style={metaLabel(L1col)}>Audiencia estimada</div>
                          <div style={{ ...bigNum, fontSize: 18 }}>{audSize}</div>
                        </div>
                      )}
                    </div>
                  )}
                  {ch.canal === 'whatsapp' && (
                    <div style={{ marginBottom: 8 }}>
                      <div style={metaLabel(L1col)}>Tipo</div>
                      <div style={{ fontSize: 11, color: 'var(--text)', fontWeight: 600, marginBottom: 8 }}>Nurturing CRM</div>
                      <div style={metaLabel(L1col)}>Sequências</div>
                      {['Reactivação · OPs perdidas produto', 'Aceleração · AG em decisão', 'Prospecção · inactivos + open house'].map((s, j) => (
                        <div key={j} style={{ fontSize: 10, color: '#475569', display: 'flex', gap: 5, alignItems: 'flex-start', marginBottom: 3 }}>
                          <div style={dot(L1col)} />{s}
                        </div>
                      ))}
                    </div>
                  )}

                  {/* Planned dates from comm plan — não mostrar para WA (nurturing contínuo sem datas fixas) */}
                  {planRows.length > 0 && ch.canal !== 'whatsapp' && planRows.some(r => r.planned_date || r.data) && (
                    <div>
                      <div style={metaLabel(L1col)}>Datas planeadas</div>
                      {planRows.map((r, j) => (
                        <div key={j} style={{ fontSize: 10, color: '#475569', display: 'flex', gap: 6, alignItems: 'flex-start', marginBottom: 2 }}>
                          <div style={dot(L1col)} />
                          <span style={{ fontFamily: 'var(--font-mono)', fontWeight: 600 }}>{r.planned_date || r.data || '—'}</span>
                          {r.titulo && <span style={{ color: 'var(--text-muted)' }}>{r.titulo.slice(0, 40)}</span>}
                        </div>
                      ))}
                    </div>
                  )}
                </div>
              );
            })}
          </div>
        </div>
      </div>

      <Connector />

      {/* ════════════════════════════════════════
          LAYER 2 — CONVERSION HUB
      ════════════════════════════════════════ */}
      <div>
        <div style={layerHeader(_LAYER_META[1].color)}>
          <span style={lNum()}>{_LAYER_META[1].num}</span>
          {_LAYER_META[1].title}
        </div>
        <div style={layerBody(_LAYER_META[1].bg)}>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16, marginBottom: 16 }}>

            {/* Card 1 — Blog Post · Content Hub */}
            <div style={card(_LAYER_META[1].color)}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12 }}>
                <div style={{ width: 36, height: 36, borderRadius: 8, background: _LAYER_META[1].color, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
                  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
                </div>
                <div>
                  <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--navy,#112954)', fontFamily: 'var(--font-display)' }}>Blog Post</div>
                  <div style={{ fontSize: 10, color: 'var(--text-muted)' }}>Content Hub · entrada via Email / Orgânico</div>
                </div>
              </div>
              {blogRows.length > 0 ? (
                <div style={{ marginBottom: 12 }}>
                  <div style={metaLabel(_LAYER_META[1].color)}>Conteúdos planeados</div>
                  {blogRows.map((r, j) => (
                    <div key={j} style={{ display: 'flex', gap: 6, alignItems: 'flex-start', marginBottom: 4 }}>
                      <div style={dot(_LAYER_META[1].color)} />
                      <div>
                        <div style={{ fontSize: 11, color: 'var(--text)', fontWeight: 600, lineHeight: 1.3 }}>{r.titulo || r.title || `Artigo ${j+1}`}</div>
                        {r.planned_date && <div style={{ fontSize: 9, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>{r.planned_date}</div>}
                      </div>
                    </div>
                  ))}
                </div>
              ) : (
                <div style={{ marginBottom: 12 }}>
                  <div style={metaLabel(_LAYER_META[1].color)}>Função</div>
                  <div style={{ fontSize: 11, color: 'var(--text)', lineHeight: 1.5 }}>
                    Artigo de conversão com argumentos técnicos e ROI. Email linka para o blog post que fecha com CTA WhatsApp.
                  </div>
                </div>
              )}
              {msgU.decision && (
                <div>
                  <div style={metaLabel(_LAYER_META[1].color)}>Mensagem de conversão</div>
                  <div style={{ fontSize: 11, color: 'var(--text)', lineHeight: 1.5, fontStyle: 'italic' }}>"{msgU.decision}"</div>
                </div>
              )}
            </div>

            {/* Card 2 — Lista CRM · Qualificação Digi AI */}
            <div style={card(_LAYER_META[1].color)}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12 }}>
                <div style={{ width: 36, height: 36, borderRadius: 8, background: _LAYER_META[1].color, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
                  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
                </div>
                <div>
                  <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--navy,#112954)', fontFamily: 'var(--font-display)' }}>Lista CRM Marketing</div>
                  <div style={{ fontSize: 10, color: 'var(--text-muted)' }}>Leads Meta Ads · Lead Gen Form</div>
                </div>
              </div>
              <div style={{ marginBottom: 12 }}>
                <div style={metaLabel(_LAYER_META[1].color)}>Origem</div>
                <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, marginTop: 4 }}>
                  {allChannels.filter(c => c.canal === 'meta_ads' || c.canal === 'linkedin_ads').map((c, i) => (
                    <span key={i} style={smallTag(_LAYER_META[1].color)}>{CANAL_LABEL[c.canal] || c.canal}</span>
                  ))}
                  {allChannels.filter(c => c.canal === 'meta_ads' || c.canal === 'linkedin_ads').length === 0 && (
                    <span style={smallTag(_LAYER_META[1].color)}>Meta Ads</span>
                  )}
                </div>
              </div>
              {crmSegs.length > 0 ? (
                <div style={{ marginBottom: 12 }}>
                  <div style={metaLabel(_LAYER_META[1].color)}>Segmentos CRM configurados</div>
                  {crmSegs.slice(0, 3).map((seg, i) => (
                    <div key={i} style={{ display: 'flex', gap: 6, alignItems: 'flex-start', marginBottom: 4 }}>
                      <div style={dot(_LAYER_META[1].color)} />
                      <div style={{ fontSize: 11, color: 'var(--text)', lineHeight: 1.3 }}>{seg.nome || seg.name || `Segmento ${i+1}`}</div>
                    </div>
                  ))}
                </div>
              ) : (
                <div style={{ marginBottom: 12 }}>
                  <div style={metaLabel(_LAYER_META[1].color)}>Fluxo</div>
                  <div style={{ fontSize: 11, color: 'var(--text)', lineHeight: 1.5 }}>
                    Lead preenche form → entra na lista CRM Marketing → qualificação automática via Digi AI SDR.
                  </div>
                </div>
              )}
              {tom && (
                <div>
                  <div style={metaLabel(_LAYER_META[1].color)}>Tom</div>
                  <div style={{ fontSize: 11, color: 'var(--text)', fontWeight: 600 }}>{tom}</div>
                </div>
              )}
            </div>
          </div>
        </div>
      </div>

      <Connector />

      {/* ════════════════════════════════════════
          LAYER 3 — DIGI AI · QUALIFICACAO
      ════════════════════════════════════════ */}
      <div>
        <div style={layerHeader(_LAYER_META[2].color)}>
          <span style={lNum()}>{_LAYER_META[2].num}</span>
          {_LAYER_META[2].title}
          <span style={{ marginLeft: 'auto', fontSize: 10, fontWeight: 500, opacity: .7 }}>
            {langVariants.join(' + ')} · Stage 10 → 20
          </span>
        </div>
        <div style={layerBody(_LAYER_META[2].bg)}>

          {/* SDR AI · processo completo de qualificação */}
          <div style={card(_LAYER_META[2].color)}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 14 }}>
              <div style={{ width: 28, height: 28, background: _LAYER_META[2].color, borderRadius: 7, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
              </div>
              <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--navy,#112954)', fontFamily: 'var(--font-display)' }}>Digi AI · Processo de Qualificação SDR</div>
              {langVariants.length > 1 && langVariants.map(l => <span key={l} style={{ marginLeft: 4, ...smallTag(_LAYER_META[2].color) }}>{l}</span>)}
              {sdrAI?.score_alvo && <span style={{ marginLeft: 'auto', ...smallTag(_LAYER_META[2].color) }}>{sdrAI.score_alvo}</span>}
            </div>

            {sdrAI ? (
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 14 }}>

                {/* Col 1 — Abertura + Perguntas */}
                <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
                  {sdrAI.abertura_sugerida && (
                    <div>
                      <div style={metaLabel(_LAYER_META[2].color)}>1 · Abertura WA (Msg inicial)</div>
                      <div style={{ fontSize: 11, color: 'var(--text)', lineHeight: 1.5, fontStyle: 'italic', background: 'rgba(6,95,70,.05)', padding: '8px 10px', borderRadius: 6, borderLeft: `3px solid ${_LAYER_META[2].color}` }}>"{sdrAI.abertura_sugerida}"</div>
                    </div>
                  )}
                  {sdrAI.perguntas_chave?.length > 0 && (
                    <div>
                      <div style={metaLabel(_LAYER_META[2].color)}>2 · Perguntas de qualificação</div>
                      {sdrAI.perguntas_chave.map((q, i) => (
                        <div key={i} style={{ display: 'flex', gap: 7, marginBottom: 5, alignItems: 'flex-start' }}>
                          <div style={{ background: _LAYER_META[2].color, color: '#fff', borderRadius: 4, padding: '1px 5px', fontSize: 9, fontWeight: 700, fontFamily: 'var(--font-mono)', flexShrink: 0, marginTop: 2 }}>{i+1}</div>
                          <span style={{ fontSize: 11, color: 'var(--text)', lineHeight: 1.4 }}>{q}</span>
                        </div>
                      ))}
                    </div>
                  )}
                </div>

                {/* Col 2 — Argumento produto + Argumento confiança */}
                <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
                  {sdrAI.argumento_produto && (
                    <div>
                      <div style={metaLabel(_LAYER_META[2].color)}>3 · Argumento ROI (Ten#1 Lógico)</div>
                      <div style={{ fontSize: 11, color: 'var(--text)', lineHeight: 1.5 }}>{sdrAI.argumento_produto}</div>
                    </div>
                  )}
                  {sdrAI.argumento_confianca && (
                    <div>
                      <div style={metaLabel(_LAYER_META[2].color)}>4 · Argumento confiança (Ten#2)</div>
                      <div style={{ fontSize: 11, color: 'var(--text)', lineHeight: 1.5 }}>{sdrAI.argumento_confianca}</div>
                    </div>
                  )}
                </div>

                {/* Col 3 — Objeção + Critério handoff */}
                <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
                  {sdrAI.objecao_principal && (
                    <div>
                      <div style={metaLabel(_LAYER_META[2].color)}>5 · Objeção principal + resposta</div>
                      <div style={{ fontSize: 11, color: 'var(--text)', lineHeight: 1.5 }}>{sdrAI.objecao_principal}</div>
                    </div>
                  )}
                  {sdrAI.criterio_handoff && (
                    <div style={{ background: `${_LAYER_META[2].color}0D`, borderRadius: 8, padding: '8px 10px', borderLeft: `3px solid ${_LAYER_META[2].color}` }}>
                      <div style={metaLabel(_LAYER_META[2].color)}>6 · Critério handoff → Stage 65</div>
                      <div style={{ fontSize: 11, color: 'var(--text)', lineHeight: 1.5, fontWeight: 600 }}>{sdrAI.criterio_handoff}</div>
                    </div>
                  )}
                </div>
              </div>
            ) : (() => {
              const painStr = pains.length > 0 ? (typeof pains[0] === 'string' ? pains[0] : pains[0].descricao || '') : '—';
              const uspStr  = usps.length  > 0 ? (typeof usps[0]  === 'string' ? usps[0]  : usps[0].usp || '') : '—';
              const prod    = brief.commercial_name || campanha?.titulo || 'produto';
              const decisor = brief.decision_maker || 'decisor';
              return (
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 14 }}>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
                  <div>
                    <div style={metaLabel(_LAYER_META[2].color)}>0 · Enriquecimento automático</div>
                    <div style={{ fontSize: 10, color: 'var(--text-muted)', lineHeight: 1.5 }}>Lead entra via {allChannels.map(c => CANAL_LABEL[c.canal] || c.canal).join(' / ')} → match Gestor por email/telefone → carrega histórico OP + SAT + Digi Brain (Three Tens anteriores + Pain Profile).</div>
                  </div>
                  <div>
                    <div style={metaLabel(_LAYER_META[2].color)}>1 · Abertura Straight Line (4 seg)</div>
                    <div style={{ fontSize: 10, color: 'var(--text)', lineHeight: 1.5, fontStyle: 'italic', background: 'rgba(6,95,70,.05)', padding: '6px 8px', borderRadius: 6, borderLeft: `3px solid ${_LAYER_META[2].color}` }}>
                      "Olá [nome], vi que demonstrou interesse em {prod}. Trabalha com impressão/personalização têxtil?"
                    </div>
                    <div style={{ fontSize: 9, color: 'var(--text-dim)', marginTop: 3, fontFamily: 'var(--font-mono)' }}>Tom: Absolute Certainty · Pace, Pace, Lead</div>
                  </div>
                </div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
                  <div>
                    <div style={metaLabel(_LAYER_META[2].color)}>2 · Intelligence Gathering SPIN</div>
                    {[
                      `S — Situação: que equipamento usa actualmente?`,
                      `P — Problema: qual a maior dificuldade? ("${painStr.slice(0,60)}${painStr.length>60?'...':''}")`,
                      `I — Implicação: o que isso custa em tempo/desperdício/clientes?`,
                      `N — Necessidade: o que mudaria se esse problema fosse resolvido?`,
                    ].map((q, i) => (
                      <div key={i} style={{ display: 'flex', gap: 6, marginBottom: 4 }}>
                        <div style={{ width: 5, height: 5, borderRadius: '50%', background: _LAYER_META[2].color, flexShrink: 0, marginTop: 5 }} />
                        <span style={{ fontSize: 10, color: 'var(--text)', lineHeight: 1.4 }}>{q}</span>
                      </div>
                    ))}
                  </div>
                  <div>
                    <div style={metaLabel(_LAYER_META[2].color)}>3 · Ten#1 Produto — ROI lógico → emocional</div>
                    <div style={{ fontSize: 10, color: 'var(--text)', lineHeight: 1.5 }}>Argumento lógico: "{uspStr.slice(0,80)}". Future Pacing: imagina produzir X transferências/dia sem paragens.</div>
                  </div>
                </div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
                  <div>
                    <div style={metaLabel(_LAYER_META[2].color)}>4 · Ten#2 Digi + Ten#3 Digidelta</div>
                    <div style={{ fontSize: 10, color: 'var(--text)', lineHeight: 1.5 }}>Confiança no agente: conhecimento profundo, cuidado genuíno, honestidade. Confiança na marca: Digidelta com anos de actividade, rede de suporte, casos de sucesso.</div>
                  </div>
                  <div>
                    <div style={metaLabel(_LAYER_META[2].color)}>5 · Close + Looping (máx 3 loops)</div>
                    <div style={{ fontSize: 10, color: 'var(--text)', lineHeight: 1.5 }}>Objecção → Loop (reintroduzir dor + novo argumento). Money-aside close: "Deixando o investimento de lado, a solução faz sentido para o teu negócio?"</div>
                  </div>
                  <div style={{ background: `${_LAYER_META[2].color}0D`, borderRadius: 8, padding: '8px 10px', borderLeft: `3px solid ${_LAYER_META[2].color}` }}>
                    <div style={metaLabel(_LAYER_META[2].color)}>6 · Trigger → Stage 65</div>
                    <div style={{ fontSize: 10, color: 'var(--text)', fontWeight: 600, lineHeight: 1.5 }}>Lead qualificado → demo/visita agendada → Digi AI notifica equipa comercial com perfil completo do lead.</div>
                  </div>
                </div>
              </div>
              );
            })()}
          </div>
        </div>
      </div>

      <Connector />

      {/* ════════════════════════════════════════
          LAYER 4 — HANDOFF COMERCIAL
      ════════════════════════════════════════ */}
      <div>
        <div style={layerHeader(_LAYER_META[3].color)}>
          <span style={lNum()}>{_LAYER_META[3].num}</span>
          {_LAYER_META[3].title}
        </div>
        <div style={layerBody(_LAYER_META[3].bg)}>
          {/* Trigger banner */}
          <div style={{ background: '#FEF3C7', borderRadius: 8, padding: '8px 14px', marginBottom: 14, display: 'flex', alignItems: 'center', gap: 8 }}>
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#92400E" strokeWidth="2"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>
            <span style={{ fontSize: 11, color: '#92400E', fontWeight: 600 }}>
              Trigger: Stage 65 → Digi AI notifica Carina por email com perfil completo do lead → Carina abre OP no Gestor
            </span>
          </div>

          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginBottom: 12 }}>
            {/* Perfil enviado a Carina — definido no SP */}
            <div style={card(_LAYER_META[3].color)}>
              <div style={metaLabel(_LAYER_META[3].color)}>Perfil do lead · email automático para Carina</div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginTop: 6 }}>
                {[
                  { lbl: 'Produto de interesse', val: brief.commercial_name || campanha?.titulo || '—' },
                  { lbl: 'Decisor', val: (() => { const d = brief.decision_maker || '—'; return d.length > 40 ? d.slice(0, 38) + '…' : d; })() },
                  { lbl: 'Dor dominante', val: pains.length > 0 ? (typeof pains[0] === 'string' ? pains[0] : pains[0].descricao || '—') : '—' },
                  { lbl: 'Oferta a apresentar', val: offerLabel !== 'Oferta comercial' ? offerLabel : (offer.details || offer.descricao || '—') },
                  { lbl: 'Mercados', val: markets.map(m => m.country).join(' + ') || '—' },
                ].map((row, i) => (
                  <div key={i} style={{ display: 'flex', gap: 8, alignItems: 'flex-start', paddingBottom: 5, borderBottom: i < 5 ? '1px solid var(--border-light,#f1f5f9)' : 'none' }}>
                    <div style={{ fontSize: 9, fontWeight: 700, color: _LAYER_META[3].color, fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '.04em', flexShrink: 0, minWidth: 130 }}>{row.lbl}</div>
                    <div style={{ fontSize: 11, color: 'var(--text)', lineHeight: 1.4 }}>{row.val}</div>
                  </div>
                ))}
              </div>
            </div>

            {/* Top USPs · argumentos de venda */}
            <div style={card(_LAYER_META[3].color)}>
              <div style={metaLabel(_LAYER_META[3].color)}>Argumentos de venda · definidos no briefing</div>
              {usps.length === 0
                ? <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 6 }}>Sem USPs definidos no briefing</div>
                : usps.map((u, i) => (
                  <div key={i} style={{ display: 'flex', gap: 8, marginTop: 8, alignItems: 'flex-start' }}>
                    <div style={{ ...bigNum, fontSize: 16, color: _LAYER_META[3].color, flexShrink: 0, lineHeight: 1.1 }}>{i+1}</div>
                    <div style={{ fontSize: 11, color: 'var(--text)', lineHeight: 1.5 }}>{typeof u === 'string' ? u : u.usp || u.descricao || JSON.stringify(u)}</div>
                  </div>
                ))
              }
            </div>
          </div>

          {/* Próximos passos comerciais */}
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: 10 }}>
            {[
              { badge: 'Demo Showroom', title: `${brief.commercial_name || 'Produto'} · ${markets.map(m=>m.country).join(' + ')}`,
                color: '#92400E', bg: '#FEF3C7',
                rows: ['Carina abre OP Stage 65 no Gestor', 'Equipa comercial agenda visita ao showroom Lisboa', 'Demo ao vivo + amostras para testar'] },
              { badge: 'Proposta ROI', title: (() => { const pt = brief.purchase_trigger; if (!pt) return 'Intenção de compra declarada'; try { const arr = JSON.parse(pt); const first = Array.isArray(arr) ? arr[0] : pt; return typeof first === 'string' ? (first.length > 70 ? first.slice(0, 68) + '…' : first) : 'Intenção de compra declarada'; } catch { return typeof pt === 'string' ? (pt.length > 70 ? pt.slice(0, 68) + '…' : pt) : 'Intenção de compra declarada'; } })(),
                color: '#991B1B', bg: '#FEE2E2',
                rows: [
                  'Qualificação de volume, equipamento actual e urgência',
                  `Proposta personalizada · ${offerLabel}`,
                  'Validação de condições e prazo de decisão',
                ] },
              { badge: offerLabel !== 'Oferta comercial' ? (offer.type || offer.tipo || 'Oferta') : 'Oferta Financiamento',
                title: offerLabel !== 'Oferta comercial' ? offerLabel : (offer.details || 'Condições especiais campanha'),
                color: '#065F46', bg: '#CCFBF1',
                rows: [
                  offer.details || offer.descricao || 'Condições financeiras adaptadas ao perfil',
                  offer.negotiable ? 'Condições negociáveis mediante volume' : 'Garantia standard incluída',
                  'Equipa comercial valida e fecha condições',
                ] },
            ].map((c, i) => (
              <div key={i} style={{ background: 'var(--bg-card,#fff)', borderRadius: 10, border: '1px solid var(--border)', borderTop: `3px solid ${c.color}`, padding: '12px 12px 10px' }}>
                <span style={{ ...smallTag(c.color, c.bg), marginBottom: 8, display: 'inline-block' }}>{c.badge}</span>
                <div style={{ fontSize: 12, fontWeight: 700, color: 'var(--navy,#112954)', marginBottom: 8, fontFamily: 'var(--font-display)', lineHeight: 1.3 }}>{c.title}</div>
                {c.rows.map((r, j) => (
                  <div key={j} style={{ fontSize: 10, color: '#475569', display: 'flex', gap: 5, lineHeight: 1.4, marginBottom: 3 }}>
                    <span style={{ color: c.color, flexShrink: 0, fontWeight: 700 }}>→</span>{r}
                  </div>
                ))}
              </div>
            ))}
          </div>
        </div>
      </div>

      <Connector />

      {/* ════════════════════════════════════════
          LAYER 5 — NURTURING WA
      ════════════════════════════════════════ */}
      <div style={{ borderRadius: '0 0 0 0', overflow: 'hidden' }}>
        <div style={layerHeader(_LAYER_META[4].color)}>
          <span style={lNum()}>{_LAYER_META[4].num}</span>
          {_LAYER_META[4].title}
          <span style={{ marginLeft: 'auto', fontSize: 10, fontWeight: 500, opacity: .7 }}>Target CRM + Stages</span>
        </div>
        <div style={layerBody(_LAYER_META[4].bg)}>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr', gap: 14, marginBottom: 14 }}>

            {/* WA sequences per objectivo */}
            <div style={card(_LAYER_META[4].color)}>
              <div style={metaLabel(_LAYER_META[4].color)}>Qualificação 1:1 · CRM Gestor · NÃO é envio massivo</div>
              {[
                { obj: 'Reactivação', seq: 'OPs perdidas no produto · decisor · qualificação de interesse actual', color: '#4C1D95' },
                { obj: 'Aceleração (AG Decisão)', seq: 'Pipeline quente em decisão · acelerar fecho · proposta ou reunião final', color: '#7C3AED' },
              ].map((item, i) => (
                <div key={i} style={{ marginBottom: 10 }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 3 }}>
                    <div style={dot(item.color)} />
                    <div style={{ fontSize: 11, fontWeight: 700, color: 'var(--navy,#112954)' }}>{item.obj}</div>
                  </div>
                  <div style={{ fontSize: 10, color: 'var(--text-muted)', paddingLeft: 14, lineHeight: 1.5 }}>{item.seq}</div>
                </div>
              ))}
            </div>
          </div>

          {/* Stage progression */}
          <div style={card(_LAYER_META[4].color)}>
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12 }}>
              <div style={metaLabel(_LAYER_META[4].color)}>Progressao de stages · WA Nurturing via Digi AI</div>
            </div>
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(6,1fr)', gap: 8 }}>
              {_NURTURE_STAGES.map((stage, i) => (
                <div key={i} style={{ background: 'var(--bg-sunken)', borderRadius: 8, padding: '8px 10px', borderLeft: `3px solid ${_LAYER_META[4].color}`, position: 'relative' }}>
                  {i < _NURTURE_STAGES.length - 1 && (
                    <div style={{ position: 'absolute', right: -14, top: '50%', transform: 'translateY(-50%)', zIndex: 1, color: _LAYER_META[4].color, fontSize: 10, fontWeight: 700 }}>›</div>
                  )}
                  <div style={{ fontSize: 9, fontWeight: 700, color: _LAYER_META[4].color, letterSpacing: '0.06em', textTransform: 'uppercase', fontFamily: 'var(--font-mono)' }}>{stage.num}</div>
                  <div style={{ fontSize: 10, fontWeight: 700, color: 'var(--navy,#112954)', margin: '3px 0', fontFamily: 'var(--font-display)', lineHeight: 1.2 }}>{stage.name}</div>
                  <div style={{ fontSize: 9, color: 'var(--text-muted)', lineHeight: 1.4 }}>{stage.action}</div>
                </div>
              ))}
            </div>
          </div>
        </div>
      </div>

      {/* Footer */}
      <div style={{ background: 'var(--navy,#112954)', padding: '10px 24px', display: 'flex', alignItems: 'center', justifyContent: 'space-between', borderRadius: '0 0 10px 10px' }}>
        <div style={{ fontSize: 11, fontWeight: 700, color: 'rgba(255,255,255,.5)', letterSpacing: '0.06em', fontFamily: 'var(--font-mono)' }}>
          {brief.brand_name || ''}{brief.brand_name && campanha?.titulo ? ' · ' : ''}{campanha?.titulo || ''}
        </div>
        <div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
          {kpis.length > 0 && (
            <span style={{ fontSize: 10, color: 'rgba(255,255,255,.4)', fontFamily: 'var(--font-mono)' }}>
              KPIs: {kpis.slice(0,2).map(k => typeof k === 'string' ? k : k.kpi || '').join(' · ')}
            </span>
          )}
          <div style={{ fontSize: 10, color: 'rgba(255,255,255,.35)', fontFamily: 'var(--font-mono)' }}>
            {new Date().toLocaleDateString('pt-PT', { month: 'long', year: 'numeric' })}
          </div>
        </div>
      </div>

    </div>
  );
};


// ── TabAnunciosFull — hierarquia Campaign→AdSet→Ad para canais pagos ───────────
const PAID_CHANNELS = new Set(['meta_ads','linkedin_ads','google_ads_search','google_ads_display','muppi_led']);
const PAID_LABELS   = { meta_ads:'Meta Ads', linkedin_ads:'LinkedIn Ads', google_ads_search:'Google Search', google_ads_display:'Google Display', muppi_led:'LED/Muppi' };

