/* screen_marketing_activacao_setup.jsx
   Activação — consola de setup-para-publicar (padrão Briefings).
   Carregado DEPOIS de screen_marketing_activacao.jsx e redefine window.MktActivacaoScreen.
*/

const ACT_CHANNELS = [
  { id: 'meta_ads',          label: 'Meta Ads',          short: 'META' },
  { id: 'facebook',          label: 'Facebook',          short: 'FB' },
  { id: 'instagram',         label: 'Instagram',         short: 'IG' },
  { id: 'linkedin',          label: 'LinkedIn',          short: 'LI' },
  { id: 'linkedin_ads',      label: 'LinkedIn Ads',      short: 'LIN' },
  { id: 'google_ads_search', label: 'Google Search',     short: 'GGL' },
  { id: 'google_ads_display',label: 'Google Display',    short: 'DISP' },
  { id: 'email',             label: 'Email',             short: 'EML' },
  { id: 'whatsapp',          label: 'WhatsApp',          short: 'WA' },
  { id: 'website',           label: 'Website',           short: 'WEB' },
  { id: 'muppi_led',         label: 'Painéis LED',       short: 'LED' },
];
const ACT_ORGANIC = new Set(['facebook', 'instagram', 'linkedin']);
const ACT_PAID = new Set(['meta_ads', 'linkedin_ads', 'google_ads_search', 'google_ads_display']);
const ACT_OWNED = new Set(['email', 'whatsapp', 'website', 'muppi_led']);
const ACT_CHAN_COLOR = {
  meta_ads: '#3859D0', facebook: '#1877F2', instagram: '#E1306C', linkedin: '#0A66C2',
  linkedin_ads: '#0A66C2', google_ads_search: '#EA4335', google_ads_display: '#34A853',
  email: '#7C3AED', whatsapp: '#16A34A', website: '#0891B2', muppi_led: '#D97706',
};

function actPrettyHandle(social, canal) {
  const handle = String(social?.handle || '').replace(/^@/, '');
  if (handle) return canal === 'linkedin' ? handle : `@${handle}`;
  const url = String(social?.url || '');
  const ig = url.match(/instagram\.com\/([^/?#]+)/i);
  if (ig) return `@${ig[1]}`;
  const li = url.match(/linkedin\.com\/company\/([^/?#]+)/i);
  if (li) return li[1];
  const fbId = url.match(/facebook\.com\/profile\.php\?id=(\d+)/i);
  if (fbId) return 'Página Facebook';
  const fb = url.match(/facebook\.com\/([^/?#]+)/i);
  if (fb) return fb[1];
  return 'Conta por ligar';
}

function actProgress(n, total) {
  const t = Number(total) || 0;
  const x = Number(n) || 0;
  const pct = t ? Math.min(100, Math.round((x / t) * 100)) : 0;
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 110 }}>
      <div style={{ flex: 1, height: 4, background: '#E8EDF5', borderRadius: 99, overflow: 'hidden' }}>
        <div style={{ width: `${pct}%`, height: '100%', background: x ? '#3859D0' : '#D5DCE8' }} />
      </div>
      <span style={{ fontSize: 11, fontFamily: 'var(--font-mono)', color: 'var(--text-muted)', whiteSpace: 'nowrap' }}>{x}/{t}</span>
    </div>
  );
}

const ACT_ESTADO = {
  previsto:    { label: 'PREVISTO',    color: '#6b7fa3', bg: 'rgba(107,127,163,0.12)' },
  a_produzir:  { label: 'A PRODUZIR',  color: '#F59E0B', bg: 'rgba(245,158,11,0.12)' },
  setup:       { label: 'SETUP',       color: '#3859D0', bg: 'rgba(56,89,208,0.12)' },
  agendado:    { label: 'AGENDADO',    color: '#3859D0', bg: 'rgba(56,89,208,0.12)' },
  publicado:   { label: 'PUBLICADO',   color: '#10B981', bg: 'rgba(16,185,129,0.12)' },
  falhou:      { label: 'FALHOU',      color: '#EF4444', bg: 'rgba(239,68,68,0.12)' },
  desactivado: { label: 'DESACTIVADO', color: '#B45309', bg: 'rgba(217,119,6,0.14)' },
};

function ActDesactivarBtn({ onClick, disabled, label, title, full }) {
  return (
    <button
      type="button"
      onClick={onClick}
      disabled={!!disabled}
      title={title || 'Pára o que está no ar ou agendado neste canal'}
      style={{
        height: 30, padding: '0 12px', fontSize: 12, fontWeight: 650, borderRadius: 6,
        border: '1px solid #FECACA', background: '#fff', color: '#B91C1C', fontFamily: 'inherit',
        width: full ? '100%' : undefined, boxSizing: 'border-box',
        cursor: disabled ? 'default' : 'pointer', opacity: disabled ? 0.45 : 1, flexShrink: 0,
      }}
    >{label || 'Desactivar'}</button>
  );
}

const ACT_SEND = new Set(['email', 'whatsapp']);
const ACT_BRAND_ORDER = ['mimaki', 'biond', 'decal', 'alldecor', 'sensek', 'netscreen', 'digidelta'];
const ACT_BRAND_NAMES = { mimaki: 'Mimaki', biond: 'BIOND', decal: 'Decal', alldecor: 'AllDecor', sensek: 'Sensek', netscreen: 'NetScreen', digidelta: 'Digidelta' };
const ACT_BRAND_COLORS = { mimaki: '#0066CC', biond: '#E8A000', decal: '#00A86B', alldecor: '#7C3AED', sensek: '#DC2626', netscreen: '#0EA5E9', digidelta: '#3859D0' };

const actThSt = {
  textAlign: 'left', padding: '8px 12px',
  fontSize: 10, fontFamily: 'var(--font-display)', fontWeight: 600,
  letterSpacing: '0.10em', textTransform: 'uppercase',
  color: 'var(--text-muted)', whiteSpace: 'nowrap',
};

function actGetUser() {
  const params = new URLSearchParams(window.location.search);
  if (params.get('dev') === 'costa') {
    return { email: 'fabio.costa@digidelta.pt', nome_apresentar: 'Fábio Costa' };
  }
  return window.currentUser || { email: 'fabio.costa@digidelta.pt', nome_apresentar: 'Fábio Costa' };
}

function actParseHash() {
  const h = window.location.hash.replace(/^#/, '');
  const params = new URLSearchParams(h);
  return { camp: params.get('camp') || null, canal: params.get('canal') || null, tab: params.get('tab') || null };
}

function actSetCamp(campId, canal, tab) {
  const params = new URLSearchParams(window.location.hash.replace(/^#/, ''));
  params.set('screen', 'marketing');
  params.set('sub', 'publicacao');
  if (campId) params.set('camp', campId);
  else params.delete('camp');
  if (campId && canal) params.set('canal', canal);
  else params.delete('canal');
  if (campId && canal && tab) params.set('tab', tab);
  else params.delete('tab');
  params.delete('analise');
  params.delete('correr');
  params.delete('registo');
  window.location.hash = params.toString();
}

function actAbrirAnalise(campId) {
  const params = new URLSearchParams(window.location.hash.replace(/^#/, ''));
  params.set('screen', 'marketing');
  params.set('sub', 'analise-campanhas');
  params.set('analise', campId);
  params.set('correr', '1');
  params.delete('registo');
  params.delete('camp');
  params.delete('canal');
  params.delete('tab');
  window.location.hash = params.toString();
}

function actClearCampFromHash() {
  const raw = String(window.location.hash || '').replace(/^#/, '');
  const params = new URLSearchParams(raw);
  if (!params.has('camp') && !params.has('canal')) return;
  params.delete('camp');
  params.delete('canal');
  const next = params.toString();
  const url = `${window.location.pathname}${window.location.search}${next ? `#${next}` : ''}`;
  window.history.replaceState(null, '', url);
}

function actFmtDate(iso) {
  if (!iso) return '—';
  const d = new Date(iso);
  if (isNaN(d)) return String(iso).slice(0, 10);
  return d.toLocaleDateString('pt-PT', { day: '2-digit', month: 'short', year: 'numeric' });
}

function actFmtDateTime(iso) {
  if (!iso) return '—';
  const d = new Date(iso);
  if (isNaN(d)) return String(iso);
  return d.toLocaleString('pt-PT', { day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' });
}

function actPaisNome(code) {
  return { PT: 'Portugal', ES: 'Espanha' }[String(code || '').toUpperCase()] || code || '—';
}

function actSomaDias(whenLocal, dias) {
  if (!whenLocal) return null;
  const base = new Date(whenLocal);
  if (Number.isNaN(base.getTime())) return null;
  const next = new Date(base.getTime());
  next.setDate(next.getDate() + Number(dias || 0));
  return next;
}

function actFmtTime(iso) {
  if (!iso) return '';
  const d = new Date(iso);
  if (isNaN(d)) return '';
  return d.toLocaleTimeString('pt-PT', { hour: '2-digit', minute: '2-digit' });
}

function actLocaleLabel(key) {
  const n = Number(key);
  const names = { 31: 'Português (Portugal)', 6: 'Português (Brasil)', 10: 'Italiano' };
  return names[n] ? `${names[n]} · ${n}` : `key ${key}`;
}

function actPlacementLine(placements) {
  const p = placements || {};
  const fb = p.facebook_positions || [];
  const ig = p.instagram_positions || [];
  const pubs = p.publisher_platforms || [];
  if (!fb.length && !ig.length && !pubs.length) return null;
  return {
    platforms: pubs.length ? pubs.map(x => x === 'facebook' ? 'Facebook' : x === 'instagram' ? 'Instagram' : x).join(' · ') : null,
    facebook: fb.length ? `Facebook · ${fb.join(', ')}` : null,
    instagram: ig.length ? `Instagram · ${ig.join(', ')}` : null,
  };
}

function actIsoDate(iso) {
  if (!iso) return '';
  const m = String(iso).match(/(\d{4}-\d{2}-\d{2})/);
  if (m) return m[1];
  const d = new Date(iso);
  if (isNaN(d)) return '';
  return new Intl.DateTimeFormat('en-CA', { timeZone: 'Europe/Lisbon', year: 'numeric', month: '2-digit', day: '2-digit' }).format(d);
}

function actFmtN(n) {
  if (n == null || n === '') return '—';
  const x = Number(n);
  if (!Number.isFinite(x)) return String(n);
  return x.toLocaleString('pt-PT');
}

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

function actOrganicSlot(peca, fallbackIso) {
  if (peca?.organic_scheduled_for) return peca.organic_scheduled_for;
  const raw = String(peca?.planned_date || fallbackIso || '');
  if (/T\d{2}:\d{2}/.test(raw)) return raw;
  const day = raw.match(/(\d{4}-\d{2}-\d{2})/);
  return day ? `${day[1]}T09:00:00` : (fallbackIso || '');
}

function actEstadoBadge(estado, canal) {
  if (!estado) return <span style={{ color: 'var(--text-dim)', fontSize: 12 }}>—</span>;
  const cfg = ACT_ESTADO[estado] || ACT_ESTADO.previsto;
  let label = cfg.label;
  if (estado === 'publicado' && ACT_SEND.has(canal)) label = 'ENVIADO';
  return (
    <span style={{
      fontSize: 10, fontFamily: 'var(--font-mono)', fontWeight: 600,
      padding: '2px 7px', borderRadius: 4, display: 'inline-block',
      color: cfg.color, background: cfg.bg, letterSpacing: '0.04em', whiteSpace: 'nowrap',
    }}>{label}</span>
  );
}

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

const ActSortHeader = ({ label, sortKey, sortBy, onSort, style: extra }) => {
  const active = sortBy.key === sortKey;
  return (
    <th style={{ ...actThSt, cursor: sortKey ? 'pointer' : 'default', userSelect: 'none', ...extra }}
      onClick={() => sortKey && onSort(sortKey)}>
      <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}>
        {label}
        {active && sortKey && window.Icon &&
          <window.Icon name={sortBy.dir === 'asc' ? 'arrowUp' : 'arrowDown'} size={9} style={{ color: 'var(--ai-500)' }} />}
      </span>
    </th>
  );
};

function actSetupFetch(url, opts) {
  const method = opts && opts.method ? opts.method : 'GET';
  const body = opts && opts.body != null ? opts.body : null;
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open(method, url, true);
    xhr.setRequestHeader('Accept', 'application/json');
    if (body != null) xhr.setRequestHeader('Content-Type', 'application/json');
    xhr.onload = () => {
      let data = {};
      try { data = JSON.parse(xhr.responseText || '{}'); } catch (e) { data = {}; }
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve(data);
        return;
      }
      const err = new Error(data.error || ('HTTP ' + xhr.status));
      err.code = data.code || null;
      err.envelope = data.envelope || null;
      reject(err);
    };
    xhr.onerror = () => reject(new Error('Não consegui contactar o servidor.'));
    xhr.send(body);
  });
}

async function actFetchEnvelope(url, body) {
  try {
    return await actSetupFetch(url, { method: 'POST', body: JSON.stringify(body) });
  } catch (e) {
    if (e.code !== 'excesso_envelope') throw e;
    const ok = window.confirm(`${e.message}\n\nPublicar na mesma?`);
    if (!ok) throw e;
    return actSetupFetch(url, { method: 'POST', body: JSON.stringify({ ...body, confirmar_excesso: true }) });
  }
}

const ActJobContext = React.createContext(null);

const ActSpinner = () => (
  <span style={{
    width: 18, height: 18, borderRadius: '50%', flexShrink: 0,
    border: '2px solid rgba(56,89,208,0.2)', borderTopColor: '#3859D0',
    animation: 'act-spin 0.7s linear infinite', display: 'inline-block',
  }} />
);

const ActJobOverlay = ({ job }) => {
  const [visible, setVisible] = React.useState(false);
  React.useEffect(() => {
    if (!job) { setVisible(false); return undefined; }
    const t = setTimeout(() => setVisible(true), 80);
    return () => clearTimeout(t);
  }, [job]);
  if (!job || !visible) return null;
  const lines = Array.isArray(job.lines) ? job.lines : [];
  return (
    <div style={{
      position: 'absolute', inset: 0, zIndex: 80,
      background: 'rgba(248,250,252,0.78)',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      backdropFilter: 'blur(2px)',
    }}>
      <div style={{
        width: 440, maxWidth: '92%',
        background: '#fff', border: '1px solid var(--border, #dde3ef)',
        borderRadius: 12, boxShadow: '0 16px 40px rgba(17,41,84,0.14)',
        padding: 22,
      }}>
        <div style={{ display: 'flex', gap: 12, alignItems: 'flex-start' }}>
          <ActSpinner />
          <div style={{ minWidth: 0, flex: 1 }}>
            <div style={{
              fontSize: 10, fontFamily: 'var(--font-mono)', letterSpacing: '0.10em',
              textTransform: 'uppercase', color: 'var(--text-muted, #4a5e7a)',
            }}>{job.title || 'A trabalhar'}</div>
            <div style={{
              fontSize: 15, fontWeight: 700, color: 'var(--text, #112954)',
              marginTop: 4, lineHeight: 1.35,
            }}>{job.step || 'A processar…'}</div>
          </div>
        </div>
        {lines.length > 0 && (
          <div className="scrollbar" style={{
            marginTop: 14, maxHeight: 168, overflowY: 'auto',
            fontSize: 11, fontFamily: 'var(--font-mono)',
            color: 'var(--text-muted, #4a5e7a)', lineHeight: 1.55,
            background: 'var(--bg, #f8fafc)', border: '1px solid var(--border, #dde3ef)',
            borderRadius: 8, padding: '8px 10px',
          }}>
            {lines.slice(-10).map((l, i) => (
              <div key={`${i}-${l}`}>{l}</div>
            ))}
          </div>
        )}
      </div>
    </div>
  );
};

const useActJobRunner = () => {
  const [job, setJob] = React.useState(null);
  const start = React.useCallback((title, step) => {
    setJob({ title, step: step || title, lines: [], error: null, started: Date.now() });
  }, []);
  const update = React.useCallback((patch) => {
    setJob(j => (j ? { ...j, ...patch } : { title: 'A trabalhar', lines: [], ...patch }));
  }, []);
  const finish = React.useCallback(() => setJob(null), []);
  const pollAds = React.useCallback((campId) => {
    let dead = false;
    const tick = async () => {
      if (dead || !campId) return;
      try {
        const r = await fetch(`/api/marketing/activacao/ads/progress/${encodeURIComponent(campId)}`);
        if (!r.ok) return;
        const d = await r.json();
        const p = d.progress;
        if (!p || dead) return;
        setJob(j => ({
          ...(j || { title: 'Meta Ads', lines: [] }),
          title: p.title || j?.title || 'Meta Ads',
          step: p.detail || p.title || j?.step,
          lines: Array.isArray(p.log) ? p.log : (j?.lines || []),
          error: p.error || null,
        }));
      } catch (_) { /* poll */ }
    };
    tick();
    const id = setInterval(tick, 650);
    return () => { dead = true; clearInterval(id); };
  }, []);
  return { job, start, update, finish, pollAds };
};

const ActJobProvider = ({ children }) => {
  const api = useActJobRunner();
  return (
    <ActJobContext.Provider value={api}>
      <div style={{ position: 'relative', height: '100%', flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
        {children}
        <ActJobOverlay job={api.job} />
      </div>
    </ActJobContext.Provider>
  );
};

const useActJob = () => React.useContext(ActJobContext) || {
  job: null,
  start: () => {},
  update: () => {},
  finish: () => {},
  pollAds: () => () => {},
};

const ActBanner = ({ kpis, campanhas, userName, marcaNome, outras }) => {
  const first = (userName || '').split(' ')[0] || 'Olá';
  const pl = (n, s, p) => n === 1 ? s : p;
  let heading = `${first}, pipeline de activação.`;
  let body = 'As campanhas com setup e planeamento aparecem aqui. Produção preenche o conteúdo quando estiver pronta.';
  if (kpis.falhas > 0) {
    heading = `${first}, ${kpis.falhas} ${pl(kpis.falhas, 'canal falhou', 'canais falharam')} na publicação.`;
    body = 'Revê as campanhas com falha antes de voltar a activar.';
  } else if (kpis.agendadas > 0) {
    heading = `${first}, ${kpis.agendadas} ${pl(kpis.agendadas, 'campanha está agendada', 'campanhas estão agendadas')}.`;
    body = 'Quando a data e hora do planeamento chegarem, o sistema publica ou envia.';
  } else if (kpis.setup > 0) {
    heading = `${first}, ${kpis.setup} ${pl(kpis.setup, 'campanha tem setup pronto', 'campanhas têm setup pronto')}.`;
    body = 'Abre a campanha, confirma Meta Ads, email e datas, e activa quando as peças existirem.';
  } else if (kpis.a_produzir > 0) {
    heading = `${first}, ${kpis.a_produzir} ${pl(kpis.a_produzir, 'campanha espera produção', 'campanhas esperam produção')}.`;
    body = 'O setup e as datas já estão. O conteúdo chega de Produção — até lá o botão Activar fica desligado.';
  } else if (kpis.total === 0 && outras > 0) {
    heading = `${first}, a ${marcaNome} não tem campanhas em activação.`;
    body = `Há ${outras} ${pl(outras, 'campanha noutra marca', 'campanhas noutras marcas')}. As que já estão a enviar continuam lá.`;
  } else if (kpis.total === 0) {
    heading = `${first}, ainda não há campanhas para activar.`;
    body = 'Aprova o setup numa campanha para ela aparecer nesta tabela.';
  }
  return (
    <div style={{ flexShrink: 0, padding: '10px 0 0' }}>
      <div style={{ fontSize: 22, fontWeight: 700, color: 'var(--text)', fontFamily: 'var(--font-display)', marginBottom: 6, letterSpacing: '-0.02em' }}>{heading}</div>
      <div style={{ fontSize: 13, color: 'var(--text-muted)', lineHeight: 1.75 }}>{body}</div>
    </div>
  );
};

const ActLista = ({ campanhas, isAll, onOpen, filterEstado, vazio }) => {
  const [sortBy, setSortBy] = React.useState({ key: 'updated_at', dir: 'desc' });
  const onSort = (key) => setSortBy(s => ({ key, dir: s.key === key && s.dir === 'asc' ? 'desc' : 'asc' }));

  const rows = React.useMemo(() => {
    const arr = Array.isArray(campanhas) ? campanhas : [];
    const filtered = filterEstado === 'all'
      ? arr
      : arr.filter(c => Object.values(c.canais || {}).some(x => x.estado === filterEstado));
    return filtered.slice().sort((a, b) => {
      const av = a[sortBy.key] ?? '';
      const bv = b[sortBy.key] ?? '';
      if (av < bv) return sortBy.dir === 'asc' ? -1 : 1;
      if (av > bv) return sortBy.dir === 'asc' ? 1 : -1;
      return 0;
    });
  }, [campanhas, sortBy, filterEstado]);

  if (campanhas === null) {
    return (
      <div>
        {[1, 2, 3, 4].map(i => (
          <div key={i} style={{ height: 44, borderRadius: 6, background: 'var(--bg-sunken, #eef1f6)', marginBottom: 6, opacity: 1 - i * 0.2 }} />
        ))}
      </div>
    );
  }
  if (!rows.length) {
    return (
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 8, color: 'var(--text-muted)', marginTop: 18 }}>
        <div style={{ fontSize: 14 }}>Sem campanhas para os filtros actuais.</div>
        {vazio}
      </div>
    );
  }

  return (
    <div style={{ paddingBottom: 48 }}>
      <div style={{ overflowX: 'auto', width: '100%' }}>
        <table style={{ width: isAll ? 2034 : 1894, tableLayout: 'fixed', borderCollapse: 'collapse', fontSize: 12.5, fontFamily: 'var(--font-sans)' }}>
          <colgroup>
            <col style={{ width: 280 }} />
            {isAll && <col style={{ width: 140 }} />}
            {ACT_CHANNELS.map(ch => <col key={ch.id} style={{ width: 118 }} />)}
            <col style={{ width: 120 }} />
          </colgroup>
          <thead style={{ position: 'sticky', top: 0, zIndex: 2, background: 'var(--bg-elev)' }}>
            <tr style={{ borderBottom: '1px solid var(--border)' }}>
              <ActSortHeader label="Campanha" sortKey="titulo" sortBy={sortBy} onSort={onSort} />
              {isAll && <ActSortHeader label="Marca" sortKey="marca_slug" sortBy={sortBy} onSort={onSort} />}
              {ACT_CHANNELS.map(ch => (
                <th key={ch.id} style={{ ...actThSt, textAlign: 'center' }}>{ch.short}</th>
              ))}
              <ActSortHeader label="Actualizado" sortKey="updated_at" sortBy={sortBy} onSort={onSort} />
            </tr>
          </thead>
          <tbody>
            {rows.map((c, i) => {
              const bc = c.brand_color || ACT_BRAND_COLORS[c.marca_slug] || '#3859D0';
              return (
                <tr key={c.id} onClick={() => onOpen(c.id)}
                  style={{
                    cursor: 'pointer', borderBottom: '1px solid var(--border)',
                    transition: 'background 150ms ease',
                    animation: 'slide-up 0.2s ease-out both',
                    animationDelay: `${Math.min(i, 9) * 25}ms`,
                  }}
                  onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-hover, #f1f5f9)'}
                  onMouseLeave={e => e.currentTarget.style.background = 'transparent'}
                >
                  <td style={{ padding: '10px 12px', overflow: 'hidden' }}>
                    <div style={{ fontWeight: 500, color: 'var(--text)', fontSize: 13, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                      {c.titulo || 'Sem título'}
                    </div>
                  </td>
                  {isAll && (
                    <td style={{ padding: '10px 12px', overflow: 'hidden' }}>
                      <span style={{ fontSize: 11, fontFamily: 'var(--font-display)', fontWeight: 700, letterSpacing: '0.06em', color: bc }}>
                        {(c.brand_name || c.marca_slug || '').toUpperCase()}
                      </span>
                    </td>
                  )}
                  {ACT_CHANNELS.map(ch => {
                    const cell = c.canais?.[ch.id];
                    return (
                      <td key={ch.id} style={{ padding: '10px 8px', textAlign: 'center' }}>
                        {actEstadoBadge(cell?.estado, ch.id)}
                      </td>
                    );
                  })}
                  <td style={{ padding: '10px 12px', fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--text-muted)' }}>
                    {actFmtDate(c.updated_at)}
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>
    </div>
  );
};

const ActEmailTeste = ({ campId, pecaId, userEmail, disabled, compacto, camada = 80 }) => {
  const [open, setOpen] = React.useState(false);
  const [pessoas, setPessoas] = React.useState([]);
  const [empresa, setEmpresa] = React.useState('all');
  const [depto, setDepto] = React.useState('all');
  const [q, setQ] = React.useState('');
  const [sel, setSel] = React.useState(() => new Set(userEmail ? [String(userEmail).toLowerCase()] : []));
  const [sender, setSender] = React.useState(() => String(userEmail || '').toLowerCase());
  const [lista, setLista] = React.useState('remetente');
  const [busy, setBusy] = React.useState(false);
  const [loadingPessoas, setLoadingPessoas] = React.useState(false);
  const [msg, setMsg] = React.useState(null);
  const box = React.useRef(null);
  const painel = React.useRef(null);
  const { start, update, finish } = useActJob();

  React.useEffect(() => {
    if (!open) return;
    let alive = true;
    setLoadingPessoas(true);
    actSetupFetch('/api/marketing/activacao/destinatarios-teste')
      .then(d => {
        if (!alive) return;
        const lista = d.pessoas || [];
        setPessoas(lista);
        setSender(prev => (lista.some(p => p.email === prev) ? prev : ''));
      })
      .catch(e => { if (alive) setMsg(e.message); })
      .finally(() => { if (alive) setLoadingPessoas(false); });
    return () => { alive = false; };
  }, [open]);

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

  const empresas = React.useMemo(() => [...new Set(pessoas.map(p => p.empresa))].sort(), [pessoas]);
  const deptos = React.useMemo(() => {
    const src = empresa === 'all' ? pessoas : pessoas.filter(p => p.empresa === empresa);
    return [...new Set(src.map(p => p.departamento))].sort();
  }, [pessoas, empresa]);

  const filtered = pessoas.filter(p => {
    if (empresa !== 'all' && p.empresa !== empresa) return false;
    if (depto !== 'all' && p.departamento !== depto) return false;
    if (q) {
      const s = (p.nome + ' ' + p.email).toLowerCase();
      if (!s.includes(q.toLowerCase())) return false;
    }
    return true;
  });

  const toggle = (email) => {
    setSel(prev => {
      const next = new Set(prev);
      if (next.has(email)) next.delete(email);
      else if (next.size >= 10) return prev;
      else next.add(email);
      return next;
    });
  };

  const enviar = async (e) => {
    e?.preventDefault?.();
    e?.stopPropagation?.();
    if (!sender || !sel.size || busy) return;
    const remetente = pessoas.find(p => p.email === sender);
    setBusy(true); setMsg(null);
    start('Enviar teste', `A enviar o email de teste de ${remetente?.nome || 'o remetente'} para ${sel.size} destinatário${sel.size === 1 ? '' : 's'}…`);
    try {
      const res = await window.fetch('/api/marketing/activacao/email/teste', {
        method: 'POST',
        cache: 'no-store',
        redirect: 'error',
        credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
        body: JSON.stringify({ campanha_id: campId, peca_id: pecaId, emails: [...sel], sender_email: sender }),
      });
      const raw = await res.text();
      let data = null;
      try { data = JSON.parse(raw); } catch { data = null; }
      const sent = Number(data?.sent);
      if (!res.ok || !Number.isFinite(sent) || sent < 1) {
        throw new Error(data?.error || 'O teste não saiu. Actualiza a página e tenta outra vez.');
      }
      const viaTpl = data.template_name ? ` com o template ${data.template_name}` : '';
      const deQuem = data.sender_name ? ` como ${data.sender_name}` : '';
      const linha = `Enviado pela Brevo${deQuem} para ${sent} destinatário${sent === 1 ? '' : 's'}${viaTpl}.`;
      update({ step: linha });
      setMsg(linha);
    } catch (err) {
      const message = err?.message || 'O teste não saiu.';
      setMsg(message);
      update({ step: message, error: message });
    }
    finally { setBusy(false); finish(); }
  };

  const selStyle = { fontSize: 12, padding: '5px 8px', border: '1px solid var(--border)', borderRadius: 6, background: 'var(--bg)', color: 'var(--text)', fontFamily: 'inherit', width: '100%' };

  return (
    <div ref={box} style={{ position: 'relative', width: compacto ? 'auto' : '100%', marginTop: compacto ? 0 : 8, flexShrink: 0 }}>
      <button
        type="button"
        className="btn"
        style={{ height: compacto ? 32 : 36, width: compacto ? 'auto' : '100%', padding: '0 12px', fontSize: compacto ? 12 : 13, fontWeight: 650, opacity: disabled ? 0.45 : 1 }}
        disabled={disabled}
        onClick={() => setOpen(v => !v)}
      >
        Enviar teste
      </button>
      {open && ReactDOM.createPortal(
        <div onClick={() => setOpen(false)} style={{ position: 'fixed', inset: 0, zIndex: camada, background: 'rgba(17,41,84,0.45)', display: 'flex', justifyContent: 'center', alignItems: 'flex-start', padding: 24, overflow: 'hidden' }}>
        <div ref={painel} onClick={e => e.stopPropagation()} style={{
          width: 'min(480px, 100%)', maxHeight: 'calc(100vh - 48px)', display: 'flex', flexDirection: 'column', background: '#fff',
          borderRadius: 12, boxShadow: '0 16px 48px rgba(17,41,84,0.18)', padding: 16, overflow: 'hidden',
        }}>
          <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.08em', textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: 8 }}>
            Teste interno · máx. 10
          </div>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6, marginBottom: 8 }}>
            {[{ id: 'remetente', label: 'Remetente' }, { id: 'destinatarios', label: 'Destinatários' }].map(op => {
              const on = lista === op.id;
              return (
                <button key={op.id} type="button" onClick={() => setLista(op.id)} style={{
                  height: 30, borderRadius: 6, fontSize: 12, fontWeight: 650, cursor: 'pointer',
                  border: `1px solid ${on ? '#3859D0' : 'var(--border)'}`,
                  background: on ? '#3859D0' : 'var(--bg)',
                  color: on ? '#fff' : 'var(--text)',
                }}>{op.label}</button>
              );
            })}
          </div>
          <div style={{ fontSize: 11, color: 'var(--text-muted)', lineHeight: 1.4, marginBottom: 8 }}>
            {lista === 'remetente'
              ? 'Um utilizador da plataforma. O nome, o email e o telemóvel entram na assinatura.'
              : 'Até 10 destinatários internos.'}
          </div>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6, marginBottom: 6 }}>
            <select value={empresa} onChange={e => { setEmpresa(e.target.value); setDepto('all'); }} style={selStyle}>
              <option value="all">Todas as empresas</option>
              {empresas.map(x => <option key={x} value={x}>{x}</option>)}
            </select>
            <select value={depto} onChange={e => setDepto(e.target.value)} style={selStyle}>
              <option value="all">Todos os departamentos</option>
              {deptos.map(x => <option key={x} value={x}>{x}</option>)}
            </select>
          </div>
          <input
            value={q} onChange={e => setQ(e.target.value)} placeholder="Procurar nome ou email…"
            style={{ ...selStyle, marginBottom: 8 }}
          />
          <div style={{ border: '1px solid var(--border)', borderRadius: 6, overflow: 'auto', maxHeight: 'min(360px, calc(100vh - 280px))' }}>
            {loadingPessoas && (
              <div style={{ padding: 10, fontSize: 12, color: 'var(--text-muted)', display: 'flex', alignItems: 'center', gap: 8 }}>
                <ActSpinner /> A carregar destinatários internos…
              </div>
            )}
            {!loadingPessoas && filtered.length === 0 && (
              <div style={{ padding: 10, fontSize: 12, color: 'var(--text-muted)' }}>Sem resultados.</div>
            )}
            {filtered.map(p => {
              const on = lista === 'remetente' ? sender === p.email : sel.has(p.email);
              return (
                <label key={p.email} style={{
                  display: 'flex', gap: 8, alignItems: 'flex-start', padding: '7px 8px',
                  borderBottom: '1px solid var(--border)', cursor: 'pointer',
                  background: on ? 'rgba(56,89,208,0.06)' : 'transparent',
                }}>
                  <input
                    type={lista === 'remetente' ? 'radio' : 'checkbox'}
                    name={lista === 'remetente' ? 'act-email-remetente' : undefined}
                    checked={on}
                    onChange={() => (lista === 'remetente' ? setSender(p.email) : toggle(p.email))}
                    style={{ marginTop: 2 }}
                  />
                  <span style={{ minWidth: 0 }}>
                    <span style={{ display: 'block', fontSize: 12, fontWeight: 600, color: 'var(--text)' }}>{p.nome}</span>
                    <span style={{ display: 'block', fontSize: 10, color: 'var(--text-muted)' }}>{p.email} · {p.empresa} · {p.departamento}</span>
                  </span>
                </label>
              );
            })}
          </div>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 10, gap: 8 }}>
            <span style={{ fontSize: 11, color: 'var(--text-muted)', minWidth: 0 }}>
              {pessoas.find(p => p.email === sender)?.nome || 'Remetente por escolher'}
              {' · '}{sel.size}/10
            </span>
            <button
              type="button"
              className="btn btn-ai"
              style={{ height: 28, padding: '0 12px', fontSize: 12, opacity: sender && sel.size && !busy ? 1 : 0.45 }}
              disabled={!sender || !sel.size || busy}
              onClick={enviar}
            >
              {busy ? 'A enviar o teste…' : `Enviar teste (${sel.size})`}
            </button>
          </div>
          {msg && <div style={{ marginTop: 8, fontSize: 12, color: /Enviado/.test(msg) ? '#166534' : '#b91c1c' }}>{msg}</div>}
        </div>
        </div>,
        document.body
      )}
    </div>
  );
};

const ActAdsSharePicker = ({ via, campId, camp, items, userEmail, disabled, btnBase }) => {
  const [open, setOpen] = React.useState(false);
  const [pessoas, setPessoas] = React.useState([]);
  const [q, setQ] = React.useState('');
  const [sel, setSel] = React.useState(() => new Set(userEmail ? [String(userEmail).toLowerCase()] : []));
  const [busy, setBusy] = React.useState(false);
  const [loadingPessoas, setLoadingPessoas] = React.useState(false);
  const [msg, setMsg] = React.useState(null);
  const box = React.useRef(null);
  const { start, update, finish } = useActJob();
  const isWa = via === 'wa';

  React.useEffect(() => {
    if (!open) return;
    let alive = true;
    setLoadingPessoas(true);
    actSetupFetch('/api/marketing/activacao/destinatarios-teste')
      .then(d => { if (alive) setPessoas(d.pessoas || []); })
      .catch(e => { if (alive) setMsg(e.message); })
      .finally(() => { if (alive) setLoadingPessoas(false); });
    return () => { alive = false; };
  }, [open]);

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

  const filtered = pessoas.filter(p => {
    if (isWa && !p.phone) return false;
    if (!q) return true;
    const s = `${p.nome} ${p.email} ${p.phone || ''}`.toLowerCase();
    return s.includes(q.toLowerCase());
  });

  const toggle = (email) => {
    setSel(prev => {
      const next = new Set(prev);
      if (next.has(email)) next.delete(email);
      else if (next.size >= 10) return prev;
      else next.add(email);
      return next;
    });
  };

  const enviar = async () => {
    if (!sel.size || !items.length) return;
    setBusy(true); setMsg(null);
    start(
      isWa ? 'Partilhar no WhatsApp' : 'Partilhar por email',
      isWa
        ? `A enviar template de validação para ${sel.size} WhatsApp…`
        : `A enviar copy + imagem para ${sel.size} email${sel.size === 1 ? '' : 's'}…`
    );
    try {
      const r = await actSetupFetch('/api/marketing/activacao/ads/partilhar', {
        method: 'POST',
        body: JSON.stringify({
          campanha_id: campId,
          via: isWa ? 'wa' : 'email',
          emails: [...sel],
          items: items.map(p => ({
            headline: p.headline || p.claim || '',
            primary_text: p.primary_text || p.body || '',
            description: p.description || '',
            cta: actCtaPt(p),
            thumb: p.thumb || '',
          })),
        }),
      });
      const ok = isWa
        ? `Enviado para ${r.sent} destinatário${r.sent === 1 ? '' : 's'} · ${r.n_ads} anúncio${r.n_ads === 1 ? '' : 's'} (template interno)`
        : `Enviado para ${r.sent} destinatário${r.sent === 1 ? '' : 's'} · ${r.n_ads} anúncio${r.n_ads === 1 ? '' : 's'} com imagem`;
      setMsg(ok);
      update({ step: ok });
    } catch (e) {
      setMsg(e.message);
      update({ step: e.message, error: e.message });
    } finally {
      setBusy(false);
      finish();
    }
  };

  const selStyle = { fontSize: 12, padding: '5px 8px', border: '1px solid var(--border)', borderRadius: 6, background: 'var(--bg)', color: 'var(--text)', fontFamily: 'inherit', width: '100%' };

  return (
    <div ref={box} style={{ position: 'relative' }}>
      <button
        type="button"
        className="btn"
        style={{ ...btnBase, opacity: disabled || !items.length ? 0.4 : 1 }}
        disabled={disabled || !items.length}
        onClick={() => setOpen(v => !v)}
      >
        {isWa ? 'WhatsApp' : 'Email'}
      </button>
      {open && (
        <div style={{
          position: 'absolute', right: 0, bottom: 'calc(100% + 6px)', zIndex: 40,
          width: 280, background: '#fff', border: '1px solid var(--border)',
          borderRadius: 8, boxShadow: '0 12px 32px rgba(17,41,84,0.14)', padding: 12,
        }}>
          <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.08em', textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: 8 }}>
            {isWa ? 'WhatsApp · template interno' : 'Email · copy + imagem'}
          </div>
          <input
            value={q} onChange={e => setQ(e.target.value)} placeholder={isWa ? 'Procurar nome ou telemóvel…' : 'Procurar nome ou email…'}
            style={{ ...selStyle, marginBottom: 8 }}
          />
          <div style={{ maxHeight: 200, overflowY: 'auto', border: '1px solid var(--border)', borderRadius: 6 }}>
            {loadingPessoas && (
              <div style={{ padding: 10, fontSize: 12, color: 'var(--text-muted)' }}>A carregar contas do portal…</div>
            )}
            {!loadingPessoas && filtered.length === 0 && (
              <div style={{ padding: 10, fontSize: 12, color: 'var(--text-muted)' }}>
                {isWa ? 'Sem telemóvel nas contas do portal.' : 'Sem resultados.'}
              </div>
            )}
            {filtered.map(p => {
              const on = sel.has(p.email);
              return (
                <label key={p.email} style={{
                  display: 'flex', gap: 8, alignItems: 'flex-start', padding: '7px 8px',
                  borderBottom: '1px solid var(--border)', cursor: 'pointer',
                  background: on ? 'rgba(56,89,208,0.06)' : 'transparent',
                }}>
                  <input type="checkbox" checked={on} onChange={() => toggle(p.email)} style={{ marginTop: 2 }} />
                  <span style={{ minWidth: 0 }}>
                    <span style={{ display: 'block', fontSize: 12, fontWeight: 600, color: 'var(--text)' }}>{p.nome}</span>
                    <span style={{ display: 'block', fontSize: 10, color: 'var(--text-muted)' }}>
                      {isWa ? (p.phone || 'sem telemóvel') : p.email}
                    </span>
                  </span>
                </label>
              );
            })}
          </div>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 10, gap: 8 }}>
            <span style={{ fontSize: 11, color: 'var(--text-muted)' }}>{sel.size}/10</span>
            <button
              type="button"
              className="btn btn-ai"
              style={{ height: 28, padding: '0 12px', fontSize: 12, opacity: sel.size && items.length && !busy ? 1 : 0.45 }}
              disabled={!sel.size || !items.length || busy}
              onClick={enviar}
            >
              {busy ? 'A enviar…' : `Enviar (${sel.size})`}
            </button>
          </div>
          {msg && <div style={{ marginTop: 8, fontSize: 12, color: /Enviado/.test(msg) ? '#166534' : /PENDING|ainda está/i.test(msg) ? '#92400E' : '#b91c1c' }}>{msg}</div>}
        </div>
      )}
    </div>
  );
};

const ActChip = ({ children, tone }) => {
  const map = {
    warn: { color: '#92400E', bg: 'rgba(245,158,11,0.12)' },
    ok: { color: '#166534', bg: 'rgba(16,185,129,0.12)' },
    info: { color: '#3859D0', bg: 'rgba(56,89,208,0.10)' },
    mute: { color: '#4a5e7a', bg: 'rgba(107,127,163,0.10)' },
  };
  const t = map[tone] || map.mute;
  return (
    <span style={{
      fontSize: 10, fontWeight: 700, fontFamily: 'var(--font-mono)', letterSpacing: '0.03em',
      padding: '2px 7px', borderRadius: 4, color: t.color, background: t.bg, whiteSpace: 'nowrap',
    }}>{children}</span>
  );
};

const ActCanalResumo = ({ canal, onOpen, onAnalise, onDesactivar, compact }) => {
  const meta = ACT_CHANNELS.find(c => c.id === canal.canal) || { label: canal.canal, id: canal.canal };
  const pecas = canal.pecas || [];
  const peca = pecas[0] || null;
  const isEmail = canal.canal === 'email';
  const reais = pecas.filter(p => !p.virtual);
  const destSel = (canal.destinos || []).filter(d => d.selected);
  const audSel = (canal.audiencias || []).filter(a => a.selected);
  const nContactos = destSel.reduce((acc, d) => acc + (Number(d.n_contactos) || 0), 0)
    + audSel.reduce((acc, a) => acc + (Number(a.contagem) || 0), 0);
  const modoEmail = canal.setup?.modo === 'unico' || canal.setup?.modo === 'workflow'
    ? canal.setup.modo
    : (reais.length > 1 ? 'workflow' : 'unico');
  const diasPorPeca = new Map((canal.setup?.passos || []).map(p => [String(p.peca_id), Number(p.dias_depois)]));
  const faltaDias = isEmail && modoEmail === 'workflow' && reais.length > 1
    && reais.slice(1).some(p => !(diasPorPeca.get(String(p.id)) >= 1));
  const when = isEmail
    ? (reais.find(p => p.scheduled_for)?.scheduled_for || null)
    : (peca?.scheduled_for || canal.suggested_at);
  const empty = pecas.length === 0;
  const titulo = isEmail
    ? (reais.length > 1
        ? (modoEmail === 'workflow' ? `${reais.length} emails em sequência` : 'Envio único')
        : (reais.length === 1 ? '1 email' : 'Sem peça de email'))
    : (peca?.headline || peca?.subject || (peca ? (peca.content_type || 'Peça pronta') : 'À espera de Produção de Conteúdos'));
  const chips = [];
  if (isEmail) {
    chips.push({ tone: 'info', text: modoEmail === 'workflow' && reais.length > 1 ? 'Workflow' : 'Envio único' });
    if (reais.length && !destSel.length && !audSel.length) chips.push({ tone: 'warn', text: 'Escolhe audiência' });
    else if (audSel.length === 1) chips.push({ tone: 'ok', text: audSel[0].nome });
    else if (audSel.length > 1) chips.push({ tone: 'ok', text: `${audSel.length} audiências · ${actFmtN(nContactos)}` });
    else if (destSel.length) chips.push({ tone: 'ok', text: `${destSel.map(d => d.country).join('+')} · ${actFmtN(nContactos)}` });
    if (faltaDias) chips.push({ tone: 'warn', text: 'Dias por indicar' });
  } else if (canal.canal === 'meta_ads' && canal.ads) {
    const ads = canal.ads;
    const miss = (ads.checklist || []).filter(x => !x.ok);
    chips.push({ tone: 'info', text: ads.account_name || 'Conta ads' });
    chips.push(ads.daily_budget_euros
      ? { tone: 'ok', text: `${ads.daily_budget_euros} €/dia` }
      : { tone: 'warn', text: 'Budget em falta' });
    if (ads.delivery_gap) chips.push({ tone: 'warn', text: 'Conjunto incompleto' });
    else if (ads.is_active) chips.push({ tone: 'ok', text: 'ACTIVE' });
    else if (['ARCHIVED', 'DELETED'].includes(String(ads.meta_status || '').toUpperCase())) chips.push({ tone: 'warn', text: 'Arquivada no Facebook' });
    else if (ads.draft?.meta_id) chips.push({ tone: 'ok', text: 'PAUSED no Facebook' });
    if (miss.length) chips.push({ tone: 'warn', text: `${miss.length} no checklist` });
  } else if ((canal.segmentacao || []).length) {
    chips.push({ tone: 'mute', text: canal.segmentacao.map(s => s.country || s.nome).filter(Boolean).join(' · ') });
  }
  const color = ACT_CHAN_COLOR[canal.canal] || '#3859D0';
  const showAnalise = canal.canal === 'meta_ads' && canal.estado === 'publicado' && typeof onAnalise === 'function';
  return (
    <div
      style={{
        display: 'flex', alignItems: 'center', gap: 8, width: '100%',
        background: empty ? 'transparent' : 'var(--bg-elev)',
        border: '1px solid var(--border)',
        borderRadius: 10, padding: compact ? '8px 10px 8px 12px' : '10px 12px 10px 16px',
        marginBottom: 8, fontFamily: 'inherit',
        opacity: empty ? 0.78 : 1,
      }}
    >
    <button
      type="button"
      onClick={() => onOpen(canal.canal)}
      style={{
        display: 'flex', alignItems: 'center', gap: 14, flex: 1, minWidth: 0, textAlign: 'left',
        background: 'transparent', border: 0, padding: compact ? '4px 4px' : '6px 4px',
        cursor: 'pointer', fontFamily: 'inherit', color: 'inherit',
      }}
    >
      <div style={{
        width: compact ? 56 : 80, height: compact ? 56 : 80, borderRadius: 8,
        background: 'var(--bg)', overflow: 'hidden', flexShrink: 0,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        boxShadow: peca?.thumb ? 'inset 0 0 0 1px rgba(17,41,84,0.06)' : 'none',
      }}>
        {peca?.thumb
          ? <img src={peca.thumb} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
          : <span style={{ fontSize: 10, fontWeight: 700, color: color, fontFamily: 'var(--font-mono)' }}>{meta.short}</span>}
      </div>
      <div style={{ minWidth: 0, flex: 1 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
          <span style={{ width: 8, height: 8, borderRadius: 99, background: color, flexShrink: 0 }} />
          <span style={{ fontFamily: 'var(--font-display)', fontSize: 15, fontWeight: 700, color: 'var(--text)' }}>{meta.label}</span>
          {actEstadoBadge(canal.estado, canal.canal)}
        </div>
        <div style={{ fontSize: 13, color: empty ? 'var(--text-muted)' : 'var(--text)', lineHeight: 1.35 }}>{titulo}</div>
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 8, alignItems: 'center' }}>
          {chips.map((c, i) => <ActChip key={i} tone={c.tone}>{c.text}</ActChip>)}
          {when && <span style={{ fontSize: 11, color: 'var(--text-muted)' }}>{actFmtDateTime(when)}</span>}
        </div>
      </div>
      <span style={{ fontSize: 12, color: '#3859D0', fontWeight: 600, flexShrink: 0 }}>{empty ? 'Ver canal →' : 'Abrir setup →'}</span>
    </button>
    {canal.can_desactivar && onDesactivar && (
      <ActDesactivarBtn onClick={() => onDesactivar(canal.canal)} />
    )}
    {showAnalise && (
      <button
        type="button"
        onClick={() => onAnalise(canal)}
        style={{
          flexShrink: 0, border: '1px solid rgba(56,89,208,0.35)', background: 'rgba(56,89,208,0.08)',
          color: '#3859D0', fontWeight: 700, fontSize: 12, borderRadius: 8, padding: '8px 12px', cursor: 'pointer',
        }}
      >Análise</button>
    )}
    </div>
  );
};

const ActSocialGroup = ({ canais, onOpen, onDesactivar }) => {
  const first = canais[0];
  const pecas = first?.pecas || [];
  const ready = pecas.filter(p => !p.virtual && ['approved', 'ready_review', 'pending_activation'].includes(p.status)).length;
  const total = pecas.length || first?.social?.n_planned || 0;
  const thumbs = pecas.filter(p => p.thumb && !p.virtual).slice(0, 8);
  const extraThumbs = Math.max(0, pecas.filter(p => p.thumb).length - thumbs.length);
  return (
    <div style={{
      background: 'var(--bg-elev)', border: '1px solid var(--border)', borderRadius: 12,
      padding: 16, marginBottom: 18,
    }}>
      <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 12, marginBottom: 12 }}>
        <div>
          <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', letterSpacing: '0.10em', textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: 4 }}>Social orgânico</div>
          <div style={{ fontFamily: 'var(--font-display)', fontSize: 16, fontWeight: 700, color: 'var(--text)' }}>Facebook · Instagram · LinkedIn</div>
          <div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 4 }}>
            Mesmo criativo que Meta Ads · publica ou agenda neste ecrã, uma rede de cada vez
          </div>
        </div>
        {actProgress(ready, total)}
      </div>
      {thumbs.length > 0 && (
        <div style={{ display: 'flex', gap: 6, marginBottom: 14, overflowX: 'auto' }}>
          {thumbs.map((p, i) => (
            <div key={p.id || i} title={p.headline || ''} style={{
              width: 52, height: 52, borderRadius: 6, overflow: 'hidden', flexShrink: 0,
              opacity: p.virtual ? 0.45 : 1, boxShadow: 'inset 0 0 0 1px rgba(17,41,84,0.08)',
            }}>
              <img src={p.thumb} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
            </div>
          ))}
          {extraThumbs > 0 && (
            <div style={{
              width: 52, height: 52, borderRadius: 6, flexShrink: 0, background: 'var(--bg)',
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              fontSize: 11, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)',
            }}>+{extraThumbs}</div>
          )}
        </div>
      )}
      <div style={{ display: 'grid', gap: 6 }}>
        {canais.map(ch => {
          const meta = ACT_CHANNELS.find(c => c.id === ch.canal) || { label: ch.canal };
          const color = ACT_CHAN_COLOR[ch.canal] || '#3859D0';
          const handle = actPrettyHandle(ch.social, ch.canal);
          const next = (ch.pecas || []).find(p => !p.virtual && p.planned_date)?.planned_date
            || (ch.pecas || []).find(p => p.planned_date)?.planned_date
            || ch.suggested_at;
          const can = !!(ch.can_activar || ch.social?.can_activar);
          const nReady = ch.social?.n_ready ?? ready;
          const tokenMiss = (ch.social?.checklist || []).some(x => x.id === 'token' && !x.ok);
          const rowHint = tokenMiss ? 'por ligar' : `${nReady}/${total} prontos`;
          const cta = tokenMiss ? 'Ligar →' : 'Setup →';
          return (
            <div
              key={ch.canal}
              style={{
                display: 'flex', alignItems: 'center', gap: 12, width: '100%', textAlign: 'left',
                background: 'var(--bg)', border: '1px solid var(--border)', borderRadius: 8,
                padding: '10px 12px', fontFamily: 'inherit',
              }}
            >
            <button
              type="button"
              onClick={() => onOpen(ch.canal)}
              style={{
                display: 'flex', alignItems: 'center', gap: 12, flex: 1, minWidth: 0, textAlign: 'left',
                background: 'transparent', border: 0, padding: 0, cursor: 'pointer', fontFamily: 'inherit', color: 'inherit',
              }}
            >
              <span style={{
                width: 28, height: 28, borderRadius: 7, background: color + '18', color,
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                fontSize: 10, fontWeight: 800, fontFamily: 'var(--font-mono)', flexShrink: 0,
              }}>{meta.short}</span>
              <div style={{ minWidth: 0, flex: 1 }}>
                <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)' }}>{meta.label}</div>
                <div style={{ fontSize: 11, color: 'var(--text-muted)' }}>{handle}</div>
              </div>
              <div style={{ textAlign: 'right', flexShrink: 0 }}>
                <div style={{ fontSize: 11, color: 'var(--text-muted)' }}>{next ? actFmtDate(next) : '—'}</div>
                <div style={{ fontSize: 10, color: tokenMiss ? '#92400E' : (can ? '#166534' : 'var(--text-dim)'), marginTop: 2 }}>{rowHint}</div>
              </div>
              <span style={{ fontSize: 12, color: tokenMiss ? '#92400E' : '#3859D0', fontWeight: 600 }}>{cta}</span>
            </button>
              {ch.can_desactivar && onDesactivar && (
                <ActDesactivarBtn onClick={() => onDesactivar(ch.canal)} />
              )}
            </div>
          );
        })}
      </div>
    </div>
  );
};

const ActCampanhaOverview = ({ canais, onOpen, onAnalise, onDesactivar, tab = 'todos' }) => {
  const [filterCanal, setFilterCanal] = React.useState('all');
  const [filterKpi, setFilterKpi] = React.useState('all');
  const [filterMarket, setFilterMarket] = React.useState('all');
  React.useEffect(() => { setFilterCanal('all'); setFilterKpi('all'); }, [tab]);

  const inGroup = (c) => {
    if (tab === 'paid') return ACT_PAID.has(c.canal);
    if (tab === 'social') return ACT_ORGANIC.has(c.canal);
    if (tab === 'owned') return ACT_OWNED.has(c.canal);
    return true;
  };
  const group = canais.filter(inGroup);
  const paid = group.filter(c => ACT_PAID.has(c.canal));
  const organic = group.filter(c => ACT_ORGANIC.has(c.canal));
  const owned = group.filter(c => ACT_OWNED.has(c.canal));
  const other = group.filter(c => !ACT_PAID.has(c.canal) && !ACT_ORGANIC.has(c.canal) && !ACT_OWNED.has(c.canal));
  const nSetup = group.filter(c => c.estado === 'setup' || c.estado === 'agendado').length;
  const nWait = group.filter(c => c.estado === 'a_produzir' || c.estado === 'previsto').length;
  const nLive = group.filter(c => c.estado === 'publicado').length;

  const canalOpts = group.map(c => {
    const meta = ACT_CHANNELS.find(x => x.id === c.canal);
    return { value: c.canal, label: meta?.label || c.canal };
  });
  const mercados = [...new Set(group.flatMap(c => [
    ...(c.destinos || []).map(d => d.country),
    ...(c.segmentacao || []).map(s => s.country),
  ].filter(Boolean)))];

  const matchKpi = (c) => {
    if (filterKpi === 'setup') return c.estado === 'setup' || c.estado === 'agendado';
    if (filterKpi === 'wait') return c.estado === 'a_produzir' || c.estado === 'previsto';
    if (filterKpi === 'live') return c.estado === 'publicado';
    return true;
  };
  const matchCanal = (c) => filterCanal === 'all' || c.canal === filterCanal;
  const matchMarket = (c) => {
    if (filterMarket === 'all') return true;
    const countries = [
      ...(c.destinos || []).map(d => d.country),
      ...(c.segmentacao || []).map(s => s.country),
    ];
    return countries.includes(filterMarket);
  };
  const visible = (arr) => arr.filter(c => matchKpi(c) && matchCanal(c) && matchMarket(c));
  const visPaid = visible(paid);
  const visOrg = visible(organic);
  const visOwned = visible(owned);
  const visOther = visible(other);
  const filteredN = visPaid.length + visOrg.length + visOwned.length + visOther.length;
  const filtersOn = filterCanal !== 'all' || filterKpi !== 'all' || filterMarket !== 'all';

  const Section = ({ title, hint, children }) => (
    <div style={{ marginBottom: 22 }}>
      <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 10 }}>
        <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', letterSpacing: '0.10em', textTransform: 'uppercase', color: 'var(--text-muted)' }}>{title}</div>
        {hint && <div style={{ fontSize: 11, color: 'var(--text-dim)' }}>{hint}</div>}
      </div>
      {children}
    </div>
  );
  return (
    <div>
      <div style={{ display: 'flex', gap: 8, alignItems: 'center', padding: '10px 0', fontSize: 11, flexWrap: 'wrap', marginBottom: 4 }}>
        <ActDropdown
          label="Canal"
          value={filterCanal}
          onChange={setFilterCanal}
          options={[{ value: 'all', label: 'Todos os canais' }, ...canalOpts]}
        />
        {mercados.length > 0 && (
          <ActDropdown
            label="Mercado"
            value={filterMarket}
            onChange={setFilterMarket}
            options={[{ value: 'all', label: 'Todos mercados' }, ...mercados.map(m => ({ value: m, label: m }))]}
          />
        )}
        <span style={{ color: 'var(--text-dim, #7a8fa8)', fontSize: 11 }}>{filteredN} de {group.length}</span>
        {filtersOn && (
          <button type="button" onClick={() => { setFilterCanal('all'); setFilterKpi('all'); setFilterMarket('all'); }}
            style={{ background: 'transparent', border: 0, padding: '4px 2px', fontSize: 11, color: '#3859D0', cursor: 'pointer' }}>
            Limpar filtros
          </button>
        )}
      </div>
      <ActKpiCards
        filterStatus={filterKpi}
        onFilter={setFilterKpi}
        cards={[
          { key: 'all', label: 'CANAIS', value: group.length, accent: '#3859D0', activeBg: '#EFF3FF' },
          { key: 'setup', label: 'EM SETUP', value: nSetup, accent: '#0891B2', activeBg: '#E0F7FB' },
          { key: 'wait', label: 'A PRODUZIR', value: nWait, accent: '#F59E0B', activeBg: '#FEF3C7' },
          { key: 'live', label: 'PUBLICADOS', value: nLive, accent: '#166534', activeBg: '#DCFCE7' },
        ]}
      />
      {filteredN === 0 && (
        <div style={{ padding: 28, textAlign: 'center', fontSize: 12, color: '#94a3b8', background: '#f8fafc', borderRadius: 8, border: '1px dashed #cbd5e1', marginBottom: 16 }}>
          Nenhum canal corresponde aos filtros.
        </div>
      )}
      {visPaid.length > 0 && (
        <Section title="Paid media" hint="Anúncios pagos · rascunho PAUSED">
          {visPaid.map(ch => <ActCanalResumo key={ch.canal} canal={ch} onOpen={onOpen} onAnalise={onAnalise} onDesactivar={onDesactivar} />)}
        </Section>
      )}
      {visOrg.length > 0 && (
        <Section title="Social orgânico" hint="Feed da marca · Instagram Story 9:16 15s 1080p">
          <ActSocialGroup canais={visOrg} onOpen={onOpen} onDesactivar={onDesactivar} />
        </Section>
      )}
      {visOwned.length > 0 && (
        <Section title="Canais owned" hint="Email · WhatsApp · site">
          {visOwned.map(ch => <ActCanalResumo key={ch.canal} canal={ch} onOpen={onOpen} onAnalise={onAnalise} onDesactivar={onDesactivar} compact />)}
        </Section>
      )}
      {visOther.map(ch => <ActCanalResumo key={ch.canal} canal={ch} onOpen={onOpen} onAnalise={onAnalise} onDesactivar={onDesactivar} />)}
    </div>
  );
};

const ActEmailCard = ({ canal, onActivar, onDesactivar, onReload, busy, campId, user, camp, onGuardado }) => {
  const pecas = (canal.pecas || []).filter(p => !p.virtual);
  const ordenadas = [...pecas].sort((a, b) => Number(a.source_comm_plan_item_index || 0) - Number(b.source_comm_plan_item_index || 0));
  const envio = canal.setup || canal.envio || {};
  const varios = ordenadas.length > 1;
  const [modo, setModo] = React.useState(varios ? (envio.modo === 'unico' ? 'unico' : 'workflow') : 'unico');
  const [pecaId, setPecaId] = React.useState(envio.peca_id || ordenadas[0]?.id || '');
  const [dias, setDias] = React.useState(() => {
    const map = {};
    (envio.passos || []).forEach(p => { if (p?.peca_id) map[p.peca_id] = p.dias_depois; });
    return map;
  });
  const peca = ordenadas.find(p => p.id === pecaId) || ordenadas[0] || null;
  const destinos = Array.isArray(canal.destinos) ? canal.destinos : [];
  const remetente = canal.remetente || {};
  const pecaAgendada = pecas.find(p => p.scheduled_for)?.scheduled_for;
  const quandoServidor = pecaAgendada || envio.primeiro_em || envio.inicio || '';
  const quandoVisto = React.useRef(quandoServidor);
  const [whenLocal, setWhenLocal] = React.useState(() => actToLocalInput(quandoServidor || canal.suggested_at));
  const catalogo = Array.isArray(canal.audiencias) ? canal.audiencias : [];
  const [sel, setSel] = React.useState(() => new Set(destinos.filter(d => d.selected).map(d => d.country)));
  const [audSel, setAudSel] = React.useState(() => new Set(catalogo.filter(a => a.selected).map(a => a.id)));
  const [audQ, setAudQ] = React.useState('');
  const [audFonte, setAudFonte] = React.useState('prospeccao');
  const [audOpen, setAudOpen] = React.useState(false);
  const [previewAberto, setPreviewAberto] = React.useState(false);
  const [draftSel, setDraftSel] = React.useState(() => new Set());
  const [draftAud, setDraftAud] = React.useState(() => new Set());
  const [savingDest, setSavingDest] = React.useState(false);
  const [destErr, setDestErr] = React.useState(null);
  const [wfTick, setWfTick] = React.useState(0);
  const wfAccao = React.useRef(null);
  const onWfAccaoTick = React.useCallback(() => setWfTick(t => t + 1), []);

  React.useEffect(() => {
    if (quandoServidor === quandoVisto.current) return;
    quandoVisto.current = quandoServidor;
    if (!quandoServidor) return;
    setWhenLocal(actToLocalInput(quandoServidor));
  }, [quandoServidor]);
  const destKey = (canal.destinos || []).filter(d => d.selected).map(d => d.country).join('|');
  const audKey = (canal.audiencias || []).filter(a => a.selected).map(a => a.id).join('|');
  const savingRef = React.useRef(false);
  const audHold = React.useRef(false);
  React.useEffect(() => {
    if (savingRef.current) return;
    setSel(new Set(destKey ? destKey.split('|') : []));
  }, [destKey]);
  React.useEffect(() => {
    if (savingRef.current) return;
    if (audHold.current) { audHold.current = false; return; }
    setAudSel(new Set(audKey ? audKey.split('|') : []));
  }, [audKey]);

  const whenDate = whenLocal ? new Date(whenLocal) : null;
  const whenPast = !!(whenDate && !isNaN(whenDate.getTime()) && whenDate.getTime() <= Date.now());
  const selectedDest = destinos.filter(d => sel.has(d.country));
  const selectedAud = catalogo.filter(a => audSel.has(a.id));
  const hasDest = sel.size > 0 || audSel.size > 0;
  const nContactos = selectedDest.reduce((acc, d) => acc + (Number(d.n_contactos) || 0), 0)
    + selectedAud.reduce((acc, a) => acc + (Number(a.contagem) || 0), 0);
  const canArmar = !!(canal.can_activar && hasDest && pecas.length);
  const passosEnvio = ordenadas.map((p, i) => ({
    peca_id: p.id,
    dias_depois: i === 0 ? 0 : Math.max(0, Number(dias[p.id]) || 0),
  }));
  const diasOk = modo !== 'workflow' || passosEnvio.slice(1).every(p => p.dias_depois >= 1);
  const canAgendar = canArmar && whenLocal && !whenPast && diasOk;
  let acumulado = 0;
  const linhaTempo = ordenadas.map((p, i) => {
    const espera = i === 0 ? 0 : Math.max(0, Number(dias[p.id]) || 0);
    if (i > 0) acumulado += espera;
    return {
      p,
      i,
      acumulado,
      quando: actSomaDias(whenLocal, acumulado),
      diasEmFalta: i > 0 && !(Number(dias[p.id]) >= 1),
    };
  });
  const armar = (sendNow) => {
    if (!confirmar(sendNow)) return;
    onActivar('email', {
      send_now: !!sendNow,
      scheduled_for: sendNow ? null : (whenDate ? whenDate.toISOString() : null),
      modo: varios ? modo : 'unico',
      peca_id: (varios ? modo : 'unico') === 'unico' ? (peca?.id || null) : null,
      passos: (varios ? modo : 'unico') === 'workflow' ? passosEnvio : undefined,
    });
  };

  const { start, update, finish } = useActJob();
  const persistDestinos = async (nextSet, nextAud) => {
    setSavingDest(true); setDestErr(null);
    savingRef.current = true;
    const n = nextSet.size + nextAud.size;
    start('Audiências de email', n ? `A guardar ${n} audiência${n === 1 ? '' : 's'}…` : 'A limpar a selecção…');
    try {
      const resp = await actSetupFetch('/api/marketing/activacao/email/setup', {
        method: 'POST',
        body: JSON.stringify({
          campanha_id: campId,
          destinos: [...nextSet],
          audiencias: [...nextAud],
        }),
      });
      const gravadas = Array.isArray(resp.audiencias) ? resp.audiencias.map(String) : [];
      const pedidas = [...nextAud].map(String);
      const incompleta = pedidas.length > 0 && gravadas.length < pedidas.length;
      if (incompleta) setDestErr('A selecção não ficou gravada. Volta a escolher e confirma.');
      audHold.current = true;
      setSel(new Set(Array.isArray(resp.destinos) ? resp.destinos.map(String) : []));
      setAudSel(new Set(gravadas));
      if (onGuardado) onGuardado(resp);
      update({ step: incompleta ? 'A selecção não ficou gravada.' : (n ? `${n} audiência${n === 1 ? '' : 's'} guardada${n === 1 ? '' : 's'}.` : 'Selecção limpa.') });
      return !incompleta;
    } catch (e) {
      setDestErr(e.message);
      update({ step: e.message, error: e.message });
      setSel(new Set(destinos.filter(d => d.selected).map(d => d.country)));
      setAudSel(new Set(catalogo.filter(a => a.selected).map(a => a.id)));
      return false;
    } finally { savingRef.current = false; setSavingDest(false); finish(); }
  };

  const guardarSetup = async () => {
    setSavingDest(true); setDestErr(null);
    savingRef.current = true;
    start('Setup de email', 'A guardar a sequência, a data e as audiências…');
    try {
      const resp = await actSetupFetch('/api/marketing/activacao/email/setup', {
        method: 'POST',
        body: JSON.stringify({
          campanha_id: campId,
          destinos: [...sel],
          audiencias: [...audSel],
          modo: varios ? modo : 'unico',
          peca_id: peca?.id || null,
          passos: passosEnvio,
          primeiro_em: whenDate && !Number.isNaN(whenDate.getTime()) ? whenDate.toISOString() : null,
        }),
      });
      const primeiroIso = whenDate && !Number.isNaN(whenDate.getTime()) ? whenDate.toISOString() : null;
      const gravadas = Array.isArray(resp.audiencias) ? resp.audiencias.map(String) : [];
      if ([...audSel].length && gravadas.length < [...audSel].length) {
        setDestErr('A selecção não ficou gravada. Volta a escolher e confirma.');
      }
      setSel(new Set(Array.isArray(resp.destinos) ? resp.destinos.map(String) : []));
      setAudSel(new Set(gravadas));
      if (onGuardado) onGuardado({ ...resp, primeiro_em: primeiroIso });
      update({ step: 'Setup guardado.' });
    } catch (e) {
      setDestErr(e.message);
      update({ step: e.message, error: e.message });
    } finally { savingRef.current = false; setSavingDest(false); finish(); }
  };

  const toggleDest = (country) => {
    const next = new Set(sel);
    if (next.has(country)) next.delete(country);
    else next.add(country);
    setSel(next);
    persistDestinos(next, audSel);
  };

  const toggleAud = (id) => {
    const alvo = catalogo.find(a => a.id === id);
    if (alvo && alvo.compativel === false) return;
    const next = new Set(audSel);
    if (next.has(id)) next.delete(id);
    else next.add(id);
    setAudSel(next);
    persistDestinos(sel, next);
  };

  const confirmar = (sendNow) => {
    const linhas = [
      ...selectedDest.map(d =>
        `• ${actPaisNome(d.country)} — ${actFmtN(d.n_contactos)} contactos (${actFmtN(d.n_entidades)} empresas)`
      ),
      ...selectedAud.map(a => `• ${a.nome} — ${actFmtN(a.contagem)} contactos`),
    ].join('\n');
    const quando = sendNow
      ? 'nos próximos segundos'
      : (whenDate ? actFmtDateTime(whenDate.toISOString()) : 'na data escolhida');
    const sequencia = modo === 'workflow' && varios
      ? `\n\n${linhaTempo.map(passo => {
          const assunto = passo.p.subject || passo.p.headline || 'Email';
          const hora = passo.diasEmFalta ? 'dias por indicar' : (passo.quando ? actFmtDateTime(passo.quando.toISOString()) : 'data por escolher');
          return `${passo.i + 1}. ${assunto} — ${hora}`;
        }).join('\n')}\n\nCada email seguinte sai os dias indicados depois do anterior, à mesma hora. Quem responder à caixa do remetente sai da sequência.`
      : `\n\nEnvio único ${quando}. Não há emails seguintes.`;
    return window.confirm(
      `Armar envio Email para:\n\n${linhas || '— nenhum mercado —'}${sequencia}\n\nO teste interno continua a ser «Enviar teste».`
    );
  };

  const pos = Math.max(0, ordenadas.findIndex(p => p.id === peca?.id));
  const assunto = peca?.subject || peca?.headline || peca?.item_title || 'Email';
  const preheader = peca?.preheader || '';
  const tituloPeca = peca?.item_title || `Email ${pos + 1}`;
  const audEscolhiveis = catalogo.filter(a => a.compativel !== false);
  const audVisiveis = audEscolhiveis.filter(a => {
    const q = audQ.trim().toLowerCase();
    return !q || String(a.nome || '').toLowerCase().includes(q);
  });
  const audProsp = audVisiveis.filter(a => a.origem === 'prospeccao');
  const audCrm = audVisiveis.filter(a => a.origem !== 'prospeccao');
  const audProspTodas = audEscolhiveis.filter(a => a.origem === 'prospeccao');
  const audCrmTodas = audEscolhiveis.filter(a => a.origem !== 'prospeccao');
  const qAud = audQ.trim().toLowerCase();
  const mercadosVisiveis = destinos.filter(d => !qAud || actPaisNome(d.country).toLowerCase().includes(qAud));
  const fontesAud = [
    destinos.length ? {
      id: 'mercados', titulo: 'Mercados', n: destinos.length,
      marcadas: destinos.filter(d => draftSel.has(d.country)).length,
    } : null,
    {
      id: 'prospeccao', titulo: 'Prospeção', n: audProspTodas.length,
      marcadas: audProspTodas.filter(a => draftAud.has(a.id)).length,
    },
    {
      id: 'crm', titulo: 'CRM', n: audCrmTodas.length,
      marcadas: audCrmTodas.filter(a => draftAud.has(a.id)).length,
    },
  ].filter(Boolean);
  const nomesAud = [
    ...selectedDest.map(d => actPaisNome(d.country)),
    ...selectedAud.map(a => a.nome),
  ];
  const abrirAud = () => {
    setDraftSel(new Set(sel));
    setDraftAud(new Set(audSel));
    setAudQ('');
    setDestErr(null);
    const escolheuCrm = audEscolhiveis.some(a => a.origem !== 'prospeccao' && audSel.has(a.id));
    const escolheuProsp = audEscolhiveis.some(a => a.origem === 'prospeccao' && audSel.has(a.id));
    if (sel.size && !escolheuProsp && !escolheuCrm && destinos.length) setAudFonte('mercados');
    else if (escolheuCrm && !escolheuProsp) setAudFonte('crm');
    else if (audEscolhiveis.some(a => a.origem === 'prospeccao')) setAudFonte('prospeccao');
    else setAudFonte('crm');
    setAudOpen(true);
  };
  const conjuntosIguais = (a, b) => {
    if (a.size !== b.size) return false;
    for (const item of a) if (!b.has(item)) return false;
    return true;
  };
  const guardarAud = async () => {
    if (savingDest) return;
    if (conjuntosIguais(draftSel, sel) && conjuntosIguais(draftAud, audSel)) {
      setAudOpen(false);
      return;
    }
    setSel(new Set(draftSel));
    setAudSel(new Set(draftAud));
    const ok = await persistDestinos(draftSel, draftAud);
    if (ok) setAudOpen(false);
  };
  const lab = { fontSize: 11, fontWeight: 700, letterSpacing: '0.06em', color: '#64748b', textTransform: 'uppercase', margin: '0 0 6px' };
  const mod = { background: '#fff', border: '1px solid #ECEFF5', borderRadius: 10, padding: 12 };
  const irPeca = (delta) => {
    const n = ordenadas[pos + delta];
    if (n) setPecaId(n.id);
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column' }}>
      <window.CtdBreadcrumb
        trail={[
          { label: camp?.titulo || 'Campanha', onClick: () => actSetCamp(campId) },
          { label: 'Email' },
          { label: tituloPeca, badge: { text: 'Email', color: '#8b5cf6' } },
        ]}
        onBack={() => actSetCamp(campId)}
        currentPos={pos + 1}
        totalItems={ordenadas.length}
        onPrev={pos > 0 ? () => irPeca(-1) : null}
        onNext={pos < ordenadas.length - 1 ? () => irPeca(1) : null}
      />

      <div style={{ display: 'grid', gridTemplateColumns: 'minmax(280px, 360px) minmax(0, 1fr)', gap: 16, alignItems: 'start' }}>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 12, position: 'sticky', top: 0, alignSelf: 'start', zIndex: 2 }}>
        <div style={mod}>
          <div style={lab}>Sequência</div>
          {varios && (
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6, marginBottom: 12 }}>
              {[{ id: 'unico', label: 'Envio único' }, { id: 'workflow', label: 'Workflow' }].map(op => {
                const on = modo === op.id;
                return (
                  <button key={op.id} type="button" onClick={() => setModo(op.id)} disabled={!!busy} style={{
                    height: 32, borderRadius: 6, fontSize: 12, fontWeight: 650, cursor: 'pointer',
                    border: `1px solid ${on ? '#3859D0' : '#ECEFF5'}`,
                    background: on ? '#3859D0' : '#fff',
                    color: on ? '#fff' : '#112954',
                  }}>{op.label}</button>
                );
              })}
            </div>
          )}
          <div style={{ fontSize: 12, color: '#64748b', lineHeight: 1.45, marginBottom: 12 }}>
            {modo === 'workflow' && varios
              ? 'Triggers, data do primeiro email e activação estão no workflow.'
              : 'Sai o email seleccionado, em dia útil, entre as 9h e as 21h.'}
          </div>
          {!(modo === 'workflow' && varios) && (
            <>
          <div style={lab}>Data e hora</div>
          <input
            type="datetime-local"
            value={whenLocal}
            onChange={e => setWhenLocal(e.target.value)}
            disabled={!canal.can_activar || !!busy}
            style={{
              fontSize: 12, fontFamily: 'inherit', color: '#112954',
              border: `1px solid ${whenPast ? '#EF4444' : '#ECEFF5'}`,
              borderRadius: 6, padding: '6px 8px', background: '#fff', width: '100%', marginBottom: 4,
            }}
          />
          <div style={{ fontSize: 11, color: whenPast ? '#EF4444' : '#64748b', marginBottom: 12, lineHeight: 1.4 }}>
            {whenPast ? 'Já passou — altera a hora ou envia já. ' : ''}
            Dias úteis, das 9h às 21h, hora de Lisboa. Fim de semana, feriado ou fora desse horário passa ao período útil seguinte.
          </div>
            </>
          )}
          {!(modo === 'workflow' && varios) && <div style={{ display: 'flex', flexDirection: 'column', marginBottom: 8 }}>
            {linhaTempo.map((passo, idx) => {
              const activo = peca?.id === passo.p.id;
              const assuntoPasso = passo.p.subject || passo.p.headline || `Email ${idx + 1}`;
              const hora = passo.diasEmFalta
                ? 'Indica os dias'
                : (passo.quando ? actFmtDateTime(passo.quando.toISOString()) : 'Escolhe a data');
              return (
                <div key={passo.p.id}>
                  {idx > 0 && modo === 'workflow' && (
                    <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '8px 0 8px 28px' }}>
                      <input
                        type="number"
                        min="1"
                        value={dias[passo.p.id] ?? ''}
                        placeholder="dias"
                        aria-label={`Dias até ao email ${idx + 1}`}
                        onChange={e => setDias(prev => ({ ...prev, [passo.p.id]: e.target.value }))}
                        disabled={!!busy}
                        style={{ width: 64, fontSize: 12, padding: '4px 6px', borderRadius: 6, border: `1px solid ${passo.diasEmFalta ? '#D97706' : '#ECEFF5'}`, background: '#fff', color: '#112954' }}
                      />
                      <span style={{ fontSize: 11, color: passo.diasEmFalta ? '#92400E' : '#64748b' }}>dias depois do anterior</span>
                    </div>
                  )}
                  <button type="button" onClick={() => setPecaId(passo.p.id)} style={{
                    display: 'grid', gridTemplateColumns: '28px minmax(0, 1fr)', gap: 8, width: '100%',
                    textAlign: 'left', cursor: 'pointer', borderRadius: 8, padding: '10px 10px',
                    border: `1px solid ${activo ? '#3859D0' : '#ECEFF5'}`,
                    background: activo ? 'rgba(56,89,208,0.06)' : '#fff',
                  }}>
                    <span style={{ fontFamily: 'var(--font-mono)', fontSize: 12, fontWeight: 700, color: '#3859D0' }}>{idx + 1}</span>
                    <span style={{ minWidth: 0 }}>
                      <span style={{ display: 'block', fontSize: 12, fontWeight: 650, color: '#112954', lineHeight: 1.35 }}>{assuntoPasso}</span>
                      <span style={{ display: 'block', fontSize: 11, color: '#64748b', marginTop: 4 }}>
                        {modo === 'workflow' && varios ? hora : (activo ? 'Este é o email do envio' : 'Clica para escolher')}
                      </span>
                    </span>
                  </button>
                </div>
              );
            })}
          </div>}

          {envio.stats && (
            <div style={{ fontSize: 12, color: '#112954', lineHeight: 1.5, padding: '8px 10px', borderRadius: 6, background: '#F8FAFC', border: '1px solid #ECEFF5', margin: '8px 0 12px' }}>
              <div style={lab}>{envio.stats.modo === 'workflow' ? 'Workflow em curso' : 'Envio único em curso'}</div>
              <div>{envio.stats.activos} a seguir · {envio.stats.saidas} saíram por resposta · {envio.stats.total} no total</div>
              {envio.stats.proximo_em && <div style={{ color: '#64748b' }}>Próximo passo {actFmtDateTime(envio.stats.proximo_em)}</div>}
            </div>
          )}
          {!envio.stats && envio.mail_estado === 'pausado' && (
            <div style={{ fontSize: 12, color: '#92400E', lineHeight: 1.5, padding: '8px 10px', borderRadius: 6, background: '#FFFBEB', border: '1px solid #FDE68A', margin: '8px 0 12px' }}>
              Envio desactivado. Os emails seguintes não saem. Os contactos mantêm-se na lista do SDR.
            </div>
          )}

        </div>

        <div style={mod}>
          <div style={lab}>Audiências</div>
          <div style={{ fontSize: 12, color: '#64748b', lineHeight: 1.45, marginBottom: 8 }}>
            Podes escolher mais do que uma. No CRM entra email válido sem bloqueio de RGPD. Na prospeção entram emails empresariais com opt-in explícito.
          </div>
          {nomesAud.length > 0 && (
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: 8 }}>
              {nomesAud.map(nome => (
                <span key={nome} style={{ fontSize: 11, fontWeight: 650, color: '#112954', background: 'rgba(56,89,208,0.08)', borderRadius: 999, padding: '3px 8px' }}>{nome}</span>
              ))}
            </div>
          )}
          <button type="button" onClick={abrirAud} disabled={!!busy} style={{
            height: 34, width: '100%', borderRadius: 6, border: '1px solid #ECEFF5', background: '#fff',
            color: '#112954', fontSize: 12, fontWeight: 650, cursor: 'pointer',
          }}>
            {hasDest ? 'Alterar audiências' : 'Escolher audiências'}
          </button>
          <div style={{ fontSize: 11, color: hasDest ? '#64748b' : '#92400E', marginTop: 6 }}>
            {savingDest ? 'A guardar…'
              : destErr ? destErr
              : hasDest ? `${sel.size + audSel.size} seleccionada${sel.size + audSel.size === 1 ? '' : 's'} · ${actFmtN(nContactos)} contactos`
              : 'Escolhe pelo menos uma audiência para armar o envio.'}
          </div>
        </div>

        {!(modo === 'workflow' && varios) && (
        <div style={mod}>
          <div style={lab}>Remetente</div>
          <div style={{ fontSize: 13, fontWeight: 600, color: '#112954' }}>{remetente.nome || 'Digidelta Marketing'}</div>
          <div style={{ fontSize: 11, color: '#64748b' }}>{remetente.email || '—'}</div>
        </div>
        )}

        <div style={mod}>
          {modo === 'workflow' && varios ? (
            <>
              <button
                type="button"
                style={{
                  height: 36, width: '100%', marginTop: 8, fontSize: 13, fontWeight: 650, borderRadius: 6,
                  border: '1px solid #ECEFF5', background: '#fff', color: '#112954',
                  cursor: savingDest || busy || wfAccao.current?.busy || wfAccao.current?.readOnly ? 'default' : 'pointer',
                  opacity: wfAccao.current?.readOnly ? 0.55 : 1,
                }}
                disabled={!!savingDest || !!busy || !!wfAccao.current?.busy || !!wfAccao.current?.readOnly}
                onClick={async () => {
                  await guardarSetup();
                  if (wfAccao.current?.gravar) await wfAccao.current.gravar();
                  setWfTick(t => t + 1);
                }}
              >
                {savingDest || wfAccao.current?.busy ? 'A guardar…' : 'Guardar'}
              </button>
              {wfAccao.current?.estado === 'activo' ? (
                <button
                  type="button"
                  style={{
                    height: 36, width: '100%', marginTop: 8, fontSize: 13, fontWeight: 650, borderRadius: 6,
                    border: '1px solid #ECEFF5', background: '#fff', color: '#112954',
                    cursor: busy || wfAccao.current?.busy ? 'default' : 'pointer',
                  }}
                  disabled={!!busy || !!wfAccao.current?.busy}
                  onClick={async () => {
                    if (!window.confirm('Desactivar o workflow? Os emails seguintes param. Os contactos ficam na lista do SDR.')) return;
                    if (wfAccao.current?.pausar) await wfAccao.current.pausar();
                    if (onReload) await onReload();
                    setWfTick(t => t + 1);
                  }}
                >
                  {wfAccao.current?.busy ? 'A desactivar…' : 'Desactivar'}
                </button>
              ) : (
                <button
                  type="button"
                  style={{
                    height: 36, width: '100%', marginTop: 8, fontSize: 13, fontWeight: 650, borderRadius: 6, border: 0,
                    background: '#1F8A52', color: '#fff',
                    cursor: busy || wfAccao.current?.busy ? 'default' : 'pointer',
                  }}
                  disabled={!!busy || !!wfAccao.current?.busy}
                  onClick={async () => {
                    if (wfAccao.current?.activar) await wfAccao.current.activar();
                    setWfTick(t => t + 1);
                  }}
                >
                  {wfAccao.current?.busy ? 'A activar…' : 'Activar'}
                </button>
              )}
            </>
          ) : (
            <>
              <button
                type="button"
                style={{
                  height: 36, width: '100%', marginTop: 8, fontSize: 13, fontWeight: 650, borderRadius: 6,
                  border: '1px solid #ECEFF5', background: '#fff', color: '#112954', cursor: savingDest || busy ? 'default' : 'pointer',
                }}
                disabled={!!savingDest || !!busy}
                onClick={guardarSetup}
              >
                {savingDest ? 'A guardar…' : 'Guardar'}
              </button>
              <ActEmailTeste
                campId={campId}
                pecaId={peca?.id}
                userEmail={user?.email}
                disabled={!pecas.length || !!busy}
              />
              <button
                type="button"
                className="btn"
                style={{ height: 34, width: '100%', marginTop: 8, fontSize: 12, opacity: canArmar ? 1 : 0.45 }}
                disabled={!canArmar || busy}
                title={hasDest ? 'Arma o envio para já' : 'Escolhe pelo menos uma audiência'}
                onClick={() => armar(true)}
              >
                {busy === 'email' ? 'A armar o envio…' : 'Enviar à lista'}
              </button>
              <button
                type="button"
                style={{
                  height: 36, width: '100%', marginTop: 8, fontSize: 13, fontWeight: 650, borderRadius: 6, border: 0,
                  background: '#1F8A52', color: '#fff', cursor: canAgendar && !busy ? 'pointer' : 'default',
                  opacity: canAgendar ? 1 : 0.45,
                }}
                disabled={!canAgendar || busy}
                title={!hasDest ? 'Escolhe pelo menos uma audiência' : !diasOk ? 'Indica os dias de cada email seguinte' : (whenPast ? 'A hora já passou' : 'Agendar para esta data e hora')}
                onClick={() => armar(false)}
              >
                {busy === 'email' ? 'A agendar o envio…' : 'Agendar'}
              </button>
              {canal.can_desactivar && onDesactivar && (
                <div style={{ marginTop: 8 }}>
                  <ActDesactivarBtn
                    full
                    onClick={() => onDesactivar('email')}
                    disabled={!!busy}
                    label="Desactivar envio"
                    title="Pára o envio único. Os emails seguintes não saem."
                  />
                </div>
              )}
            </>
          )}
        </div>
        </div>

        <div style={{ display: 'flex', flexDirection: 'column', gap: 12, minWidth: 0 }}>
          {modo === 'workflow' && varios && window.ActWorkflowEditor && (
            <window.ActWorkflowEditor
              embutido
              campId={campId}
              nome={camp?.titulo}
              primeiroEm={whenLocal}
              onPrimeiroEm={setWhenLocal}
              audiencias={nomesAud}
              nContactos={nContactos}
              onAbrirAudiencias={abrirAud}
              acaoRef={wfAccao}
              onAccaoTick={onWfAccaoTick}
              onArmar={(passos, quandoIso) => onActivar('email', {
                send_now: false,
                scheduled_for: quandoIso || (whenDate ? whenDate.toISOString() : null),
                modo: 'workflow',
                peca_id: null,
                passos,
              })}
              onEscolherPeca={id => {
                setPecaId(id);
                setPreviewAberto(true);
              }}
            />
          )}
          {!(modo === 'workflow' && varios) && (
          <div id="act-email-preview" style={{ background: '#fff', border: '1px solid #ECEFF5', borderRadius: 10 }}>
            {!peca ? (
              <div style={{ fontSize: 13, color: '#64748b', padding: 16 }}>À espera de Produção.</div>
            ) : (
              <>
                <div style={{ padding: '14px 16px', borderBottom: '1px solid #ECEFF5' }}>
                  <div style={lab}>Assunto</div>
                  <div style={{ fontSize: 14, fontWeight: 650, color: '#112954', lineHeight: 1.35 }}>{assunto}</div>
                  <div style={{ ...lab, marginTop: 12 }}>Preview text</div>
                  <div style={{ fontSize: 13, color: '#475569', lineHeight: 1.45 }}>{preheader || '—'}</div>
                </div>
                <window.CtdBrevoPreview key={peca.id} html={peca.html || ''} empty="Sem HTML compilado nesta peça." />
              </>
            )}
          </div>
          )}
        </div>
      </div>
      {previewAberto && peca && ReactDOM.createPortal(
        <div
          onClick={() => setPreviewAberto(false)}
          style={{ position: 'fixed', inset: 0, background: 'rgba(17,41,84,0.45)', zIndex: 90, display: 'flex', justifyContent: 'center', alignItems: 'flex-start', padding: 24, overflow: 'hidden' }}
        >
          <div
            onClick={e => e.stopPropagation()}
            style={{ width: 'min(760px, 100%)', maxHeight: 'calc(100vh - 48px)', display: 'flex', flexDirection: 'column', background: '#fff', borderRadius: 12, boxShadow: '0 16px 48px rgba(17,41,84,0.18)', overflow: 'hidden' }}
          >
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, padding: '14px 16px', borderBottom: '1px solid #ECEFF5', flexShrink: 0 }}>
              <div style={{ minWidth: 0 }}>
                <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.06em', color: '#64748b', textTransform: 'uppercase' }}>Pré-visualização</div>
                <div style={{ fontSize: 14, fontWeight: 650, color: '#112954', lineHeight: 1.35, marginTop: 4 }}>{peca.subject || peca.headline || assunto || 'Email'}</div>
                <div style={{ fontSize: 12, color: '#475569', lineHeight: 1.4, marginTop: 4 }}>{peca.preheader || preheader || '—'}</div>
              </div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0 }}>
                <ActEmailTeste
                  compacto
                  camada={120}
                  campId={campId}
                  pecaId={peca.id}
                  userEmail={user?.email}
                  disabled={!!busy}
                />
                <button type="button" onClick={() => setPreviewAberto(false)} style={{ height: 32, padding: '0 12px', borderRadius: 6, border: '1px solid #ECEFF5', background: '#fff', color: '#112954', fontSize: 12, cursor: 'pointer' }}>Fechar</button>
              </div>
            </div>
            <div style={{ flex: '1 1 auto', minHeight: 0, overflow: 'auto', maxHeight: 'calc(100vh - 160px)' }}>
              <window.CtdBrevoPreview key={peca.id} html={peca.html || ''} empty="Sem HTML compilado nesta peça." />
            </div>
          </div>
        </div>,
        document.body
      )}
      {audOpen && ReactDOM.createPortal(
        <div
          onClick={guardarAud}
          style={{ position: 'fixed', inset: 0, background: 'rgba(17,41,84,0.45)', zIndex: 240, overflowY: 'auto', padding: 24 }}
        >
          <div
            onClick={e => e.stopPropagation()}
            style={{ width: 'min(560px, 100%)', margin: '24px auto', background: '#fff', borderRadius: 12, display: 'flex', flexDirection: 'column', boxShadow: '0 16px 48px rgba(17,41,84,0.18)' }}
          >
            <div style={{ padding: '16px 16px 8px' }}>
              <div style={{ fontSize: 16, fontWeight: 700, color: '#112954' }}>Escolher audiências</div>
              <div style={{ fontSize: 12, color: '#64748b', lineHeight: 1.45, marginTop: 4 }}>
                Mais do que uma. Prospeção: emails empresariais com opt-in explícito. CRM: email válido e sem bloqueio de RGPD.
              </div>
              <div style={{ display: 'flex', gap: 8, marginTop: 12 }}>
                {fontesAud.map(fonte => {
                  const activa = audFonte === fonte.id;
                  return (
                    <button
                      key={fonte.id}
                      type="button"
                      onClick={() => setAudFonte(fonte.id)}
                      style={{
                        flex: 1, textAlign: 'left', padding: '10px 12px', borderRadius: 8, cursor: 'pointer',
                        border: `1px solid ${activa ? '#3859D0' : '#ECEFF5'}`,
                        background: activa ? 'rgba(56,89,208,0.06)' : '#fff',
                        fontFamily: 'inherit',
                      }}
                    >
                      <span style={{ display: 'block', fontSize: 11, fontWeight: 700, letterSpacing: '0.06em', color: '#64748b', textTransform: 'uppercase' }}>{fonte.titulo}</span>
                      <span style={{ display: 'block', fontSize: 13, fontWeight: 650, color: '#112954', marginTop: 3 }}>{fonte.n} audiência{fonte.n === 1 ? '' : 's'}</span>
                      <span style={{ display: 'block', fontSize: 11, color: fonte.marcadas ? '#3859D0' : '#94A3B8', marginTop: 2 }}>{fonte.marcadas} nesta selecção</span>
                    </button>
                  );
                })}
              </div>
              <input
                value={audQ}
                onChange={e => setAudQ(e.target.value)}
                placeholder={audFonte === 'crm' ? 'Procurar no CRM' : audFonte === 'mercados' ? 'Procurar mercado' : 'Procurar na prospeção'}
                style={{ width: '100%', marginTop: 12, fontSize: 13, padding: '8px 10px', borderRadius: 6, border: '1px solid #ECEFF5', color: '#112954' }}
              />
            </div>
            <div style={{ padding: '4px 16px 12px', maxHeight: 280, overflowY: 'auto' }}>
              {audFonte === 'mercados' && (
                mercadosVisiveis.length === 0
                  ? <div style={{ fontSize: 12, color: '#94A3B8', padding: '8px 0' }}>Nenhum mercado com esta pesquisa.</div>
                  : mercadosVisiveis.map(d => {
                    const on = draftSel.has(d.country);
                    return (
                      <label key={d.country} style={{
                        display: 'flex', gap: 8, alignItems: 'flex-start', padding: '8px 10px',
                        border: `1px solid ${on ? '#3859D0' : '#ECEFF5'}`, borderRadius: 6, marginBottom: 6, cursor: 'pointer',
                        background: on ? 'rgba(56,89,208,0.06)' : '#fff',
                      }}>
                        <input type="checkbox" checked={on} onChange={() => setDraftSel(prev => {
                          const next = new Set(prev);
                          if (next.has(d.country)) next.delete(d.country); else next.add(d.country);
                          return next;
                        })} style={{ marginTop: 3 }} />
                        <span>
                          <span style={{ display: 'block', fontSize: 12, fontWeight: 650, color: '#112954' }}>{actPaisNome(d.country)}</span>
                          <span style={{ display: 'block', fontSize: 11, color: '#94A3B8' }}>{actFmtN(d.n_contactos)} contactos</span>
                        </span>
                      </label>
                    );
                  })
              )}
              {audFonte !== 'mercados' && (() => {
                const items = audFonte === 'crm' ? audCrm : audProsp;
                const outra = audFonte === 'crm' ? audProsp : audCrm;
                const nomeOutra = audFonte === 'crm' ? 'Prospeção' : 'CRM';
                if (!items.length) {
                  return (
                    <div style={{ fontSize: 12, color: '#94A3B8', padding: '8px 0' }}>
                      Nenhuma audiência nesta base{qAud ? ' com esta pesquisa' : ''}.
                      {qAud && outra.length > 0 && (
                        <button type="button" onClick={() => setAudFonte(audFonte === 'crm' ? 'prospeccao' : 'crm')} style={{ display: 'block', marginTop: 8, padding: 0, border: 0, background: 'transparent', color: '#3859D0', fontSize: 12, fontWeight: 650, cursor: 'pointer', fontFamily: 'inherit' }}>
                          Ver {outra.length} em {nomeOutra}
                        </button>
                      )}
                    </div>
                  );
                }
                return items.map(a => {
                  const on = draftAud.has(a.id);
                  return (
                    <label key={a.id} style={{
                      display: 'flex', gap: 8, alignItems: 'flex-start', padding: '8px 10px',
                      border: `1px solid ${on ? '#3859D0' : '#ECEFF5'}`, borderRadius: 6, marginBottom: 6, cursor: 'pointer',
                      background: on ? 'rgba(56,89,208,0.06)' : '#fff',
                    }}>
                      <input type="checkbox" checked={on} onChange={() => setDraftAud(prev => {
                        const next = new Set(prev);
                        if (next.has(a.id)) next.delete(a.id); else next.add(a.id);
                        return next;
                      })} style={{ marginTop: 3 }} />
                      <span style={{ minWidth: 0 }}>
                        <span style={{ display: 'block', fontSize: 12, fontWeight: 650, color: '#112954', lineHeight: 1.35 }}>{a.nome}</span>
                        <span style={{ display: 'block', fontSize: 11, color: '#94A3B8' }}>{actFmtN(a.contagem)} contactos</span>
                      </span>
                    </label>
                  );
                });
              })()}
            </div>
            {destErr && <div style={{ fontSize: 12, color: '#B91C1C', lineHeight: 1.4, padding: '0 16px 8px' }}>{destErr}</div>}
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, padding: 12, borderTop: '1px solid #ECEFF5' }}>
              <span style={{ fontSize: 12, color: '#64748b' }}>{draftSel.size + draftAud.size} seleccionada{draftSel.size + draftAud.size === 1 ? '' : 's'}</span>
              <div style={{ display: 'flex', gap: 8 }}>
                <button type="button" onClick={() => setAudOpen(false)} disabled={savingDest} style={{ height: 34, padding: '0 12px', borderRadius: 6, border: '1px solid #ECEFF5', background: '#fff', color: '#112954', fontSize: 12, cursor: 'pointer' }}>Cancelar</button>
                <button type="button" onClick={guardarAud} disabled={savingDest} style={{ height: 34, padding: '0 14px', borderRadius: 6, border: 0, background: '#1F8A52', color: '#fff', fontSize: 12, fontWeight: 650, cursor: savingDest ? 'default' : 'pointer' }}>{savingDest ? 'A guardar…' : 'Guardar'}</button>
              </div>
            </div>
          </div>
        </div>,
        document.body
      )}
    </div>
  );
};

const ACT_ADS_TABS = [
  { id: 'campanha', label: 'Campanha' },
  { id: 'orcamento', label: 'Orçamento' },
  { id: 'publicos', label: 'Públicos' },
  { id: 'form', label: 'Instant Form' },
];
const ACT_SOCIAL_BADGE = {
  facebook: { label: 'FACEBOOK', bg: '#1877F218', fg: '#1877F2' },
  instagram: { label: 'INSTAGRAM', bg: '#E1306C18', fg: '#E1306C' },
  linkedin: { label: 'LINKEDIN', bg: '#0A66C218', fg: '#0A66C2' },
};

const ACT_OVERVIEW_TABS = [
  { id: 'todos', label: 'Canais' },
  { id: 'paid', label: 'Paid media' },
  { id: 'social', label: 'Social' },
  { id: 'owned', label: 'Owned' },
];

const ActKpiCards = ({ cards, filterStatus, onFilter }) => (
  <div style={{ display: 'grid', gridTemplateColumns: cards.length > 4 ? 'repeat(auto-fit, minmax(140px, 1fr))' : `repeat(${Math.max(cards.length, 1)}, 1fr)`, gap: 12, marginBottom: 20 }}>
    {cards.map(card => {
      const isActive = filterStatus === card.key;
      return (
        <div key={card.key}
          onClick={() => onFilter && onFilter(isActive && card.key !== 'all' ? 'all' : card.key)}
          style={{
            background: isActive ? card.activeBg : 'var(--bg-surface, #fff)',
            border: '1px solid ' + (isActive ? card.accent + '50' : 'var(--border, #E3E6EB)'),
            borderRadius: 8, padding: '14px 16px',
            cursor: onFilter ? 'pointer' : 'default',
          }}>
          <div style={{ fontSize: 9, fontWeight: 700, letterSpacing: '0.10em', color: isActive ? card.accent : 'var(--text-muted)', fontFamily: 'var(--font-mono, monospace)', marginBottom: 6 }}>{card.label}</div>
          <div style={{ fontSize: 28, fontWeight: 700, color: isActive ? card.accent : 'var(--text, #112954)', lineHeight: 1 }}>{card.value}</div>
          <div style={{ marginTop: 10, height: 3, background: isActive ? card.accent : 'var(--border, #E3E6EB)', borderRadius: 2, opacity: isActive ? 1 : 0.35 }} />
        </div>
      );
    })}
  </div>
);

const ACT_STALE_INTEREST = /pequena empresa|small business|serigrafia/i;
const ACT_STALE_INTEREST_IDS = new Set(['6002884511422', '6003713694960']);

function actTargetStale(ads) {
  const names = [];
  const ids = [];
  for (const a of ads?.adsets || []) {
    (a.interesses || []).forEach(n => names.push(n));
    (a.interest_ids || []).forEach(id => ids.push(String(id)));
  }
  (ads?.targeting_interests || []).forEach(n => names.push(n));
  const staleName = names.some(n => ACT_STALE_INTEREST.test(String(n)));
  const staleId = ids.some(id => ACT_STALE_INTEREST_IDS.has(id));
  return staleName || staleId;
}

const ACT_FORM_Q = {
  FULL_NAME: 'Nome completo',
  WORK_EMAIL: 'Email profissional',
  EMAIL: 'Email',
  PHONE: 'Telefone',
  COMPANY_NAME: 'Empresa',
  JOB_TITLE: 'Cargo',
};

function actListNames(arr) {
  if (!Array.isArray(arr) || !arr.length) return [];
  return arr.map(x => (typeof x === 'string' ? x : (x?.name || x?.label || x?.id || ''))).filter(Boolean);
}

function actToChips(arr, ids) {
  if (!Array.isArray(arr) || !arr.length) return [];
  return arr.map((x, i) => {
    if (typeof x === 'string' || typeof x === 'number') {
      const name = String(x);
      const id = ids && ids[i] ? String(ids[i]) : (/^\d{5,}$/.test(name) ? name : null);
      return { key: id || name, name, id };
    }
    const name = x.name || x.label || String(x.id || '');
    const id = x.id ? String(x.id) : (ids && ids[i] ? String(ids[i]) : null);
    return { key: id || name, name, id };
  }).filter(x => x.name);
}

function ActRemovableChips({ items, onRemove, disabled, empty }) {
  if (!items || !items.length) {
    return <div style={{ fontSize: 13, color: 'var(--text-muted)' }}>{empty || '—'}</div>;
  }
  return (
    <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
      {items.map((it, i) => (
        <span key={it.key || it.name || i} style={{
          display: 'inline-flex', alignItems: 'center', gap: 6,
          padding: '4px 8px', borderRadius: 6, fontSize: 12,
          background: 'var(--bg, #f8fafc)', border: '1px solid var(--border-2, #E3E6EB)',
        }}>
          {it.name}
          {!disabled && onRemove ? (
            <button type="button" onClick={() => onRemove(i)} disabled={disabled} style={{
              border: 0, background: 'transparent', cursor: 'pointer',
              color: 'var(--text-muted)', fontSize: 14, lineHeight: 1, padding: 0,
            }} aria-label={'Retirar ' + it.name}>×</button>
          ) : null}
        </span>
      ))}
    </div>
  );
}

function actFormQLabel(q) {
  if (!q) return '—';
  if (q.label) return q.label;
  return ACT_FORM_Q[q.type] || q.key || q.type || 'Pergunta';
}

function actPecaTitle(p, i) {
  return (p && (p.item_title || p.claim || p.headline || p.content_type)) || `AD ${String((i || 0) + 1).padStart(2, '0')}`;
}

const ACT_META_CTA_PT = {
  GET_OFFER: 'Obter oferta',
  GET_QUOTE: 'Pedir orçamento',
  LEARN_MORE: 'Saber mais',
  CONTACT_US: 'Contactar-nos',
  SIGN_UP: 'Subscrever',
  SHOP_NOW: 'Comprar',
  DOWNLOAD: 'Descarregar',
  APPLY_NOW: 'Candidatar-me agora',
  ORDER_NOW: 'Reservar agora',
};

function actCtaPt(peca) {
  const mapped = ACT_META_CTA_PT[peca?.cta_meta];
  if (mapped) return mapped;
  const label = String(peca?.cta || '').trim();
  if (label && !/^[A-Z][A-Z0-9_]+$/.test(label)) return label;
  return label || 'Saber mais';
}

function actFeedL1(text) {
  const raw = String(text || '').trim();
  if (!raw) return '';
  const l1 = raw.split(/\n\n/)[0].replace(/\s+/g, ' ').trim();
  if (l1.length <= 140) return l1;
  return `${l1.slice(0, 137).replace(/\s+\S*$/, '')}…`;
}

function actFeedBlocks(text) {
  const raw = String(text || '').trim();
  if (!raw) return { hook: '', hookShown: '', hasMore: false, full: '' };
  const blocks = raw.split(/\n\n+/).map(b => b.trim()).filter(Boolean);
  const hook = (blocks[0] || raw).split(/\n+/)[0] || '';
  const hasMore = hook.length > 125 || blocks.length > 1 || raw.length > 160;
  const hookShown = hasMore && hook.length > 125
    ? `${hook.slice(0, 125).replace(/\s+\S*$/, '')}…`
    : hook;
  return { hook, hookShown, hasMore, full: blocks.join('\n\n') || raw };
}

function actExtHref(href) {
  const s = String(href || '').trim();
  return /^https?:\/\//i.test(s) ? s : null;
}

function actFbHref(url, pageId) {
  return actExtHref(url) || (pageId ? `https://www.facebook.com/${pageId}` : null);
}

function actIgHref(url, handle) {
  const direct = actExtHref(url);
  if (direct) return direct;
  const h = String(handle || '').replace(/^@/, '');
  if (h && !/^\d{10,}$/.test(h)) return `https://www.instagram.com/${h}/`;
  return null;
}

function ActExtLink({ href, children }) {
  if (!href) return null;
  return (
    <a href={href} target="_blank" rel="noopener noreferrer" style={{ color: '#3859D0', wordBreak: 'break-all', textDecoration: 'none' }}>
      {children || href.replace(/^https?:\/\//, '')}
    </a>
  );
}

function actAdsSharePayload({ camp, pecas, startIndex = 0 }) {
  const title = camp?.titulo || 'Campanha Meta Ads';
  const lines = pecas.map((p, i) => {
    const n = String(startIndex + i + 1).padStart(2, '0');
    const h = p.headline || p.claim || actPecaTitle(p, i);
    const l1 = actFeedL1(p.primary_text || p.body || '');
    return [
      `AD ${n} — ${h}`,
      l1 || null,
      `CTA: ${actCtaPt(p)}`,
      p.thumb || null,
    ].filter(Boolean).join('\n');
  });
  return {
    subject: pecas.length > 1
      ? `Validação Meta Ads — ${title} · ${pecas.length} anúncios`
      : `Validação Meta Ads — ${title} · ${pecas[0] ? (pecas[0].headline || pecas[0].claim || 'anúncio') : 'anúncio'}`,
    body: [`Validação Meta Ads — ${title}`, '', ...lines].join('\n\n'),
  };
}

const ActAdsPipeStep = ({ n, label, detail, state, onClick }) => {
  const Icon = window.Icon;
  const pal = state === 'done'
    ? { bg: '#DCFCE7', fg: '#166534', ring: '#86EFAC' }
    : state === 'current'
      ? { bg: '#EFF3FF', fg: '#3859D0', ring: '#3859D0' }
      : { bg: '#F8FAFC', fg: '#94A3B8', ring: '#E2E8F0' };
  const body = (
    <div style={{ display: 'flex', alignItems: 'flex-start', gap: 10, textAlign: 'left', width: '100%' }}>
      <div style={{
        width: 22, height: 22, borderRadius: '50%', flexShrink: 0,
        background: pal.bg, color: pal.fg, border: `1.5px solid ${pal.ring}`,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
      }}>
        {state === 'done'
          ? (Icon ? <Icon name="check" size={11} /> : '✓')
          : <span style={{ fontSize: 10, fontWeight: 700, fontFamily: 'var(--font-mono, monospace)' }}>{n}</span>}
      </div>
      <div style={{ minWidth: 0, paddingTop: 1 }}>
        <div style={{ fontSize: 12, fontWeight: 600, color: pal.fg, lineHeight: 1.2 }}>{label}</div>
        {detail && <div style={{ fontSize: 11, color: 'var(--text-muted, #4a5e7a)', marginTop: 2, lineHeight: 1.35 }}>{detail}</div>}
      </div>
    </div>
  );
  if (!onClick) return body;
  return (
    <button type="button" onClick={onClick} style={{
      display: 'block', width: '100%', border: 0, background: 'none', padding: 0, cursor: 'pointer',
    }}>{body}</button>
  );
};

const ActAdsFieldCard = ({ title, extra, children }) => (
  <div style={{ background: '#fff', border: '1px solid var(--border-1, #ECEFF5)', borderRadius: 8, overflow: 'hidden' }}>
    {(title || extra) && (
      <div style={{
        padding: '10px 14px', borderBottom: '1px solid var(--border-1, #ECEFF5)',
        display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8,
      }}>
        <span style={{ fontSize: 9, fontFamily: 'var(--font-mono)', letterSpacing: '0.10em', textTransform: 'uppercase', color: 'var(--text-muted)' }}>{title}</span>
        {extra}
      </div>
    )}
    <div style={{ padding: 14 }}>{children}</div>
  </div>
);

const ActAdsKv = ({ k, v, extra }) => (
  <div style={{ marginBottom: 12 }}>
    <div style={{ fontSize: 9, fontFamily: 'var(--font-mono)', letterSpacing: '0.10em', textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: 4 }}>{k}</div>
    <div style={{ fontSize: 13, color: 'var(--text)', lineHeight: 1.45, wordBreak: 'break-word' }}>{v || '—'}</div>
    {extra}
  </div>
);

const ActAdsCard = ({ canal, campId, camp, onReload, onDesactivar, tab, setTab }) => {
  const ads = canal.ads || {};
  const pecas = canal.pecas || [];
  const checklist = Array.isArray(ads.checklist) ? ads.checklist : [];
  const [budget, setBudget] = React.useState(ads.daily_budget_euros != null ? String(ads.daily_budget_euros) : '');
  const [saving, setSaving] = React.useState(false);
  const [saveMsg, setSaveMsg] = React.useState(null);
  const [sel, setSel] = React.useState(0);
  const [feedOpen, setFeedOpen] = React.useState(false);
  const [tabLocal, setTabLocal] = React.useState('campanha');
  const tabId = tab || tabLocal;
  const goTab = setTab || setTabLocal;
  const [shareScope, setShareScope] = React.useState('one');
  const [draftBusy, setDraftBusy] = React.useState(false);
  const [draftMsg, setDraftMsg] = React.useState(null);
  const [actBusy, setActBusy] = React.useState(false);
  const [confirmGo, setConfirmGo] = React.useState(false);
  const [repairBusy, setRepairBusy] = React.useState(false);
  const [saveBusy, setSaveBusy] = React.useState(false);
  const { start, update, finish, pollAds } = useActJob();

  React.useEffect(() => {
    if (ads.rascunho_apagado && !ads.draft?.meta_id) setDraftMsg(ads.rascunho_apagado);
  }, [ads.rascunho_apagado, ads.draft?.meta_id]);

  React.useEffect(() => {
    const fromOrc = ads.orcamento?.daily_eur;
    const next = fromOrc != null ? fromOrc : ads.daily_budget_euros;
    setBudget(next != null ? String(next) : '');
  }, [ads.orcamento?.daily_eur, ads.daily_budget_euros]);

  const persistBudget = async (raw, { silent } = {}) => {
    const trimmed = String(raw ?? '').trim();
    const n = trimmed === '' ? null : Number(trimmed.replace(',', '.'));
    if (trimmed !== '' && (!Number.isFinite(n) || n < 0)) {
      setSaveMsg('Budget inválido');
      return null;
    }
    setSaving(true); setSaveMsg(null);
    if (!silent) start('Guardar budget', 'A guardar o budget diário…');
    else update({ step: 'A guardar o budget diário…' });
    try {
      await actSetupFetch('/api/marketing/activacao/ads/setup', {
        method: 'POST',
        body: JSON.stringify({ campanha_id: campId, canal: 'meta_ads', daily_budget_euros: n }),
      });
      if (!silent) setSaveMsg(n == null ? 'Budget limpo' : `Guardado ${n} €/dia`);
      if (onReload && !silent) {
        update({ step: 'A actualizar o ecrã…' });
        await onReload();
      }
      return n;
    } catch (e) {
      setSaveMsg(e.message);
      if (!silent) update({ step: e.message, error: e.message });
      return null;
    } finally {
      setSaving(false);
      if (!silent) finish();
    }
  };

  const orc = ads.orcamento || null;
  const budgetNum = orc?.daily_eur != null ? Number(orc.daily_eur) : Number(String(budget).replace(',', '.'));
  const budgetOk = Number.isFinite(budgetNum) && budgetNum > 0;
  const canDraft = !!(ads.can_rascunho || (budgetOk && ads.account_id && ads.page_id && ads.api_pronta && pecas.some(p => p.primary_text)));
  const already = ads.draft || null;
  const deliveryGap = !!ads.delivery_gap;
  const isActive = !deliveryGap && !!(ads.is_active || String(already?.status || '').toUpperCase() === 'ACTIVE' || String(ads.meta_status || '').toUpperCase() === 'ACTIVE');
  const archivedOnFb = ['ARCHIVED', 'DELETED'].includes(String(ads.meta_status || '').toUpperCase());
  const canActivar = !isActive && !deliveryGap && !!(ads.can_activar || canDraft);
  const canReparar = !isActive && !!(ads.can_reparar || (already?.meta_id && Number(already?.ads_created || 0) > 0));
  const canEdit = !!(ads.can_edit && already?.meta_id && !isActive);
  const planJanela = ads.planeamento_janela || {};
  const firstAd = (ads.adsets || [])[0] || {};
  const [editBid, setEditBid] = React.useState('LOWEST_COST_WITHOUT_CAP');
  const [editDaily, setEditDaily] = React.useState('');
  const [editAgeMin, setEditAgeMin] = React.useState('');
  const [editAgeMax, setEditAgeMax] = React.useState('');
  const [editEnd, setEditEnd] = React.useState('');
  const [editInterests, setEditInterests] = React.useState([]);
  React.useEffect(() => {
    setEditBid(ads.bid_strategy || 'LOWEST_COST_WITHOUT_CAP');
    setEditDaily(ads.daily_budget_euros != null ? String(ads.daily_budget_euros) : '');
    setEditAgeMin(firstAd.age_min != null ? String(firstAd.age_min) : '');
    setEditAgeMax(firstAd.age_max != null ? String(firstAd.age_max) : '');
    setEditEnd(actIsoDate(planJanela.end_effective || planJanela.end || ads.janela_end));
    setEditInterests(firstAd.interesses_items?.length
      ? actToChips(firstAd.interesses_items)
      : actToChips(firstAd.interesses, firstAd.interest_ids));
  }, [
    ads.live_fetched_at,
    ads.bid_strategy,
    ads.daily_budget_euros,
    firstAd.age_min,
    firstAd.age_max,
    (firstAd.interest_ids || []).join('|'),
    (firstAd.interesses || []).join('|'),
    planJanela.end,
    planJanela.end_effective,
    ads.janela_end,
  ]);
  const busyAds = draftBusy || actBusy || repairBusy || saving || saveBusy;

  const criarRascunho = async () => {
    if (!budgetOk) { setDraftMsg('Define o budget diário primeiro.'); return; }
    setDraftBusy(true); setDraftMsg(null);
    start('Criar rascunho PAUSED', 'A preparar o rascunho no Ads Manager…');
    const stopPoll = pollAds(campId);
    try {
      await persistBudget(String(budgetNum), { silent: true });
      update({ step: 'A criar campanha, Ad Set, Instant Form e anúncios…' });
      const out = await actFetchEnvelope('/api/marketing/activacao/ads/rascunho', {
        campanha_id: campId, daily_budget_euros: budgetNum,
      });
      const n = out?.summary?.ads_created_on_meta ?? 0;
      const pend = out?.summary?.ads_pending_image ?? 0;
      const fw = out?.summary?.form_warning;
      setDraftMsg(fw
        ? `Rascunho PAUSED criado · ${n} ads · Instant Form em falta: ${fw}`
        : `Rascunho PAUSED criado · ${n} ads no Meta${pend ? ` · ${pend} sem imagem` : ''}`);
      update({ step: 'A actualizar o ecrã…' });
      if (onReload) await onReload();
    } catch (e) {
      setDraftMsg(e.message);
      update({ step: e.message, error: e.message });
    } finally { stopPoll(); setDraftBusy(false); finish(); }
  };

  const activarAgora = async () => {
    if (!budgetOk) { setDraftMsg('Define o budget diário primeiro.'); return; }
    setConfirmGo(false);
    setActBusy(true); setDraftMsg(null);
    start('Enviar agora', already?.meta_id
      ? 'A activar a campanha no Ads Manager…'
      : 'A criar o rascunho e a activar no Ads Manager…');
    const stopPoll = pollAds(campId);
    try {
      await persistBudget(String(budgetNum), { silent: true });
      if (!already?.meta_id) {
        update({ step: 'A criar campanha, Ad Set, Instant Form e anúncios…' });
        await actFetchEnvelope('/api/marketing/activacao/ads/rascunho', {
          campanha_id: campId, daily_budget_euros: budgetNum,
        });
      }
      const activar = () => actFetchEnvelope('/api/marketing/activacao/ads/activar', { campanha_id: campId });
      update({ step: 'A ler o estado no Facebook…' });
      let out;
      try {
        out = await activar();
      } catch (e) {
        if (e.code !== 'campanha_arquivada') throw e;
        update({ step: 'A campanha no Facebook está arquivada. A criar uma nova, uma só vez…' });
        const draft = await actFetchEnvelope('/api/marketing/activacao/ads/rascunho', {
          campanha_id: campId, daily_budget_euros: budgetNum,
        });
        const created = draft?.summary?.ads_created_on_meta ?? 0;
        if (!created) throw new Error('A campanha nova ficou sem anúncios. Não a passei a ACTIVE.');
        update({ step: 'A passar a campanha nova a ACTIVE no Facebook…' });
        out = await activar();
      }
      const nAds = out?.summary?.ads_activated ?? 0;
      const nSets = out?.summary?.adsets_activated ?? 0;
      setDraftMsg(out?.already ? 'Já está ACTIVE no Facebook.' : `ACTIVE no Facebook · ${nSets} ad set(s) · ${nAds} ad(s)`);
      update({ step: 'A actualizar o ecrã…' });
      if (onReload) await onReload();
    } catch (e) {
      setDraftMsg(e.message);
      update({ step: e.message, error: e.message });
      if (onReload) await onReload();
    } finally { stopPoll(); setActBusy(false); finish(); }
  };

  const repararAgora = async () => {
    if (!already?.meta_id) { setDraftMsg('Não há rascunho para reparar.'); return; }
    setRepairBusy(true); setDraftMsg(null);
    start('Reparar no Meta', 'A alinhar o Ad Set com o segmento do Target…');
    const stopPoll = pollAds(campId);
    try {
      update({ step: 'A actualizar públicos do Ad Set e Instant Form — sem nova campanha…' });
      const out = await actSetupFetch('/api/marketing/activacao/ads/reparar', {
        method: 'POST',
        body: JSON.stringify({ campanha_id: campId }),
      });
      const nSets = out?.summary?.adsets_updated ?? 0;
      const ints = (out?.summary?.targeting_interests || []).join(' · ');
      const loc = (out?.summary?.locales || []).join(',');
      setDraftMsg(`Ad Set actualizado · ${nSets} set · ${ints || 'interesses do Target'} · locale ${loc || 'PT'} · form ${out?.summary?.lead_gen_form_id || '—'}`);
      update({ step: 'A actualizar o ecrã…' });
      if (onReload) await onReload();
    } catch (e) {
      setDraftMsg(e.message);
      update({ step: e.message, error: e.message });
    } finally { stopPoll(); setRepairBusy(false); finish(); }
  };

  const guardarNaMeta = async () => {
    if (!canEdit) { setDraftMsg('Só se edita em PAUSED, com rascunho na Meta.'); return; }
    setSaveBusy(true); setDraftMsg(null);
    start('Guardar na Meta', 'A escrever janela e campos no Ads Manager (PAUSED)…');
    try {
      update({ step: 'A gravar data de fim, públicos do Target e campos editados…' });
      const out = await actSetupFetch('/api/marketing/activacao/ads/atualizar', {
        method: 'POST',
        body: JSON.stringify({
          campanha_id: campId,
          end_date: editEnd || null,
          daily_budget_euros: editDaily === '' ? null : Number(String(editDaily).replace(',', '.')),
          bid_strategy: editBid || null,
          age_min: editAgeMin === '' ? null : Number(editAgeMin),
          age_max: editAgeMax === '' ? null : Number(editAgeMax),
          interesses: editInterests,
        }),
      });
      const j = out?.janela || {};
      setDraftMsg(`Guardado na Meta · ${out.adsets_updated || 0} Ad Set · ${j.start || '—'} → ${j.end || '—'}${j.start_clamped ? ' · início actualizado para hoje' : ''}`);
      update({ step: 'A reler o Ads Manager…' });
      if (onReload) await onReload();
    } catch (e) {
      setDraftMsg(e.message);
      update({ step: e.message, error: e.message });
    } finally { setSaveBusy(false); finish(); }
  };

  const peca = pecas[sel] || pecas[0] || null;
  React.useEffect(() => { setFeedOpen(false); }, [sel, peca?.id]);
  const nOk = checklist.filter(x => x.ok && !x.warn).length;
  const nMiss = checklist.filter(x => !x.ok).length;
  const nWarn = checklist.filter(x => x.ok && x.warn).length;
  const obj = ads.objective || {};
  const ctaLabel = actCtaPt(peca);
  const primary = peca?.primary_text || peca?.body || '';
  const feed = actFeedBlocks(primary);
  const fbHref = actFbHref(ads.page_url, ads.page_id);
  const igHref = actIgHref(ads.instagram_url, ads.instagram_handle);
  const igLabel = ads.instagram_handle
    ? (String(ads.instagram_handle).startsWith('@') ? ads.instagram_handle : `@${ads.instagram_handle}`)
    : null;
  const form = ads.lead_form || already?.lead_form || {};
  const formId = ads.lead_form_id || already?.lead_form_id || form.id || null;
  const formQs = Array.isArray(form.questions) ? form.questions : [];
  const placements = ads.placements
    || (ads.adsets && ads.adsets[0] && ads.adsets[0].placements)
    || already?.placements
    || { publisher_platforms: [], facebook_positions: [], instagram_positions: [] };
  const segmento = ads.segmento_canonico || (ads.adsets && ads.adsets[0] && ads.adsets[0].segmento_canonico) || null;
  const locales = (ads.locales && ads.locales.length)
    ? ads.locales
    : ((ads.adsets && ads.adsets[0] && ads.adsets[0].locales) || already?.locales || []);
  const pageOk = !!ads.page_id && !/^615\d{10,}$/.test(String(ads.page_id || ''));
  const contaOk = !!ads.account_id;
  const objOk = !!obj.meta;
  const pubOk = (ads.adsets || []).length > 0;
  const formOk = !!formId && formQs.length > 0;
  const metaOk = !!already?.meta_id;
  const pipeState = (ok) => (ok ? 'done' : 'wait');
  const janelaStart = ads.janela_start;
  const janelaEnd = ads.janela_end;
  const janela = janelaStart
    ? `${actFmtDate(janelaStart)}${janelaEnd ? ' → ' + actFmtDate(janelaEnd) : ''}`
    : (already?.meta_id ? 'Sem datas na Meta' : 'Ainda sem rascunho na Meta');
  const janelaNota = (() => {
    const planStart = planJanela.start;
    const planEnd = planJanela.end;
    const bits = [];
    if (planStart || planEnd) bits.push(`TAB Planeamento: ${planStart ? actFmtDate(planStart) : '—'} → ${planEnd ? actFmtDate(planEnd) : '—'}.`);
    if (planJanela.start_meta_locked) bits.push('A Meta não altera o start_time deste rascunho (já publicado).');
    else if (planJanela.start_clamped && planStart) bits.push(`Início do Planeamento ${actFmtDate(planStart)} já passou — rascunhos novos usam hoje.`);
    return bits.join(' ');
  })();
  const liveOk = ads.live === true;
  const liveHint = liveOk
    ? `Ads Manager · lido ${ads.live_fetched_at ? actFmtDateTime(ads.live_fetched_at) : 'agora'}`
    : (ads.live_error ? `Meta indisponível: ${ads.live_error}` : 'Ainda sem objectos na Meta');
  const pecaLabel = actPecaTitle(peca, sel);
  const goPrev = pecas.length > 1 && sel > 0 ? () => setSel(sel - 1) : null;
  const goNext = pecas.length > 1 && sel < pecas.length - 1 ? () => setSel(sel + 1) : null;
  const btnBase = { height: 34, width: '100%', padding: '0 12px', fontSize: 12, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', textDecoration: 'none', boxSizing: 'border-box' };
  const chkFail = checklist.filter(x => !x.ok && x.id !== 'skill');
  const shareList = shareScope === 'all' ? pecas : (peca ? [peca] : []);
  const shareUser = actGetUser();
  const inputSt = {
    fontSize: 12, fontFamily: 'inherit', color: 'var(--text)',
    border: '1px solid var(--border-2, #E3E6EB)', borderRadius: 6, padding: '8px 10px',
    background: 'var(--bg, #f8fafc)', width: '100%',
  };

  const centerCampanha = (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
      <ActAdsFieldCard title="Conta e página" extra={<span style={{ fontSize: 11, color: 'var(--text-muted)' }}>{liveHint}</span>}>
        <ActAdsKv k="Janela" v={janela} extra={
          <div style={{ marginTop: 8 }}>
            {canEdit ? (
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
                <div>
                  <div style={{ fontSize: 9, fontFamily: 'var(--font-mono)', letterSpacing: '0.10em', textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: 4 }}>Início</div>
                  <div style={{ ...inputSt, background: 'transparent' }}>{actFmtDate(janelaStart)}</div>
                </div>
                <div>
                  <div style={{ fontSize: 9, fontFamily: 'var(--font-mono)', letterSpacing: '0.10em', textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: 4 }}>Fim</div>
                  <input type="date" value={editEnd} min={actIsoDate(janelaStart)} onChange={e => setEditEnd(e.target.value)} disabled={busyAds} style={inputSt} />
                </div>
              </div>
            ) : null}
            {janelaNota ? (
              <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 6, lineHeight: 1.4 }}>{janelaNota}</div>
            ) : null}
          </div>
        } />
        <ActAdsKv k="Conta ads" v={ads.account_name || 'Em falta'} extra={ads.account_id ? <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 2, fontFamily: 'var(--font-mono)' }}>{ads.account_id}</div> : null} />
        <ActAdsKv
          k="Página Facebook"
          v={ads.page_name || ads.page_id || 'Em falta no catálogo'}
          extra={
            <div style={{ fontSize: 11, marginTop: 4, lineHeight: 1.5 }}>
              {ads.page_id ? <div style={{ fontFamily: 'var(--font-mono)', color: 'var(--text-muted)' }}>{ads.page_id}</div> : null}
              {fbHref ? <ActExtLink href={fbHref}>{ads.page_url || fbHref}</ActExtLink> : null}
            </div>
          }
        />
        <ActAdsKv
          k="Instagram"
          v={ads.instagram_name || igLabel || ads.instagram_user_id || 'Em falta no catálogo'}
          extra={
            <div style={{ fontSize: 11, marginTop: 4, lineHeight: 1.5 }}>
              {igLabel ? <div style={{ color: 'var(--text-muted)' }}>{igLabel}</div> : null}
              {ads.instagram_user_id ? <div style={{ fontFamily: 'var(--font-mono)', color: 'var(--text-muted)' }}>{ads.instagram_user_id}</div> : null}
              {igHref ? <ActExtLink href={igHref}>{igHref}</ActExtLink> : null}
            </div>
          }
        />
      </ActAdsFieldCard>
      <ActAdsFieldCard title="Objectivo e bid">
        <ActAdsKv k="Objectivo" v={obj.meta || '—'} extra={<div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 2 }}>{[obj.otimizacao, obj.destino].filter(Boolean).join(' · ') || (liveOk ? '' : 'Ainda sem campanha na Meta')}{!liveOk && obj.meta ? ' · ainda sem campanha na Meta' : ''}</div>} />
        {canEdit ? (
          <div style={{ marginBottom: 12 }}>
            <div style={{ fontSize: 9, fontFamily: 'var(--font-mono)', letterSpacing: '0.10em', textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: 4 }}>Bid</div>
            <select value={editBid} onChange={e => setEditBid(e.target.value)} disabled={busyAds} style={inputSt}>
              <option value="LOWEST_COST_WITHOUT_CAP">LOWEST_COST_WITHOUT_CAP</option>
              <option value="LOWEST_COST_WITH_BID_CAP">LOWEST_COST_WITH_BID_CAP</option>
              <option value="COST_CAP">COST_CAP</option>
            </select>
          </div>
        ) : (
          <ActAdsKv k="Bid" v={ads.bid_strategy || '—'} />
        )}
      </ActAdsFieldCard>
    </div>
  );

  const centerOrcamento = (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
      <ActAdsFieldCard title="Orçamento · Meta Ads" extra={<span style={{ fontSize: 11, color: 'var(--text-muted)' }}>{liveHint}</span>}>
        {liveOk && orc ? (
          <>
            {canEdit ? (
              <div style={{ marginBottom: 12 }}>
                <div style={{ fontSize: 9, fontFamily: 'var(--font-mono)', letterSpacing: '0.10em', textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: 4 }}>Budget diário (€)</div>
                <input type="number" min="1" step="1" value={editDaily} onChange={e => setEditDaily(e.target.value)} disabled={busyAds} style={inputSt} />
              </div>
            ) : (
              <ActAdsKv k="Budget diário" v={orc.daily_eur != null ? `${orc.daily_eur} €/dia` : '—'} />
            )}
            <ActAdsKv k="Janela na Meta" v={janela} />
            {(orc.rows || []).map((r, i) => (
              <ActAdsKv key={r.nome || r.country || i} k={r.nome || `Ad Set ${r.country}`} v={r.daily_eur != null ? `${r.daily_eur} €/dia` : '—'} extra={r.total_eur ? <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 2 }}>Lifetime €{r.total_eur}</div> : null} />
            ))}
            <div style={{ fontSize: 11, color: 'var(--text-muted)', lineHeight: 1.45, marginTop: 4 }}>
              Fonte: Ads Manager (Ad Set, ABO). O que vês é o daily que a Meta tem agora — não o Orçamento do portal.
            </div>
          </>
        ) : orc ? (
          <>
            <ActAdsKv k="Budget diário no plano" v={orc.daily_eur != null ? `${orc.daily_eur} €/dia` : '—'} extra={orc.total_eur ? <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 2 }}>€{orc.total_eur} / {orc.days} dias</div> : null} />
            <div style={{ fontSize: 11, color: 'var(--text-muted)', lineHeight: 1.45, marginTop: 4 }}>
              Ainda não está no Ads Manager. O rascunho PAUSED escreve este valor.
            </div>
          </>
        ) : (
          <div style={{ fontSize: 13, color: 'var(--text-muted)', lineHeight: 1.45 }}>
            {ads.live_error || 'Ainda sem Ad Set na Meta. Cria o rascunho PAUSED para este ecrã ler o budget do Ads Manager.'}
          </div>
        )}
      </ActAdsFieldCard>
    </div>
  );

  const placeLine = actPlacementLine(placements);
  const centerPublicos = (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
      <ActAdsFieldCard title="Targeting na Meta" extra={<span style={{ fontSize: 11, color: 'var(--text-muted)' }}>{liveHint}</span>}>
        <ActAdsKv k="Locales" v={(locales || []).length ? locales.map(actLocaleLabel).join(' · ') : '—'} />
        <ActAdsKv
          k="Placements"
          v={(placeLine && (placeLine.facebook || placeLine.platforms)) || '—'}
          extra={placeLine?.instagram ? <div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 2 }}>{placeLine.instagram}</div> : null}
        />
      </ActAdsFieldCard>
      {(ads.adsets || []).length === 0 && (
        <ActAdsFieldCard title="Ad Sets">
          <div style={{ fontSize: 13, color: 'var(--text-muted)' }}>{ads.live_error || 'Ainda sem Ad Set na Meta.'}</div>
        </ActAdsFieldCard>
      )}
      {(ads.adsets || []).map((a, i) => {
        const interesses = a.interesses || [];
        const ids = a.interest_ids || [];
        return (
          <ActAdsFieldCard key={a.id || i} title={a.nome || `Ad Set ${i + 1}`} extra={<span style={{ fontSize: 11, color: 'var(--text-muted)' }}>{a.status || a.id || ''}</span>}>
            <ActAdsKv k="Geo" v={Array.isArray(a.geo) && a.geo.length ? a.geo.join(', ') : '—'} />
            {canEdit && i === 0 ? (
              <div style={{ marginBottom: 12 }}>
                <div style={{ fontSize: 9, fontFamily: 'var(--font-mono)', letterSpacing: '0.10em', textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: 4 }}>Idade</div>
                <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
                  <input type="number" min="18" max="65" value={editAgeMin} onChange={e => setEditAgeMin(e.target.value)} disabled={busyAds} style={inputSt} />
                  <input type="number" min="18" max="65" value={editAgeMax} onChange={e => setEditAgeMax(e.target.value)} disabled={busyAds} style={inputSt} />
                </div>
              </div>
            ) : (
              <ActAdsKv k="Idade" v={a.age_min != null ? `${a.age_min}–${a.age_max} anos` : '—'} />
            )}
            <div style={{ marginBottom: 12 }}>
              <div style={{ fontSize: 9, fontFamily: 'var(--font-mono)', letterSpacing: '0.10em', textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: 6 }}>
                Interesses · {(i === 0 ? editInterests : actToChips(interesses, ids)).length}
              </div>
              <ActRemovableChips
                items={i === 0 ? editInterests : actToChips(interesses, ids)}
                onRemove={canEdit && i === 0 ? (idx) => setEditInterests(editInterests.filter((_, n) => n !== idx)) : null}
                disabled={!canEdit || busyAds || i !== 0}
                empty="Sem interesses"
              />
            </div>
          </ActAdsFieldCard>
        );
      })}
    </div>
  );

  const centerForm = (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
      <ActAdsFieldCard title="Instant Form" extra={<span style={{ fontSize: 11, color: 'var(--text-muted)' }}>{liveHint}</span>}>
        {form.planned ? <div style={{ fontSize: 12, color: 'var(--text-muted)', lineHeight: 1.45, marginBottom: 10 }}>Ainda não está na Meta. O rascunho PAUSED cria este formulário.</div> : null}
        <ActAdsKv k="Nome" v={form.name || '—'} />
        <ActAdsKv k="ID" v={formId || 'Ainda sem form na Meta'} extra={formId ? <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 4 }}>{form.status || 'Ads Manager'}</div> : null} />
        <ActAdsKv k="Locale" v={form.locale || '—'} />
        {form.form_type ? <ActAdsKv k="Tipo" v={form.form_type} /> : null}
      </ActAdsFieldCard>
      <ActAdsFieldCard title="Perguntas">
        {formQs.length === 0 && <div style={{ fontSize: 13, color: 'var(--text-muted)' }}>Ainda sem perguntas na Meta.</div>}
        {formQs.map((q, i) => (
          <div key={q.id || q.key || q.type || i} style={{ display: 'flex', gap: 8, padding: '6px 0', borderBottom: i < formQs.length - 1 ? '1px solid var(--border-1, #ECEFF5)' : 0 }}>
            <span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-muted)', minWidth: 18 }}>{i + 1}</span>
            <div>
              <div style={{ fontSize: 13, color: 'var(--text)' }}>{q.label || actFormQLabel(q)}</div>
              <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 2 }}>{q.type}{q.options ? ` · ${q.options.map(o => o.value || o.key).join(' / ')}` : ''}</div>
            </div>
          </div>
        ))}
      </ActAdsFieldCard>
      <ActAdsFieldCard title="Privacy e thank-you">
        <ActAdsKv k="Mensagem de uso dos dados" v={form.headline || '—'} />
        <ActAdsKv k="Privacy URL" v={form.privacy || '—'} />
        {form.context_card ? <ActAdsKv k="Context card" v={form.context_card.title || '—'} extra={form.context_card.body ? <div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 4, lineHeight: 1.45 }}>{form.context_card.body}</div> : null} /> : null}
        <ActAdsKv k="Thank-you" v={form.thank_you?.title || '—'} extra={form.thank_you?.body ? <div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 4, lineHeight: 1.45 }}>{form.thank_you.body}</div> : null} />
      </ActAdsFieldCard>
    </div>
  );

  const centerTab = tabId === 'orcamento' ? centerOrcamento
    : tabId === 'publicos' ? centerPublicos
    : tabId === 'form' ? centerForm
    : centerCampanha;
  const statusBorder = isActive ? '#BBF7D0' : (metaOk ? '#FDE68A' : '#E2E8F0');
  const statusHeadBg = isActive ? '#DCFCE7' : (metaOk ? '#FFFBEB' : '#F8FAFC');
  const statusFg = isActive ? '#166534' : (metaOk ? '#92400E' : '#475569');

  return (
    <div style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }}>
      <div style={{
        display: 'flex', alignItems: 'center', gap: 10, padding: '10px 14px', marginBottom: 14, flexShrink: 0,
        background: 'var(--bg-surface, #fff)', borderRadius: 8,
        boxShadow: 'var(--shadow-card, 0 1px 4px rgba(40,41,61,0.04), 0 4px 16px rgba(96,97,112,0.08))',
        border: '1px solid var(--border-1, #ECEFF5)', flexWrap: 'wrap',
      }}>
        <button type="button" onClick={() => actSetCamp(campId)} style={{ background: 'transparent', border: 0, cursor: 'pointer', padding: 0, fontSize: 12, color: '#3859D0' }}>{camp?.titulo || 'Campanha'}</button>
        <span style={{ color: '#7a8fa8', fontSize: 12 }}>›</span>
        <span style={{ fontSize: 12, color: 'var(--text)' }}>Anúncios</span>
        <span style={{ color: '#7a8fa8', fontSize: 12 }}>›</span>
        <span style={{
          fontSize: 9, fontWeight: 700, letterSpacing: '0.06em', padding: '3px 7px', borderRadius: 4,
          background: '#7C3AED18', color: '#7C3AED', fontFamily: 'var(--font-mono)',
        }}>META ADS</span>
        <span style={{ color: '#7a8fa8', fontSize: 12 }}>›</span>
        <span style={{ fontSize: 12, color: 'var(--text)', fontWeight: 700 }}>{pecaLabel}</span>
        {peca?.imagem_por_aprovar && (
          <span style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.04em', padding: '3px 7px', borderRadius: 4, background: '#FEF3C7', color: '#92400E' }}>Imagem nova por aprovar</span>
        )}
        {pecas.length > 1 && (
          <div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 6 }}>
            <button type="button" onClick={goPrev} disabled={!goPrev}
              style={{ background: '#fff', border: '1px solid #E3E6EB', padding: '2px 10px', borderRadius: 4, fontSize: 14, cursor: goPrev ? 'pointer' : 'not-allowed', opacity: goPrev ? 1 : 0.4, color: '#112954' }}>←</button>
            <span style={{ fontSize: 12, color: 'var(--text-muted)', minWidth: 44, textAlign: 'center' }}>{sel + 1} / {pecas.length}</span>
            <button type="button" onClick={goNext} disabled={!goNext}
              style={{ background: '#fff', border: '1px solid #E3E6EB', padding: '2px 10px', borderRadius: 4, fontSize: 14, cursor: goNext ? 'pointer' : 'not-allowed', opacity: goNext ? 1 : 0.4, color: '#112954' }}>→</button>
          </div>
        )}
      </div>

      <div className="scrollbar" style={{ flex: 1, minHeight: 0, overflowY: 'auto' }}>
        <div style={{ display: 'grid', gridTemplateColumns: 'minmax(292px, 320px) minmax(0, 1fr) 300px', gap: 20, alignItems: 'start', paddingBottom: 32 }}>
          <div style={{ minWidth: 0, display: 'flex', flexDirection: 'column', gap: 12 }}>
            <div style={{ background: '#fff', border: `1px solid ${statusBorder}`, borderRadius: 8, overflow: 'hidden' }}>
              <div style={{ padding: '10px 14px', borderBottom: `1px solid ${statusBorder}`, background: statusHeadBg, display: 'flex', justifyContent: 'space-between', gap: 8 }}>
                <span style={{ fontSize: 12, fontWeight: 700, color: statusFg }}>
                  {isActive ? 'ACTIVE no Facebook' : deliveryGap ? 'Incompleto no Facebook' : archivedOnFb ? 'Arquivada no Facebook' : (metaOk ? 'PAUSED no Facebook' : 'Ainda sem rascunho')}
                </span>
                <span style={{ fontSize: 11, color: statusFg }}>{nOk}/{checklist.length}</span>
              </div>
              <div style={{ padding: 14, display: 'flex', flexDirection: 'column', gap: 12 }}>
                {already?.meta_id && (
                  <div style={{ fontSize: 11, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)', wordBreak: 'break-all' }}>{already.meta_id}</div>
                )}
                {draftMsg && (
                  <div style={{ fontSize: 12, lineHeight: 1.45, color: /ACTIVE|actualizado|criado|PAUSED/i.test(draftMsg) ? '#166534' : '#92400E' }}>{draftMsg}</div>
                )}
                <ActAdsPipeStep n="1" label="Conta + Página" detail={[ads.account_name, ads.page_name].filter(Boolean).filter((v, i, a) => a.findIndex(x => String(x).toLowerCase() === String(v).toLowerCase()) === i).join(' · ') || ads.page_id || 'Em falta'} state={tabId === 'campanha' ? 'current' : pipeState(contaOk && pageOk)} onClick={() => goTab('campanha')} />
                <ActAdsPipeStep n="2" label="Orçamento" detail={budgetOk ? `${budgetNum} €/dia${ads.live ? '' : ' no plano'}` : 'Define o budget'} state={tabId === 'orcamento' ? 'current' : pipeState(budgetOk)} onClick={() => goTab('orcamento')} />
                <ActAdsPipeStep n="3" label="Público" detail={(ads.adsets || []).length ? `${(ads.adsets || []).length} ${ads.live ? 'Ad Set' : 'no plano'}` : 'Ainda sem Ad Set'} state={tabId === 'publicos' ? 'current' : pipeState(pubOk)} onClick={() => goTab('publicos')} />
                <ActAdsPipeStep n="4" label="Instant Form" detail={formOk ? (form.name || formId) : (form.planned ? 'Por criar na Meta' : (form.name || formId || 'Por criar'))} state={tabId === 'form' ? 'current' : pipeState(formOk)} onClick={() => goTab('form')} />
                <ActAdsPipeStep n="5" label="Meta" detail={isActive ? 'ACTIVE' : (metaOk ? 'PAUSED · Enviar agora gasta budget' : 'Criar rascunho primeiro')} state={pipeState(isActive)} />
              </div>
            </div>

            <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
              {already?.meta_url && (
                <a className="btn" href={already.meta_url} target="_blank" rel="noreferrer" style={btnBase}>Ads Manager</a>
              )}
              <button
                className="btn"
                style={{ ...btnBase, opacity: canDraft && !busyAds && !already?.meta_id ? 1 : 0.4, cursor: canDraft && !busyAds && !already?.meta_id ? 'pointer' : 'not-allowed' }}
                disabled={!canDraft || busyAds || !!already?.meta_id}
                title={!budgetOk ? 'Define o budget diário' : (already?.meta_id ? 'Rascunho já criado' : 'Cria campanha PAUSED sem gastar budget')}
                onClick={criarRascunho}
              >
                {draftBusy ? 'A criar no Meta…' : (already?.meta_id ? (isActive ? 'Já ACTIVE' : 'Já criado · PAUSED') : 'Criar rascunho PAUSED')}
              </button>
              <button
                className="btn"
                style={{ ...btnBase, opacity: canReparar && !busyAds ? 1 : 0.4, cursor: canReparar && !busyAds ? 'pointer' : 'not-allowed' }}
                disabled={!canReparar || busyAds}
                title={isActive ? 'Repara só em PAUSED' : (canReparar ? 'Alinha o público do Ad Set e o Instant Form com o segmento do Target' : 'Cria o rascunho primeiro')}
                onClick={repararAgora}
              >
                {repairBusy ? 'A reparar no Meta…' : 'Reparar no Meta'}
              </button>
              <button
                className="btn"
                style={{ ...btnBase, opacity: canEdit && !busyAds ? 1 : 0.4, cursor: canEdit && !busyAds ? 'pointer' : 'not-allowed' }}
                disabled={!canEdit || busyAds}
                title={canEdit ? 'Escreve data de fim, públicos e campos editados no Ads Manager (PAUSED)' : 'Só com rascunho PAUSED'}
                onClick={guardarNaMeta}
              >
                {saveBusy ? 'A guardar na Meta…' : 'Guardar na Meta'}
              </button>
              {confirmGo && (
                <div style={{ background: '#fff', border: '1px solid #FCD34D', borderRadius: 8, padding: 14 }}>
                  <div style={{ fontSize: 13, fontWeight: 700, color: '#112954', marginBottom: 6 }}>Activar no Ads Manager</div>
                  <div style={{ fontSize: 12, color: '#4a5e7a', lineHeight: 1.45 }}>
                    {archivedOnFb
                      ? `A campanha actual está arquivada no Facebook. Este clique cria uma campanha nova, uma vez, passa-a a ACTIVE e começa a gastar ${budgetNum} €/dia.`
                      : `A campanha passa a ACTIVE no Facebook e começa a gastar ${budgetNum} €/dia.`}
                    {orc?.envelope && !orc.envelope.cabe
                      ? ` O envelope Meta de ${String(orc.envelope.mes).padStart(2, '0')}/${orc.envelope.ano} tem €${orc.envelope.restante} restantes e este compromisso fica €${orc.envelope.excesso} acima.`
                      : ''}
                  </div>
                  <div style={{ fontSize: 12, color: '#112954', marginTop: 8, lineHeight: 1.45 }}>
                    {camp?.titulo || 'Campanha'} · {archivedOnFb ? 'arquivada no Facebook' : (already?.meta_id ? 'PAUSED no Facebook' : 'ainda sem rascunho')}
                  </div>
                  <div style={{ display: 'flex', gap: 8, marginTop: 12 }}>
                    <button type="button" className="btn" style={btnBase} onClick={() => setConfirmGo(false)}>Cancelar</button>
                    <button type="button" className="btn btn-ai" style={btnBase} disabled={busyAds} onClick={activarAgora}>Activar</button>
                  </div>
                </div>
              )}
              {isActive && onDesactivar && (
                <ActDesactivarBtn
                  full
                  onClick={() => onDesactivar('meta_ads')}
                  disabled={busyAds}
                  label="Desactivar anúncio"
                  title="Põe a campanha, os conjuntos e os anúncios em PAUSED. O gasto pára."
                />
              )}
              <button
                className="btn btn-ai"
                style={{ ...btnBase, opacity: canActivar && !busyAds ? 1 : 0.4, cursor: canActivar && !busyAds ? 'pointer' : 'not-allowed' }}
                disabled={!canActivar || busyAds}
                title={!budgetOk ? 'Define o budget diário' : (isActive ? 'Já está ACTIVE' : 'Activa campaign + ad sets + ads no Ads Manager')}
                onClick={() => { if (!budgetOk) { setDraftMsg('Define o budget diário primeiro.'); return; } setConfirmGo(true); }}
              >
                {actBusy ? 'A activar no Meta…' : (isActive ? 'Já ACTIVE' : (deliveryGap ? 'Conjunto incompleto' : 'Enviar agora'))}
              </button>
              {goNext && (
                <button type="button" className="btn" style={btnBase} onClick={goNext}>
                  Próximo anúncio
                </button>
              )}
            </div>

            <div style={{ background: '#fff', border: '1px solid var(--border-1, #ECEFF5)', borderRadius: 8, padding: 14 }}>
              <div style={{ fontSize: 9, fontFamily: 'var(--font-mono)', letterSpacing: '0.10em', textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: 8 }}>
                Partilhar para validação
              </div>
              <div style={{ display: 'flex', gap: 6, marginBottom: 10 }}>
                <button type="button" onClick={() => setShareScope('one')} style={{
                  flex: 1, height: 30, fontSize: 12, borderRadius: 6, cursor: 'pointer',
                  border: `1px solid ${shareScope === 'one' ? '#3859D0' : 'var(--border-2, #E3E6EB)'}`,
                  background: shareScope === 'one' ? '#EFF3FF' : '#fff', color: shareScope === 'one' ? '#3859D0' : 'var(--text)',
                }}>Este anúncio</button>
                <button type="button" onClick={() => setShareScope('all')} style={{
                  flex: 1, height: 30, fontSize: 12, borderRadius: 6, cursor: pecas.length ? 'pointer' : 'not-allowed',
                  border: `1px solid ${shareScope === 'all' ? '#3859D0' : 'var(--border-2, #E3E6EB)'}`,
                  background: shareScope === 'all' ? '#EFF3FF' : '#fff', color: shareScope === 'all' ? '#3859D0' : 'var(--text)',
                  opacity: pecas.length ? 1 : 0.4,
                }} disabled={!pecas.length}>Todos · {pecas.length}</button>
              </div>
              <div style={{ fontSize: 11, color: 'var(--text-muted)', lineHeight: 1.4, marginBottom: 10 }}>
                WhatsApp interno: template UTILITY aprovado pela Meta, só contas do portal. A primeira partilha pode ficar à espera da aprovação. Clientes ficam no canal WhatsApp da campanha.
              </div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
                <ActAdsSharePicker via="email" campId={campId} items={shareList} userEmail={shareUser?.email} disabled={!shareList.length} btnBase={btnBase} />
                <ActAdsSharePicker via="wa" campId={campId} items={shareList} userEmail={shareUser?.email} disabled={!shareList.length} btnBase={btnBase} />
              </div>
            </div>

            {chkFail.length > 0 && (
              <div style={{ background: '#fff', border: '1px solid var(--border-1, #ECEFF5)', borderRadius: 8, padding: 14 }}>
                <div style={{ fontSize: 9, fontFamily: 'var(--font-mono)', letterSpacing: '0.10em', textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: 8 }}>
                  Em falta · {nMiss}{nWarn ? ` · ${nWarn} aviso` : ''}
                </div>
                {chkFail.map(item => (
                  <div key={item.id} style={{ fontSize: 11.5, color: '#92400E', marginBottom: 6, lineHeight: 1.4 }}>
                    <b>{item.label}</b>
                    {item.detail ? <span style={{ color: 'var(--text-muted)', display: 'block' }}>{item.detail}</span> : null}
                  </div>
                ))}
              </div>
            )}
          </div>

          <div style={{ minWidth: 0, display: 'flex', flexDirection: 'column', gap: 12 }}>
            {centerTab}
          </div>

          <div style={{ position: 'sticky', top: 0, minWidth: 0, display: 'flex', flexDirection: 'column', gap: 12 }}>
            <div style={{ background: '#fff', border: '1px solid var(--border-1, #ECEFF5)', borderRadius: 8, overflow: 'hidden' }}>
              <div style={{ padding: '10px 14px', borderBottom: '1px solid var(--border-1, #ECEFF5)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                <span style={{ fontSize: 9, fontFamily: 'var(--font-mono)', letterSpacing: '0.10em', textTransform: 'uppercase', color: 'var(--text-muted)' }}>Pré-visualização</span>
                <span style={{ fontSize: 11, color: 'var(--text-muted)' }}>Feed · 4:5</span>
              </div>
              {!peca ? (
                <div style={{ padding: 16, fontSize: 12, color: 'var(--text-muted)', lineHeight: 1.45 }}>À espera de Produção.</div>
              ) : (
                <div style={{ fontFamily: 'Helvetica, Arial, sans-serif' }}>
                  <div style={{ padding: '12px 14px 10px', display: 'flex', gap: 10, alignItems: 'center' }}>
                    <div style={{
                      width: 36, height: 36, borderRadius: 18, background: (camp?.brand_color || '#E8A000') + '22',
                      color: camp?.brand_color || '#E8A000', display: 'flex', alignItems: 'center', justifyContent: 'center',
                      fontSize: 11, fontWeight: 800, flexShrink: 0,
                    }}>{String(ads.account_name || camp?.brand_name || 'B').slice(0, 1)}</div>
                    <div style={{ minWidth: 0 }}>
                      <div style={{ fontSize: 13, fontWeight: 700, color: '#1c1e21', lineHeight: 1.25 }}>{ads.page_name || ads.account_name || camp?.brand_name || 'Marca'}</div>
                      <div style={{ fontSize: 11, color: '#65676b', marginTop: 1 }}>Patrocinado</div>
                    </div>
                  </div>
                  {feed.full && (
                    <div style={{
                      padding: '0 14px 12px', fontSize: 13, color: '#1c1e21',
                      lineHeight: 1.45, overflowWrap: 'break-word', wordBreak: 'normal',
                    }}>
                      {feedOpen ? (
                        <div style={{ whiteSpace: 'pre-wrap' }}>{feed.full}</div>
                      ) : (
                        <div>
                          {feed.hookShown}
                          {feed.hasMore && (
                            <>
                              {' '}
                              <button
                                type="button"
                                onClick={() => setFeedOpen(true)}
                                style={{ border: 0, background: 'none', padding: 0, color: '#65676b', fontWeight: 600, fontSize: 13, cursor: 'pointer' }}
                              >
                                Ver mais
                              </button>
                            </>
                          )}
                        </div>
                      )}
                      {feedOpen && feed.hasMore && (
                        <button
                          type="button"
                          onClick={() => setFeedOpen(false)}
                          style={{ border: 0, background: 'none', padding: 0, marginTop: 4, color: '#65676b', fontWeight: 600, fontSize: 13, cursor: 'pointer' }}
                        >
                          Ver menos
                        </button>
                      )}
                    </div>
                  )}
                  <div style={{ width: '100%', background: '#f0f2f5', aspectRatio: '4 / 5', display: 'flex', alignItems: 'center', justifyContent: 'center', overflow: 'hidden' }}>
                    {peca.thumb
                      ? <img src={peca.thumb} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
                      : <span style={{ fontSize: 12, color: '#65676b' }}>sem imagem</span>}
                  </div>
                  {peca.imagem_por_aprovar && (
                    <div style={{ padding: '8px 14px 0', fontSize: 12, lineHeight: 1.45, color: '#92400E' }}>A imagem nova ainda não está aprovada. Este anúncio mantém a peça que já está na activação.</div>
                  )}
                  <div style={{ padding: '10px 12px 14px', display: 'flex', alignItems: 'center', gap: 12, borderTop: '1px solid #e4e6eb' }}>
                    <div style={{ minWidth: 0, flex: 1 }}>
                      <div style={{ fontSize: 11, color: '#65676b', lineHeight: 1.3 }}>
                        {(igLabel || ads.instagram_name || ads.page_name || ads.account_name || camp?.brand_name || 'marca').replace(/^@/, '')}
                      </div>
                      <div style={{ fontSize: 13, fontWeight: 700, color: '#1c1e21', marginTop: 2, lineHeight: 1.3, overflowWrap: 'anywhere' }}>
                        {peca.headline || peca.claim || '—'}
                      </div>
                      {peca.description ? (
                        <div style={{ fontSize: 12, color: '#65676b', marginTop: 2, lineHeight: 1.3 }}>{peca.description}</div>
                      ) : null}
                    </div>
                    <div style={{
                      flexShrink: 0, background: '#e4e6eb', color: '#050505',
                      fontSize: 13, fontWeight: 600, padding: '8px 12px', borderRadius: 6,
                      whiteSpace: 'nowrap', letterSpacing: 0, textTransform: 'none',
                    }}>{ctaLabel}</div>
                  </div>
                </div>
              )}
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

const ActLinkedInTokenBox = ({ social }) => {
  const tokenOk = (social?.checklist || []).some(x => x.id === 'token' && x.ok);
  const destUrn = social?.destination?.organization_urn || '';
  const openLigacoes = () => {
    if (window.mktNavToSub) window.mktNavToSub('ligacoes');
  };
  return (
    <div style={{
      marginTop: 10, padding: 10, border: '1px solid var(--border)',
      borderRadius: 8, background: 'var(--bg)',
    }}>
      <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', letterSpacing: '0.08em', textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: 8 }}>
        {tokenOk ? 'LinkedIn · ligado' : 'LinkedIn · por ligar'}
      </div>
      {tokenOk && destUrn && (
        <div style={{ fontSize: 11, color: '#166534', marginBottom: 8, fontFamily: 'var(--font-mono)', wordBreak: 'break-all' }}>
          {destUrn}
        </div>
      )}
      <div style={{ fontSize: 11, color: 'var(--text-muted)', lineHeight: 1.45, marginBottom: 8 }}>
        {tokenOk
          ? 'Token da marca está no cofre. Enviar agora publica na company page.'
          : 'Ainda não há token LinkedIn nesta VPS. Em Ligações → LinkedIn: Client ID + Secret e Autorizar com uma conta Admin da página biond-films.'}
      </div>
      <button type="button" className="btn btn-ai" onClick={openLigacoes} style={{ height: 28, padding: '0 12px', fontSize: 12 }}>
        Abrir Ligações
      </button>
    </div>
  );
};

const ActSocialCard = ({ canal, camp, campId, onReload, onDesactivar }) => {
  const social = canal.social || {};
  const pecas = canal.pecas || [];
  const checklist = Array.isArray(social.checklist) ? social.checklist : [];
  const order = pecas.map((_, i) => i).sort((a, b) => {
    const sa = actOrganicSlot(pecas[a], canal.suggested_at);
    const sb = actOrganicSlot(pecas[b], canal.suggested_at);
    return String(sa).localeCompare(String(sb));
  });
  const firstReady = order.find(i => pecas[i] && !pecas[i].virtual);
  const [sel, setSel] = React.useState(firstReady != null ? firstReady : (order[0] ?? 0));
  React.useEffect(() => {
    setSel(i => {
      if (pecas[i] && !pecas[i].virtual) return i;
      const r = order.find(n => pecas[n] && !pecas[n].virtual);
      return r != null ? r : (order[0] ?? 0);
    });
  }, [pecas.map(p => p.id).join('|')]);
  const peca = pecas[sel] || pecas[0] || null;
  const caption = peca?.primary_text || peca?.body || '';
  const label = (ACT_CHANNELS.find(c => c.id === canal.canal) || {}).label || canal.canal;
  const handle = actPrettyHandle(social, canal.canal);
  const ready = pecas.filter(p => !p.virtual).length;
  const slotIso = actOrganicSlot(peca, canal.suggested_at);
  const [whenLocal, setWhenLocal] = React.useState(() => actToLocalInput(slotIso));
  const [busy, setBusy] = React.useState(false);
  const [msg, setMsg] = React.useState(null);
  const [feedOpen, setFeedOpen] = React.useState(false);
  const { start, update, finish, pollAds } = useActJob();
  React.useEffect(() => { setFeedOpen(false); }, [sel, peca?.id]);
  React.useEffect(() => {
    setWhenLocal(actToLocalInput(actOrganicSlot(peca, canal.suggested_at)));
  }, [peca?.id, peca?.organic_scheduled_for, peca?.planned_date, canal.suggested_at]);

  const whenDate = whenLocal ? new Date(whenLocal) : null;
  const whenPast = !!(whenDate && !isNaN(whenDate.getTime()) && whenDate.getTime() <= Date.now());
  const pecaReady = !!(peca && !peca.virtual && peca.primary_text);
  const alreadyPub = peca?.organic_status === 'published';
  const scheduled = peca?.organic_status === 'scheduled';
  const failed = peca?.organic_status === 'failed';
  const canOrg = !!(canal.can_activar || social.can_activar);
  const canNow = canOrg && pecaReady && !alreadyPub && !busy;
  const canSched = canOrg && pecaReady && !alreadyPub && whenLocal && !whenPast && !busy;

  const runSocial = async ({ sendNow }) => {
    if (!pecaReady) { setMsg('Escolhe uma peça pronta — as virtuais ainda estão em Conteúdos.'); return; }
    if (sendNow) {
      const ok = window.confirm(
        `Isto publica agora no ${label} (${handle}).\n\nPeça: ${peca.headline || peca.id}\n\nContinuar?`
      );
      if (!ok) return;
    } else {
      const ok = window.confirm(
        `Confirmar agendamento no ${label} para ${whenDate ? actFmtDateTime(whenDate.toISOString()) : 'a data do Planeamento'}?\n\nPeça: ${peca.headline || peca.id}`
      );
      if (!ok) return;
    }
    setBusy(true); setMsg(null);
    start(sendNow ? `Publicar em ${label}` : `Agendar ${label}`, sendNow
      ? `A publicar «${peca.headline || 'post'}» no ${label}…`
      : `A confirmar o slot de «${peca.headline || 'post'}» no ${label}…`);
    const stopPoll = pollAds(campId);
    try {
      const out = await actSetupFetch('/api/marketing/activacao/social/activar', {
        method: 'POST',
        body: JSON.stringify({
          campanha_id: campId,
          canal: canal.canal,
          peca_id: peca.id,
          send_now: !!sendNow,
          scheduled_for: sendNow ? undefined : (whenDate ? whenDate.toISOString() : null),
        }),
      });
      setMsg(out.status === 'published'
        ? `Publicado em ${label}${out.url ? ` · ${out.url}` : ''}`
        : `Agendado em ${label} · ${out.scheduled_for ? actFmtDateTime(out.scheduled_for) : ''}`);
      update({ step: 'A actualizar o ecrã…' });
      if (onReload) await onReload();
    } catch (e) {
      setMsg(e.message);
      update({ step: e.message, error: e.message });
    } finally { stopPoll(); setBusy(false); finish(); }
  };

  const dest = social.destination || {};
  const pecaLabel = actPecaTitle(peca, sel);
  const goPrev = order.length > 1 && order.indexOf(sel) > 0 ? () => setSel(order[order.indexOf(sel) - 1]) : null;
  const goNext = order.length > 1 && order.indexOf(sel) < order.length - 1 ? () => setSel(order[order.indexOf(sel) + 1]) : null;
  const btnBase = { height: 34, width: '100%', padding: '0 12px', fontSize: 12, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', textDecoration: 'none', boxSizing: 'border-box' };
  const inputSt = {
    fontSize: 12, fontFamily: 'inherit', color: 'var(--text)',
    border: '1px solid var(--border-2, #E3E6EB)', borderRadius: 6, padding: '8px 10px',
    background: 'var(--bg, #f8fafc)', width: '100%',
  };
  const feed = actFeedBlocks(caption);
  const badge = ACT_SOCIAL_BADGE[canal.canal] || { label: label.toUpperCase(), bg: '#3859D018', fg: '#3859D0' };
  const avatarR = canal.canal === 'instagram' ? 8 : 18;
  const previewSub = alreadyPub
    ? 'Publicado'
    : (scheduled && peca.organic_scheduled_for
      ? `Agendado · ${actFmtDateTime(peca.organic_scheduled_for)}`
      : `${whenDate && !isNaN(whenDate.getTime()) ? actFmtDateTime(whenDate.toISOString()) : 'Slot do Planeamento'} · orgânico`);
  const tokenMiss = (checklist || []).some(x => x.id === 'token' && !x.ok);
  const pecaHint = (p) => {
    if (p.organic_status === 'published') return 'publicado';
    if (p.organic_status === 'scheduled') return p.organic_scheduled_for ? `agendado ${actFmtDateTime(p.organic_scheduled_for)}` : 'agendado';
    if (p.organic_status === 'cancelado') return 'desactivado';
    if (p.organic_status === 'failed') return 'falhou';
    if (p.imagem_por_aprovar) return 'Imagem nova por aprovar';
    if (p.virtual) return 'Conteúdos';
    return 'pronto';
  };

  return (
    <div style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }}>
      <div style={{
        display: 'flex', alignItems: 'center', gap: 10, padding: '10px 14px', marginBottom: 14, flexShrink: 0,
        background: 'var(--bg-surface, #fff)', borderRadius: 8,
        boxShadow: 'var(--shadow-card, 0 1px 4px rgba(40,41,61,0.04), 0 4px 16px rgba(96,97,112,0.08))',
        border: '1px solid var(--border-1, #ECEFF5)', flexWrap: 'wrap',
      }}>
        <button type="button" onClick={() => actSetCamp(campId)} style={{ background: 'transparent', border: 0, cursor: 'pointer', padding: 0, fontSize: 12, color: '#3859D0' }}>{camp?.titulo || 'Campanha'}</button>
        <span style={{ color: '#7a8fa8', fontSize: 12 }}>›</span>
        <span style={{ fontSize: 12, color: 'var(--text)' }}>Social</span>
        <span style={{ color: '#7a8fa8', fontSize: 12 }}>›</span>
        <span style={{
          fontSize: 9, fontWeight: 700, letterSpacing: '0.06em', padding: '3px 7px', borderRadius: 4,
          background: badge.bg, color: badge.fg, fontFamily: 'var(--font-mono)',
        }}>{badge.label}</span>
        <span style={{ color: '#7a8fa8', fontSize: 12 }}>›</span>
        <span style={{ fontSize: 12, color: 'var(--text)', fontWeight: 700 }}>{pecaLabel}</span>
        {peca?.imagem_por_aprovar && (
          <span style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.04em', padding: '3px 7px', borderRadius: 4, background: '#FEF3C7', color: '#92400E' }}>Imagem nova por aprovar</span>
        )}
        {order.length > 1 && (
          <div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 6 }}>
            <button type="button" onClick={goPrev} disabled={!goPrev}
              style={{ background: '#fff', border: '1px solid #E3E6EB', padding: '2px 10px', borderRadius: 4, fontSize: 14, cursor: goPrev ? 'pointer' : 'not-allowed', opacity: goPrev ? 1 : 0.4, color: '#112954' }}>←</button>
            <span style={{ fontSize: 12, color: 'var(--text-muted)', minWidth: 44, textAlign: 'center' }}>{order.indexOf(sel) + 1} / {order.length}</span>
            <button type="button" onClick={goNext} disabled={!goNext}
              style={{ background: '#fff', border: '1px solid #E3E6EB', padding: '2px 10px', borderRadius: 4, fontSize: 14, cursor: goNext ? 'pointer' : 'not-allowed', opacity: goNext ? 1 : 0.4, color: '#112954' }}>→</button>
          </div>
        )}
      </div>

      <div className="scrollbar" style={{ flex: 1, minHeight: 0, overflowY: 'auto' }}>
        <div style={{ display: 'grid', gridTemplateColumns: 'minmax(360px, 1fr) 300px', gap: 20, alignItems: 'start', paddingBottom: 32 }}>
          <div style={{ minWidth: 0, display: 'flex', flexDirection: 'column', gap: 12 }}>
            <div style={{ background: '#fff', border: '1px solid var(--border-1, #ECEFF5)', borderRadius: 8, padding: '12px 14px' }}>
              <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)' }}>{handle}</div>
              <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 4 }}>
                {ready}/{pecas.length} prontos · data do Planeamento · hora 09:00 Lisboa até haver análise
              </div>
            </div>

            {canal.canal === 'linkedin' && tokenMiss && <ActLinkedInTokenBox social={social} />}

            {msg && (
              <div style={{ fontSize: 12, lineHeight: 1.45, color: /Publicado|Agendado/i.test(msg) ? '#166534' : '#92400E' }}>{msg}</div>
            )}

            <div style={{ background: '#fff', border: '1px solid var(--border-1, #ECEFF5)', borderRadius: 8, overflow: 'hidden' }}>
              <div style={{ padding: '10px 14px', borderBottom: '1px solid var(--border-1, #ECEFF5)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                <span style={{ fontSize: 9, fontFamily: 'var(--font-mono)', letterSpacing: '0.10em', textTransform: 'uppercase', color: 'var(--text-muted)' }}>Publicações</span>
                <span style={{ fontSize: 11, color: 'var(--text-muted)' }}>{order.length}</span>
              </div>
              <div>
                {order.map(i => {
                  const p = pecas[i];
                  const slot = actOrganicSlot(p, canal.suggested_at);
                  const active = sel === i;
                  return (
                    <button
                      key={p.id}
                      type="button"
                      onClick={() => setSel(i)}
                      style={{
                        display: 'flex', gap: 10, alignItems: 'center', width: '100%', textAlign: 'left',
                        border: 0, borderBottom: '1px solid var(--border-1, #ECEFF5)',
                        background: active ? '#EFF3FF' : '#fff',
                        padding: '10px 14px', cursor: 'pointer', fontFamily: 'inherit',
                      }}
                    >
                      <div style={{ minWidth: 88, flexShrink: 0 }}>
                        <div style={{ fontSize: 12, fontWeight: 700, color: 'var(--text)', fontFamily: 'var(--font-mono)' }}>{slot ? actFmtDate(slot) : '—'}</div>
                        <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 2 }}>{slot ? actFmtTime(slot) : ''}</div>
                      </div>
                      <div style={{ width: 40, height: 40, borderRadius: 6, overflow: 'hidden', background: '#E8EDF5', flexShrink: 0 }}>
                        {p.thumb ? <img src={p.thumb} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} /> : null}
                      </div>
                      <div style={{ minWidth: 0, flex: 1 }}>
                        <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--text)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.headline || actPecaTitle(p, i)}</div>
                        <div style={{ fontSize: 11, color: p.organic_status === 'failed' ? '#b91c1c' : (p.virtual ? '#92400E' : 'var(--text-muted)'), marginTop: 2 }}>
                          {pecaHint(p)}
                        </div>
                      </div>
                    </button>
                  );
                })}
              </div>
            </div>

            <div style={{ background: '#fff', border: '1px solid var(--border-1, #ECEFF5)', borderRadius: 8, padding: 14 }}>
              <div style={{ fontSize: 9, fontFamily: 'var(--font-mono)', letterSpacing: '0.10em', textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: 8 }}>
                Confirmar data e hora
              </div>
              <input
                type="datetime-local"
                value={whenLocal}
                onChange={e => setWhenLocal(e.target.value)}
                disabled={busy || alreadyPub || peca?.virtual}
                style={{ ...inputSt, borderColor: whenPast ? '#EF4444' : 'var(--border-2, #E3E6EB)' }}
              />
              <div style={{ fontSize: 11, color: whenPast ? '#EF4444' : 'var(--text-muted)', marginTop: 8, lineHeight: 1.4 }}>
                {peca?.virtual
                  ? 'Ainda em Conteúdos — não agenda.'
                  : (whenPast
                    ? 'Este slot já passou. Confirma outra hora ou Enviar agora.'
                    : 'Pré-preenchido com a data do Planeamento e 09:00 Lisboa. Só confirmas.')}
              </div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginTop: 12 }}>
                {alreadyPub && peca.organic_url && (
                  <a className="btn" href={peca.organic_url} target="_blank" rel="noreferrer" style={btnBase}>Ver publicação</a>
                )}
                <button
                  className="btn btn-ai"
                  style={{ ...btnBase, opacity: canSched ? 1 : 0.4, cursor: canSched ? 'pointer' : 'not-allowed' }}
                  disabled={!canSched}
                  title={!whenLocal ? 'Sem data do Planeamento' : (whenPast ? 'A hora já passou' : `Confirma o slot no ${label}`)}
                  onClick={() => runSocial({ sendNow: false })}
                >
                  {busy ? 'A agendar…' : (scheduled ? 'Reconfirmar agendamento' : 'Confirmar agendamento')}
                </button>
                <button
                  className="btn"
                  style={{ ...btnBase, opacity: canNow ? 1 : 0.4, cursor: canNow ? 'pointer' : 'not-allowed' }}
                  disabled={!canNow}
                  title={alreadyPub ? 'Já publicado nesta rede' : `Publica agora no ${label} (excepção)`}
                  onClick={() => runSocial({ sendNow: true })}
                >
                  {busy ? 'A publicar…' : (alreadyPub ? 'Já publicado' : 'Enviar agora')}
                </button>
                {canal.can_desactivar && onDesactivar && (
                  <ActDesactivarBtn
                    full
                    onClick={() => onDesactivar(canal.canal)}
                    disabled={busy}
                    label="Desactivar agendamentos"
                    title="Cancela as publicações agendadas nesta rede. O que já está publicado mantém-se."
                  />
                )}
              </div>
            </div>
          </div>

          <div style={{ position: 'sticky', top: 0, minWidth: 0 }}>
            <div style={{ background: '#fff', border: '1px solid var(--border-1, #ECEFF5)', borderRadius: 8, overflow: 'hidden' }}>
              <div style={{ padding: '10px 14px', borderBottom: '1px solid var(--border-1, #ECEFF5)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                <span style={{ fontSize: 9, fontFamily: 'var(--font-mono)', letterSpacing: '0.10em', textTransform: 'uppercase', color: 'var(--text-muted)' }}>Copy</span>
                <span style={{ fontSize: 11, color: 'var(--text-muted)' }}>{label}</span>
              </div>
              {!peca ? (
                <div style={{ padding: 16, fontSize: 12, color: 'var(--text-muted)', lineHeight: 1.45 }}>À espera de Produção.</div>
              ) : (
                <div style={{ fontFamily: 'Helvetica, Arial, sans-serif' }}>
                  <div style={{ padding: '12px 14px 10px', display: 'flex', gap: 10, alignItems: 'center' }}>
                    <div style={{
                      width: 36, height: 36, borderRadius: avatarR, flexShrink: 0,
                      background: (camp?.brand_color || '#E8A000') + '22', color: camp?.brand_color || '#E8A000',
                      display: 'flex', alignItems: 'center', justifyContent: 'center',
                      fontSize: 11, fontWeight: 800,
                    }}>{String(social.handle || camp?.brand_name || 'B').replace(/^@/, '').slice(0, 1).toUpperCase()}</div>
                    <div style={{ minWidth: 0 }}>
                      <div style={{ fontSize: 13, fontWeight: 700, color: '#1c1e21', lineHeight: 1.25 }}>{handle}</div>
                      <div style={{ fontSize: 11, color: '#65676b', marginTop: 1 }}>{previewSub}{peca.virtual ? ' · Conteúdos' : ''}</div>
                    </div>
                  </div>
                  {feed.full && (
                    <div style={{
                      padding: '0 14px 12px', fontSize: 13, color: '#1c1e21',
                      lineHeight: 1.45, overflowWrap: 'break-word', wordBreak: 'normal',
                    }}>
                      {feedOpen ? (
                        <div style={{ whiteSpace: 'pre-wrap' }}>{feed.full}</div>
                      ) : (
                        <div>
                          {feed.hookShown}
                          {feed.hasMore && (
                            <>
                              {' '}
                              <button type="button" onClick={() => setFeedOpen(true)} style={{ border: 0, background: 'none', padding: 0, color: '#65676b', fontWeight: 600, fontSize: 13, cursor: 'pointer' }}>
                                Ver mais
                              </button>
                            </>
                          )}
                        </div>
                      )}
                      {feedOpen && feed.hasMore && (
                        <button type="button" onClick={() => setFeedOpen(false)} style={{ border: 0, background: 'none', padding: 0, marginTop: 4, color: '#65676b', fontWeight: 600, fontSize: 13, cursor: 'pointer' }}>
                          Ver menos
                        </button>
                      )}
                    </div>
                  )}
                  <div style={{ width: '100%', background: '#f0f2f5', aspectRatio: '4 / 5', display: 'flex', alignItems: 'center', justifyContent: 'center', overflow: 'hidden' }}>
                    {peca.thumb
                      ? <img src={peca.thumb} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
                      : <span style={{ fontSize: 12, color: '#65676b' }}>sem imagem</span>}
                  </div>
                  {peca.imagem_por_aprovar && (
                    <div style={{ padding: '8px 14px 0', fontSize: 12, lineHeight: 1.45, color: '#92400E' }}>A imagem nova ainda não está aprovada. Este anúncio mantém a peça que já está na activação.</div>
                  )}
                  {peca.headline && (
                    <div style={{ padding: '10px 14px 14px', fontSize: 13, fontWeight: 700, color: '#1c1e21', lineHeight: 1.3 }}>{peca.headline}</div>
                  )}
                </div>
              )}
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

const ActCanalCard = ({ canal, onActivar, busy, campId, user }) => {
  const meta = ACT_CHANNELS.find(c => c.id === canal.canal) || { label: canal.canal, id: canal.canal };
  const s = canal.setup || {};
  const pecas = canal.pecas || [];
  const isAds = ['meta_ads', 'linkedin_ads', 'google_ads_search', 'google_ads_display'].includes(canal.canal);
  const pecaAgendada = pecas.find(p => p.scheduled_for)?.scheduled_for;
  const [whenLocal, setWhenLocal] = React.useState(() => actToLocalInput(pecaAgendada || canal.suggested_at));
  React.useEffect(() => {
    setWhenLocal(actToLocalInput(pecaAgendada || canal.suggested_at));
  }, [pecaAgendada, canal.suggested_at]);
  const whenDate = whenLocal ? new Date(whenLocal) : null;
  const whenPast = !!(whenDate && !isNaN(whenDate.getTime()) && whenDate.getTime() <= Date.now());

  const kv = (k, v) => (
    <div style={{ display: 'contents' }}>
      <div style={{ color: 'var(--text-muted)', fontSize: 12 }}>{k}</div>
      <div style={{ color: 'var(--text)', fontSize: 12 }}>{v || '—'}</div>
    </div>
  );

  return (
    <div style={{ background: 'var(--bg-elev)', border: '1px solid var(--border)', borderRadius: 8, padding: 20, marginBottom: 16 }}>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, marginBottom: 14 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
          <div style={{ fontFamily: 'var(--font-display)', fontSize: 15, fontWeight: 700 }}>{meta.label}</div>
          {actEstadoBadge(canal.estado, canal.canal)}
        </div>
        <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
          <button
            className="btn"
            style={{ height: 30, padding: '0 12px', fontSize: 12, opacity: canal.can_activar ? 1 : 0.45 }}
            disabled={!canal.can_activar || busy}
            onClick={() => onActivar(canal.canal, { send_now: true })}
            title="Publica ou envia daqui a segundos"
          >
            {busy === canal.canal ? 'A enviar agora…' : 'Enviar agora'}
          </button>
          <button
            className="btn btn-ai"
            style={{ height: 30, padding: '0 14px', fontSize: 12, opacity: canal.can_activar && whenLocal && !whenPast ? 1 : 0.45 }}
            disabled={!canal.can_activar || !whenLocal || whenPast || busy}
            onClick={() => onActivar(canal.canal, { scheduled_for: whenDate ? whenDate.toISOString() : null })}
            title={whenPast ? 'A hora já passou — altera ou usa Enviar agora' : 'Armar para esta data e hora (Lisboa)'}
          >
            {busy === canal.canal ? 'A agendar…' : 'Activar'}
          </button>
        </div>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 20 }}>
        <div>
          <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', letterSpacing: '0.10em', textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: 10 }}>Setup</div>
          <div style={{ display: 'grid', gridTemplateColumns: '140px 1fr', gap: '6px 12px' }}>
            {kv('Janela planeamento', canal.planeamento?.start_date ? `${actFmtDate(canal.planeamento.start_date)}${canal.planeamento.end_date ? ' → ' + actFmtDate(canal.planeamento.end_date) : ''}` : null)}
            <div style={{ color: 'var(--text-muted)', fontSize: 12 }}>Data e hora</div>
            <div>
              <input
                type="datetime-local"
                value={whenLocal}
                onChange={e => setWhenLocal(e.target.value)}
                disabled={!canal.can_activar || !!busy}
                style={{
                  fontSize: 12, fontFamily: 'inherit', color: 'var(--text)',
                  border: `1px solid ${whenPast ? '#EF4444' : 'var(--border)'}`,
                  borderRadius: 6, padding: '5px 8px', background: 'var(--bg)',
                }}
              />
              <div style={{ fontSize: 10, color: whenPast ? '#EF4444' : 'var(--text-muted)', marginTop: 4 }}>
                {whenPast ? 'Já passou — altera a hora ou Enviar agora.' : 'Europe/Lisbon · usado só neste canal'}
              </div>
            </div>
            {isAds && kv('Conta ads', s.conta_ads)}
            {isAds && kv('Budget', s.budget_sugerido?.percentagem_total != null ? `${s.budget_sugerido.percentagem_total}% · ${s.budget_sugerido.racional || ''}` : s.budget_sugerido?.racional)}
            {isAds && kv('Bid', s.bid_strategy)}
            {isAds && kv('Objectivo', s.objectivo_campanha)}
            {!isAds && kv('Destino', s.conta_publicacao || s.website_destino)}
            {!isAds && kv('Formato', s.formato_publicacao)}
            {s.horario_publicacao && kv('Horário', s.horario_publicacao)}
          </div>
          {(canal.segmentacao || []).length > 0 && (
            <div style={{ marginTop: 14 }}>
              <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', letterSpacing: '0.10em', textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: 8 }}>Segmentação</div>
              {canal.segmentacao.map((sg, i) => (
                <div key={i} style={{ fontSize: 12, color: 'var(--text)', marginBottom: 4 }}>
                  <b>{sg.nome || 'Audiência'}</b>
                  {sg.country ? ` · ${sg.country}` : ''}
                  {sg.audiencia?.audience_size ? ` · ${sg.audiencia.audience_size}` : ''}
                </div>
              ))}
            </div>
          )}
          {(canal.listas || []).length > 0 && (
            <div style={{ marginTop: 10, fontSize: 12, color: 'var(--text-muted)' }}>
              Lista: {canal.listas.map(l => `${l.tipo}${l.contagem != null ? ` (${l.contagem})` : ''}`).join(' · ')}
            </div>
          )}
        </div>

        <div>
          <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', letterSpacing: '0.10em', textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: 10 }}>Conteúdo</div>
          {pecas.length === 0 ? (
            <div style={{ fontSize: 13, color: 'var(--text-muted)', padding: '16px 0' }}>À espera de Produção. O setup e a data já estão definidos.</div>
          ) : pecas.map(p => (
            <div key={p.id} style={{ display: 'flex', gap: 12, marginBottom: 12, paddingBottom: 12, borderBottom: '1px solid var(--border)' }}>
              <div style={{ width: 72, height: 72, borderRadius: 6, background: 'var(--bg)', overflow: 'hidden', flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                {p.thumb
                  ? <img src={p.thumb} alt="" style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain' }} />
                  : <span style={{ fontSize: 10, color: 'var(--text-dim)' }}>sem preview</span>}
              </div>
              <div style={{ minWidth: 0, flex: 1 }}>
                <div style={{ display: 'flex', gap: 8, alignItems: 'center', marginBottom: 4 }}>
                  <span style={{ fontSize: 12, fontWeight: 600 }}>{p.content_type || p.canal_filho || 'peça'}</span>
                  {actEstadoBadge(
                    p.status === 'published' ? 'publicado'
                      : p.status === 'scheduled' ? 'agendado'
                      : p.status === 'failed_publish' ? 'falhou'
                      : p.status === 'approved' || p.status === 'ready_review' || p.status === 'pending_activation' ? 'setup'
                      : 'a_produzir',
                    canal.canal
                  )}
                </div>
                {p.subject && <div style={{ fontSize: 12 }}><b>Assunto:</b> {p.subject}</div>}
                {p.headline && <div style={{ fontSize: 12 }}><b>Headline:</b> {p.headline}</div>}
                {p.imagem_por_aprovar && <div style={{ fontSize: 12, color: '#92400E', marginTop: 4 }}>Imagem nova por aprovar. Mantém a peça já na activação.</div>}
                {p.cta && <div style={{ fontSize: 12, color: 'var(--text-muted)' }}>CTA: {p.cta}</div>}
                {p.scheduled_for && <div style={{ fontSize: 11, fontFamily: 'var(--font-mono)', color: 'var(--text-muted)', marginTop: 4 }}>Agendado {actFmtDateTime(p.scheduled_for)}</div>}
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
};

const ActDetalhe = ({ campId, canalId, initialTab, user, onBack, onOpenCanal }) => {
  const [data, setData] = React.useState(null);
  const [error, setError] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  window.useMktZoneLoading(loading && !data);
  const [busy, setBusy] = React.useState(null);
  const [adsTab, setAdsTab] = React.useState('campanha');
  const [campTab, setCampTab] = React.useState('todos');
  const tabInit = React.useRef(initialTab || null);
  const { start, update, finish } = useActJob();
  React.useEffect(() => {
    const next = tabInit.current || 'campanha';
    tabInit.current = null;
    setAdsTab(next);
    setCampTab('todos');
  }, [campId, canalId]);

  const reload = React.useCallback(async () => {
    try {
      setError(null);
      const d = await actSetupFetch(`/api/marketing/activacao/setup/${campId}`);
      setData(d);
    } catch (e) { setError(e.message); }
    finally { setLoading(false); }
  }, [campId]);
  React.useEffect(() => {
    let alive = true;
    setLoading(true);
    start('Carregar setup', 'A carregar o setup da campanha…');
    reload().finally(() => { if (alive) finish(); });
    return () => { alive = false; };
  }, [reload, start, finish]);

  const marcarAudiencias = (payload) => {
    const destinos = new Set((payload && payload.destinos ? payload.destinos : []).map(String));
    const audiencias = new Set((payload && payload.audiencias ? payload.audiencias : []).map(String));
    setData(prev => {
      if (!prev) return prev;
      return {
        ...prev,
        canais: (prev.canais || []).map(c => {
          if (c.canal !== 'email') return c;
          return {
            ...c,
            destinos: (c.destinos || []).map(d => ({ ...d, selected: destinos.has(String(d.country)) })),
            audiencias: (c.audiencias || []).map(a => ({ ...a, selected: audiencias.has(String(a.id)) })),
            setup: Object.assign({}, c.setup || {}, c.envio || {}, {
              destinos: [...destinos],
              audiencias: [...audiencias],
              ...(Object.prototype.hasOwnProperty.call(payload || {}, 'primeiro_em') ? { primeiro_em: payload.primeiro_em } : {}),
            }),
          };
        }),
      };
    });
  };

  const activar = async (canal, opts = {}) => {
    const sendNow = !!opts.send_now;
    const email = canal === 'email';
    const label = email
      ? (opts.modo === 'workflow'
        ? (sendNow ? 'A armar o workflow. O primeiro email sai já…' : 'A armar o workflow de email…')
        : (sendNow ? 'A armar o envio único para a lista…' : 'A agendar o envio único…'))
      : (sendNow ? `A enviar ${canal} agora…` : `A activar ${canal}…`);
    start(sendNow ? 'Enviar agora' : 'Activar', label);
    setBusy(canal || 'all');
    try {
      const resp = await actSetupFetch(email ? '/api/marketing/activacao/email/armar' : '/api/marketing/activacao/activar', {
        method: 'POST',
        body: JSON.stringify({
          campanha_id: campId,
          canal_pai: email ? undefined : (canal || undefined),
          actor_email: user.email,
          actor_name: user.nome_apresentar,
          send_now: sendNow,
          scheduled_for: sendNow ? undefined : (opts.scheduled_for || undefined),
          modo: opts.modo,
          peca_id: opts.peca_id,
          passos: opts.passos,
        }),
      });
      update({
        step: resp?.distribuicao
          ? `Sequência armada. ${resp.distribuicao.inscritos} entram pelo território. ${resp.distribuicao.sem_territorio} sem território e ${resp.distribuicao.varios} com mais do que um comercial ficam de fora.`
          : (email && resp?.horario_ajustado && resp.inicio
            ? `O primeiro email fica para ${actFmtDateTime(resp.inicio)}. Dias úteis, das 9h às 21h.`
            : 'A actualizar o ecrã…'),
      });
      await reload();
    } catch (e) { alert(e.message); update({ step: e.message, error: e.message }); }
    finally { setBusy(null); finish(); }
  };

  const desactivar = async (canal) => {
    const meta = canal ? (ACT_CHANNELS.find(c => c.id === canal) || { label: canal }) : null;
    const texto = canal
      ? `Desactivar ${meta.label}? O que está no ar ou agendado neste canal pára. O resto da campanha mantém-se.`
      : 'Desactivar a campanha? Param os anúncios Meta, os envios de email (envio único e workflow) e as publicações agendadas. O que já está publicado nas redes mantém-se.';
    if (!window.confirm(texto)) return;
    const userNow = actGetUser();
    start('Desactivar', canal ? `A desactivar ${meta.label}…` : 'A desactivar a campanha…');
    setBusy(canal || 'campanha');
    try {
      const resp = await actSetupFetch('/api/marketing/activacao/desactivar', {
        method: 'POST',
        body: JSON.stringify({
          campanha_id: campId,
          canal: canal || undefined,
          actor_email: userNow.email,
          actor_name: userNow.nome_apresentar,
        }),
      });
      update({ step: resp.resumo || 'Desactivado.' });
      await reload();
    } catch (e) {
      alert(e.message);
      update({ step: e.message, error: e.message });
    } finally { setBusy(null); finish(); }
  };

  const camp = data?.campanha;
  const bc = camp?.brand_color || ACT_BRAND_COLORS[camp?.marca_slug] || '#3859D0';
  const canalAberto = (data?.canais || []).find(c => c.canal === canalId) || null;
  const canalMeta = canalAberto ? (ACT_CHANNELS.find(c => c.id === canalAberto.canal) || { label: canalAberto.canal }) : null;
  const isMetaAds = canalId === 'meta_ads';
  const isOrganic = ACT_ORGANIC.has(canalId);
  const backLabel = canalId ? '← Campanha' : '← Voltar';
  const backAction = canalId ? () => actSetCamp(campId) : onBack;

  const chromeTabs = ACT_ADS_TABS;
  const chromeTab = adsTab;
  const setChromeTab = setAdsTab;
  const adsTabIdx = chromeTabs.findIndex(t => t.id === chromeTab);
  const adsPrevTab = adsTabIdx > 0 ? chromeTabs[adsTabIdx - 1] : null;
  const adsNextTab = adsTabIdx >= 0 && adsTabIdx < chromeTabs.length - 1 ? chromeTabs[adsTabIdx + 1] : null;
  const campTabIdx = ACT_OVERVIEW_TABS.findIndex(t => t.id === campTab);
  const campPrevTab = campTabIdx > 0 ? ACT_OVERVIEW_TABS[campTabIdx - 1] : null;
  const campNextTab = campTabIdx >= 0 && campTabIdx < ACT_OVERVIEW_TABS.length - 1 ? ACT_OVERVIEW_TABS[campTabIdx + 1] : null;
  const adsN = (canalAberto?.pecas || []).length;
  const adsStatus = isOrganic
    ? (canalAberto?.estado === 'publicado' ? 'PUBLICADO' : (canalAberto?.estado === 'agendado' ? 'AGENDADO' : (canalAberto?.can_activar ? 'SETUP' : null)))
    : (canalAberto?.ads?.delivery_gap
      ? 'INCOMPLETO'
      : (canalAberto?.ads?.is_active
      ? 'ACTIVE'
      : (canalAberto?.ads?.draft?.meta_id ? 'PAUSED' : null)));
  const nCanais = (data?.canais || []).length;
  const podeDesactivar = (data?.canais || []).some(c => c.can_desactivar);
  const isOverview = !canalId;

  return (
    <div data-theme="light" style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', '--bg': '#f8fafc', '--bg-elev': '#ffffff', '--bg-hover': '#f1f5f9', '--bg-sunken': '#e8edf5', '--border': '#dde3ef', '--text': '#112954', '--text-muted': '#4a5e7a', '--text-dim': '#6b7fa3', '--ai-500': '#3859D0' }}>
      {isMetaAds || isOrganic ? (
      <div style={{ background: '#ffffff', borderBottom: '1px solid #ECEFF5', padding: isOrganic ? '16px 40px 18px' : '16px 40px 0', flexShrink: 0 }}>
        <div style={{ fontSize: 11, fontWeight: 600, letterSpacing: '0.07em', color: '#94A4C4', textTransform: 'uppercase', marginBottom: 6, fontFamily: 'var(--font-mono, monospace)' }}>
          MARKETING · ACTIVACAO
        </div>
        <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16 }}>
          <div style={{ minWidth: 0 }}>
            <h1 style={{ margin: 0, fontSize: 21, fontWeight: 700, color: '#283252', fontFamily: 'var(--font-display, Montserrat, sans-serif)', letterSpacing: '-0.01em', lineHeight: 1.2 }}>
              {camp?.titulo || (loading ? 'A carregar…' : 'Campanha')}
            </h1>
            <div style={{ fontSize: 12, color: '#94A4C4', marginTop: 6, display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
              <span style={{ color: bc, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.06em', fontSize: 10, fontFamily: 'var(--font-mono, monospace)' }}>
                {(camp?.brand_name || '—').toUpperCase()}
              </span>
              <span> · {isOrganic ? `${canalMeta?.label || 'Social'} orgânico` : 'Meta Ads'}</span>
              {adsN > 0 && <span> · {adsN} {isOrganic ? (adsN === 1 ? 'post' : 'posts') : (adsN === 1 ? 'anúncio' : 'anúncios')}</span>}
              {adsStatus && (
                <span style={{
                  fontSize: 9, fontWeight: 700, letterSpacing: '0.06em', padding: '2px 8px', borderRadius: 99,
                  background: adsStatus === 'ACTIVE' || adsStatus === 'PUBLICADO' ? '#DCFCE7' : (adsStatus === 'SETUP' ? 'rgba(56,89,208,0.12)' : '#FEF3C7'),
                  color: adsStatus === 'ACTIVE' || adsStatus === 'PUBLICADO' ? '#166534' : (adsStatus === 'SETUP' ? '#3859D0' : '#92400E'),
                  border: `1px solid ${adsStatus === 'ACTIVE' || adsStatus === 'PUBLICADO' ? '#86EFAC' : (adsStatus === 'SETUP' ? 'rgba(56,89,208,0.22)' : '#FCD34D')}`,
                  fontFamily: 'var(--font-mono, monospace)',
                }}>{adsStatus}</span>
              )}
              {camp?.status && (
                <span style={{ fontSize: 10, fontWeight: 700, padding: '2px 8px', borderRadius: 4, background: 'rgba(56,89,208,0.12)', color: '#3859D0', fontFamily: 'var(--font-mono)', letterSpacing: '0.04em' }}>
                  {String(camp.status).replace(/_/g, ' ').toUpperCase()}
                </span>
              )}
            </div>
          </div>
          <div style={{ display: 'flex', gap: 8, flexShrink: 0, alignItems: 'center' }}>
            {isMetaAds && adsPrevTab && (
              <button type="button" onClick={() => setChromeTab(adsPrevTab.id)} className="btn btn-sm" style={{ fontSize: 12, display: 'inline-flex', alignItems: 'center', gap: 4 }}>
                ← {adsPrevTab.label}
              </button>
            )}
            {isMetaAds && adsNextTab && (
              <button type="button" onClick={() => setChromeTab(adsNextTab.id)} className="btn btn-sm" style={{ fontSize: 12, display: 'inline-flex', alignItems: 'center', gap: 4 }}>
                {adsNextTab.label} →
              </button>
            )}
            <button type="button" onClick={backAction} title="Fechar" style={{
              background: 'none', border: '1px solid #e2e8f0', cursor: 'pointer',
              width: 30, height: 30, borderRadius: 6, color: '#64748b',
              display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 16,
            }}>×</button>
          </div>
        </div>
        {isMetaAds && (
        <div style={{ display: 'flex', gap: 0, marginTop: 14, marginLeft: -4 }}>
          {chromeTabs.map(t => {
            const active = t.id === chromeTab;
            return (
              <button
                key={t.id}
                type="button"
                onClick={() => setChromeTab(t.id)}
                style={{
                  background: 'none', border: 0, outline: 'none',
                  borderBottom: `2px solid ${active ? bc : 'transparent'}`,
                  color: active ? '#283252' : '#94A4C4',
                  fontSize: 13, fontWeight: active ? 600 : 500,
                  fontFamily: 'var(--font-display, Montserrat, sans-serif)', letterSpacing: '.01em',
                  padding: '10px 16px 12px', cursor: 'pointer', whiteSpace: 'nowrap',
                  transition: 'color .15s, border-color .15s',
                }}
              >
                {t.label}
              </button>
            );
          })}
        </div>
        )}
      </div>
      ) : isOverview ? (
      <div style={{ background: '#ffffff', borderBottom: '1px solid #ECEFF5', padding: '16px 40px 0', flexShrink: 0 }}>
        <div style={{ fontSize: 11, fontWeight: 600, letterSpacing: '0.07em', color: '#94A4C4', textTransform: 'uppercase', marginBottom: 6, fontFamily: 'var(--font-mono, monospace)' }}>
          MARKETING · ACTIVACAO
        </div>
        <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16 }}>
          <div style={{ minWidth: 0 }}>
            <h1 style={{ margin: 0, fontSize: 21, fontWeight: 700, color: '#283252', fontFamily: 'var(--font-display, Montserrat, sans-serif)', letterSpacing: '-0.01em', lineHeight: 1.2 }}>
              {camp?.titulo || (loading ? 'A carregar…' : 'Campanha')}
            </h1>
            <div style={{ fontSize: 12, color: '#94A4C4', marginTop: 6, display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
              <span style={{ color: bc, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.06em', fontSize: 10, fontFamily: 'var(--font-mono, monospace)' }}>
                {(camp?.brand_name || '—').toUpperCase()}
              </span>
              {nCanais > 0 && <span> · {nCanais} canais</span>}
              {camp?.status && (
                <span style={{
                  fontSize: 9, fontWeight: 700, letterSpacing: '0.06em', padding: '2px 8px', borderRadius: 99,
                  background: 'rgba(56,89,208,0.12)', color: '#3859D0',
                  border: '1px solid rgba(56,89,208,0.22)',
                  fontFamily: 'var(--font-mono, monospace)',
                }}>{String(camp.status).replace(/_/g, ' ').toUpperCase()}</span>
              )}
            </div>
          </div>
          <div style={{ display: 'flex', gap: 8, flexShrink: 0, alignItems: 'center' }}>
            {podeDesactivar && (
              <ActDesactivarBtn
                onClick={() => desactivar(null)}
                disabled={!!busy}
                label={busy === 'campanha' ? 'A desactivar…' : 'Desactivar campanha'}
                title="Pára anúncios, envios de email e publicações agendadas desta campanha"
              />
            )}
            {campPrevTab && (
              <button type="button" onClick={() => setCampTab(campPrevTab.id)} className="btn btn-sm" style={{ fontSize: 12, display: 'inline-flex', alignItems: 'center', gap: 4 }}>
                ← {campPrevTab.label}
              </button>
            )}
            {campNextTab && (
              <button type="button" onClick={() => setCampTab(campNextTab.id)} className="btn btn-sm" style={{ fontSize: 12, display: 'inline-flex', alignItems: 'center', gap: 4 }}>
                {campNextTab.label} →
              </button>
            )}
            <button type="button" onClick={backAction} title="Fechar" style={{
              background: 'none', border: '1px solid #e2e8f0', cursor: 'pointer',
              width: 30, height: 30, borderRadius: 6, color: '#64748b',
              display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 16,
            }}>×</button>
          </div>
        </div>
        <div style={{ display: 'flex', gap: 0, marginTop: 14, marginLeft: -4 }}>
          {ACT_OVERVIEW_TABS.map(t => {
            const active = t.id === campTab;
            return (
              <button
                key={t.id}
                type="button"
                onClick={() => setCampTab(t.id)}
                style={{
                  background: 'none', border: 0, outline: 'none',
                  borderBottom: `2px solid ${active ? bc : 'transparent'}`,
                  color: active ? '#283252' : '#94A4C4',
                  fontSize: 13, fontWeight: active ? 600 : 500,
                  fontFamily: 'var(--font-display, Montserrat, sans-serif)', letterSpacing: '.01em',
                  padding: '10px 16px 12px', cursor: 'pointer', whiteSpace: 'nowrap',
                  transition: 'color .15s, border-color .15s',
                }}
              >
                {t.label}
              </button>
            );
          })}
        </div>
      </div>
      ) : (
      <div style={{ background: 'var(--bg-elev)', borderBottom: '1px solid var(--border)', padding: '20px 40px 16px', flexShrink: 0 }}>
        <div style={{ fontSize: 11, fontWeight: 600, letterSpacing: '0.07em', color: 'var(--text-muted)', textTransform: 'uppercase', marginBottom: 6, fontFamily: 'var(--font-mono)' }}>
          MARKETING · ACTIVACAO{canalMeta ? ` · ${String(canalMeta.label).toUpperCase()}` : ''}
        </div>
        <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16 }}>
          <div style={{ minWidth: 0 }}>
            <h1 style={{ margin: 0, fontSize: 21, fontWeight: 700, color: 'var(--text)', fontFamily: 'var(--font-display)', letterSpacing: '-0.01em', lineHeight: 1.2 }}>
              {canalAberto?.canal === 'email'
                ? (camp?.titulo || (loading ? 'A carregar…' : 'Campanha'))
                : (canalMeta ? canalMeta.label : (camp?.titulo || (loading ? 'A carregar…' : 'Campanha')))}
            </h1>
            <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginTop: 8, flexWrap: 'wrap' }}>
              {canalAberto && actEstadoBadge(canalAberto.estado, canalAberto.canal)}
              {canalMeta && camp?.titulo && canalAberto?.canal !== 'email' && (
                <span style={{ fontSize: 12, color: 'var(--text-muted)' }}>{camp.titulo}</span>
              )}
              {camp?.status && (
                <span style={{ fontSize: 10, fontWeight: 700, padding: '2px 8px', borderRadius: 4, background: 'rgba(56,89,208,0.12)', color: '#3859D0', fontFamily: 'var(--font-mono)', letterSpacing: '0.04em' }}>
                  {String(camp.status).replace(/_/g, ' ').toUpperCase()}
                </span>
              )}
              {camp?.brand_name && (
                <span style={{ fontSize: 10, fontWeight: 700, padding: '2px 8px', borderRadius: 4, background: bc + '15', color: bc, fontFamily: 'var(--font-mono)', border: `1px solid ${bc}25`, letterSpacing: '0.04em' }}>
                  {camp.brand_name.toUpperCase()}
                </span>
              )}
            </div>
          </div>
          <div style={{ display: 'flex', gap: 8, alignItems: 'center', flexShrink: 0, marginTop: 2 }}>
            <button className="btn" style={{ height: 30, padding: '0 12px', fontSize: 12 }} onClick={backAction}>{backLabel}</button>
          </div>
        </div>
      </div>
      )}
      <div className="scrollbar" style={{
        flex: 1, minHeight: 0, overflowY: canalId && canalAberto?.canal !== 'email' ? 'hidden' : 'auto',
        padding: canalId ? (isMetaAds || isOrganic ? '12px 32px 24px' : '16px 40px 24px') : '20px 32px 32px',
        background: 'var(--bg, #f8fafc)',
        display: 'flex', flexDirection: 'column',
      }}>
        {loading && <div style={{ color: 'var(--text-muted)', fontSize: 13, display: 'flex', alignItems: 'center', gap: 8 }}><ActSpinner /> A carregar o setup da campanha…</div>}
        {error && <div style={{ color: '#dc2626', fontSize: 13 }}>{error}</div>}
        {!loading && !error && data && (data.canais || []).length === 0 && (
          <div style={{ color: 'var(--text-muted)', fontSize: 14 }}>Esta campanha ainda não tem canais no briefing nem setup.</div>
        )}
        {!loading && !error && data && canalId && !canalAberto && (
          <div style={{ color: 'var(--text-muted)', fontSize: 14 }}>Canal não encontrado nesta campanha.</div>
        )}
        {!loading && !error && data && canalAberto && (
          canalAberto.canal === 'email'
            ? <ActEmailCard canal={canalAberto} onActivar={activar} onDesactivar={desactivar} onReload={reload} busy={busy} campId={campId} user={user} camp={camp} onGuardado={marcarAudiencias} />
            : canalAberto.canal === 'meta_ads'
              ? <ActAdsCard canal={canalAberto} campId={campId} camp={camp} onReload={reload} onDesactivar={desactivar} tab={adsTab} setTab={setAdsTab} />
              : ACT_ORGANIC.has(canalAberto.canal)
                ? <ActSocialCard canal={canalAberto} camp={camp} campId={campId} onReload={reload} onDesactivar={desactivar} />
                : <div className="scrollbar" style={{ flex: 1, minHeight: 0, overflowY: 'auto' }}>
                    <ActCanalCard canal={canalAberto} onActivar={activar} busy={busy} campId={campId} user={user} />
                  </div>
        )}
        {!loading && !error && data && !canalId && (
          <ActCampanhaOverview canais={data.canais || []} onOpen={onOpenCanal} onAnalise={() => actAbrirAnalise(campId)} onDesactivar={desactivar} tab={campTab} />
        )}
      </div>
    </div>
  );
};

const ActHome = ({ user, onOpen }) => {
  const { brand, setBrand, isAll } = window.useMktBrand ? window.useMktBrand() : { brand: 'todas', setBrand: () => {}, isAll: true };
  const [allBrands, setAllBrands] = React.useState([]);
  const [data, setData] = React.useState(null);
  const [error, setError] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  window.useMktZoneLoading(loading && !data);
  const [filterEstado, setFilterEstado] = React.useState('all');
  const [filterCanal, setFilterCanal] = React.useState('all');
  const { start, finish } = useActJob();

  React.useEffect(() => {
    fetch('/api/marketing/brands').then(r => r.ok ? r.json() : []).then(d => {
      const src = Array.isArray(d) ? d : (d?.brands || []);
      const list = src.filter(b => b.slug !== 'todas').map(b => ({
        ...b, name: ACT_BRAND_NAMES[b.slug] || b.name,
      }));
      list.sort((a, b) => {
        const ia = ACT_BRAND_ORDER.indexOf(a.slug), ib = ACT_BRAND_ORDER.indexOf(b.slug);
        return (ia === -1 ? 99 : ia) - (ib === -1 ? 99 : ib);
      });
      setAllBrands(list);
    }).catch(() => {});
  }, []);

  const reload = React.useCallback(async () => {
    try {
      setError(null);
      const d = await actSetupFetch('/api/marketing/activacao/matriz');
      setData(d);
    } catch (e) { setError(e.message); }
    finally { setLoading(false); }
  }, []);
  React.useEffect(() => {
    setLoading(true);
    start('Activação', 'A carregar as campanhas…');
    reload().finally(() => finish());
  }, [reload, start, finish]);

  const todas = data?.campanhas || [];
  const daMarca = isAll ? todas : todas.filter(c => c.marca_slug === brand);
  const campanhas = React.useMemo(() => {
    if (!data) return loading ? null : [];
    let arr = daMarca;
    if (filterCanal !== 'all') arr = arr.filter(c => c.canais?.[filterCanal]?.in_briefing);
    return arr;
  }, [data, daMarca, filterCanal, loading]);
  const kpis = React.useMemo(() => ({
    total: daMarca.length,
    a_produzir: daMarca.filter(c => Object.values(c.canais || {}).some(x => x.estado === 'a_produzir')).length,
    setup: daMarca.filter(c => Object.values(c.canais || {}).some(x => x.estado === 'setup')).length,
    agendadas: daMarca.filter(c => Object.values(c.canais || {}).some(x => x.estado === 'agendado')).length,
    publicadas: daMarca.filter(c => Object.values(c.canais || {}).some(x => x.estado === 'publicado')).length,
    falhas: daMarca.filter(c => Object.values(c.canais || {}).some(x => x.estado === 'falhou')).length,
  }), [daMarca]);
  const marcaNome = ACT_BRAND_NAMES[brand] || brand;
  const outras = Math.max(0, todas.length - daMarca.length);
  const kpiCards = [
    { label: 'Campanhas', value: kpis.total, filter: 'all', accent: '#3859D0', fill: 1 },
    { label: 'A produzir', value: kpis.a_produzir, filter: 'a_produzir', accent: '#F59E0B', fill: kpis.total ? kpis.a_produzir / kpis.total : 0 },
    { label: 'Setup', value: kpis.setup, filter: 'setup', accent: '#3859D0', fill: kpis.total ? kpis.setup / kpis.total : 0 },
    { label: 'Agendadas', value: kpis.agendadas, filter: 'agendado', accent: '#0f766e', fill: kpis.total ? kpis.agendadas / kpis.total : 0 },
    { label: 'Publicadas', value: kpis.publicadas, filter: 'publicado', accent: '#10B981', fill: kpis.total ? kpis.publicadas / kpis.total : 0 },
    { label: 'Falhas', value: kpis.falhas, filter: 'falhou', accent: '#EF4444', fill: kpis.total ? kpis.falhas / kpis.total : 0 },
  ];

  const hasActive = filterEstado !== 'all' || filterCanal !== 'all';
  const filteredCount = (campanhas || []).filter(c => filterEstado === 'all' || Object.values(c.canais || {}).some(x => x.estado === filterEstado)).length;

  return (
    <div data-theme="light" style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', '--bg': '#f8fafc', '--bg-elev': '#ffffff', '--bg-hover': '#f1f5f9', '--bg-sunken': '#e8edf5', '--border': '#dde3ef', '--text': '#112954', '--text-muted': '#4a5e7a', '--text-dim': '#6b7fa3', '--ai-500': '#3859D0' }}>
      <div style={{ background: 'var(--bg-elev)', borderBottom: '1px solid var(--border)', padding: '20px 40px 0', flexShrink: 0 }}>
        <div style={{ fontSize: 11, fontWeight: 600, letterSpacing: '0.07em', color: 'var(--text-muted)', textTransform: 'uppercase', marginBottom: 6, fontFamily: 'var(--font-mono)' }}>
          MARKETING · ACTIVACAO
        </div>
        <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16 }}>
          <div>
            <h1 style={{ margin: 0, fontSize: 21, fontWeight: 700, color: 'var(--text)', fontFamily: 'var(--font-display)', letterSpacing: '-0.01em', lineHeight: 1.2 }}>Activação</h1>
            <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 6 }}>
              {kpis.total} campanha{kpis.total !== 1 ? 's' : ''}{isAll ? ' · todas as marcas' : ''}
            </div>
          </div>
        </div>
        <div style={{ height: 1, background: 'var(--border)', margin: '16px -40px 0' }} />
        <div style={{ display: 'flex', gap: 0, overflowX: 'auto' }}>
          {[{ slug: 'todas', name: 'Todas' }, ...allBrands].map(b => {
            const active = b.slug === 'todas' ? isAll : brand === b.slug;
            const n = b.slug === 'todas' ? todas.length : todas.filter(c => c.marca_slug === b.slug).length;
            return (
              <button key={b.slug} onClick={() => setBrand && setBrand(b.slug === 'todas' ? 'todas' : b.slug)}
                style={{
                  background: 'none', border: 'none', outline: 'none',
                  borderBottom: `2px solid ${active ? 'var(--ai-500)' : 'transparent'}`,
                  color: active ? 'var(--text)' : 'var(--text-muted)',
                  fontSize: 13, fontWeight: active ? 600 : 500,
                  fontFamily: 'var(--font-display)', letterSpacing: '.01em',
                  padding: '10px 18px 12px', cursor: 'pointer', whiteSpace: 'nowrap',
                }}>
                {b.name}
                {data && <span style={{ marginLeft: 6, fontSize: 11, fontWeight: 650, color: n ? 'inherit' : 'var(--text-dim)' }}>{n}</span>}
              </button>
            );
          })}
        </div>
      </div>

      <div className="scrollbar" style={{ flex: 1, minHeight: 0, overflowY: 'auto', overflowX: 'clip', padding: '28px 40px 0', background: 'var(--bg, #f8fafc)', display: 'flex', flexDirection: 'column', gap: 18 }}>
        {!loading && !error && data && <ActBanner kpis={kpis} campanhas={daMarca} userName={user.nome_apresentar} marcaNome={marcaNome} outras={outras} />}

        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 8 }}>
          <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>
            <ActDropdown label="Estado" value={filterEstado} onChange={setFilterEstado} options={[
              { value: 'all', label: 'Todos' },
              { value: 'a_produzir', label: 'A produzir' },
              { value: 'setup', label: 'Setup' },
              { value: 'agendado', label: 'Agendado' },
              { value: 'publicado', label: 'Publicado' },
              { value: 'desactivado', label: 'Desactivado' },
              { value: 'falhou', label: 'Falhou' },
            ]} />
            <ActDropdown label="Canal" value={filterCanal} onChange={setFilterCanal} options={[
              { value: 'all', label: 'Todos' },
              ...ACT_CHANNELS.map(c => ({ value: c.id, label: c.label })),
            ]} />
            {hasActive && (
              <button onClick={() => { setFilterEstado('all'); setFilterCanal('all'); }} style={{
                background: 'none', border: 'none', color: 'var(--text-muted)', fontSize: 11,
                cursor: 'pointer', padding: '4px 6px', fontFamily: 'var(--font-mono)',
              }}>✕ limpar</button>
            )}
            <span style={{ fontSize: 12, color: 'var(--text-muted)', paddingLeft: 4 }}>
              {filteredCount} resultado{filteredCount !== 1 ? 's' : ''}
            </span>
          </div>
        </div>

        <div style={{ display: 'flex', gap: 12 }}>
          {kpiCards.map((k, i) => {
            const isActive = k.filter !== 'all' && filterEstado === k.filter;
            return (
              <div key={k.label} onClick={() => setFilterEstado(isActive ? 'all' : k.filter)}
                style={{
                  flex: '1 1 0', background: 'var(--bg-elev, #ffffff)',
                  border: `1px solid ${isActive ? k.accent : 'var(--border, #dde3ef)'}`,
                  borderRadius: 8, padding: '12px 16px 0',
                  display: 'flex', flexDirection: 'column', gap: 4, overflow: 'hidden',
                  cursor: 'pointer',
                  animation: 'slide-up 0.25s ease-out both',
                  animationDelay: `${i * 60}ms`,
                }}>
                <div style={{ fontSize: 9.5, fontWeight: 600, letterSpacing: '0.10em', textTransform: 'uppercase', color: isActive ? k.accent : 'var(--text-muted)', fontFamily: 'var(--font-mono)', whiteSpace: 'nowrap' }}>{k.label}</div>
                <div style={{ fontSize: 22, fontWeight: 700, fontFamily: 'var(--font-display)', color: k.accent, lineHeight: 1, paddingBottom: 10 }}>{k.value}</div>
                <div style={{ height: 3, background: 'var(--bg-sunken, #eef1f6)', overflow: 'hidden' }}>
                  <div style={{ height: '100%', width: `${(k.fill || 0) * 100}%`, background: k.accent }} />
                </div>
              </div>
            );
          })}
        </div>

        {error && <div style={{ color: '#dc2626', fontSize: 13 }}>{error}</div>}
        <ActLista campanhas={campanhas} isAll={isAll} onOpen={onOpen} filterEstado={filterEstado} vazio={!isAll && outras > 0 && (
          <button type="button" onClick={() => setBrand && setBrand('todas')} style={{ marginTop: 8, height: 32, padding: '0 12px', borderRadius: 6, border: '1px solid var(--border)', background: '#fff', color: '#112954', fontSize: 12, fontWeight: 650, fontFamily: 'inherit', cursor: 'pointer' }}>Ver todas as marcas</button>
        )} />
      </div>
    </div>
  );
};

const MktActivacaoScreen = () => {
  const [hash, setHash] = React.useState(actParseHash());
  React.useEffect(() => {
    const params = new URLSearchParams(window.location.hash.replace(/^#/, ''));
    if (params.get('sub') === 'publicacao' && params.get('screen') === 'marketing') return;
    params.set('screen', 'marketing');
    params.set('sub', 'publicacao');
    window.location.hash = params.toString();
  }, []);
  React.useEffect(() => {
    const onHash = () => setHash(actParseHash());
    window.addEventListener('hashchange', onHash);
    return () => {
      window.removeEventListener('hashchange', onHash);
      actClearCampFromHash();
    };
  }, []);
  const user = React.useMemo(() => actGetUser(), []);

  return (
    <ActJobProvider>
      <div style={{ height: '100%', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
        <style>{`
          @keyframes slide-up { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
          @keyframes act-spin { to { transform: rotate(360deg); } }
        `}</style>
        {hash.camp
          ? <ActDetalhe
              campId={hash.camp}
              canalId={hash.canal}
              initialTab={hash.tab}
              user={user}
              onBack={() => actSetCamp(null)}
              onOpenCanal={(canal) => actSetCamp(hash.camp, canal)}
            />
          : <ActHome user={user} onOpen={id => actSetCamp(id)} />
        }
      </div>
    </ActJobProvider>
  );
};

window.MktActivacaoScreen = MktActivacaoScreen;
