/* screen_marketing_campanhas.jsx
   Marketing · Campanhas — Geração de conceito, copy e prompts visuais a partir de briefings aprovados.
   Fluxo: conceito_pendente → conceito_gerado → copy_pendente → copy_gerado
          → prompts_pendente → prompts_gerado → em_producao
   Expõe window.MktCampanhasScreen.
*/

// ── Cores de marca ─────────────────────────────────────────────────────────────
const _BC = {
  mimaki: '#e60012', biond: '#00a86b', decal: '#f59e0b',
  alldecor: '#ec4899', sensek: '#8B2DE8', netscreen: '#F97316', digidelta: '#3859D0',
};
const _brandColor = (slug) => _BC[slug] || '#3859D0';

// Helper global: limpa travessões e en-dashes de todo o texto gerado por IA
const ct = (t) => !t ? t : String(t).replace(/\u2014|\u2013/g, ',').replace(/ ,/g, ',').replace(/,,+/g, ',').replace(/,\s*,/g, ',');

// Split texto em parágrafos numerados: tenta (1)(2)(3), fallback por ponto+vírgula, fallback por \n
const splitPoints = (raw) => {
  if (!raw) return [];
  const cleaned = ct(raw);
  // Tenta split por (1) (2) (3)...
  const parts = cleaned.split(/\s*\((\d+)\)\s*/).reduce((acc, seg, i, arr) => {
    if (/^\d+$/.test(seg)) return acc;
    const num = /^\d+$/.test(arr[i-1]) ? arr[i-1] : null;
    const txt = seg.trim();
    if (txt) acc.push({ num, txt });
    return acc;
  }, []);
  if (parts.length > 1) return parts;
  // Fallback 1: split por ponto e vírgula (;)
  const bySemi = cleaned.split(/;\s*/).map(t => t.trim()).filter(t => t.length > 20);
  if (bySemi.length > 1) return bySemi.map((txt, i) => ({ num: String(i + 1), txt: txt.replace(/\.$/, '') + '.' }));
  // Fallback 2: split por ". " seguido de nome de canal ou palavra maiúscula (para abordagem)
  const byPeriod = cleaned.split(/\.\s+(?=[A-ZÁÉÍÓÚÀÂÊÔÃÕÇ])/).map(t => t.trim()).filter(t => t.length > 30);
  if (byPeriod.length > 1) return byPeriod.map((txt, i) => ({ num: String(i + 1), txt: txt.replace(/\.$/, '') + '.' }));
  // Fallback: parágrafo único
  return [{ num: null, txt: cleaned }];
};

// ── Helpers ────────────────────────────────────────────────────────────────────
// KPIs podem vir como string[] (legado) ou {canal,kpi}[] (novo formato)
const formatKpis = (arr) =>
  (Array.isArray(arr) ? arr : [])
    .filter(Boolean)
    .map(k => typeof k === 'string' ? k : (k.canal ? `${k.canal}: ${k.kpi}` : k.kpi || ''))
    .filter(Boolean)
    .join(' · ') || '—';

// ── API ────────────────────────────────────────────────────────────────────────
async function campApiCall(url, options = {}) {
  let res;
  try {
    res = await fetch(url, { headers: { 'Content-Type': 'application/json' }, ...options });
  } catch (e) {
    throw new Error(`Erro de rede: ${e.message}`);
  }
  if (!res.ok) {
    let body = {};
    try { body = await res.json(); } catch {}
    throw new Error(body.error || `HTTP ${res.status}`);
  }
  return res.json();
}

const CampAPI = {
  list:             ()           => campApiCall('/api/marketing/campanhas'),
  get:              (id)         => campApiCall(`/api/marketing/campanhas/${id}`),
  update:           (id, body)   => campApiCall(`/api/marketing/campanhas/${id}`, { method: 'PUT',    body: JSON.stringify(body) }),
  remove:           (id)         => campApiCall(`/api/marketing/campanhas/${id}`, { method: 'DELETE' }),
  arquivo:          (id)         => campApiCall(`/api/marketing/campanhas/${id}/arquivo`),
  arquivar:         (id)         => campApiCall(`/api/marketing/campanhas/${id}/arquivar`, { method: 'POST', body: '{}' }),
  restaurar:        (id)         => campApiCall(`/api/marketing/campanhas/${id}/restaurar`, { method: 'POST', body: '{}' }),
  concluir:         (id)         => campApiCall(`/api/marketing/campanhas/${id}/concluir`, { method: 'POST', body: '{}' }),
  reabrir:          (id)         => campApiCall(`/api/marketing/campanhas/${id}/reabrir`, { method: 'POST', body: '{}' }),
  generateSchedule: (id, onStep) => {
    return new Promise(async (resolve, reject) => {
      try {
        const res = await fetch(`/api/marketing/campanhas/${id}/generate-schedule`, { method: 'POST', headers: { 'Content-Type': 'application/json' } });
        const reader = res.body.getReader(); const decoder = new TextDecoder(); let buf = '';
        while (true) {
          const { done, value } = await reader.read();
          if (value) {
            buf += decoder.decode(value, { stream: !done });
            const lines = buf.split('\n'); buf = done ? '' : (lines.pop() ?? '');
            for (const line of lines) {
              if (!line.startsWith('data: ')) continue;
              let evt; try { evt = JSON.parse(line.slice(6)); } catch { continue; }
              if (evt.type === 'status' && onStep) onStep(evt.step, evt.message);
              else if (evt.type === 'done') resolve(evt);
              else if (evt.type === 'error') reject(new Error(evt.message));
            }
          }
          if (done) break;
        }
      } catch (e) { reject(e); }
    });
  },
  generateIdiomas: (id, extraLinguas, onStep) => {
    return new Promise(async (resolve, reject) => {
      try {
        const res = await fetch(`/api/marketing/campanhas/${id}/generate-idiomas`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ extra_linguas: extraLinguas || [] }) });
        const reader = res.body.getReader(); const decoder = new TextDecoder(); let buf = '';
        while (true) {
          const { done, value } = await reader.read();
          if (value) {
            buf += decoder.decode(value, { stream: !done });
            const lines = buf.split('\n'); buf = done ? '' : (lines.pop() ?? '');
            for (const line of lines) {
              if (!line.startsWith('data: ')) continue;
              let evt; try { evt = JSON.parse(line.slice(6)); } catch { continue; }
              if (evt.type === 'status' && onStep) onStep(evt.step, evt.message, evt.total, evt.langs);
              else if (evt.type === 'done') resolve(evt.data);
              else if (evt.type === 'error') reject(new Error(evt.message));
            }
          }
          if (done) { if (buf.startsWith('data: ')) { let e; try { e=JSON.parse(buf.slice(6)); if(e.type==='done') resolve(e.data); } catch {} } break; }
        }
      } catch (e) { reject(e); }
    });
  },
  patchIdiomas: (id, data) => fetch(`/api/marketing/campanhas/${id}/idiomas`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }).then(r => r.json()),
  generateOrcamento: (id, onStep, body) => {
    return new Promise(async (resolve, reject) => {
      try {
        const res = await fetch(`/api/marketing/campanhas/${id}/orcamento/generate`, {
          method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body || {}),
        });
        if (!res.ok && !(res.headers.get('content-type') || '').includes('text/event-stream')) {
          let msg = `HTTP ${res.status}`;
          try { const j = await res.json(); msg = j.error || msg; } catch {}
          return reject(new Error(msg));
        }
        const reader = res.body.getReader();
        const decoder = new TextDecoder();
        let buf = '';
        while (true) {
          const { done, value } = await reader.read();
          if (value) {
            buf += decoder.decode(value, { stream: !done });
            const lines = buf.split('\n');
            buf = done ? '' : (lines.pop() ?? '');
            for (const line of lines) {
              if (!line.startsWith('data: ')) continue;
              let evt; try { evt = JSON.parse(line.slice(6)); } catch { continue; }
              if (evt.type === 'status' && onStep) onStep(evt.step, evt.message);
              else if (evt.type === 'done') resolve(evt.data || evt);
              else if (evt.type === 'error') reject(new Error(evt.message));
            }
          }
          if (done) break;
        }
      } catch (e) { reject(e); }
    });
  },
  generateConceito: (id, onStep) => {
    return new Promise(async (resolve, reject) => {
      try {
        const res = await fetch(`/api/marketing/campanhas/${id}/generate-conceito`, { method: 'POST', headers: { 'Content-Type': 'application/json' } });
        // Verificar erro HTTP antes de ler como SSE
        if (!res.ok) {
          let msg = `HTTP ${res.status}`;
          try { const j = await res.json(); msg = j.error || msg; } catch {}
          return reject(new Error(msg));
        }
        const reader = res.body.getReader();
        const decoder = new TextDecoder();
        let buf = '';
        while (true) {
          const { done, value } = await reader.read();
          if (value) {
            buf += decoder.decode(value, { stream: !done });
            const lines = buf.split('\n');
            buf = done ? '' : (lines.pop() ?? '');
            for (const line of lines) {
              if (!line.startsWith('data: ')) continue;
              let evt; try { evt = JSON.parse(line.slice(6)); } catch { continue; }
              if (evt.type === 'status' && onStep) onStep(evt.step, evt.message);
              else if (evt.type === 'done') resolve(evt.data);
              else if (evt.type === 'error') reject(new Error(evt.message));
            }
          }
          if (done) { if (buf.startsWith('data: ')) { let e; try { e=JSON.parse(buf.slice(6)); if(e.type==='done') resolve(e.data); } catch {} } break; }
        }
      } catch (e) { reject(e); }
    });
  },
  generateCommPlan: (id, onStep) => {
    return new Promise(async (resolve, reject) => {
      try {
        const res = await fetch(`/api/marketing/campanhas/${id}/generate-comm-plan`, { method: 'POST', headers: { 'Content-Type': 'application/json' } });
        if (!res.ok) { let msg = `HTTP ${res.status}`; try { const j = await res.json(); msg = j.error || msg; } catch {} return reject(new Error(msg)); }
        const reader = res.body.getReader(); const decoder = new TextDecoder(); let buf = '';
        while (true) {
          const { done, value } = await reader.read();
          if (value) {
            buf += decoder.decode(value, { stream: !done });
            const lines = buf.split('\n'); buf = done ? '' : (lines.pop() ?? '');
            for (const line of lines) {
              if (!line.startsWith('data: ')) continue;
              let evt; try { evt = JSON.parse(line.slice(6)); } catch { continue; }
              if (evt.type === 'status' && onStep) onStep(evt.step, evt.message);
              else if (evt.type === 'done') resolve(evt);
              else if (evt.type === 'error') reject(new Error(evt.message));
            }
          }
          if (done) break;
        }
      } catch (e) { reject(e); }
    });
  },
  aprovarConceito:  (id, actor)  => campApiCall(`/api/marketing/campanhas/${id}/conceito`, { method: 'PATCH', body: JSON.stringify({ action: 'aprovar', ...(actor||{}) }) }),
  patchConceito:    (id, data)   => campApiCall(`/api/marketing/campanhas/${id}/conceito`, { method: 'PATCH', body: JSON.stringify(data) }),
  generateCopy: (id, onStep) => {
    return new Promise(async (resolve, reject) => {
      try {
        const res = await fetch(`/api/marketing/campanhas/${id}/generate-copy`, { method: 'POST', headers: { 'Content-Type': 'application/json' } });
        const reader = res.body.getReader();
        const decoder = new TextDecoder();
        let buf = '';
        while (true) {
          const { done, value } = await reader.read();
          if (value) {
            buf += decoder.decode(value, { stream: !done });
            const lines = buf.split('\n');
            buf = done ? '' : (lines.pop() ?? '');
            for (const line of lines) {
              if (!line.startsWith('data: ')) continue;
              let evt; try { evt = JSON.parse(line.slice(6)); } catch { continue; }
              if (evt.type === 'status' && onStep) onStep(evt.step, evt.message);
              else if (evt.type === 'done') resolve(evt.data);
              else if (evt.type === 'error') reject(new Error(evt.message));
            }
          }
          if (done) { if (buf.startsWith('data: ')) { let e; try { e=JSON.parse(buf.slice(6)); if(e.type==='done') resolve(e.data); } catch {} } break; }
        }
      } catch (e) { reject(e); }
    });
  },
  patchCopy:        (copyId, b)  => campApiCall(`/api/marketing/copy/${copyId}`,    { method: 'PATCH', body: JSON.stringify(b) }),
  approveCopyAll:   (id)         => campApiCall(`/api/marketing/campanhas/${id}/copy/approve-all`, { method: 'POST', body: JSON.stringify({ approver_name: window.currentUser?.nome || null, approver_email: window.currentUser?.email || null }) }),
  generatePrompts:  (id)         => campApiCall(`/api/marketing/campanhas/${id}/prepare-prompts`, { method: 'POST' }),
  patchPrompt:      (pId, b)     => campApiCall(`/api/marketing/prompts/${pId}`,    { method: 'PATCH', body: JSON.stringify(b) }),
  enviarProducao:   (id, actor)  => campApiCall(`/api/marketing/campanhas/${id}/enviar-producao`, { method: 'POST', body: JSON.stringify(actor||{}) }),
  publicar:         (id, data)   => campApiCall(`/api/marketing/campanhas/${id}/publicar`, { method: 'POST', body: JSON.stringify(data) }),
  generateCanais: (id, onStep) => new Promise(async (resolve, reject) => {
    try {
      const res = await fetch(`/api/marketing/campanhas/${id}/generate-channel-setup`, { method: 'POST', headers: { 'Content-Type': 'application/json' } });
      const reader = res.body.getReader(); const decoder = new TextDecoder(); let buf = '';
      while (true) {
        const { done, value } = await reader.read();
        if (value) {
          buf += decoder.decode(value, { stream: !done });
          const lines = buf.split('\n'); buf = done ? '' : (lines.pop() ?? '');
          for (const line of lines) {
            if (!line.startsWith('data: ')) continue;
            let evt; try { evt = JSON.parse(line.slice(6)); } catch { continue; }
            if (evt.type === 'status' && onStep) onStep(evt.step, evt.message);
            else if (evt.type === 'done') resolve(evt.data);
            else if (evt.type === 'error') reject(new Error(evt.message));
          }
        }
        if (done) break;
      }
    } catch (e) { reject(e); }
  }),
  aprovarCanais:  (id, actor) => campApiCall(`/api/marketing/campanhas/${id}/canais`, { method: 'PATCH', body: JSON.stringify({ action: 'aprovar', ...(actor||{}) }) }),
  patchIdiomas:   (id, data) => campApiCall(`/api/marketing/campanhas/${id}/idiomas`, { method: 'PATCH', body: JSON.stringify(data) }),
  getBriefing:           (bid)  => campApiCall(`/api/marketing/briefings/${bid}`),
  getBrands:             ()     => campApiCall('/api/marketing/brands'),
  availableForCampaign:  ()     => campApiCall('/api/marketing/briefings/available-for-campaign'),
  create:                (body) => campApiCall('/api/marketing/campanhas', { method: 'POST', body: JSON.stringify(body) }),
  oneShot:               (body) => campApiCall('/api/marketing/producao/one-shot', { method: 'POST', body: JSON.stringify(body) }),
  requestChanges:        (id, data) => campApiCall(`/api/marketing/campanhas/${id}/approval`, { method: 'POST', body: JSON.stringify(data) }),
  getEstrategia:    (id)         => campApiCall(`/api/marketing/campanhas/${id}/estrategia`),
  generateEstrategia: (id, onStep, opts) => new Promise(async (resolve, reject) => {
    try {
      const res = await fetch(`/api/marketing/campanhas/${id}/generate-estrategia`, {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(opts?.manterEstado ? { manter_estado: true } : {}),
      });
      const reader = res.body.getReader(); const decoder = new TextDecoder(); let buf = '';
      while (true) {
        const { done, value } = await reader.read();
        if (value) {
          buf += decoder.decode(value, { stream: !done });
          const events = buf.split('\n\n'); buf = done ? '' : (events.pop() || '');
          for (const evt of events) {
            const line = evt.trim(); if (!line.startsWith('data:')) continue;
            let parsed; try { parsed = JSON.parse(line.slice(5).trim()); } catch { continue; }
            if (parsed.type === 'status' && onStep) onStep(parsed.step || 0, parsed.message);
            else if (parsed.type === 'done')  resolve(parsed);
            else if (parsed.type === 'error') reject(new Error(parsed.message));
          }
        }
        if (done) break;
      }
    } catch (e) { reject(e); }
  }),
  aprovarEstrategia: (id, approver) => campApiCall(`/api/marketing/campanhas/${id}/estrategia/approve`, {
    method: 'POST', headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ approver_email: approver?.email, approver_name: approver?.name }),
  }),
};
window.MktCampanhasAPI = { list: CampAPI.list };

// ── Toast global partilhado ────────────────────────────────────────────────────
const MktCampToast = ({ msg, type }) => {
  if (!msg) return null;
  const bg = type === 'error' ? '#dc2626' : type === 'info' ? '#d97706' : '#15803d';
  return (
    <div style={{
      position: 'fixed', bottom: 24, right: 24, zIndex: 9999,
      padding: '12px 20px', borderRadius: 8, fontSize: 13, fontWeight: 500,
      background: bg, color: '#fff', boxShadow: '0 4px 16px rgba(0,0,0,.2)',
      fontFamily: 'var(--font-body, Inter, sans-serif)',
    }}>{msg}</div>
  );
};

// ── Permissões ─────────────────────────────────────────────────────────────────
const canActOnCampaign = (user) => {
  const u = (user && typeof user === 'object') ? user : (window.currentUser || {});
  const tipo = String(u.tipo || u.tipo_utilizador || u.role || '').toLowerCase();
  const modos = []
    .concat(Array.isArray(u.modos) ? u.modos : [])
    .concat(Array.isArray(u.modos_acesso) ? u.modos_acesso : [])
    .map(m => String(m).toLowerCase());
  return tipo === 'admin' || tipo === 'c_suite' || modos.includes('dev') || modos.includes('admin');
};
const FINAL_STATUSES = ['em_producao', 'em_activacao', 'publicado'];

const CampFichaCard = ({ title, action, children }) => (
  <div style={{ background: '#fff', border: '1px solid var(--border, #dde3ef)', borderRadius: 10, padding: 16 }}>
    <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, marginBottom: 12 }}>
      <div style={{ fontSize: 11, fontWeight: 650, letterSpacing: '.06em', textTransform: 'uppercase', color: 'var(--text-dim, #6b7fa3)' }}>{title}</div>
      {action}
    </div>
    <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>{children}</div>
  </div>
);

const CampFichaLabel = ({ children }) => (
  <div style={{ fontSize: 12, fontWeight: 650, color: 'var(--text, #112954)', marginBottom: 6 }}>{children}</div>
);

const campChip = { fontSize: 12.5, fontWeight: 650, padding: '8px 10px', borderRadius: 8, border: '1px solid var(--border, #dde3ef)', background: '#fff', color: 'var(--text, #112954)', fontFamily: 'inherit' };
const btnCarta = { fontSize: 11, height: 26, padding: '0 10px' };

const CAMP_OBJECTIVO = {
  awareness: 'Awareness',
  lead_gen: 'Geração de leads',
  activacao: 'Activação / Promoção',
  conversion: 'Conversão',
  retention: 'Retenção / Upsell',
};
const CAMP_CTA = {
  quote_request: 'Pedido de proposta',
  demo_request: 'Pedido de demonstração',
  testing_roll: 'Pedir Testing Roll',
  sample_request: 'Pedir Amostra',
  visit_showroom: 'Visita ao showroom',
  whatsapp: 'WhatsApp',
  form: 'Formulário',
  call: 'Chamada directa',
  landing: 'Landing page',
  none: 'Sem CTA específico',
};

const campanhaSeed = (campanha) => {
  const a = campanha.activacao_json && typeof campanha.activacao_json === 'object' ? campanha.activacao_json : {};
  const b = campanha.briefing || {};
  const pick = (k) => Object.prototype.hasOwnProperty.call(a, k) ? a[k] : b[k];
  const dia = (v) => v ? String(v).slice(0, 10) : '';
  return {
    geo_markets: pick('geo_markets') || [],
    objective: pick('objective') || '',
    kpis: (Array.isArray(pick('kpis')) ? pick('kpis') : []).map(k => (
      k && typeof k === 'object' && k.canal === 'daily_prospect' ? { ...k, canal: 'equipa_sdr' } : k
    )),
    channels: (() => {
      const mapa = { daily_prospect: 'equipa_sdr', sdr: 'equipa_sdr' };
      const fora = new Set(['comercial', 'logistica']);
      return [...new Set((pick('channels') || []).map(id => mapa[id] || id).filter(id => id && !fora.has(id)))];
    })(),
    meta_ads_conversao: pick('meta_ads_conversao') || '',
    tone: pick('tone') || '',
    timeline_start: dia(pick('timeline_start')),
    timeline_end: dia(pick('timeline_end')),
    key_message: pick('key_message') || '',
    previous_campaigns: pick('previous_campaigns') || [],
    commercial_offer: pick('commercial_offer') || null,
  };
};

const campanhaEmFalta = (data) => {
  const faltam = [];
  if (!Array.isArray(data.geo_markets) || data.geo_markets.length === 0) faltam.push('presença');
  if (!data.objective) faltam.push('objectivo');
  if (!Array.isArray(data.channels) || data.channels.length === 0) faltam.push('canais');
  if (!String(data.key_message || '').trim()) faltam.push('mensagem-chave');
  return faltam;
};

const campCampo = {
  width: '100%', padding: '9px 11px', borderRadius: 8, border: '1px solid var(--border, #dde3ef)',
  fontSize: 13, fontFamily: 'inherit', boxSizing: 'border-box', background: '#fff', color: 'var(--text, #112954)',
};
const CampEscolha = ({ on, onClick, children }) => (
  <button type="button" onClick={onClick} style={{
    padding: '8px 10px', borderRadius: 8, cursor: 'pointer', fontFamily: 'inherit',
    border: `1px solid ${on ? 'var(--ai-500, #3859D0)' : 'var(--border, #dde3ef)'}`,
    background: on ? 'color-mix(in oklch, var(--ai-500, #3859D0) 12%, #fff)' : '#fff',
    color: on ? 'var(--ai-500, #3859D0)' : 'var(--text, #112954)',
    fontSize: 12.5, fontWeight: 650, lineHeight: 1.25,
  }}>{children}</button>
);
const CampChipRemovivel = ({ children, onRemove }) => (
  <span style={{ ...campChip, display: 'inline-flex', alignItems: 'center', gap: 6 }}>
    {children}
    <button type="button" onClick={onRemove} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--dd-primary-900,#112954)', fontSize: 14, lineHeight: 1, padding: 0 }}>×</button>
  </span>
);

const CampPresenca = ({ brand, value, onChange }) => {
  const [q, setQ] = React.useState('');
  const [aberto, setAberto] = React.useState(false);
  const caixa = React.useRef(null);
  const selected = (value || []).map(m => (m && typeof m === 'object') ? (m.country || m.name || '') : String(m || '')).filter(Boolean);
  const toggle = (nome) => onChange(selected.includes(nome) ? selected.filter(x => x !== nome) : [...selected, nome]);
  const cfg = (typeof BRAND_MARKETS_CONFIG !== 'undefined' && BRAND_MARKETS_CONFIG[brand]) || null;
  React.useEffect(() => {
    if (!aberto) return;
    const fechar = (e) => { if (caixa.current && !caixa.current.contains(e.target)) setAberto(false); };
    document.addEventListener('mousedown', fechar);
    return () => document.removeEventListener('mousedown', fechar);
  }, [aberto]);
  const chips = (
    <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
      {selected.map(nome => <CampChipRemovivel key={nome} onRemove={() => toggle(nome)}>{nome}</CampChipRemovivel>)}
    </div>
  );
  if (cfg?.tipo === 'fixo') {
    const restam = (cfg.opcoes || []).filter(m => !selected.includes(m));
    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
        {chips}
        {restam.length > 0 && (
          <select value="" onChange={e => e.target.value && toggle(e.target.value)} style={{ ...campCampo, maxWidth: 220 }}>
            <option value="">Adicionar país</option>
            {restam.map(m => <option key={m} value={m}>{m}</option>)}
          </select>
        )}
      </div>
    );
  }
  const hier = typeof GEO_HIERARCHY !== 'undefined' ? GEO_HIERARCHY : {};
  const termo = q.trim().toLowerCase();
  const sugestoes = termo ? Object.entries(hier).flatMap(([regiao, paises]) => (paises || []).map(p => ({ p, regiao })))
    .filter(x => !selected.includes(x.p) && x.p.toLowerCase().includes(termo))
    .sort((a, b) => (a.p.toLowerCase().startsWith(termo) ? 0 : 1) - (b.p.toLowerCase().startsWith(termo) ? 0 : 1) || a.p.localeCompare(b.p, 'pt'))
    .slice(0, 8) : [];
  const adicionar = (nome) => { onChange([...selected, nome]); setQ(''); setAberto(false); };
  return (
    <div ref={caixa} style={{ display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center' }}>
      {chips}
      <div style={{ position: 'relative', width: 200 }}>
        <input value={q} placeholder="Adicionar país" onChange={e => { setQ(e.target.value); setAberto(true); }} onFocus={() => setAberto(true)}
          onKeyDown={e => { if (e.key === 'Enter' && sugestoes[0]) { e.preventDefault(); adicionar(sugestoes[0].p); } if (e.key === 'Escape') setAberto(false); }}
          style={campCampo} />
        {aberto && termo && (
          <div style={{ position: 'absolute', zIndex: 20, top: 'calc(100% + 4px)', left: 0, right: 0, background: '#fff', border: '1px solid var(--border, #dde3ef)', borderRadius: 8, boxShadow: '0 4px 12px rgba(0,0,0,0.08)', overflow: 'hidden' }}>
            {sugestoes.length === 0 ? (
              <div style={{ padding: '8px 10px', fontSize: 12, color: 'var(--fg-3)' }}>Sem países com esse nome.</div>
            ) : sugestoes.map(s => (
              <button key={s.p} type="button" onClick={() => adicionar(s.p)} style={{ display: 'flex', width: '100%', justifyContent: 'space-between', gap: 8, background: 'none', border: 'none', cursor: 'pointer', padding: '8px 10px', fontSize: 13, color: 'var(--fg-1,#112954)', textAlign: 'left' }}>
                <span>{s.p}</span>
                <span style={{ fontSize: 11, color: 'var(--fg-3)' }}>{s.regiao}</span>
              </button>
            ))}
          </div>
        )}
      </div>
    </div>
  );
};

const CampKpisEditor = ({ items, channels, onChange }) => {
  const rows = (Array.isArray(items) ? items : []).map(k => (
    typeof k === 'string' ? { canal: 'negocio', kpi: k } : { canal: k?.canal || 'negocio', kpi: k?.kpi || '' }
  ));
  const canais = ['negocio', ...(channels || []).filter(c => c !== 'negocio')];
  rows.forEach(r => { if (r.canal && !canais.includes(r.canal)) canais.push(r.canal); });
  const semPaga = new Set(['negocio', 'linkedin_organic', 'equipa_consumiveis', 'equipa_equipamentos', 'equipa_sdr', 'comercial', 'daily_prospect', 'distribuidores', 'logistica', 'email', 'whatsapp', 'website']);
  const setRow = (i, patch) => onChange(rows.map((r, idx) => idx === i ? { ...r, ...patch } : r));
  return (
    <div>
      {rows.map((k, i) => {
        const avisoPago = semPaga.has(k.canal) && /\b(cpl|cpc|roas)\b/i.test(k.kpi || '');
        return (
          <div key={i} style={{ marginBottom: 8 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
              <select value={k.canal || 'negocio'} onChange={e => setRow(i, { canal: e.target.value })} style={{ ...campCampo, width: 180, flexShrink: 0 }}>
                {canais.map(c => <option key={c} value={c}>{KPIS_CANAL_LABELS[c] || c}</option>)}
              </select>
              <input value={k.kpi} placeholder="Alvo deste KPI" onChange={e => setRow(i, { kpi: e.target.value })} style={{ ...campCampo, flex: 1 }} />
              <button type="button" onClick={() => onChange(rows.filter((_, idx) => idx !== i))} style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#dc2626', fontSize: 16, padding: 0, flexShrink: 0 }}>×</button>
            </div>
            {avisoPago && <div style={{ fontSize: 11, color: '#92400e', marginTop: 3 }}>CPL, CPC e ROAS só em canais pagos.</div>}
          </div>
        );
      })}
      <button type="button" className="btn" style={{ fontSize: 11, height: 26, padding: '0 10px' }} onClick={() => onChange([...rows, { canal: 'negocio', kpi: '' }])}>+ KPI</button>
    </div>
  );
};

const CampOfertaEditor = ({ brand, offer, onChange }) => {
  const [solutions, setSolutions] = React.useState({});
  React.useEffect(() => {
    if (!brand) { setSolutions({}); return; }
    fetch(`/api/marketing/brand-solutions?marca_slug=${encodeURIComponent(brand)}`)
      .then(r => r.ok ? r.json() : {})
      .then(d => setSolutions(d || {}))
      .catch(() => setSolutions({}));
  }, [brand]);
  const catalogo = Object.keys(solutions || {});
  const mecanicas = [...new Set([...catalogo, ...OFFER_ALWAYS_MECHANICS, ...(offer?.type ? [offer.type] : [])])];
  const tiers = offer?.type ? (solutions[offer.type] || []) : [];
  const escolher = (type) => {
    if (!type) { onChange(null); return; }
    if (offer?.type === type) return;
    if (type === 'promo_bundle') onChange({ type, buy_qty: 2, get_qty: 1, get_item: '', condition: '', validity: '', details: '', primary_cta: '', negotiable: false });
    else if (type === 'custom') onChange({ type, headline: '', condition: '', validity: '', details: '', primary_cta: '', negotiable: false });
    else onChange({ type, tier: null, details: '', primary_cta: '', negotiable: false });
  };
  const upd = (field, val) => onChange({ ...(offer || {}), [field]: val });
  const promo = offer?.type === 'promo_bundle';
  const custom = offer?.type === 'custom';
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
      <div>
        <CampFichaLabel>Mecânica</CampFichaLabel>
        <select value={offer?.type || ''} onChange={e => escolher(e.target.value)} style={{ ...campCampo, maxWidth: 320 }}>
          <option value="">Sem oferta</option>
          {mecanicas.map(type => <option key={type} value={type}>{OFFER_MECHANIC_LABELS[type] || type}</option>)}
        </select>
      </div>
      {offer?.type && (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
          {promo && (
            <div style={{ display: 'grid', gridTemplateColumns: '120px 120px 1fr', gap: 12 }}>
              <div>
                <CampFichaLabel>Compra</CampFichaLabel>
                <input type="number" value={offer.buy_qty ?? ''} onChange={e => upd('buy_qty', e.target.value === '' ? '' : Number(e.target.value))} style={campCampo} />
              </div>
              <div>
                <CampFichaLabel>Leva</CampFichaLabel>
                <input type="number" value={offer.get_qty ?? ''} onChange={e => upd('get_qty', e.target.value === '' ? '' : Number(e.target.value))} style={campCampo} />
              </div>
              <div>
                <CampFichaLabel>O que leva</CampFichaLabel>
                <input value={offer.get_item || ''} onChange={e => upd('get_item', e.target.value)} style={campCampo} />
              </div>
            </div>
          )}
          {custom && (
            <div>
              <CampFichaLabel>Título</CampFichaLabel>
              <input value={offer.headline || ''} onChange={e => upd('headline', e.target.value)} style={campCampo} />
            </div>
          )}
          {(promo || custom) && (
            <div>
              <CampFichaLabel>Condição</CampFichaLabel>
              <textarea rows={2} value={offer.condition || ''} onChange={e => upd('condition', e.target.value)} style={{ ...campCampo, resize: 'vertical' }} />
            </div>
          )}
          {!promo && !custom && tiers.length > 0 && (
            <div>
              <CampFichaLabel>Tier</CampFichaLabel>
              <select value={offer.tier || ''} onChange={e => upd('tier', e.target.value || null)} style={{ ...campCampo, maxWidth: 320 }}>
                <option value="">Escolher tier</option>
                {tiers.map(t => <option key={t.tier} value={t.tier}>{t.label || t.tier}</option>)}
              </select>
              {tiers.find(t => t.tier === offer.tier)?.description && (
                <div style={{ fontSize: 12, color: 'var(--fg-3)', marginTop: 6, lineHeight: 1.45 }}>{tiers.find(t => t.tier === offer.tier).description}</div>
              )}
            </div>
          )}
          <div>
            <CampFichaLabel>Detalhe</CampFichaLabel>
            <textarea rows={2} value={offer.details || offer.descricao || ''} onChange={e => upd('details', e.target.value)} style={{ ...campCampo, resize: 'vertical' }} />
          </div>
          {(promo || custom) && (
            <div>
              <CampFichaLabel>Validade</CampFichaLabel>
              <input value={offer.validity || ''} onChange={e => upd('validity', e.target.value)} style={{ ...campCampo, maxWidth: 280 }} />
            </div>
          )}
          <div>
            <CampFichaLabel>CTA</CampFichaLabel>
            <select value={offer.primary_cta || ''} onChange={e => upd('primary_cta', e.target.value)} style={{ ...campCampo, maxWidth: 320 }}>
              <option value="">Sem CTA</option>
              {Object.entries(CAMP_CTA).map(([id, label]) => <option key={id} value={id}>{label}</option>)}
            </select>
          </div>
          <label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13, color: 'var(--fg-1)' }}>
            <input type="checkbox" checked={!!offer.negotiable} onChange={e => upd('negotiable', e.target.checked)} />
            Condições negociáveis
          </label>
        </div>
      )}
    </div>
  );
};

const CampHistoricoEditor = ({ items, onChange }) => {
  const [texto, setTexto] = React.useState('');
  const lista = (items || []).map(x => (x && typeof x === 'object') ? (x.titulo || x.name || '') : String(x || '')).filter(Boolean);
  const adicionar = () => {
    const nome = texto.trim();
    if (!nome) return;
    onChange([...lista, nome]);
    setTexto('');
  };
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
      <div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
        <input value={texto} placeholder="Nome da campanha anterior" onChange={e => setTexto(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); adicionar(); } }} style={campCampo} />
        <button type="button" className="btn" style={btnCarta} onClick={adicionar}>Adicionar</button>
      </div>
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
        {lista.map((nome, i) => (
          <CampChipRemovivel key={i} onRemove={() => onChange(lista.filter((_, idx) => idx !== i))}>{nome}</CampChipRemovivel>
        ))}
      </div>
    </div>
  );
};

const TabCampanha = ({ campanha, onSaved }) => {
  const arquivada = campanha.status === 'arquivada' || campanha.status === 'cancelada';
  const concluida = campanha.status === 'concluida';
  const live = FINAL_STATUSES.includes(campanha.status) || arquivada || concluida;
  const seed = () => campanhaSeed(campanha);
  const [data, setData] = React.useState(seed);
  const [saving, setSaving] = React.useState(false);
  const [aSugerir, setASugerir] = React.useState(false);
  const [aviso, setAviso] = React.useState(null);
  React.useEffect(() => {
    setData(seed());
  }, [campanha.id, campanha.updated_at, campanha.status]);
  const upd = (field, value) => setData(d => ({ ...d, [field]: value }));
  const save = async () => {
    setSaving(true); setAviso(null);
    const payload = {
      ...data,
      kpis: (Array.isArray(data.kpis) ? data.kpis : []).filter(k => String(k?.kpi || k || '').trim()),
    };
    try {
      const res = await fetch(`/api/marketing/campanhas/${campanha.id}/activacao`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ activacao: payload, aprovar: true }),
      });
      const body = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(body.error || 'Não foi possível aprovar a campanha.');
      setAviso({
        tipo: 'ok',
        texto: body.activacao_invalidou
          ? 'Aprovada. A estratégia neste módulo deixa de valer. Conteúdos e activação mantêm-se.'
          : 'Campanha aprovada.',
      });
      onSaved?.(body);
    } catch (e) {
      setAviso({ tipo: 'erro', texto: e.message });
    } finally {
      setSaving(false);
    }
  };
  const cancelar = () => {
    setData(seed());
    setAviso(null);
  };
  const comValor = (novo, actual) => {
    if (Array.isArray(novo)) return novo.length ? novo : actual;
    if (novo && typeof novo === 'object') return novo;
    if (typeof novo === 'string' && novo.trim()) return novo;
    return actual;
  };
  const sugerir = async () => {
    setASugerir(true);
    setAviso(null);
    try {
      const res = await fetch(`/api/marketing/campanhas/${campanha.id}/sugerir-ficha`, { method: 'POST' });
      const body = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(body.error || 'Não foi possível sugerir a ficha.');
      const f = body.ficha || {};
      setData(d => ({
        ...d,
        geo_markets: comValor(f.geo_markets, d.geo_markets),
        objective: comValor(f.objective, d.objective),
        kpis: comValor(f.kpis, d.kpis),
        channels: comValor(f.channels, d.channels),
        meta_ads_conversao: comValor(f.meta_ads_conversao, d.meta_ads_conversao),
        tone: comValor(f.tone, d.tone),
        timeline_start: comValor(f.timeline_start, d.timeline_start),
        timeline_end: comValor(f.timeline_end, d.timeline_end),
        key_message: comValor(f.key_message, d.key_message),
        previous_campaigns: comValor(f.previous_campaigns, d.previous_campaigns),
        commercial_offer: comValor(f.commercial_offer, d.commercial_offer),
      }));
      setAviso({ tipo: 'ok', texto: 'Sugestão aplicada na ficha. Revê e aprova. Cancelar repõe o que está gravado.' });
    } catch (e) {
      setAviso({ tipo: 'erro', texto: e.message });
    } finally {
      setASugerir(false);
    }
  };
  const brand = campanha.brand_slug || campanha.briefing?.brand_slug;
  const faltam = campanhaEmFalta(data);
  const pronta = faltam.length === 0;
  const canais = data.channels || [];
  const offer = data.commercial_offer && typeof data.commercial_offer === 'object' ? data.commercial_offer : null;
  const fotoFicha = (d) => JSON.stringify(['geo_markets', 'objective', 'kpis', 'channels', 'meta_ads_conversao', 'tone', 'timeline_start', 'timeline_end', 'key_message', 'previous_campaigns', 'commercial_offer'].map(k => d?.[k] ?? null));
  const suja = fotoFicha(data) !== fotoFicha(seed());
  const activacao = campanha.activacao_json && typeof campanha.activacao_json === 'object' ? campanha.activacao_json : {};
  const aprovada = !suja && !!activacao.approved_at;
  const aprovadaPor = activacao.approved_by ? String(activacao.approved_by).split('@')[0] : '';
  const liveTexto = concluida
    ? 'Campanha concluída. A ficha fica só de leitura até ser reaberta.'
    : arquivada
      ? 'Campanha arquivada. A ficha fica só de leitura até ser restaurada.'
      : 'Campanha em produção ou activa. A ficha fica só de leitura.';

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 14px', background: '#fff', borderRadius: 8, border: '1px solid var(--border, #dde3ef)', flexWrap: 'wrap' }}>
        <span style={{ fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em', padding: '3px 8px', borderRadius: 'var(--radius-xs,4px)', background: live ? '#FEF3C7' : aprovada ? 'var(--green-100,#E1F7E6)' : 'var(--dd-blue-100,#EBEFF9)', color: live ? '#92400e' : aprovada ? 'var(--green-700,#1F8A52)' : 'var(--dd-primary-600,#3859D0)', fontFamily: 'var(--font-mono)' }}>
          {live ? 'Só de leitura' : aprovada ? 'Aprovada' : pronta ? 'Aguarda aprovação' : 'Incompleta'}
        </span>
        <span style={{ fontSize: 11, color: 'var(--fg-3)' }}>
          {live ? liveTexto : pronta ? 'Presença, objectivo, canais e mensagem-chave estão definidos.' : `Falta ${faltam.join(', ')}.`}
        </span>
        {aprovada && activacao.approved_at && (
          <span style={{ fontSize: 11, color: 'var(--green-700, #1F8A52)' }}>
            · aprovada {new Date(activacao.approved_at).toLocaleString('pt-PT')}{aprovadaPor ? ` por ${aprovadaPor}` : ''}
          </span>
        )}
        {!live && (
          <div style={{ marginLeft: 'auto', display: 'flex', gap: 6, alignItems: 'center' }}>
            <button type="button" onClick={sugerir} disabled={aSugerir || saving} className="btn" style={{ height: 28, padding: '0 12px', fontSize: 12 }}>
              {aSugerir ? 'A sugerir…' : 'Sugerir com Digi AI'}
            </button>
            {!aprovada && suja && <button type="button" onClick={cancelar} className="btn" style={{ height: 28, padding: '0 12px', fontSize: 12 }}>Cancelar</button>}
            {!aprovada && (
              <button type="button" onClick={save} disabled={saving || aSugerir || !pronta} className="btn btn-ai" style={{ height: 28, padding: '0 14px', fontSize: 12 }} title={pronta ? 'Aprovar a ficha da campanha' : `Falta ${faltam.join(', ')}.`}>
                {saving ? 'A aprovar…' : 'Aprovar Campanha'}
              </button>
            )}
          </div>
        )}
      </div>

      {aviso && (
        <div style={{ padding: 12, background: aviso.tipo === 'erro' ? 'rgba(220,38,38,.06)' : '#E1F7E6', border: `1px solid ${aviso.tipo === 'erro' ? 'rgba(220,38,38,.2)' : '#BBF7D0'}`, borderRadius: 8, fontSize: 12, color: aviso.tipo === 'erro' ? '#dc2626' : '#1F8A52' }}>{aviso.texto}</div>
      )}

      <div style={{ display: 'flex', flexDirection: 'column', gap: 14, ...(live ? { pointerEvents: 'none' } : {}) }}>
      <CampFichaCard title="Base da campanha">
        <div>
          <CampFichaLabel>Presença</CampFichaLabel>
          <CampPresenca brand={brand} value={data.geo_markets || []} onChange={v => upd('geo_markets', v)} />
        </div>
        <div>
          <CampFichaLabel>Objectivo</CampFichaLabel>
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
            {Object.entries(CAMP_OBJECTIVO).map(([id, label]) => (
              <CampEscolha key={id} on={data.objective === id} onClick={() => upd('objective', id)}>{label}</CampEscolha>
            ))}
          </div>
        </div>
        <div>
          <CampFichaLabel>KPIs de partida</CampFichaLabel>
          <CampKpisEditor items={data.kpis} channels={canais} onChange={v => upd('kpis', v)} />
          <div style={{ fontSize: 11, color: 'var(--fg-3)', marginTop: 6 }}>Ponto de partida. Os alvos por mercado ficam na tab Estratégia.</div>
        </div>
        <div>
          <CampFichaLabel>Canais</CampFichaLabel>
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
            {CHANNELS_OPTS.map(ch => (
              <CampEscolha key={ch.id} on={canais.includes(ch.id)} onClick={() => upd('channels', canais.includes(ch.id) ? canais.filter(id => id !== ch.id) : [...canais, ch.id])}>{ch.label}</CampEscolha>
            ))}
          </div>
          {canais.includes('meta_ads') && (
            <div style={{ marginTop: 12 }}>
              <CampFichaLabel>Conversão Meta</CampFichaLabel>
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
                {META_CONVERSAO_OPTS.map(o => (
                  <CampEscolha key={o.value} on={data.meta_ads_conversao === o.value} onClick={() => upd('meta_ads_conversao', data.meta_ads_conversao === o.value ? '' : o.value)}>{o.label}</CampEscolha>
                ))}
              </div>
            </div>
          )}
        </div>
        <div>
          <CampFichaLabel>Tom</CampFichaLabel>
          <textarea rows={2} value={data.tone || ''} onChange={e => upd('tone', e.target.value)} style={{ ...campCampo, resize: 'vertical' }} />
        </div>
        <div style={{ display: 'flex', gap: 16 }}>
          <div style={{ width: 180 }}>
            <CampFichaLabel>Início</CampFichaLabel>
            <input type="date" value={data.timeline_start || ''} onChange={e => upd('timeline_start', e.target.value)} style={campCampo} />
          </div>
          <div style={{ width: 180 }}>
            <CampFichaLabel>Fim</CampFichaLabel>
            <input type="date" value={data.timeline_end || ''} onChange={e => upd('timeline_end', e.target.value)} style={campCampo} />
          </div>
        </div>
        <div>
          <CampFichaLabel>Mensagem-chave</CampFichaLabel>
          <textarea rows={4} value={data.key_message || ''} onChange={e => upd('key_message', e.target.value)} style={{ ...campCampo, resize: 'vertical' }} />
        </div>
      </CampFichaCard>

      <CampFichaCard title="Oferta comercial">
        <CampOfertaEditor brand={brand} offer={offer} onChange={v => upd('commercial_offer', v)} />
      </CampFichaCard>

      <CampFichaCard title="Histórico">
        <CampHistoricoEditor items={data.previous_campaigns} onChange={v => upd('previous_campaigns', v)} />
      </CampFichaCard>
      </div>
    </div>
  );
};

// Campanhas com mecânica de risco alto requerem aprovação executiva (Chairman) antes de produção
const requiresExecutiveApproval = (campanha) => {
  if (!campanha?.briefing) return false;
  let offer = campanha?.activacao_json?.commercial_offer || campanha?.briefing?.commercial_offer || campanha?.commercial_offer;
  if (!offer) return false;
  if (typeof offer === 'string') {
    try { offer = JSON.parse(offer); }
    catch {
      console.warn('[requiresExecutiveApproval] parse failed, fail-safe → true');
      return true; // fail-safe: escalar para Chairman quando há dúvida
    }
  }
  return ['large','premium','enterprise'].includes(offer.tier) || ['digirent','trade_in'].includes(offer.type);
};

// ── Kanban ─────────────────────────────────────────────────────────────────────
const KANBAN_COLS = [
  { id: 'campanha',    label: 'Campanha',    color: '#1e40af', statuses: ['estrategia_pendente'] },
  { id: 'estrategia',  label: 'Estratégia',  color: '#5B43C5', statuses: ['estrategia_gerada', 'estrategia_aprovada', 'conceito_pendente'] },
  { id: 'conceito',    label: 'Conceito',    color: '#94a3b8', statuses: ['conceito_gerado', 'conceito', 'canais_pendente', 'canais_gerado', 'canais_aprovado', 'copy_pendente', 'copy_gerado', 'copy_aprovado', 'idiomas_pendente', 'idiomas_gerado', 'idiomas_gerados', 'idiomas_aprovado', 'idiomas_aprovados', 'prompts_pendente', 'prompts_pendentes', 'prompts_gerado', 'prompts_gerados', 'prompts_aprovado', 'geracao', 'aprovacao'] },
  { id: 'orcamento',   label: 'Orçamento',   color: '#0891b2', statuses: ['orcamento_pendente', 'orcamento_gerado', 'orcamento_aprovado'] },
  { id: 'segmentacao', label: 'Target', color: '#7c3aed', statuses: ['segmentacao_pendente', 'segmentacao_gerada', 'segmentacao_aprovada'] },
  { id: 'planeamento', label: 'Planeamento', color: '#0f766e', statuses: ['planeamento_pendente', 'planeamento_gerado', 'planeamento_aprovado'] },
  { id: 'funil',       label: 'Funil',       color: '#0369a1', statuses: ['funil_pendente', 'funil_gerado', 'funil_revisto', 'funil_aprovado'] },
  { id: 'sdr',         label: 'SDR AI',      color: '#7c3aed', statuses: ['sdr_ai_pendente', 'sdr_ai_aprovado'] },
  { id: 'aprovacao',   label: 'Aprovação',   color: '#ea580c', statuses: ['pending_executive', 'em_aprovacao', 'aprovado'] },
  { id: 'producao',    label: 'Produção',    color: '#22c55e', statuses: ['em_producao'] },
  { id: 'activacao',   label: 'Activação',   color: '#3859D0', statuses: ['em_activacao', 'publicado'] },
  { id: 'concluidas',  label: 'Concluídas',  color: '#0e7490', statuses: ['concluida'] },
];

const STATUS_COL = {};
KANBAN_COLS.forEach(col => col.statuses.forEach(s => { STATUS_COL[s] = col; }));

const CANAL_ICON = {
  // IDs antigos (backward compat com briefings e comm_plans existentes)
  instagram: '📸', linkedin: '💼', email: '✉️', whatsapp: '💬',
  ads: '📢', site: '🌐', tiktok: '🎵', led: '💡',
  google_ads: '🔍', facebook: '📘', youtube: '▶️', social: '📱',
  // Novos IDs de canal (briefings 2026+)
  meta_ads: '📢', linkedin_ads: '💼',
  google_ads_search: '🔍', google_ads_display: '🖼',
  muppi_led: '💡', website: '🌐',
  // Formatos específicos gerados pelo conceito
  instagram_post: '📸', instagram_reel: '🎬', instagram_story: '📸', instagram_carrossel: '📸',
  facebook_post: '📘', facebook_reel: '🎬', facebook_story: '📘', facebook_carrossel: '📘',
  linkedin_post: '💼', linkedin_carrossel: '💼', linkedin_video: '🎬',
  youtube_short: '▶️',
};
const CANAL_LABEL = {
  // IDs antigos (backward compat)
  instagram: 'Instagram', linkedin: 'LinkedIn', email: 'Email', whatsapp: 'WhatsApp',
  ads: 'Ads', site: 'Site', tiktok: 'TikTok', led: 'LED/Muppi',
  google_ads: 'Google Ads', facebook: 'Facebook', youtube: 'YouTube', social: 'Social',
  // Novos IDs de canal (briefings 2026+)
  meta_ads: 'Meta Ads', linkedin_ads: 'LinkedIn Ads',
  equipa_consumiveis: 'Equipa Consumíveis', equipa_equipamentos: 'Equipa Equipamentos', equipa_sdr: 'Equipa SDR',
  comercial: 'Equipa comercial', daily_prospect: 'Daily Prospect', distribuidores: 'Distribuidores', logistica: 'Logística',
  google_ads_search: 'Google Search', google_ads_display: 'Google Display',
  muppi_led: 'Painéis LED', website: 'Blog', site: 'Blog',
  email_interno: 'Email Interno', whatsapp_interno: 'WhatsApp Interno',
  portal_notificacao: 'Portal · Notificação',
  // Canal composto de comunicação interna
  'email_interno+whatsapp_interno+portal_notificacao': 'Comunicação Interna',
  // Formatos específicos
  instagram_post: 'Instagram · Post', instagram_reel: 'Instagram · Reel',
  instagram_story: 'Instagram · Story', instagram_carrossel: 'Instagram · Carrossel',
  facebook_post: 'Facebook · Post', facebook_reel: 'Facebook · Reel',
  facebook_story: 'Facebook · Story', facebook_carrossel: 'Facebook · Carrossel',
  linkedin_post: 'LinkedIn · Post', linkedin_carrossel: 'LinkedIn · Carrossel',
  linkedin_video: 'LinkedIn · Vídeo', youtube_short: 'YouTube · Short',
};

// ── Helpers ────────────────────────────────────────────────────────────────────
const CampDropdown = ({ label, value, options, onChange, disabled }) => {
  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={() => !disabled && setOpen(v => !v)} disabled={disabled} style={{
        background: '#ffffff', color: disabled ? 'var(--text-dim, #475569)' : 'var(--text, #1e293b)',
        border: '1px solid var(--border, #e2e8f0)', borderRadius: 6,
        padding: '6px 10px', fontSize: 11.5,
        display: 'inline-flex', alignItems: 'center', gap: 8,
        cursor: disabled ? 'not-allowed' : 'pointer', opacity: disabled ? 0.55 : 1,
        fontFamily: 'inherit', transition: 'border-color .15s',
      }}>
        <span style={{ color: 'var(--text-dim, #475569)', fontSize: 10, textTransform: 'uppercase', letterSpacing: '.06em', fontFamily: 'var(--font-mono, monospace)' }}>{label}</span>
        <span style={{ fontWeight: 500, color: disabled ? 'var(--text-dim, #475569)' : 'var(--text, #1e293b)' }}>{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: '#ffffff', border: '1px solid var(--border, #e2e8f0)', borderRadius: 6,
          boxShadow: '0 8px 24px rgba(0,0,0,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, #1e293b)',
              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 CampDateRangePicker = ({ value, onChange }) => {
  const ref = React.useRef(null);
  const [open, setOpen] = React.useState(false);
  const [draftStart, setDraftStart] = React.useState(value.start);
  const [draftEnd, setDraftEnd] = React.useState(value.end);
  const [draftPreset, setDraftPreset] = React.useState(value.preset);
  const [anchor, setAnchor] = React.useState(() => {
    const d = new Date(value.end); d.setDate(1); d.setMonth(d.getMonth() - 1); return d;
  });
  const [pickStart, setPickStart] = React.useState(true);
  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 stripTime = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate());
  const addDays = (d, n) => { const x = new Date(d); x.setDate(x.getDate() + n); return x; };
  const fmt = (d) => d ? d.toLocaleDateString('pt-PT', { day: 'numeric', month: 'short', year: 'numeric' }).replace('.', '') : '—';
  const fmtShort = (d) => d ? d.toLocaleDateString('pt-PT', { day: 'numeric', month: 'short' }).replace('.', '') : '—';
  const presets = [
    { id: 'all', label: 'Todo o período' }, { id: 'custom', label: 'Personalizado' },
    { id: 'today', label: 'Hoje' }, { id: 'yesterday', label: 'Ontem' },
    { id: 'last_7d', label: 'Últimos 7 dias' }, { id: 'last_30d', label: 'Últimos 30 dias' },
    { id: 'this_month', label: 'Este mês' }, { id: 'last_month', label: 'Mês passado' },
    { id: 'last_90d', label: 'Últimos 90 dias' }, { id: 'this_year', label: 'Este ano' },
  ];
  const presetLabels = Object.fromEntries(presets.map(p => [p.id, p.label]));
  const applyPreset = (id) => {
    if (id === 'all') { setDraftPreset('all'); return; }
    const today = stripTime(new Date()); let s = today, e = today;
    switch (id) {
      case 'today': break; case 'yesterday': s = e = addDays(today, -1); break;
      case 'last_7d': s = addDays(today, -6); break; case 'last_30d': s = addDays(today, -29); break;
      case 'last_90d': s = addDays(today, -89); break;
      case 'this_month': s = new Date(today.getFullYear(), today.getMonth(), 1); break;
      case 'last_month': s = new Date(today.getFullYear(), today.getMonth() - 1, 1); e = new Date(today.getFullYear(), today.getMonth(), 0); break;
      case 'this_year': s = new Date(today.getFullYear(), 0, 1); break;
      case 'custom': return; default: return;
    }
    setDraftPreset(id); setDraftStart(s); setDraftEnd(e);
    setAnchor(new Date(e.getFullYear(), e.getMonth() - 1, 1)); setPickStart(true);
  };
  const handleDayClick = (d) => {
    if (pickStart || !draftStart || (draftStart && draftEnd && d < draftStart)) {
      setDraftStart(d); setDraftEnd(d); setPickStart(false); setDraftPreset('custom');
    } else {
      if (d < draftStart) setDraftStart(d);
      else { setDraftEnd(d); setPickStart(true); }
      setDraftPreset('custom');
    }
  };
  const apply = () => { onChange({ preset: draftPreset, start: draftStart, end: draftEnd }); setOpen(false); };
  const cancel = () => { setDraftStart(value.start); setDraftEnd(value.end); setDraftPreset(value.preset); setOpen(false); };
  const triggerLabel = presetLabels[value.preset] || 'Personalizado';
  const triggerRange = value.preset === 'all' ? '' : `${fmtShort(value.start)} – ${fmt(value.end)}`;
  const nextAnchor = new Date(anchor.getFullYear(), anchor.getMonth() + 1, 1);
  const Month = ({ date }) => {
    const y = date.getFullYear(), m = date.getMonth();
    const firstDay = new Date(y, m, 1).getDay(), daysInMonth = new Date(y, m + 1, 0).getDate();
    const monthLabel = date.toLocaleDateString('pt-PT', { month: 'long', year: 'numeric' }).toUpperCase();
    const cells = [];
    for (let i = 0; i < firstDay; i++) cells.push(null);
    for (let d = 1; d <= daysInMonth; d++) cells.push(new Date(y, m, d));
    const inRange = (d) => draftStart && draftEnd && d >= draftStart && d <= draftEnd;
    const isStart = (d) => draftStart && d.getTime() === stripTime(draftStart).getTime();
    const isEnd = (d) => draftEnd && d.getTime() === stripTime(draftEnd).getTime();
    return (
      <div style={{ marginBottom: 10 }}>
        <div style={{ fontSize: 10.5, fontFamily: 'var(--font-mono, monospace)', color: 'var(--text-dim, #475569)', letterSpacing: '.08em', padding: '4px 6px' }}>{monthLabel}</div>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 1, fontSize: 11 }}>
          {cells.map((d, i) => {
            if (!d) return <div key={i} />;
            const r = inRange(d), s = isStart(d), e = isEnd(d), isEdge = s || e;
            return (
              <button key={i} onClick={() => handleDayClick(d)} style={{
                width: 30, height: 30, lineHeight: '30px', textAlign: 'center', padding: 0,
                border: 'none', cursor: 'pointer', fontFamily: 'inherit',
                background: isEdge ? 'var(--ai-500, #3859D0)' : (r ? 'color-mix(in oklch, var(--ai-500, #3859D0) 18%, transparent)' : 'transparent'),
                color: isEdge ? '#fff' : (r ? 'var(--ai-500, #3859D0)' : 'var(--text, #1e293b)'),
                borderRadius: isEdge ? '50%' : (r ? 0 : 4), fontWeight: isEdge ? 600 : 500, fontSize: 11.5,
              }}>{d.getDate()}</button>
            );
          })}
        </div>
      </div>
    );
  };
  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <button onClick={() => setOpen(v => !v)} style={{
        background: '#ffffff', color: 'var(--text, #1e293b)',
        border: '1px solid var(--border, #e2e8f0)', 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)' }}>Período</span>
        <span style={{ fontWeight: 600, color: 'var(--ai-500, #3859D0)', background: 'color-mix(in oklch, var(--ai-500, #3859D0) 12%, transparent)', padding: '1px 6px', borderRadius: 3 }}>{triggerLabel}</span>
        {triggerRange && <span style={{ color: 'var(--text-muted, #64748b)', fontFamily: 'var(--font-mono, monospace)', fontSize: 11 }}>{triggerRange}</span>}
        <span style={{ color: 'var(--text-muted, #64748b)', fontSize: 9 }}>▾</span>
      </button>
      {open && (
        <div style={{
          position: 'absolute', top: 'calc(100% + 6px)', right: 0, zIndex: 60,
          background: '#ffffff', border: '1px solid var(--border, #e2e8f0)', borderRadius: 8,
          boxShadow: '0 12px 32px rgba(0,0,0,0.18)', display: 'flex', minWidth: 560,
        }}>
          <div style={{ width: 190, borderRight: '1px solid var(--border, #e2e8f0)', padding: '6px 0', maxHeight: 460, overflowY: 'auto' }}>
            {presets.map(p => (
              <button key={p.id} onClick={() => applyPreset(p.id)} style={{
                display: 'block', width: '100%', textAlign: 'left',
                background: draftPreset === p.id ? 'color-mix(in oklch, var(--ai-500, #3859D0) 12%, transparent)' : 'transparent',
                color: draftPreset === p.id ? 'var(--ai-500, #3859D0)' : 'var(--text, #1e293b)',
                border: 'none', padding: '8px 14px', fontSize: 12.5, fontWeight: draftPreset === p.id ? 600 : 500,
                cursor: 'pointer', fontFamily: 'inherit',
              }}>{p.label}</button>
            ))}
          </div>
          <div style={{ flex: 1, padding: 14, display: 'flex', flexDirection: 'column' }}>
            <div style={{ display: 'flex', gap: 8, marginBottom: 10 }}>
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 9.5, color: 'var(--text-dim, #475569)', textTransform: 'uppercase', letterSpacing: '.08em', fontFamily: 'var(--font-mono, monospace)', marginBottom: 3 }}>Data início</div>
                <div style={{ border: `1px solid ${pickStart ? 'var(--ai-500, #3859D0)' : 'var(--border, #e2e8f0)'}`, borderRadius: 4, padding: '5px 8px', fontSize: 12, background: 'var(--bg, #f8fafc)' }}>{fmt(draftStart)}</div>
              </div>
              <div style={{ alignSelf: 'flex-end', padding: '6px 0', color: 'var(--text-muted, #64748b)' }}>—</div>
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 9.5, color: 'var(--text-dim, #475569)', textTransform: 'uppercase', letterSpacing: '.08em', fontFamily: 'var(--font-mono, monospace)', marginBottom: 3 }}>Data fim</div>
                <div style={{ border: `1px solid ${!pickStart ? 'var(--ai-500, #3859D0)' : 'var(--border, #e2e8f0)'}`, borderRadius: 4, padding: '5px 8px', fontSize: 12, background: 'var(--bg, #f8fafc)' }}>{fmt(draftEnd)}</div>
              </div>
            </div>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }}>
              <button onClick={() => setAnchor(new Date(anchor.getFullYear(), anchor.getMonth() - 1, 1))} style={{ background: 'none', border: '1px solid var(--border, #e2e8f0)', borderRadius: 4, width: 24, height: 24, color: 'var(--text-muted, #64748b)', cursor: 'pointer' }}>‹</button>
              <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 1, flex: 1, padding: '0 8px' }}>
                {['D','S','T','Q','Q','S','S'].map((d, i) => (
                  <div key={i} style={{ textAlign: 'center', fontSize: 10, color: 'var(--text-dim, #475569)', fontFamily: 'var(--font-mono, monospace)', textTransform: 'uppercase' }}>{d}</div>
                ))}
              </div>
              <button onClick={() => setAnchor(new Date(anchor.getFullYear(), anchor.getMonth() + 1, 1))} style={{ background: 'none', border: '1px solid var(--border, #e2e8f0)', borderRadius: 4, width: 24, height: 24, color: 'var(--text-muted, #64748b)', cursor: 'pointer' }}>›</button>
            </div>
            <div style={{ flex: 1, overflowY: 'auto', maxHeight: 360, padding: '0 30px' }}>
              <Month date={anchor} />
              <Month date={nextAnchor} />
            </div>
            <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 10, paddingTop: 10, borderTop: '1px solid var(--border, #e2e8f0)' }}>
              <button onClick={cancel} className="btn btn-xs">Cancelar</button>
              <button onClick={apply} className="btn btn-xs btn-ai">Aplicar</button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
};

const CampStatusBadge = ({ status }) => {
  // Fase · fase X/8 · cor da fase
  const MAP = {
    estrategia_pendente:['Campanha',           '#1e40af', 'rgba(30,64,175,.10)',  ''],
    estrategia_gerada:  ['Estratégia',         '#5B43C5', 'rgba(91,67,197,.14)',  '1/8'],
    estrategia_aprovada:['Estratégia ✓',       '#5B43C5', 'rgba(91,67,197,.18)',  '1/8'],
    conceito_pendente:  ['Conceito',          '#3859D0', 'rgba(56,89,208,.10)', '2/8'],
    conceito_gerado:    ['Conceito',          '#3859D0', 'rgba(56,89,208,.10)', '2/8'],
    conceito:           ['Conceito',          '#3859D0', 'rgba(56,89,208,.10)', '2/8'],
    canais_pendente:    ['Conceito',          '#3859D0', 'rgba(56,89,208,.10)', '2/8'],
    canais_gerado:      ['Conceito',          '#3859D0', 'rgba(56,89,208,.10)', '2/8'],
    canais_aprovado:    ['Conceito',          '#3859D0', 'rgba(56,89,208,.10)', '2/8'],
    copy_pendente:      ['Conceito',          '#3859D0', 'rgba(56,89,208,.10)', '2/8'],
    copy_gerado:        ['Conceito',          '#3859D0', 'rgba(56,89,208,.10)', '2/8'],
    copy_aprovado:      ['Conceito',          '#3859D0', 'rgba(56,89,208,.10)', '2/8'],
    idiomas_pendente:   ['Conceito',          '#3859D0', 'rgba(56,89,208,.10)', '2/8'],
    idiomas_gerado:     ['Conceito',          '#3859D0', 'rgba(56,89,208,.10)', '2/8'],
    idiomas_gerados:    ['Conceito',          '#3859D0', 'rgba(56,89,208,.10)', '2/8'],
    idiomas_aprovado:   ['Conceito',          '#3859D0', 'rgba(56,89,208,.10)', '2/8'],
    idiomas_aprovados:  ['Conceito',          '#3859D0', 'rgba(56,89,208,.10)', '2/8'],
    prompts_pendente:   ['Conceito',          '#3859D0', 'rgba(56,89,208,.10)', '2/8'],
    prompts_pendentes:  ['Conceito',          '#3859D0', 'rgba(56,89,208,.10)', '2/8'],
    prompts_gerado:     ['Conceito',          '#3859D0', 'rgba(56,89,208,.10)', '2/8'],
    prompts_gerados:    ['Conceito',          '#3859D0', 'rgba(56,89,208,.10)', '2/8'],
    geracao:            ['Conceito',          '#3859D0', 'rgba(56,89,208,.10)', '2/8'],
    aprovacao:          ['Conceito',          '#3859D0', 'rgba(56,89,208,.10)', '2/8'],
    prompts_aprovado:   ['Conceito',          '#3859D0', 'rgba(56,89,208,.10)', '2/8'],
    pending_executive:  ['Aguarda Aprovação', '#d97706', 'rgba(217,119,6,.12)',  '7/8'],
    em_producao:        ['Em Produção',        '#15803d', 'rgba(21,128,61,.12)', '8/8'],
    em_activacao:       ['Em Activação',       '#3859D0', 'rgba(56,89,208,.14)', '9/9'],
    publicado:          ['Publicado',          '#3859D0', 'rgba(56,89,208,.14)', '9/9'],
    concluida:          ['Concluída',          '#0e7490', 'rgba(14,116,144,.14)', ''],
    arquivada:          ['Arquivada',          '#64748b', 'rgba(100,116,139,.14)', ''],
    cancelada:          ['Cancelada',          '#64748b', 'rgba(100,116,139,.14)', ''],
  };
  const [label, color, bg, step] = MAP[status] || [status, '#94a3b8', 'rgba(148,163,184,.12)', '—'];
  return (
    <div style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}>
      <span style={{ fontSize: 10, fontWeight: 600, padding: '2px 8px', borderRadius: 99, color, background: bg, fontFamily: 'var(--font-mono, monospace)' }}>{label}</span>
      {step && <span style={{ fontSize: 9, color: '#94a3b8', fontFamily: 'var(--font-mono, monospace)' }}>{step}</span>}
    </div>
  );
};

// ── Banner contextual ──────────────────────────────────────────────────────────
const CampBanner = ({ kpi, onFilter, campanhas = [], userEmail = '' }) => {
  const hour    = new Date().getHours();
  const [seed]  = React.useState(Math.random); // seed fixo por mount → saudação diferente a cada entrada
  const rawName = window.currentUser?.nome_apresentar || window.currentUser?.nome || '';
  const fromEmail = userEmail ? userEmail.split('@')[0].split('.')[0] : '';
  const raw     = rawName.split(' ')[0] || fromEmail || '';
  const first   = raw ? raw.charAt(0).toUpperCase() + raw.slice(1).toLowerCase() : '';
  const pl = (n, s, p) => n === 1 ? s : p;

  const { heading, body } = React.useMemo(() => {
    // ── Grupos de campanhas por estado ──────────────────────────────────────────
    const G = {
      semConceito:   campanhas.filter(c => ['conceito_pendente'].includes(c.status)),
      conceitoOK:    campanhas.filter(c => ['conceito_gerado'].includes(c.status)),
      canaisOK:      campanhas.filter(c => ['canais_pendente','canais_gerado'].includes(c.status)),
      copyOK:        campanhas.filter(c => ['copy_pendente','copy_gerado'].includes(c.status)),
      idiomasOK:     campanhas.filter(c => ['idiomas_pendente','idiomas_gerado'].includes(c.status)),
      promptsOK:     campanhas.filter(c => ['prompts_pendente','prompts_gerado'].includes(c.status)),
      aprovExec:     campanhas.filter(c => ['prompts_aprovado','pending_executive'].includes(c.status)),
      emProducao:    campanhas.filter(c => ['em_producao'].includes(c.status)),
      emActivacao:   campanhas.filter(c => c.status === 'em_activacao'),
      publicadas:    campanhas.filter(c => c.status === 'publicado'),
    };
    const nomes = (arr) => { if (!arr.length) return ''; if (arr.length <= 2) return arr.map(c=>c.titulo).join(' e '); return `${arr.slice(0,-1).map(c=>c.titulo).join(', ')} e ${arr[arr.length-1].titulo}`; };

    // ── Pool de saudações com humor — contextual ao pipeline real ──────────────
    let pool;
    if (G.copyOK.length > 0) {
      pool = [
        `${first ? `${first}, a` : 'A'} AI fez a parte dela. O copy está gerado${G.copyOK.length > 1 ? ` em ${G.copyOK.length} campanhas` : ` — ${G.copyOK[0].titulo}`}. Agora és tu.`,
        `${first ? `${first}: copy` : 'Copy'} pronto. A bola está no teu campo.`,
        `${first || 'Olá'}${first ? ',' : ''} tens copy para aprovar. A Digi AI não vai fazer esse trabalho por ti.`,
      ];
    } else if (G.aprovExec.length > 0) {
      pool = [
        `${first ? `${first}, o` : 'O'} pipeline parou na aprovação. ${nomes(G.aprovExec)} aguarda luz verde.`,
        `${first || 'Ei'}${first ? ',' : ''} ${G.aprovExec.length} ${pl(G.aprovExec.length, 'campanha está', 'campanhas estão')} à espera de aprovação executiva.`,
      ];
    } else if (G.conceitoOK.length > 0) {
      pool = [
        `${first ? `${first}, ` : ''}${G.conceitoOK.length} ${pl(G.conceitoOK.length, 'conceito gerado', 'conceitos gerados')} à tua espera. A Digi AI já pensou — agora tu.`,
        `${first || 'Olá'}${first ? ',' : ''} tens ${G.conceitoOK.length} ${pl(G.conceitoOK.length, 'conceito', 'conceitos')} para rever e aprovar.`,
        `${first ? `${first}: ` : ''}ideia criativa no forno. Aprova e avança.`,
      ];
    } else if (G.semConceito.length > 0) {
      pool = [
        `${first ? `${first}, ` : ''}${G.semConceito.length} ${pl(G.semConceito.length, 'campanha', 'campanhas')} sem conceito. A Digi AI está à espera de ordem de marcha.`,
        `${first || 'Olá'}${first ? ',' : ''} ${nomes(G.semConceito)} não ${pl(G.semConceito.length, 'vai gerar-se sozinha', 'vão gerar-se sozinhas')}.`,
      ];
    } else if (G.emProducao.length > 0 && kpi.total === G.emProducao.length + G.publicadas.length) {
      pool = [
        `${first ? `${first}, ` : ''}pipeline limpo. ${G.emProducao.length + G.publicadas.length} ${pl(G.emProducao.length + G.publicadas.length, 'campanha', 'campanhas')} em produção ou publicada. Raro, mas acontece.`,
        `${first || 'Olá'}${first ? ',' : ''} tudo a correr. Aproveita — amanhã há mais.`,
      ];
    } else if (kpi.total === 0) {
      pool = [
        `${first ? `${first}, ` : ''}pipeline vazio. Aprova um briefing e dá ordem à Digi AI para trabalhar.`,
        `${first || 'Olá'}${first ? ',' : ''} ainda não há campanhas. Começa por criar uma.`,
      ];
    } else {
      const h = hour < 12 ? 'Bom dia' : hour < 19 ? 'Boa tarde' : 'Boa noite';
      pool = [
        `${h}${first ? `, ${first}` : ''}. ${kpi.total} ${pl(kpi.total, 'campanha activa', 'campanhas activas')} no pipeline.`,
        `${first ? `${first}, tens` : 'Tens'} ${kpi.total} ${pl(kpi.total, 'campanha', 'campanhas')} em curso. Vê o que precisa de atenção.`,
      ];
    }
    const heading = pool[Math.floor(seed * pool.length)];

    // ── Parágrafo fluido e humano sobre todo o pipeline ────────────────────────
    const partes = [];
    if (campanhas.length === 0) {
      partes.push(`Ainda não há campanhas activas. Aprova um briefing e dá a ordem à Digi AI para começar a trabalhar.`);
    } else {
      const lista = (arr) => {
        if (!arr.length) return '';
        if (arr.length === 1) return arr[0].titulo;
        if (arr.length === 2) return `${arr[0].titulo} e ${arr[1].titulo}`;
        return `${arr.slice(0,-1).map(c => c.titulo).join(', ')} e ${arr[arr.length-1].titulo}`;
      };
      if (G.copyOK.length)    partes.push(`${lista(G.copyOK)} ${pl(G.copyOK.length,'tem','têm')} copy gerado e ${pl(G.copyOK.length,'está','estão')} à espera que ${pl(G.copyOK.length,'aproves','aprovem')}.`);
      if (G.aprovExec.length) partes.push(`${lista(G.aprovExec)} ${pl(G.aprovExec.length,'está bloqueada','estão bloqueadas')} na aprovação executiva — precisa de luz verde de ${first || 'ti'}, do Rui ou do Armando.`);
      if (G.conceitoOK.length) partes.push(`${lista(G.conceitoOK)} ${pl(G.conceitoOK.length,'tem','têm')} conceito gerado e ${pl(G.conceitoOK.length,'aguarda','aguardam')} a tua aprovação antes de avançar.`);
      if (G.canaisOK.length)  partes.push(`${lista(G.canaisOK)} ${pl(G.canaisOK.length,'tem','têm')} o setup de canais ${pl(G.canaisOK.length,'pronto','prontos')} — aprova para começar a gerar copy.`);
      if (G.idiomasOK.length) partes.push(`${lista(G.idiomasOK)} ${pl(G.idiomasOK.length,'tem','têm')} traduções geradas para rever.`);
      if (G.promptsOK.length) partes.push(`${lista(G.promptsOK)} ${pl(G.promptsOK.length,'tem','têm')} prompts visuais prontos para aprovares.`);
      if (G.semConceito.length) partes.push(`${lista(G.semConceito)} ainda não ${pl(G.semConceito.length,'tem','têm')} conceito — quando quiseres, a Digi AI gera em segundos.`);
      if (G.emProducao.length) partes.push(`${lista(G.emProducao)} ${pl(G.emProducao.length,'está','estão')} em produção${G.emProducao.length === 1 ? ' — a equipa já pode começar' : ''}.`);
      if (G.publicadas.length) partes.push(`${lista(G.publicadas)} já ${pl(G.publicadas.length,'está publicada','estão publicadas')}.`);
    }
    const body = partes.join(' ');
    return { heading, body };
  }, [kpi, campanhas, hour, first, seed]);

  return (
    <div style={{ flexShrink: 0, padding: '10px 0 20px' }}>
      <div style={{ fontSize: 22, fontWeight: 700, color: 'var(--text, #1d2e38)', fontFamily: 'var(--font-display, Montserrat, sans-serif)', marginBottom: 8, letterSpacing: '-0.02em' }}>
        {heading}
      </div>
      <div style={{ fontSize: 13, color: 'var(--text-muted, #64748b)', lineHeight: 1.75 }}>
        {body}
      </div>
    </div>
  );
};

// ── Modal de confirmação de regenerar ─────────────────────────────────────────
const RegenerateConfirmModal = ({ phase, itemCount, approvedCount, hasManualEdits, onConfirm, onClose }) => {
  const PHASE_MSG = {
    conceito:  hasManualEdits
      ? 'Vai apagar o conceito actual (Big Idea, narrativa, ângulos). Atenção: tens edições manuais feitas após a última geração — essas alterações serão perdidas.'
      : 'Vai apagar o conceito actual (Big Idea, narrativa, ângulos). Edições manuais serão perdidas.',
    canais:    'Vai apagar a configuração de canais (audiências, formatos, orçamentos sugeridos).',
    copy:      itemCount ? `Vai apagar ${itemCount} peças de copy${approvedCount ? ` (${approvedCount} aprovadas)` : ''}.` : 'Vai apagar todo o copy gerado.',
    schedule:  'Vai recalcular o calendário editorial com novas datas.',
    idiomas:   itemCount ? `Vai apagar ${itemCount} traduções${approvedCount ? ` (${approvedCount} aprovadas)` : ''}.` : 'Vai apagar todas as traduções.',
    prompts:   itemCount ? `Vai apagar ${itemCount} prompts AI${approvedCount ? ` (${approvedCount} aprovados)` : ''}.` : 'Vai apagar todos os prompts gerados.',
  };
  const msg = PHASE_MSG[phase] || 'Vai regenerar este conteúdo do zero.';
  return (
    <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,.45)', zIndex: 9000, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24 }}>
      <div style={{ background: '#fff', borderRadius: 12, width: '100%', maxWidth: 420, boxShadow: '0 16px 48px rgba(0,0,0,.2)', display: 'flex', flexDirection: 'column' }}>
        <div style={{ padding: '18px 24px 14px', borderBottom: '1px solid #f1f5f9', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
          <div style={{ fontSize: 14, fontWeight: 700, color: '#1d2e38', fontFamily: 'Montserrat, sans-serif' }}>Confirmar regeneração</div>
          <button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#94a3b8', padding: 4, lineHeight: 1, display: 'flex' }}>
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
          </button>
        </div>
        <div style={{ padding: '16px 24px 20px' }}>
          <div style={{ fontSize: 13, color: '#475569', lineHeight: 1.6, marginBottom: 8 }}>{msg}</div>
          {approvedCount > 0 && (
            <div style={{ fontSize: 12, color: '#d97706', background: 'rgba(217,119,6,.08)', border: '1px solid rgba(217,119,6,.25)', borderRadius: 6, padding: '8px 12px' }}>
              Atenção: {approvedCount} {approvedCount === 1 ? 'item aprovado será perdido' : 'itens aprovados serão perdidos'}.
            </div>
          )}
        </div>
        <div style={{ padding: '0 24px 18px', display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
          <button onClick={onClose} className="btn" style={{ fontSize: 12 }}>Cancelar</button>
          <button onClick={onConfirm} className="btn" style={{ fontSize: 12, background: '#dc2626', color: '#fff', borderColor: '#dc2626' }}>Regenerar</button>
        </div>
      </div>
    </div>
  );
};

// ── Modal de confirmação de eliminação de campanha ─────────────────────────────
const CampDeleteConfirmModal = ({ campanha, onConfirm, onClose }) => {
  const [confirmText, setConfirmText] = React.useState('');
  const canConfirm = confirmText === 'DELETE';
  return (
    <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,.45)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24 }}>
      <div style={{ background: '#fff', borderRadius: 14, width: '100%', maxWidth: 480, boxShadow: '0 24px 80px rgba(0,0,0,.2)', display: 'flex', flexDirection: 'column' }}>
        <div style={{ padding: '18px 24px 14px', borderBottom: '1px solid #f1f5f9' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <div style={{ width: 30, height: 30, borderRadius: 8, background: 'rgba(220,38,38,.1)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#dc2626" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                <polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"/>
                <path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4a1 1 0 011-1h4a1 1 0 011 1v2"/>
              </svg>
            </div>
            <div style={{ flex: 1 }}>
              <div style={{ fontSize: 14, fontWeight: 700, color: '#1d2e38', fontFamily: 'Montserrat, sans-serif' }}>Eliminar campanha</div>
              <div style={{ fontSize: 11, color: '#94a3b8' }}>Esta acção é permanente e não pode ser revertida</div>
            </div>
            <button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#94a3b8', padding: 4, lineHeight: 1, display: 'flex' }}>
              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
            </button>
          </div>
        </div>
        <div style={{ padding: '20px 24px', display: 'flex', flexDirection: 'column', gap: 16 }}>
          <div style={{ borderRadius: 8, background: 'rgba(220,38,38,.04)', border: '1px solid rgba(220,38,38,.15)', padding: '10px 14px' }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>
              <div style={{ width: 4, height: 4, borderRadius: '50%', background: '#dc2626', flexShrink: 0 }} />
              <span style={{ fontSize: 12, color: '#1d2e38', fontWeight: 500 }}>{campanha?.titulo || `Campanha #${campanha?.id}`}</span>
            </div>
            <div style={{ fontSize: 11, color: '#64748b', lineHeight: 1.5, paddingLeft: 12 }}>
              Apaga esta campanha e o que foi gerado nela. O briefing do produto mantém-se Aprovado.
            </div>
          </div>
          <div>
            <label style={{ fontSize: 11, fontWeight: 600, color: '#64748b', letterSpacing: '0.04em', display: 'block', marginBottom: 6 }}>
              ESCREVE{' '}
              <span style={{ fontFamily: 'monospace', color: '#dc2626', background: 'rgba(220,38,38,.08)', padding: '1px 5px', borderRadius: 3 }}>DELETE</span>
              {' '}PARA CONFIRMAR
            </label>
            <input autoFocus type="text" value={confirmText}
              onChange={e => setConfirmText(e.target.value)}
              placeholder="DELETE"
              onKeyDown={e => e.key === 'Enter' && canConfirm && onConfirm()}
              style={{ width: '100%', boxSizing: 'border-box', padding: '9px 12px', borderRadius: 8, fontSize: 13, border: `1px solid ${canConfirm ? 'rgba(220,38,38,.4)' : '#e2e8f0'}`, outline: 'none', fontFamily: 'Inter, sans-serif', color: '#1d2e38', letterSpacing: '0.05em', transition: 'border-color 0.15s' }}
            />
          </div>
        </div>
        <div style={{ padding: '14px 24px', borderTop: '1px solid #f1f5f9', display: 'flex', gap: 8, justifyContent: 'space-between' }}>
          <button onClick={onClose} className="btn" style={{ fontSize: 12 }}>Cancelar</button>
          <button onClick={onConfirm} disabled={!canConfirm} className="btn"
            style={{ fontSize: 12, background: canConfirm ? '#dc2626' : '#f1f5f9', color: canConfirm ? '#fff' : '#94a3b8', borderColor: canConfirm ? '#dc2626' : '#e2e8f0', cursor: canConfirm ? 'pointer' : 'default', transition: 'all 0.15s' }}>
            Eliminar campanha
          </button>
        </div>
      </div>
    </div>
  );
};

// ── Modal de pedir alterações / rejeitar fase ─────────────────────────────────
const PhaseActionModal = ({ phase, action, onConfirm, onClose }) => {
  const [notes, setNotes] = React.useState('');
  const isReject = action === 'rejected';
  const title = isReject ? 'Rejeitar fase' : 'Pedir alterações';
  const phaseLabel = { conceito: 'Conceito', canais: 'Canais', copy: 'Copy', idiomas: 'Idiomas', prompts: 'Prompts', producao: 'Produção' }[phase] || phase;
  return (
    <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,.45)', zIndex: 9001, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24 }}>
      <div style={{ background: '#fff', borderRadius: 12, width: '100%', maxWidth: 460, boxShadow: '0 16px 48px rgba(0,0,0,.2)', display: 'flex', flexDirection: 'column' }}>
        <div style={{ padding: '18px 24px 14px', borderBottom: '1px solid #f1f5f9', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
          <div>
            <div style={{ fontSize: 14, fontWeight: 700, color: '#1d2e38', fontFamily: 'Montserrat, sans-serif' }}>{title} — {phaseLabel}</div>
            <div style={{ fontSize: 11, color: '#94a3b8', marginTop: 2 }}>Trabalho não será apagado. Notas ficam visíveis no painel de aprovações.</div>
          </div>
          <button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#94a3b8', padding: 4, lineHeight: 1, display: 'flex' }}>
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
          </button>
        </div>
        <div style={{ padding: '16px 24px 20px' }}>
          <label style={{ fontSize: 11, fontWeight: 600, color: '#64748b', letterSpacing: '0.04em', display: 'block', marginBottom: 6 }}>NOTAS {isReject ? '(motivo de rejeição)' : '(o que precisa de ser corrigido)'} *</label>
          <textarea autoFocus value={notes} onChange={e => setNotes(e.target.value)} rows={4}
            placeholder={isReject ? 'Indica o motivo da rejeição...' : 'Indica as alterações necessárias...'}
            style={{ width: '100%', boxSizing: 'border-box', padding: '9px 12px', borderRadius: 7, fontSize: 13, border: '1px solid #e2e8f0', outline: 'none', resize: 'vertical', fontFamily: 'Inter, sans-serif', lineHeight: 1.6 }} />
        </div>
        <div style={{ padding: '0 24px 18px', display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
          <button onClick={onClose} className="btn" style={{ fontSize: 12 }}>Cancelar</button>
          <button onClick={() => notes.trim() && onConfirm(notes)} disabled={!notes.trim()} className="btn"
            style={{ fontSize: 12, background: isReject ? '#dc2626' : '#d97706', color: '#fff', borderColor: isReject ? '#dc2626' : '#d97706', opacity: notes.trim() ? 1 : 0.5, cursor: notes.trim() ? 'pointer' : 'default' }}>
            {isReject ? 'Rejeitar' : 'Pedir Alterações'}
          </button>
        </div>
      </div>
    </div>
  );
};

// ── KanbanCampCard ───────────────────────────────────────────────────────────────
const NEXT_ACTION_BY_STATUS = {
  estrategia_pendente:'Definir campanha',
  conceito_pendente:  'Gerar conceito',
  conceito_gerado:    'Rever conceito',
  conceito:           'Rever conceito',
  canais_pendente:    'Rever conceito',
  canais_gerado:      'Rever conceito',
  canais_aprovado:    'Rever conceito',
  copy_pendente:      'Rever conceito',
  copy_gerado:        'Rever conceito',
  copy_aprovado:      'Rever conceito',
  idiomas_pendente:   'Rever conceito',
  idiomas_gerado:     'Rever conceito',
  idiomas_aprovado:   'Rever conceito',
  prompts_pendente:   'Rever conceito',
  prompts_gerado:     'Rever conceito',
  geracao:            'Rever conceito',
  aprovacao:          'Rever conceito',
  prompts_aprovado:   'Rever conceito',
  pending_executive:  'Aguarda aprovação',
  em_producao:        'Publicar',
  em_activacao:       'Activar',
  publicado:          'Publicado',
};

const PROGRESS_BY_STATUS = {
  copy_pendente:      { key: 'conceito', label: 'Conceito' },
  copy_gerado:        { key: 'conceito', label: 'Conceito' },
  copy_aprovado:      { key: 'conceito', label: 'Conceito' },
  canais_pendente:    { key: 'conceito', label: 'Conceito' },
  canais_gerado:      { key: 'conceito', label: 'Conceito' },
  canais_aprovado:    { key: 'conceito', label: 'Conceito' },
  idiomas_pendente:   { key: 'conceito', label: 'Conceito' },
  idiomas_gerado:     { key: 'conceito', label: 'Conceito' },
  idiomas_aprovado:   { key: 'conceito', label: 'Conceito' },
  prompts_pendente:   { key: 'conceito', label: 'Conceito' },
  prompts_gerado:     { key: 'conceito', label: 'Conceito' },
  geracao:            { key: 'conceito', label: 'Conceito' },
  aprovacao:          { key: 'conceito', label: 'Conceito' },
  prompts_aprovado:   { key: 'conceito', label: 'Conceito' },
  pending_executive:  { key: 'prompts', label: 'Prompts' },
  em_producao:        { key: 'prompts', label: 'Prompts' },
  publicado:          { key: 'prompts', label: 'Prompts' },
};

const CHAN_ABBR = {
  meta_ads: 'Meta Ads', linkedin_ads: 'LinkedIn Ads', google_ads_search: 'Google Search',
  google_ads_display: 'Google Display', email: 'Email', whatsapp: 'WhatsApp',
  website: 'Website', muppi_led: 'LED/Muppi',
  instagram: 'Instagram', facebook: 'Facebook', linkedin: 'LinkedIn', google_ads: 'Google Ads',
  site: 'Blog', led: 'LED', tiktok: 'TikTok', youtube: 'YouTube', social: 'Social',
  ads: 'Ads',
};

// SVG icons monochromos 14x14 stroke, sem emojis
const CampanhaChanIcon = ({ ch, size = 14, color = '#64748b' }) => {
  const p = { fill: 'none', stroke: 'currentColor', strokeWidth: 1.6, strokeLinecap: 'round', strokeLinejoin: 'round' };
  const paths = {
    meta_ads:            <g {...p}><path d="M3 11l18-4v10L3 13v-2z"/><path d="M7 12v6"/></g>,
    facebook:            <g {...p}><path d="M3 11l18-4v10L3 13v-2z"/><path d="M7 12v6"/></g>,
    ads:                 <g {...p}><path d="M3 11l18-4v10L3 13v-2z"/><path d="M7 12v6"/></g>,
    linkedin_ads:        <g {...p}><path d="M6 9v9"/><circle cx="6" cy="5.5" r="1"/><path d="M10 18v-5a2 2 0 014 0v5"/><path d="M18 18v-4"/></g>,
    linkedin:            <g {...p}><path d="M6 9v9"/><circle cx="6" cy="5.5" r="1"/><path d="M10 18v-5a2 2 0 014 0v5"/><path d="M18 18v-4"/></g>,
    google_ads_search:   <g {...p}><circle cx="11" cy="11" r="6"/><path d="M20 20l-4-4"/></g>,
    google_ads:          <g {...p}><circle cx="11" cy="11" r="6"/><path d="M20 20l-4-4"/></g>,
    google_ads_display:  <g {...p}><rect x="3" y="4" width="18" height="14" rx="2"/><path d="M10 9l5 3-5 3z"/></g>,
    email:               <g {...p}><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 7l9 6 9-6"/></g>,
    whatsapp:            <g {...p}><path d="M12 2a10 10 0 00-8.5 15.2L2 22l4.9-1.3A10 10 0 1012 2z"/></g>,
    website:             <g {...p}><circle cx="12" cy="12" r="9"/><path d="M3 12h18"/><path d="M12 3a15 15 0 010 18"/></g>,
    site:                <g {...p}><circle cx="12" cy="12" r="9"/><path d="M3 12h18"/><path d="M12 3a15 15 0 010 18"/></g>,
    muppi_led:           <g {...p}><rect x="2" y="4" width="20" height="14" rx="2"/><path d="M9 22h6"/><path d="M12 18v4"/></g>,
    led:                 <g {...p}><rect x="2" y="4" width="20" height="14" rx="2"/><path d="M9 22h6"/><path d="M12 18v4"/></g>,
    instagram:           <g {...p}><rect x="3" y="3" width="18" height="18" rx="4"/><circle cx="12" cy="12" r="4"/></g>,
    youtube:             <g {...p}><rect x="2" y="6" width="20" height="12" rx="3"/><path d="M10 10l5 2-5 2z"/></g>,
    tiktok:              <g {...p}><path d="M9 3v12a3 3 0 11-3-3"/><path d="M9 3a5 5 0 005 5"/></g>,
    social:              <g {...p}><circle cx="12" cy="12" r="9"/><path d="M8 12h8M12 8v8"/></g>,
  };
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" style={{ color, flexShrink: 0 }}>
      {paths[ch] || <circle cx="12" cy="12" r="2" {...p} />}
    </svg>
  );
};

const CampConcluirModal = ({ campanha, onClose, onDone }) => {
  const [impact, setImpact] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState('');
  React.useEffect(() => {
    let live = true;
    CampAPI.arquivo(campanha.id).then(d => { if (live) setImpact(d); }).catch(() => {});
    return () => { live = false; };
  }, [campanha.id]);
  const confirm = async () => {
    setBusy(true);
    setError('');
    try {
      await CampAPI.concluir(campanha.id);
      onDone();
    } catch (e) {
      setError(e.message || 'Não concluí a campanha.');
      setBusy(false);
    }
  };
  const metaOn = Number(impact?.meta_activas || 0);
  return (
    <div style={{ position: 'fixed', inset: 0, background: 'rgba(15,23,42,.45)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24 }} onClick={onClose}>
      <div onClick={e => e.stopPropagation()} style={{ background: '#fff', borderRadius: 14, width: '100%', maxWidth: 460, boxShadow: '0 24px 80px rgba(0,0,0,.2)' }}>
        <div style={{ padding: '18px 22px 12px', borderBottom: '1px solid #f1f5f9' }}>
          <div style={{ fontSize: 15, fontWeight: 700, color: '#1d2e38' }}>Concluir campanha</div>
          <div style={{ fontSize: 13, color: '#334155', marginTop: 6 }}>{campanha.titulo || 'Sem título'}</div>
        </div>
        <div style={{ padding: '16px 22px', display: 'flex', flexDirection: 'column', gap: 10, fontSize: 13, lineHeight: 1.45, color: '#334155' }}>
          <div>Sai de Activação e fica na coluna Concluídas. O briefing e os conteúdos mantêm-se.</div>
          {metaOn > 0 && (
            <div style={{ padding: '8px 10px', borderRadius: 8, background: '#FEF3C7', color: '#92400E' }}>
              {metaOn === 1 ? 'Há 1 anúncio ACTIVE na Meta.' : `Há ${metaOn} anúncios ACTIVE na Meta.`} Concluir não os tira do ar.
            </div>
          )}
          {error && <div style={{ color: '#991B1B' }}>{error}</div>}
        </div>
        <div style={{ padding: '12px 22px 16px', borderTop: '1px solid #f1f5f9', display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
          <button type="button" onClick={onClose} className="btn" style={{ fontSize: 12 }} disabled={busy}>Cancelar</button>
          <button type="button" onClick={confirm} disabled={busy} className="btn btn-ai" style={{ fontSize: 12 }}>{busy ? 'A concluir…' : 'Concluir'}</button>
        </div>
      </div>
    </div>
  );
};

const CampArquivarModal = ({ campanha, onClose, onDone }) => {
  const [impact, setImpact] = React.useState(null);
  const [loadError, setLoadError] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState('');
  React.useEffect(() => {
    let live = true;
    CampAPI.arquivo(campanha.id).then(d => {
      if (!live) return;
      setImpact(d);
    }).catch(e => { if (live) setLoadError(e.message || 'Não consegui ler o impacto.'); });
    return () => { live = false; };
  }, [campanha.id]);
  const confirm = async () => {
    setBusy(true);
    setError('');
    try {
      await CampAPI.arquivar(campanha.id);
      onDone();
    } catch (e) {
      setError(e.message || 'Não arquivei a campanha.');
      setBusy(false);
    }
  };
  const metaOn = Number(impact?.meta_activas || 0);
  const emailsOn = Number(impact?.emails_activos || 0) + Number(impact?.workflows_activos || 0);
  const sessoes = Number(impact?.sessoes || 0);
  const leads = Number(impact?.leads || 0);
  return (
    <div style={{ position: 'fixed', inset: 0, background: 'rgba(15,23,42,.45)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24 }} onClick={onClose}>
      <div onClick={e => e.stopPropagation()} style={{ background: '#fff', borderRadius: 14, width: '100%', maxWidth: 480, boxShadow: '0 24px 80px rgba(0,0,0,.2)' }}>
        <div style={{ padding: '18px 22px 12px', borderBottom: '1px solid #f1f5f9' }}>
          <div style={{ fontSize: 15, fontWeight: 700, color: '#1d2e38' }}>Arquivar campanha</div>
          <div style={{ fontSize: 13, color: '#334155', marginTop: 6, lineHeight: 1.4 }}>{campanha.titulo || 'Sem título'}</div>
        </div>
        <div style={{ padding: '16px 22px', display: 'flex', flexDirection: 'column', gap: 10, fontSize: 13, lineHeight: 1.45, color: '#334155' }}>
          <div>Sai do quadro e fica em Campanhas Arquivadas. Copies, imagens, custos e leads continuam nesta campanha. Anúncios na Meta e envios de email param-se à parte, na Activação.</div>
          {impact?.briefing_reabre && <div>É a última campanha deste briefing. O briefing continua Aprovado, sem campanhas activas.</div>}
          {!impact?.briefing_reabre && Number(impact?.outras_campanhas) > 0 && <div>Este briefing tem outras campanhas. Elas ficam.</div>}
          {impact?.sdr_activa && <div>A Digi deixa de usar a ficha em conversas novas.{sessoes > 0 ? ` As ${sessoes} sessões já tidas ficam no histórico.` : ''}</div>}
          {leads > 0 && <div>{leads} lead{leads === 1 ? '' : 's'} continuam a apontar para esta campanha.</div>}
          {metaOn > 0 && (
            <div style={{ padding: '8px 10px', borderRadius: 8, background: '#FEF3C7', color: '#92400E' }}>
              {metaOn === 1 ? 'Há 1 anúncio ACTIVE na Meta.' : `Há ${metaOn} anúncios ACTIVE na Meta.`} O gasto continua até o pausares na Activação.
            </div>
          )}
          {emailsOn > 0 && (
            <div style={{ padding: '8px 10px', borderRadius: 8, background: '#FEF3C7', color: '#92400E' }}>
              {emailsOn === 1 ? 'Há 1 envio de email activo.' : `Há ${emailsOn} envios de email activos.`} Continuam a sair até os parares na Activação.
            </div>
          )}
          {!impact && !loadError && <div style={{ color: '#94a3b8' }}>A ler o que esta campanha tem no ar…</div>}
          {loadError && <div style={{ color: '#991B1B' }}>{loadError}</div>}
          {error && <div style={{ color: '#991B1B' }}>{error}</div>}
        </div>
        <div style={{ padding: '12px 22px 16px', borderTop: '1px solid #f1f5f9', display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
          <button type="button" onClick={onClose} className="btn" style={{ fontSize: 12 }} disabled={busy}>Cancelar</button>
          <button type="button" onClick={confirm} disabled={busy || !!loadError} className="btn" style={{ fontSize: 12, background: '#1d2e38', color: '#fff', borderColor: '#1d2e38' }}>
            {busy ? 'A arquivar…' : 'Arquivar'}
          </button>
        </div>
      </div>
    </div>
  );
};

const CampRestaurarModal = ({ campanha, onClose, onDone }) => {
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState('');
  const anterior = campanha.status_antes_arquivo;
  const conhecido = anterior && !['arquivada', 'cancelada'].includes(anterior);
  const confirm = async () => {
    setBusy(true);
    setError('');
    try {
      await CampAPI.restaurar(campanha.id);
      onDone(conhecido);
    } catch (e) {
      setError(e.message || 'Não restaurei a campanha.');
      setBusy(false);
    }
  };
  return (
    <div style={{ position: 'fixed', inset: 0, background: 'rgba(15,23,42,.45)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24 }} onClick={onClose}>
      <div onClick={e => e.stopPropagation()} style={{ background: '#fff', borderRadius: 14, width: '100%', maxWidth: 460, boxShadow: '0 24px 80px rgba(0,0,0,.2)' }}>
        <div style={{ padding: '18px 22px 12px', borderBottom: '1px solid #f1f5f9' }}>
          <div style={{ fontSize: 15, fontWeight: 700, color: '#1d2e38' }}>Restaurar campanha</div>
          <div style={{ fontSize: 13, color: '#334155', marginTop: 6 }}>{campanha.titulo || 'Sem título'}</div>
        </div>
        <div style={{ padding: '16px 22px', fontSize: 13, lineHeight: 1.5, color: '#334155', display: 'flex', flexDirection: 'column', gap: 10 }}>
          {conhecido
            ? <div>Volta ao quadro no estado em que estava. <CampStatusBadge status={anterior} /></div>
            : <div>O estado anterior não ficou registado. Volta ao quadro em Conceito.</div>}
          <div>O briefing continua Aprovado.</div>
          {error && <div style={{ color: '#991B1B' }}>{error}</div>}
        </div>
        <div style={{ padding: '12px 22px 16px', borderTop: '1px solid #f1f5f9', display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
          <button type="button" onClick={onClose} className="btn" style={{ fontSize: 12 }} disabled={busy}>Cancelar</button>
          <button type="button" onClick={confirm} disabled={busy} className="btn btn-ai" style={{ fontSize: 12 }}>{busy ? 'A restaurar…' : 'Restaurar'}</button>
        </div>
      </div>
    </div>
  );
};

const campDataCurta = (v) => {
  if (!v) return '—';
  const d = new Date(v);
  if (Number.isNaN(d.getTime())) return '—';
  return d.toLocaleDateString('pt-PT', { day: '2-digit', month: 'short', year: 'numeric' });
};

const CampArquivoLista = ({ linhas, canAct, onOpen, onRestaurar }) => {
  const th = { textAlign: 'left', fontSize: 10, fontWeight: 600, color: 'var(--text-dim, #6b7fa3)', textTransform: 'uppercase', letterSpacing: '.04em', padding: '8px 10px', borderBottom: '1px solid var(--border, #dde3ef)', whiteSpace: 'nowrap', background: '#fff' };
  const td = { fontSize: 12, padding: '10px', borderBottom: '1px solid var(--border, #dde3ef)', verticalAlign: 'middle', color: 'var(--text, #112954)' };
  if (!linhas.length) {
    return <div style={{ padding: '36px 0', textAlign: 'center', fontSize: 13, color: 'var(--text-muted, #64748b)' }}>Ainda não há campanhas arquivadas para estes filtros.</div>;
  }
  return (
    <div style={{ background: '#fff', border: '1px solid var(--border, #dde3ef)', borderRadius: 10, overflowX: 'auto' }}>
      <table style={{ width: '100%', borderCollapse: 'collapse', minWidth: 760 }}>
        <thead>
          <tr>
            <th style={th}>Campanha</th>
            <th style={th}>Marca</th>
            <th style={th}>Produto</th>
            <th style={th}>Estado anterior</th>
            <th style={th}>Arquivada</th>
            {canAct && <th style={{ ...th, textAlign: 'right' }}>Acção</th>}
          </tr>
        </thead>
        <tbody>
          {linhas.map(c => (
            <tr key={c.id} onClick={() => onOpen(c)} style={{ cursor: 'pointer' }}
              onMouseEnter={e => { e.currentTarget.style.background = '#f8fafc'; }}
              onMouseLeave={e => { e.currentTarget.style.background = 'transparent'; }}>
              <td style={{ ...td, fontWeight: 650, maxWidth: 280 }}>{c.titulo || 'Sem título'}</td>
              <td style={td}>{c.brand_name || '—'}</td>
              <td style={td}>{c.commercial_name || c.product_name || '—'}</td>
              <td style={td}>{c.status_antes_arquivo ? <CampStatusBadge status={c.status_antes_arquivo} /> : <span style={{ color: '#94a3b8' }}>Não registado</span>}</td>
              <td style={td}>
                <div>{campDataCurta(c.arquivada_em || c.updated_at)}</div>
                {c.arquivada_por && <div style={{ fontSize: 11, color: '#94a3b8', marginTop: 2 }}>{String(c.arquivada_por).split('@')[0]}</div>}
              </td>
              {canAct && (
                <td style={{ ...td, textAlign: 'right' }} onClick={e => e.stopPropagation()}>
                  <button type="button" className="btn" style={{ height: 28, padding: '0 10px', fontSize: 12 }} onClick={() => onRestaurar(c)}>Restaurar</button>
                </td>
              )}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
};

const _firstNameFromEmail = (email) => {
  if (!email) return null;
  const local = String(email).split('@')[0].split('.')[0];
  if (!local) return null;
  return local.charAt(0).toUpperCase() + local.slice(1);
};

const KanbanCampCard = ({ c, onClick, onEdit, onDelete, onArchive, onConcluir, onReabrir, cardIndex, userEmail, colColor }) => {
  const [hover,    setHover]    = React.useState(false);
  const [menuOpen, setMenuOpen] = React.useState(false);
  const menuRef    = React.useRef(null);
  const [campUsage, setCampUsage] = React.useState(null);
  React.useEffect(() => {
    if (!c?.id) return;
    fetch(`/api/marketing/campanhas/${c.id}/usage`)
      .then(r => r.ok ? r.json() : null)
      .then(data => { if (data) setCampUsage(data); })
      .catch(() => {});
  }, [c?.id]);
  const bCol     = _brandColor(c.brand_slug);
  const stageCol = colColor || bCol; // cor da coluna do kanban — usada na borda e pills
  const channels = Array.isArray(c.channels) ? c.channels : [];

  // Progresso global — sempre 3 fases (Copy / Idiomas / Prompts)
  const stages = [
    { label: 'Copy',    done: c.num_copy_aprovado    || 0, total: c.num_copy    || 0, color: '#f59e0b' },
    { label: 'Idiomas', done: c.num_idiomas_aprovado || 0, total: c.num_idiomas || 0, color: '#8b5cf6' },
    { label: 'Prompts', done: c.num_prompts_aprovado || 0, total: c.num_prompts || 0, color: '#ec4899' },
  ];

  const nextAction = NEXT_ACTION_BY_STATUS[c.status] || null;
  const ownerName  = _firstNameFromEmail(userEmail);

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

  return (
    <div
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      style={{
        background: '#ffffff',
        borderTop: '1px solid #ECEFF5',
        borderRight: '1px solid #ECEFF5',
        borderBottom: '1px solid #ECEFF5',
        borderLeft: `3px solid ${stageCol}`, borderRadius: 8, padding: '12px 14px',
        boxShadow: hover ? '0 4px 12px rgba(0,0,0,0.08)' : '0 1px 3px rgba(0,0,0,0.04)',
        transition: 'box-shadow 150ms ease, transform 150ms ease', cursor: 'pointer', position: 'relative',
        transform: hover ? 'translateY(-1px)' : 'translateY(0)',
      }}
    >
      {/* Header: Marca + Kebab */}
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
        <span style={{ fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em', color: bCol, fontFamily: 'var(--font-mono, monospace)' }}>
          {c.brand_name}
        </span>
        <div style={{ display: 'flex', alignItems: 'center', gap: 6 }} onClick={e => e.stopPropagation()}>
          <div ref={menuRef} style={{ position: 'relative' }}>
            <button
              onClick={() => setMenuOpen(o => !o)}
              style={{
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                width: 22, height: 22, borderRadius: 6, cursor: 'pointer',
                background: menuOpen ? 'rgba(0,0,0,0.06)' : 'transparent',
                border: 'none', color: '#94A4C4', fontSize: 15, lineHeight: 1,
                transition: 'background 120ms ease',
              }}
              onMouseEnter={e => { if (!menuOpen) e.currentTarget.style.background = 'rgba(0,0,0,0.06)'; }}
              onMouseLeave={e => { if (!menuOpen) e.currentTarget.style.background = 'transparent'; }}
            >⋮</button>
            {menuOpen && (
              <div className="animate-in" style={{
                position: 'absolute', top: 'calc(100% + 4px)', right: 0, zIndex: 9999,
                background: '#ffffff', border: '1px solid #ECEFF5',
                borderRadius: 8, minWidth: 160, padding: 4,
                boxShadow: '0 4px 16px rgba(17,41,84,0.10)',
              }}>
                <button onClick={() => { setMenuOpen(false); onEdit(c); }}
                  style={{ display: 'block', width: '100%', textAlign: 'left', background: 'transparent', border: 'none', padding: '8px 12px', borderRadius: 4, fontSize: 13, color: '#283252', cursor: 'pointer', fontFamily: 'inherit' }}
                  onMouseEnter={e => e.currentTarget.style.background = 'rgba(0,0,0,0.04)'}
                  onMouseLeave={e => e.currentTarget.style.background = 'transparent'}
                >Editar título</button>
                {onConcluir && ['em_activacao', 'publicado'].includes(c.status) && (
                  <button onClick={() => { setMenuOpen(false); onConcluir(c); }}
                    style={{ display: 'block', width: '100%', textAlign: 'left', background: 'transparent', border: 'none', padding: '8px 12px', borderRadius: 4, fontSize: 13, color: '#283252', cursor: 'pointer', fontFamily: 'inherit' }}
                    onMouseEnter={e => e.currentTarget.style.background = 'rgba(0,0,0,0.04)'}
                    onMouseLeave={e => e.currentTarget.style.background = 'transparent'}
                  >Concluir</button>
                )}
                {onReabrir && c.status === 'concluida' && (
                  <button onClick={() => { setMenuOpen(false); onReabrir(c); }}
                    style={{ display: 'block', width: '100%', textAlign: 'left', background: 'transparent', border: 'none', padding: '8px 12px', borderRadius: 4, fontSize: 13, color: '#283252', cursor: 'pointer', fontFamily: 'inherit' }}
                    onMouseEnter={e => e.currentTarget.style.background = 'rgba(0,0,0,0.04)'}
                    onMouseLeave={e => e.currentTarget.style.background = 'transparent'}
                  >Reabrir</button>
                )}
                {onArchive && (
                  <button onClick={() => { setMenuOpen(false); onArchive(c); }}
                    style={{ display: 'block', width: '100%', textAlign: 'left', background: 'transparent', border: 'none', padding: '8px 12px', borderRadius: 4, fontSize: 13, color: '#283252', cursor: 'pointer', fontFamily: 'inherit' }}
                    onMouseEnter={e => e.currentTarget.style.background = 'rgba(0,0,0,0.04)'}
                    onMouseLeave={e => e.currentTarget.style.background = 'transparent'}
                  >Arquivar</button>
                )}
                {!['arquivada', 'cancelada'].includes(c.status) && (
                <div style={{ height: 1, background: '#ECEFF5', margin: '4px 0' }} />
                )}
                {!['arquivada', 'cancelada'].includes(c.status) && (
                <button onClick={() => { setMenuOpen(false); onDelete(c); }}
                  style={{ display: 'block', width: '100%', textAlign: 'left', background: 'transparent', border: 'none', padding: '8px 12px', borderRadius: 4, fontSize: 13, color: '#DC2626', cursor: 'pointer', fontFamily: 'inherit' }}
                  onMouseEnter={e => e.currentTarget.style.background = 'rgba(220,38,38,0.06)'}
                  onMouseLeave={e => e.currentTarget.style.background = 'transparent'}
                >Apagar</button>
                )}
              </div>
            )}
          </div>
        </div>
      </div>

      {/* Content — click area */}
      <div onClick={onClick}>

        {/* Título */}
        <div style={{ fontSize: 13, fontWeight: 700, color: '#1d2e38', fontFamily: 'var(--font-display, Montserrat, sans-serif)', lineHeight: 1.35, marginBottom: 6, overflow: 'hidden', display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical' }}>
          {c.titulo}
        </div>
        {(c.one_shot === true || String(c.one_shot) === 'true') && (
          <div style={{ margin: '-2px 0 6px' }}>
            <span style={{ fontSize: 8, fontWeight: 700, padding: '1px 6px', borderRadius: 99, background: '#3859D018', color: '#3859D0', fontFamily: 'var(--font-mono,monospace)', textTransform: 'uppercase', letterSpacing: '.04em' }}>Execução directa</span>
          </div>
        )}
        {(c.commercial_name || (Array.isArray(c.geo_markets) && c.geo_markets.length > 0)) && (
          <div style={{ fontSize: 11, color: '#64748b', marginBottom: 8, lineHeight: 1.4 }}>
            {[c.commercial_name, Array.isArray(c.geo_markets) ? c.geo_markets.slice(0, 3).join(', ') : ''].filter(Boolean).join(' · ')}
          </div>
        )}

        {/* Canais (SVG icons) */}
        {channels.length > 0 && (
          <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 10 }}>
            {channels.slice(0, 6).map(ch => (
              <span key={ch} title={CHAN_ABBR[ch] || ch} style={{ display: 'inline-flex' }}>
                <CampanhaChanIcon ch={ch} size={13} color="#64748b" />
              </span>
            ))}
            {channels.length > 6 && (
              <span style={{ fontSize: 9, color: '#94a3b8', fontFamily: 'var(--font-mono, monospace)' }}>+{channels.length - 6}</span>
            )}
          </div>
        )}

        {/* Conteúdo por fase */}
        {(() => {
          const st = c.status || '';
          const fmtD = d => d ? new Date(d).toLocaleDateString('pt-PT', { day:'numeric', month:'short' }) : null;
          const pill = (label, col) => (
            <span style={{ fontSize:8, fontWeight:700, padding:'1px 6px', borderRadius:99, background:col+'18', color:col, fontFamily:'var(--font-mono,monospace)', textTransform:'uppercase', letterSpacing:'.04em' }}>{label}</span>
          );

          if (st === 'estrategia_pendente') return (
            <div style={{ display:'flex', flexDirection:'column', gap:4, marginBottom:8 }}>
              {pill('Pendente', stageCol)}
              {channels.length > 0 && <div style={{ fontSize:9, color:'#64748b' }}>{channels.slice(0,3).map(ch => CHAN_ABBR[ch]||ch).join(' · ')}</div>}
            </div>
          );

          // Estratégia
          if (/^estrategia/.test(st)) return (
            <div style={{ display:'flex', flexDirection:'column', gap:4, marginBottom:8 }}>
              {pill(st === 'estrategia_aprovada' ? 'Aprovada' : st === 'estrategia_gerada' ? 'Gerada' : 'Pendente', stageCol)}
              {channels.length > 0 && <div style={{ fontSize:9, color:'#64748b' }}>{channels.slice(0,3).map(ch => CHAN_ABBR[ch]||ch).join(' · ')}</div>}
            </div>
          );

          // Conceito
          if (/^conceito/.test(st)) return (
            <div style={{ display:'flex', flexDirection:'column', gap:4, marginBottom:8 }}>
              {pill(st === 'conceito_gerado' ? 'Gerado' : 'Pendente', stageCol)}
              {c.big_idea && <div style={{ fontSize:10, color:'#475569', lineHeight:1.4, display:'-webkit-box', WebkitLineClamp:2, WebkitBoxOrient:'vertical', overflow:'hidden' }}>{c.big_idea}</div>}
            </div>
          );

          // Orçamento
          if (/^orcamento/.test(st)) return (
            <div style={{ display:'flex', flexDirection:'column', gap:4, marginBottom:8 }}>
              {pill(st === 'orcamento_aprovado' ? 'Aprovado' : st === 'orcamento_gerado' ? 'Gerado' : 'Pendente', stageCol)}
              {(c.timeline_start || c.timeline_end) && <div style={{ fontSize:9, color:'#64748b' }}>{[fmtD(c.timeline_start), fmtD(c.timeline_end)].filter(Boolean).join(' → ')}</div>}
            </div>
          );

          // Target
          if (/^segmentacao/.test(st)) return (
            <div style={{ display:'flex', flexDirection:'column', gap:4, marginBottom:8 }}>
              {pill(st === 'segmentacao_aprovada' ? 'Confirmado' : st === 'segmentacao_gerada' ? 'Gerado' : 'Pendente', stageCol)}
              {channels.length > 0 && <div style={{ fontSize:9, color:'#64748b' }}>{channels.slice(0,3).map(ch => CHAN_ABBR[ch]||ch).join(' · ')}</div>}
            </div>
          );

          // Planeamento
          if (/^planeamento/.test(st)) return (
            <div style={{ display:'flex', flexDirection:'column', gap:4, marginBottom:8 }}>
              {pill(st === 'planeamento_aprovado' ? 'Aprovado' : 'Gerado', stageCol)}
              {(c.timeline_start || c.timeline_end) && <div style={{ fontSize:9, color:'#64748b' }}>{[fmtD(c.timeline_start), fmtD(c.timeline_end)].filter(Boolean).join(' → ')}</div>}
            </div>
          );

          // Funil
          if (/^funil/.test(st)) return (
            <div style={{ display:'flex', flexDirection:'column', gap:4, marginBottom:8 }}>
              {pill(st === 'funil_revisto' ? 'Revisto' : 'Pendente', stageCol)}
              <div style={{ fontSize:9, color:'#64748b' }}>{channels.length > 0 ? `${channels.length} canais` : 'Funil multicanal'}</div>
            </div>
          );

          // Aprovação executiva
          if (/^pending_executive|^em_aprovacao/.test(st)) return (
            <div style={{ display:'flex', flexDirection:'column', gap:4, marginBottom:8 }}>
              {pill('Aprovação exec.', '#d97706')}
              <div style={{ fontSize:9, color:'#92400e' }}>Aguarda Board / CEO</div>
            </div>
          );

          // Produção
          if (st === 'concluida') return (
            <div style={{ display:'flex', flexDirection:'column', gap:4, marginBottom:8 }}>
              {pill('Concluída', '#0e7490')}
              <div style={{ fontSize:9, color:'#0e7490' }}>Activação terminada</div>
            </div>
          );

          if (/^em_activacao/.test(st)) return (
            <div style={{ display:'flex', flexDirection:'column', gap:4, marginBottom:8 }}>
              {pill('Activação', '#3859D0')}
              <div style={{ fontSize:9, color:'#3859D0' }}>Setup e publicação de canais</div>
            </div>
          );

          if (/^em_producao|^publicado/.test(st)) return (
            <div style={{ display:'flex', flexDirection:'column', gap:5, marginBottom:8 }}>
              {stages.map(s => {
                const complete = s.total > 0 && s.done >= s.total;
                const empty = s.total === 0;
                const pct = s.total > 0 ? Math.round((s.done/s.total)*100) : 0;
                const barCol = complete ? '#22c55e' : (empty ? '#e2e8f0' : s.color);
                const txtCol = empty ? '#cbd5e1' : (complete ? '#22c55e' : '#64748b');
                return (
                  <div key={s.label}>
                    <div style={{ display:'flex', justifyContent:'space-between', marginBottom:2 }}>
                      <span style={{ fontSize:9, color:txtCol, fontFamily:'var(--font-mono,monospace)', letterSpacing:'.06em', textTransform:'uppercase', fontWeight:600 }}>{s.label}</span>
                      <span style={{ fontSize:9, fontWeight:700, color:txtCol, fontFamily:'var(--font-mono,monospace)' }}>{empty ? '—' : `${s.done}/${s.total}`}{complete && ' ✓'}</span>
                    </div>
                    <div style={{ height:3, borderRadius:2, background:'rgba(0,0,0,.05)', overflow:'hidden' }}>
                      <div style={{ height:'100%', width:`${pct}%`, background:barCol, borderRadius:2, transition:'width .4s' }} />
                    </div>
                  </div>
                );
              })}
            </div>
          );

          // Fallback (briefing / outros)
          return (
            <div style={{ display:'flex', gap:4, marginBottom:8 }}>
              {pill('Briefing', stageCol)}
            </div>
          );
        })()}

        {/* Footer: próxima acção · owner */}
        {(nextAction || ownerName) && (
          <div style={{ display: 'flex', alignItems: 'baseline', gap: 6, borderTop: '1px solid #f1f5f9', paddingTop: 8, fontSize: 10.5, color: '#475569', fontFamily: 'var(--font-body, Inter, sans-serif)', lineHeight: 1.3, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
            {nextAction && (
              <>
                <span style={{ color: bCol, fontWeight: 700 }}>→</span>
                <span style={{ fontWeight: 600 }}>{nextAction}</span>
              </>
            )}
            {ownerName && <span style={{ color: '#94a3b8' }}>· {ownerName}</span>}
          </div>
        )}
      </div>

      {/* Custos/tokens no hover */}
      {hover && campUsage && (campUsage.total_custo > 0 || campUsage.total_tokens > 0) && (
        <div style={{ position: 'absolute', bottom: 2, right: 8, fontSize: 8.5, color: '#94a3b8', fontFamily: 'var(--font-mono, monospace)', background: 'rgba(255,255,255,0.95)', padding: '1px 6px', borderRadius: 4, pointerEvents: 'none' }}>
          €{parseFloat(campUsage.total_custo || 0).toFixed(4).replace(/0+$/, '').replace(/\.$/, '') || '0'}
          {campUsage.total_tokens > 0 && <> · {Number(campUsage.total_tokens).toLocaleString('pt-PT')} tok</>}
        </div>
      )}
    </div>
  );
};

// ── KanbanColumn ───────────────────────────────────────────────────────────────
const KanbanColumn = ({ col, cards, onCardClick, onEdit, onDelete, onArchive, onConcluir, onReabrir, userEmail }) => {
  return (
    <div style={{ flex: '1 1 0', display: 'flex', flexDirection: 'column', gap: 0, minWidth: 200 }}>
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 8, borderRadius: 10, padding: 4, minHeight: 80 }}>
        {cards.map((c, i) => (
          <KanbanCampCard key={c.id} c={c} onClick={() => onCardClick(c, col)} onEdit={onEdit} onDelete={onDelete} onArchive={onArchive} onConcluir={onConcluir} onReabrir={onReabrir} cardIndex={i} userEmail={userEmail} colColor={col.color} />
        ))}
        {cards.length === 0 && (
          <div style={{ height: 40, borderRadius: 8, border: '1px dashed var(--border, #e2e8f0)', opacity: 0.4 }} />
        )}
      </div>
    </div>
  );
};

const CopyCard = ({ cp, onApprove, onFlag }) => {
  const [exp, setExp] = React.useState(false);
  const canalLabel = CANAL_LABEL[cp.canal] || cp.canal || '—';
  const isApproved = cp.status === 'aprovado';
  const isFlagged  = cp.status === 'correcao';

  return (
    <div style={{
      background: 'var(--bg-sunken, #f1f5f9)', borderRadius: 8,
      border: `1px solid ${isApproved ? 'rgba(34,197,94,.25)' : isFlagged ? 'rgba(251,146,60,.25)' : 'var(--border, #e2e8f0)'}`,
    }}>
      <div onClick={() => setExp(e => !e)} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '12px 16px', cursor: 'pointer', userSelect: 'none' }}>
        <CanalSvgIcon canal={cp.canal} size={16} />
        <span style={{ fontSize: 13, fontWeight: 600, color: 'var(--text, #1e293b)', flex: 1, fontFamily: 'var(--font-display, Montserrat, sans-serif)' }}>{canalLabel}</span>
        {cp.formato && <span style={{ fontSize: 10, color: 'var(--text-dim, #475569)', fontFamily: 'var(--font-mono, monospace)' }}>{cp.formato}</span>}
        <CampStatusBadge status={cp.status} />
        <span style={{ fontSize: 10, color: 'var(--text-dim, #475569)', marginLeft: 4 }}>{exp ? '▴' : '▾'}</span>
      </div>
      {exp && (
        <div style={{ padding: '0 16px 14px', display: 'flex', flexDirection: 'column', gap: 10, borderTop: '1px solid var(--border, #e2e8f0)' }}>
          {cp.headline && <CopyField label="Headline" value={cp.headline} />}
          {cp.body     && <CopyField label="Body"     value={cp.body} multiline />}
          {cp.cta      && <CopyField label="CTA"      value={cp.cta} />}
          {cp.hashtags && <CopyField label="Hashtags" value={cp.hashtags} mono />}
          {!isApproved && (
            <div style={{ display: 'flex', gap: 8, marginTop: 4 }}>
              <button onClick={onApprove} className="btn btn-ai" style={{ fontSize: 11 }}>✓ Aprovar</button>
              <button onClick={onFlag}    className="btn" style={{ fontSize: 11, color: '#fb923c', borderColor: 'rgba(251,146,60,.3)' }}>⚑ Corrigir</button>
            </div>
          )}
          {isApproved && <div style={{ fontSize: 11, color: 'var(--success, #22c55e)', fontWeight: 500 }}>✓ Copy aprovado</div>}
        </div>
      )}
    </div>
  );
};

const CopyField = ({ label, value, multiline, mono }) => (
  <div>
    <div style={{ fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em', color: 'var(--text-dim, #475569)', fontFamily: 'var(--font-mono, monospace)', marginBottom: 4 }}>{label}</div>
    <div style={{ fontSize: 12.5, color: 'var(--text, #1e293b)', lineHeight: multiline ? 1.6 : 1.3, fontFamily: mono ? 'var(--font-mono, monospace)' : 'inherit', whiteSpace: multiline ? 'pre-wrap' : 'normal', wordBreak: 'break-word' }}>{value}</div>
  </div>
);

const CopyFieldLight = ({ label, value, multiline, mono }) => (
  <div>
    <div style={{ fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em', color: '#94a3b8', fontFamily: 'monospace', marginBottom: 4 }}>{label}</div>
    <div style={{ fontSize: 12.5, color: '#1d2e38', lineHeight: multiline ? 1.6 : 1.3, fontFamily: mono ? 'monospace' : 'inherit', whiteSpace: multiline ? 'pre-wrap' : 'normal', wordBreak: 'break-word' }}>{value}</div>
  </div>
);

const AdSpecField = ({ label, value, limit, light }) => {
  if (!value) return null;
  const len = (value || '').length;
  const over = limit && len > limit;
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em', color: light ? '#94a3b8' : 'var(--text-dim,#475569)', fontFamily: 'monospace' }}>{label}</span>
        {limit && <span style={{ fontSize: 9, fontFamily: 'monospace', color: over ? '#ef4444' : '#64748b', marginLeft: 'auto' }}>{len}/{limit}</span>}
      </div>
      <div style={{ fontSize: 12, color: over ? '#ef4444' : (light ? '#1d2e38' : 'var(--text,#f1f5f9)'), lineHeight: 1.4, wordBreak: 'break-word' }}>{value}</div>
    </div>
  );
};

const AD_SPEC_LIMITS = {
  instagram: { primary_text: 125, headline: 40, description: 30 },
  facebook:  { primary_text: 125, headline: 40, description: 30 },
  ads:       { primary_text: 125, headline: 40, description: 30 },
  google_ads:{ headline_1: 30, headline_2: 30, headline_3: 30, description_1: 90, description_2: 90 },
  linkedin:  { intro: 150, headline: 70 },
};

const AdSpecsBlock = ({ specs, canal, light }) => {
  const [open, setOpen] = React.useState(false);
  if (!specs || !Object.keys(specs).length) return null;
  const limits = AD_SPEC_LIMITS[canal] || {};
  const isGoogle = canal === 'google_ads';
  return (
    <div style={{ borderTop: `1px solid ${light ? '#f1f5f9' : 'var(--border,#1e293b)'}`, paddingTop: 10, marginTop: 4 }}>
      <button onClick={() => setOpen(o => !o)} style={{ background: 'none', border: 'none', padding: 0, cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 6, color: '#3859D0', fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em', fontFamily: 'monospace' }}>
        📐 Ad Specs {open ? '▲' : '▼'}
      </button>
      {open && (
        <div style={{ marginTop: 10, display: 'flex', flexDirection: 'column', gap: 8, background: 'rgba(56,89,208,.05)', borderRadius: 6, padding: '10px 12px' }}>
          {isGoogle ? (
            <>
              {[1,2,3].map(n => specs[`headline_${n}`] && <AdSpecField key={`h${n}`} label={`Headline ${n}`} value={specs[`headline_${n}`]} limit={limits[`headline_${n}`]} light={light} />)}
              {[1,2].map(n => specs[`description_${n}`] && <AdSpecField key={`d${n}`} label={`Description ${n}`} value={specs[`description_${n}`]} limit={limits[`description_${n}`]} light={light} />)}
            </>
          ) : (
            <>
              {specs.primary_text  && <AdSpecField label="Primary Text"  value={specs.primary_text}  limit={limits.primary_text}  light={light} />}
              {specs.intro         && <AdSpecField label="Intro Text"     value={specs.intro}          limit={limits.intro}         light={light} />}
              {specs.headline      && <AdSpecField label="Headline"       value={specs.headline}       limit={limits.headline}      light={light} />}
              {specs.description   && <AdSpecField label="Description"    value={specs.description}    limit={limits.description}   light={light} />}
            </>
          )}
        </div>
      )}
    </div>
  );
};

// ── Drawer: tab Prompts ────────────────────────────────────────────────────────
const PromptCard = ({ pr, onApprove, onFlag }) => {
  const [exp, setExp] = React.useState(false);
  const canalLabel = CANAL_LABEL[pr.canal] || pr.canal || '—';
  const tipoLabel  = pr.tipo === 'imagem' ? 'Imagem' : 'Vídeo';
  const isApproved = pr.status === 'aprovado';
  const isFlagged  = pr.status === 'correcao';

  return (
    <div style={{
      background: 'var(--bg-sunken, #f1f5f9)', borderRadius: 8,
      border: `1px solid ${isApproved ? 'rgba(34,197,94,.25)' : isFlagged ? 'rgba(251,146,60,.25)' : 'var(--border, #e2e8f0)'}`,
    }}>
      <div onClick={() => setExp(e => !e)} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '12px 16px', cursor: 'pointer', userSelect: 'none' }}>
        <CanalSvgIcon canal={pr.canal} size={16} />
        <span style={{ fontSize: 13, fontWeight: 600, color: 'var(--text, #1e293b)', flex: 1, fontFamily: 'var(--font-display, Montserrat, sans-serif)' }}>{canalLabel}</span>
        <span style={{ fontSize: 10, color: 'var(--text-dim, #475569)', fontFamily: 'var(--font-mono, monospace)' }}>{tipoLabel}</span>
        {pr.ratio && <span style={{ fontSize: 10, color: 'var(--text-dim, #475569)', fontFamily: 'var(--font-mono, monospace)' }}>{pr.ratio}</span>}
        <CampStatusBadge status={pr.status} />
        <span style={{ fontSize: 10, color: 'var(--text-dim, #475569)', marginLeft: 4 }}>{exp ? '▴' : '▾'}</span>
      </div>
      {exp && (
        <div style={{ padding: '0 16px 14px', display: 'flex', flexDirection: 'column', gap: 10, borderTop: '1px solid var(--border, #e2e8f0)' }}>
          {pr.prompt_texto && <CopyField label="Prompt" value={pr.prompt_texto} multiline />}
          {pr.estilo       && <CopyField label="Estilo"  value={pr.estilo} />}
          {pr.referencia   && <CopyField label="Referência" value={pr.referencia} />}
          {!isApproved && (
            <div style={{ display: 'flex', gap: 8, marginTop: 4 }}>
              <button onClick={onApprove} className="btn btn-ai" style={{ fontSize: 11 }}>✓ Aprovar</button>
              <button onClick={onFlag}    className="btn" style={{ fontSize: 11, color: '#fb923c', borderColor: 'rgba(251,146,60,.3)' }}>⚑ Corrigir</button>
            </div>
          )}
          {isApproved && <div style={{ fontSize: 11, color: 'var(--success, #22c55e)', fontWeight: 500 }}>✓ Prompt aprovado</div>}
        </div>
      )}
    </div>
  );
};


// ── WorkflowTracker (vertical sidebar) ────────────────────────────────────────
// ── TabEstrategiaFull — estratégia por mercado gerada com base em briefing + KB ──
const ESTRATEGIA_STEPS = [
  { step: 1, label: 'Briefing & Produto',      detail: 'Leitura dos 5 blocos do briefing · USPs · dores · restrições absolutas',                                 duration: 3000 },
  { step: 2, label: 'Preferências de Mercado', detail: 'KB market_preferences por país + segmento (channel weights · decision pattern · cultural codes)',        duration: 5000 },
  { step: 3, label: 'Validação de Dados',      detail: 'Mercados com/sem dados KB · confidence level · gaps a marcar',                                            duration: 1500 },
  { step: 4, label: 'Geração por Mercado',     detail: 'Claude · personas priorizadas · channel_fit (%) · tom cultural · mensagem unificada 3-fases · KPIs',      duration: 25000 },
  { step: 5, label: 'Guardar Estratégia',      detail: 'Persistência estrategia_json e actualização de status',                                                   duration: 1500 },
];

const CampanhaDetail = ({ campanhaSummary, onBack, onRefresh, initialTab = 'conceito', userEmail }) => {
  const [campanha,   setCampanha]   = React.useState(null);
  const [activeTab,  setActiveTab]  = React.useState(initialTab);
  const [generating, setGenerating] = React.useState({});
  const [genStep,    setGenStep]    = React.useState({ key: null, step: 0, message: '', total: 0, langs: [] });
  const [toast,      setToast]      = React.useState('');
  const [toastType,  setToastType]  = React.useState('success');

  const load = React.useCallback(async () => {
    try {
      const c = await CampAPI.get(campanhaSummary.id);
      setCampanha(c);
    } catch (e) {
      console.error('[CampanhaDetail load]', e);
    }
  }, [campanhaSummary.id]);

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

  const showToast = (msg, type = 'success', ms = 3000) => { setToast(msg); setToastType(type); setTimeout(() => setToast(''), ms); };

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

  const handleAction = async (action, ...args) => {
    if (!campanha) return;
    if (campanha.status === 'arquivada' || campanha.status === 'cancelada') {
      showToast('Esta campanha está arquivada. Restaura-a em Campanhas Arquivadas para voltar a trabalhar nela.', 'info');
      return;
    }
    if (campanha.status === 'concluida') {
      showToast('Esta campanha está concluída. Reabre-a no quadro para voltar a trabalhar nela.', 'info');
      return;
    }
    const id = campanha.id;
    try {
      if (action === 'generateConceito') {
        setGenerating({ conceito: true });
        const conceitoData = await CampAPI.generateConceito(id, (step, msg) => setGenStep({ key: 'conceito', step, message: msg, total: 0, langs: [] }));
        console.log('[DEBUG conceito] data:', conceitoData, 'big_idea:', conceitoData?.big_idea);
        if (conceitoData?.big_idea) setCampanha(prev => ({ ...(prev || {}), ...conceitoData }));
        showToast('Conceito gerado');
        if (onRefresh) onRefresh();
        return; // dados frescos do done event já aplicados — skip load()
      } else if (action === 'generateCommPlan') {
        setGenerating({ commPlan: true });
        const commPlanData = await CampAPI.generateCommPlan(id, (step, msg) => setGenStep({ key: 'commPlan', step, message: msg, total: 0, langs: [] }));
        if (commPlanData?.data) setCampanha(prev => ({ ...(prev || {}), proposta_json: { ...((prev?.proposta_json) || {}), comm_plan: commPlanData.data } }));
        showToast('Plano de comunicação gerado');
        if (onRefresh) onRefresh();
        return; // idem
      } else if (action === 'aprovarConceito') {
        setGenerating({ approve: true });
        await CampAPI.aprovarConceito(id, { approver_name: userName, approver_email: userEmail });
        showToast('Conceito aprovado');
      } else if (action === 'generateCopy') {
        setGenerating({ copy: true });
        await CampAPI.generateCopy(id, (step, msg) => setGenStep({ key: 'copy', step, message: msg, total: 0, langs: [] }));
        showToast('Copy gerado');
      } else if (action === 'approveCopy') {
        const [copyId, status] = args;
        await CampAPI.patchCopy(copyId, { status });
      } else if (action === 'aprovarCopy') {
        await CampAPI.approveCopyAll(id);
        showToast('Copy aprovado');
      } else if (action === 'saveCopy') {
        const [copyId, data] = args;
        await CampAPI.patchCopy(copyId, data);
        showToast('Copy guardado');
      } else if (action === 'regenerateCopy') {
        showToast('Regenerar individual ainda por implementar', 'info', 4000);
      } else if (action === 'generateIdiomas') {
        setGenerating({ idiomas: true });
        const [lang] = args;
        await CampAPI.generateIdiomas(id, lang ? [lang] : [], (step, msg, total, langs) => setGenStep({ key: 'idiomas', step, message: msg, total: total || 0, langs: langs || [] }));
        showToast('Idiomas gerados');
      } else if (action === 'regenerateIdioma') {
        const [copyId, lang] = args;
        setGenerating({ idiomas: true });
        await CampAPI.generateIdiomas(id, [lang], (step, msg) => setGenStep({ key: 'idiomas', step, message: msg, total: 0, langs: [] }));
        showToast('Tradução regenerada');
      } else if (action === 'aprovarIdiomas') {
        await CampAPI.patchIdiomas(id, { action: 'aprovar', approver_name: userName, approver_email: userEmail });
        showToast('Idiomas aprovados');
      } else if (action === 'generatePrompts') {
        setGenerating({ prompts: true });
        await CampAPI.generatePrompts(id);
        showToast('Prompts gerados');
      } else if (action === 'regeneratePrompt') {
        showToast('Regenerar prompt individual ainda por implementar', 'info', 4000);
      } else if (action === 'approvePrompt') {
        const [pId, status] = args;
        await CampAPI.patchPrompt(pId, { status });
      } else if (action === 'generateEstrategia' || action === 'actualizarBriefing') {
        setGenerating({ estrategia: true });
        const viva = ['em_producao', 'em_activacao', 'publicado'].includes(campanha?.status);
        await CampAPI.generateEstrategia(id, (step, msg) => setGenStep({ key: 'estrategia', step, message: msg, total: 0, langs: [] }), { manterEstado: viva || action === 'actualizarBriefing' });
        showToast(viva || action === 'actualizarBriefing'
          ? 'Estratégia actualizada. Conteúdos e activação mantêm-se.'
          : 'Estratégia gerada');
      } else if (action === 'aprovarEstrategia') {
        setGenerating({ approveEstrategia: true });
        await CampAPI.aprovarEstrategia(id, { email: userEmail, name: userEmail?.split('@')[0] });
        showToast('Estratégia aprovada');
      } else if (action === 'enviarProducao') {
        await CampAPI.enviarProducao(id, { approver_name: userName, approver_email: userEmail });
        showToast('Enviado para produção');
      } else if (action === 'refreshCampanha') {
        await load();
        if (onRefresh) onRefresh(); // recarrega kanban para reflectir novo status (ex: planeamento_aprovado → coluna Funil)
      } else if (action === 'aprovarOrcamento') {
        const opts = args[0] || {};
        setGenerating({ approve: true });
        await campApiCall(`/api/marketing/campanhas/${id}/orcamento/approve`, {
          method: 'POST', body: JSON.stringify({ user_email: userEmail, user_name: userName, confirmar_excesso: !!opts.confirmar_excesso })
        });
        showToast('Orçamento aprovado');
      } else if (action === 'generateOrcamento') {
        setGenerating({ orcamento: true });
        await CampAPI.generateOrcamento(id, (step, msg) => setGenStep({ key: 'orcamento', step, message: msg, total: 0, langs: [] }), { user_email: userEmail });
        showToast('Orçamento gerado');
      } else if (action === 'generateSegmentacao') {
        setGenerating({ segmentacao: true });
        await campApiCall(`/api/marketing/campanhas/${id}/segmentacao/generate`, { method: 'POST', body: JSON.stringify({ user_email: userEmail }) });
        showToast('Target gerado');
      } else if (action === 'generatePlaneamento') {
        setGenerating({ planeamento: true });
        await campApiCall(`/api/marketing/campanhas/${id}/planeamento/generate`, { method: 'POST', body: JSON.stringify({ user_email: userEmail }) });
        showToast('Planeamento gerado');
      } else if (action === 'enviarAprovacao') {
        // Aprovar Funil (do TabFunil) → move para SDR AI (checkpoint intermédio)
        setGenerating({ enviarAprovacao: true });
        await campApiCall(`/api/marketing/campanhas/${id}`, { method: 'PATCH', body: JSON.stringify({ status: 'sdr_ai_pendente', actor_name: userName, actor_email: userEmail }) });
        showToast('Funil aprovado — aguarda validação SDR AI');
      } else if (action === 'enviarAprovacaoExecutiva') {
        // Do TabAprovacao State 1 (checklist): enviar directamente para aprovação CEO/Chairman
        // → dispara sendFunilAprovacaoEmail no backend
        setGenerating({ enviarAprovacaoExecutiva: true });
        await campApiCall(`/api/marketing/campanhas/${id}`, { method: 'PATCH', body: JSON.stringify({ status: 'pending_executive', actor_name: userName, actor_email: userEmail }) });
        showToast('Enviado para aprovação. O email vai só para fabio.costa@digidelta.pt');
      } else if (action === 'aprovarSdrAi') {
        // Aprovar SDR AI (do TabSDRAI) → move para Aprovação executiva final
        setGenerating({ aprovarSdrAi: true });
        await campApiCall(`/api/marketing/campanhas/${id}`, { method: 'PATCH', body: JSON.stringify({ status: 'pending_executive', actor_name: userName, actor_email: userEmail }) });
        showToast('SDR AI aprovado. O email de aprovação vai só para fabio.costa@digidelta.pt');
      } else if (action === 'rejeitarAprovacao') {
        const [notes] = args;
        await CampAPI.requestChanges(id, { action: 'request_changes', notes });
        showToast('Alterações solicitadas', 'info');
      } else if (action === 'pedirAlteracoes') {
        const [{ notes, phase }] = args;
        const revertMap = {
          briefing: 'rascunho', estrategia: 'estrategia_pendente', conceito: 'conceito_pendente',
          orcamento: 'orcamento_pendente', segmentacao: 'segmentacao_pendente',
          planeamento: 'planeamento_pendente', funil: 'funil_pendente', sdr_ai: 'sdr_ai_pendente',
        };
        const revertStatus = revertMap[phase] || 'funil_pendente';
        await campApiCall(`/api/marketing/campanhas/${id}`, {
          method: 'PATCH',
          body: JSON.stringify({
            status: revertStatus, phase, notes,
            actor_name: userName, actor_email: userEmail,
          })
        });
        showToast('Alterações pedidas — campanha voltou a ' + phase + ' · email enviado ao owner', 'info');
      } else {
        console.warn('[handleAction] unknown action', action, args);
      }
      await load();
      if (onRefresh) onRefresh();
    } catch (e) {
      console.error('[handleAction]', action, e);
      showToast('Erro: ' + (e.message || 'operação falhou'), 'error', 5000);
    } finally {
      setGenerating({});
      setGenStep({ key: null, step: 0, message: '', total: 0, langs: [] });
    }
  };

  if (!campanha) {
    return (
      <div data-theme="light" style={{ height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', background: '#f8fafc' }}>
        <div style={{ fontSize: 13, color: '#64748b' }}>A carregar campanha…</div>
      </div>
    );
  }

  const bCol    = _brandColor(campanha.brand_slug);
  const copy    = campanha.copy    || [];
  const idiomas = campanha.idiomas || [];
  const prompts = campanha.prompts || [];

  const TABS = [
    { id: 'briefing',    label: 'Briefing' },
    { id: 'campanha',    label: 'Campanha' },
    { id: 'estrategia',  label: 'Estratégia' },
    { id: 'conceito',    label: 'Conceito' },
    { id: 'orcamento',   label: 'Orçamento' },
    { id: 'segmentacao', label: 'Target' },
    { id: 'planeamento', label: 'Planeamento' },
    { id: 'funil',       label: 'Funil Multicanal' },
    { id: 'sdr',         label: 'SDR AI' },
    { id: 'aprovacao',   label: 'Aprovação' },
  ];

  const conceitoStep = genStep.key === 'conceito' ? genStep.step    : 0;
  const conceitoMsg  = genStep.key === 'conceito' ? genStep.message : '';

  return (
    <div data-theme="light" style={{ height: '100%', display: 'flex', flexDirection: 'column', background: '#f8fafc' }}>
      {/* Header — padrão BriefingForm */}
      <div style={{ background: 'var(--bg-elev, #ffffff)', borderBottom: '1px solid var(--border, #ECEFF5)', padding: '20px 40px 0', flexShrink: 0 }}>

        {/* Breadcrumb */}
        <div style={{ fontSize: 11, fontWeight: 600, letterSpacing: '0.07em', color: 'var(--text-muted)', textTransform: 'uppercase', marginBottom: 6, fontFamily: 'var(--font-mono)' }}>
          MARKETING · CAMPANHAS
        </div>

        {/* Title + actions */}
        <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16, marginBottom: 10 }}>
          <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 }}>
              {campanha?.titulo || '—'}
            </h1>
            {/* Meta row: status + brand pill */}
            <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginTop: 8, flexWrap: 'wrap' }}>
              {campanha && <CampStatusBadge status={campanha.status || 'conceito_pendente'} />}
              {(campanha?.brand_name || campanha?.briefing?.brand_name) && (() => {
                const bn = campanha.brand_name || campanha.briefing?.brand_name;
                return <span style={{ fontSize: 10, fontWeight: 700, padding: '2px 8px', borderRadius: 4, background: bCol + '15', color: bCol, fontFamily: 'var(--font-mono)', border: `1px solid ${bCol}25`, letterSpacing: '0.04em' }}>{bn.toUpperCase()}</span>;
              })()}
            </div>
          </div>

          {/* Actions row — apenas navegação entre tabs + Sair (CTAs contextuais vivem dentro de cada tab) */}
          <div style={{ display: 'flex', gap: 6, alignItems: 'center', flexShrink: 0, marginTop: 4 }}>
            {/* Prev / Next com label da fase */}
            {(() => {
              const tabIds = TABS.map(t => t.id);
              const curIdx = tabIds.indexOf(activeTab);
              const prevTab = curIdx > 0 ? TABS[curIdx - 1] : null;
              const nextTab = curIdx < tabIds.length - 1 ? TABS[curIdx + 1] : null;
              const navBtn = (dir, tab, onClick) => (
                <button
                  onClick={onClick}
                  disabled={!tab}
                  className="btn"
                  title={tab ? tab.label : ''}
                  style={{ height: 32, padding: '0 10px', fontSize: 11, fontWeight: 600,
                    color: tab ? 'var(--text-muted)' : 'var(--text-dim)',
                    opacity: tab ? 1 : 0.4, display: 'flex', alignItems: 'center', gap: 5, whiteSpace: 'nowrap' }}
                >
                  {dir === 'prev' && <span style={{ fontSize: 13 }}>←</span>}
                  <span style={{ maxWidth: 80, overflow: 'hidden', textOverflow: 'ellipsis' }}>{tab ? tab.label : '—'}</span>
                  {dir === 'next' && <span style={{ fontSize: 13 }}>→</span>}
                </button>
              );
              return (
                <>
                  {navBtn('prev', prevTab, () => prevTab && setActiveTab(prevTab.id))}
                  {navBtn('next', nextTab, () => nextTab && setActiveTab(nextTab.id))}
                </>
              );
            })()}
            {/* Botão global — só quando campanha em produção/publicada — leva ao módulo Produção de Conteúdos com esta campanha aberta */}
            {campanha && ['em_producao','em_activacao','publicado'].includes(campanha.status) && (
              <>
                <div style={{ width: 1, height: 20, background: 'var(--border)', flexShrink: 0, margin: '0 2px' }} />
                <button
                  className="btn btn-ai"
                  title="Abrir esta campanha no módulo Produção de Conteúdos"
                  style={{ height: 32, padding: '0 12px', fontSize: 11, fontWeight: 600, display: 'inline-flex', alignItems: 'center', gap: 6, whiteSpace: 'nowrap' }}
                  onClick={() => {
                    const cid = campanha.id;
                    if (window.mktNavToSub) window.mktNavToSub('conteudos');
                    setTimeout(() => {
                      if (window.mktProducaoOpenCampanha) window.mktProducaoOpenCampanha(cid);
                    }, 200);
                  }}
                >
                  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14M13 5l7 7-7 7"/></svg>
                  Produção de Conteúdos
                </button>
              </>
            )}
            {campanha && ['em_activacao','publicado'].includes(campanha.status) && (
              <>
                <div style={{ width: 1, height: 20, background: 'var(--border)', flexShrink: 0, margin: '0 2px' }} />
                <button
                  className="btn btn-ai"
                  title="Abrir esta campanha no módulo Activação"
                  style={{ height: 32, padding: '0 12px', fontSize: 11, fontWeight: 600, display: 'inline-flex', alignItems: 'center', gap: 6, whiteSpace: 'nowrap' }}
                  onClick={() => {
                    const cid = campanha.id;
                    if (window.mktNavToSub) window.mktNavToSub('publicacao');
                    setTimeout(() => {
                      const params = new URLSearchParams(window.location.hash.replace(/^#/, ''));
                      params.set('screen', 'marketing');
                      params.set('camp', cid);
                      window.location.hash = params.toString();
                    }, 200);
                  }}
                >
                  Activação
                </button>
              </>
            )}
            {/* Separador antes do Sair */}
            <div style={{ width: 1, height: 20, background: 'var(--border)', flexShrink: 0, margin: '0 2px' }} />
            {/* Sair — ícone X */}
            <button
              onClick={onBack}
              className="btn"
              title="Fechar campanha e voltar à lista"
              style={{ height: 32, width: 32, padding: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--text-muted)' }}
            >
              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
            </button>
          </div>
        </div>

        {/* Tabs bar — só as tabs, scrollable */}
        <div style={{ display: 'flex', gap: 2, alignItems: 'center', overflowX: 'auto' }}>
          {TABS.map(t => (
            <button key={t.id} onClick={() => setActiveTab(t.id)} style={{
              background: 'transparent',
              border: 'none', borderBottom: `2px solid ${activeTab === t.id ? bCol : 'transparent'}`,
              padding: '10px 14px', fontSize: 12.5, fontWeight: activeTab === t.id ? 700 : 500,
              color: activeTab === t.id ? 'var(--text)' : 'var(--text-muted)', cursor: 'pointer',
              fontFamily: 'inherit', whiteSpace: 'nowrap', transition: 'color 120ms, border-color 120ms',
              marginBottom: -1,
            }}>{t.label}</button>
          ))}
        </div>
      </div>
      {/* Content */}
      <div style={{ flex: 1, overflow: 'auto', padding: '20px 40px 40px' }}>
        {(campanha?.status === 'arquivada' || campanha?.status === 'cancelada') && (
          <div style={{ marginBottom: 14, padding: '10px 14px', background: '#f8fafc', border: '1px solid #e2e8f0', borderRadius: 8, fontSize: 13, color: '#475569' }}>
            Esta campanha está em Campanhas Arquivadas. Podes consultá-la. Para a voltar a pôr no quadro, restaura-a nessa lista.
          </div>
        )}
        {campanha?.status === 'concluida' && (
          <div style={{ marginBottom: 14, padding: '10px 14px', background: '#f0fdfa', border: '1px solid #99f6e4', borderRadius: 8, fontSize: 13, color: '#115e59' }}>
            Esta campanha está concluída. Reabre-a no quadro se ainda tiver de voltar a activação.
          </div>
        )}
        {(() => {
          const bruto = campanha?.activacao_json?.estrategia_desactualizada;
          const motivo = bruto === 'briefing' ? 'briefing' : (bruto === true || bruto === 'true' || bruto === 'campanha' ? 'campanha' : null);
          if (!motivo || generating?.estrategia) return null;
          const viva = ['em_producao', 'em_activacao', 'publicado'].includes(campanha.status);
          const terminal = ['arquivada', 'cancelada', 'concluida'].includes(campanha.status);
          const aActualizar = !!(generating?.estrategia);
          const texto = motivo === 'briefing'
            ? (viva
              ? 'O briefing foi alterado. Podes actualizar a estratégia neste módulo. Conteúdos e activação mantêm-se.'
              : terminal
              ? 'O briefing foi alterado. Esta campanha está arquivada ou concluída, por isso o que foi gerado mantém-se.'
              : 'O briefing foi alterado. A estratégia e as outras tabs de Campanhas deixaram de valer. Gera a estratégia outra vez. Conteúdos e activação mantêm-se.')
            : (viva || terminal
              ? 'A ficha da campanha foi alterada. O que já estava gerado mantém-se até voltares a gerar a estratégia.'
              : 'A ficha da campanha foi alterada. A estratégia e o que já estava gerado nas outras tabs deixou de valer. Gera a estratégia outra vez.');
          return (
            <div style={{ marginBottom: 14, padding: '10px 14px', background: '#fffbeb', border: '1px solid #fde68a', borderRadius: 8, fontSize: 13, color: '#92400e', lineHeight: 1.5, display: 'flex', alignItems: 'center', gap: 12 }}>
              <span style={{ flex: 1 }}>{texto}</span>
              {motivo === 'briefing' && viva && (
                <button
                  type="button"
                  className="btn btn-ai"
                  disabled={aActualizar}
                  onClick={() => { setActiveTab('estrategia'); handleAction('actualizarBriefing'); }}
                  style={{ height: 28, padding: '0 12px', fontSize: 12, flexShrink: 0 }}
                >{aActualizar ? 'A actualizar...' : 'Actualizar'}</button>
              )}
            </div>
          );
        })()}
        {activeTab === 'briefing'    && <TabBriefingFull  campanha={campanha} />}
        {activeTab === 'campanha'    && <TabCampanha campanha={campanha} onSaved={() => { load(); onRefresh?.(); }} />}
        {activeTab === 'estrategia'  && <TabEstrategiaFull key={`estrat-${campanha.estrategia_generated_at || 'none'}-${campanha.estrategia_approved_at || 'noapp'}`} campanha={campanha} userEmail={userEmail} generating={generating} genStep={genStep} onAction={handleAction} />}
        {activeTab === 'funil'       && <TabFunilMulticanal campanha={campanha} onAction={handleAction} />}
        {activeTab === 'orcamento'    && <TabOrcamento    campanha={campanha} userEmail={userEmail} onAction={handleAction} />}
        {activeTab === 'segmentacao'  && <TabTarget       campanha={campanha} userEmail={userEmail} onAction={handleAction} />}
        {activeTab === 'planeamento'  && <TabPlaneamento  campanha={campanha} copy={copy} prompts={prompts} userEmail={userEmail} onAction={handleAction} />}
        {activeTab === 'conceito'    && <TabConceitoFull  campanha={campanha} onAction={handleAction} generating={generating} conceitoStep={conceitoStep} conceitoMsg={conceitoMsg} />}
        {activeTab === 'sdr'         && <TabSDRAI         campanha={campanha} userEmail={userEmail} onAction={handleAction} />}
        {activeTab === 'aprovacao'   && <TabAprovacao     campanha={campanha} onAction={handleAction} userEmail={userEmail} />}
      </div>
      <MktCampToast msg={toast} type={toastType} />
    </div>
  );
};

const DIRECTA_CANAIS = [
  { id: 'meta_ads', label: 'Meta Ads', sub: 'Facebook e Instagram' },
  { id: 'email', label: 'Email', sub: 'Um envio' },
  { id: 'whatsapp', label: 'WhatsApp', sub: 'Uma mensagem' },
  { id: 'blog', label: 'Blog', sub: 'Um artigo' },
  { id: 'landing', label: 'Landing', sub: 'Uma página de conversão' },
  { id: 'site', label: 'Site', sub: 'Uma página do site' },
  { id: 'interno', label: 'Comunicação Interna', sub: 'Briefing para a equipa' },
  { id: 'led', label: 'LED', sub: 'Um painel' },
];
const DIRECTA_MERCADOS = [
  { code: 'PT', label: 'Portugal', nomes: ['pt', 'portugal'] },
  { code: 'ES', label: 'Espanha', nomes: ['es', 'espanha', 'spain', 'espana'] },
  { code: 'FR', label: 'França', nomes: ['fr', 'franca', 'france'] },
  { code: 'IT', label: 'Itália', nomes: ['it', 'italia', 'italy'] },
  { code: 'UK', label: 'UK', nomes: ['uk', 'gb', 'reino unido', 'united kingdom', 'inglaterra'] },
  { code: 'DE', label: 'Alemanha', nomes: ['de', 'alemanha', 'germany', 'deutschland'] },
  { code: 'TR', label: 'Turquia', nomes: ['tr', 'turquia', 'turkey', 'turkiye'] },
];
const directaPais = (raw) => {
  const key = String(raw || '').trim().toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '');
  if (!key) return null;
  return DIRECTA_MERCADOS.find(m => m.code.toLowerCase() === key || m.nomes.includes(key)) || null;
};
const mercadosDoBriefing = (b) => {
  let lista = b?.geo_markets;
  if (typeof lista === 'string') { try { lista = JSON.parse(lista); } catch { lista = []; } }
  lista = Array.isArray(lista) ? lista : [];
  const vistos = new Set();
  const out = [];
  lista.forEach(item => {
    const texto = item && typeof item === 'object' ? (item.country || item.name || item.label || '') : item;
    const pais = directaPais(texto);
    if (!pais || vistos.has(pais.code)) return;
    vistos.add(pais.code);
    out.push(pais);
  });
  return out;
};

const ExecucaoDirectaModal = ({ onClose, onCreated, userEmail, initialBrand }) => {
  const [briefings, setBriefings] = React.useState(null);
  const [selected, setSelected] = React.useState(null);
  const [titulo, setTitulo] = React.useState('');
  const [canais, setCanais] = React.useState([]);
  const [pais, setPais] = React.useState('');
  const [mercados, setMercados] = React.useState([]);
  const [mercadoDoBriefing, setMercadoDoBriefing] = React.useState(true);
  const [saving, setSaving] = React.useState(false);
  const [error, setError] = React.useState('');
  const [brandFilter, setBrandFilter] = React.useState(initialBrand && initialBrand !== 'all' ? initialBrand : 'all');
  const lock = React.useRef(false);

  React.useEffect(() => {
    CampAPI.availableForCampaign()
      .then(d => setBriefings(Array.isArray(d) ? d : []))
      .catch(() => setBriefings([]));
  }, []);

  const escolher = (b) => {
    const doBriefing = mercadosDoBriefing(b);
    setSelected(b);
    setError('');
    setMercadoDoBriefing(doBriefing.length > 0);
    setMercados(doBriefing.length ? doBriefing : DIRECTA_MERCADOS);
    setPais(doBriefing.length === 1 ? doBriefing[0].code : '');
  };

  const criar = async () => {
    if (!selected || lock.current) return;
    const nome = (titulo || '').trim() || (Number(selected.n_campanhas) > 0 ? '' : (selected.commercial_name || selected.product_name || ''));
    if (!nome) { setError('Dá um nome a esta campanha.'); return; }
    if (!pais) { setError('Escolhe o mercado desta execução.'); return; }
    if (!canais.length) { setError('Escolhe pelo menos um canal.'); return; }
    lock.current = true;
    setSaving(true);
    setError('');
    try {
      const camp = await CampAPI.oneShot({
        titulo: nome,
        brand_slug: selected.brand_slug,
        briefing_id: selected.id,
        canais,
        pais,
        created_by_email: userEmail || '',
      });
      if (camp?.error) throw new Error(camp.error);
      onCreated(camp);
    } catch (e) {
      setError(e.message || 'Não foi possível criar.');
    } finally {
      lock.current = false;
      setSaving(false);
    }
  };

  const visiveis = (briefings || []).filter(b => brandFilter === 'all' || b.brand_slug === brandFilter);
  const pronto = !!(selected && pais && canais.length && ((titulo || '').trim() || Number(selected.n_campanhas) === 0));

  return (
    <div style={{
      position: 'fixed', inset: 0, zIndex: 200,
      background: 'rgba(0,0,0,0.55)', display: 'flex', alignItems: 'center', justifyContent: 'center',
    }} onClick={e => { if (e.target === e.currentTarget) onClose(); }}>
      <div style={{
        background: '#ffffff', border: '1px solid var(--border, #e2e8f0)',
        borderRadius: 12, width: 560, maxHeight: 'min(820px, calc(100vh - 48px))', display: 'flex', flexDirection: 'column',
        boxShadow: '0 24px 64px rgba(0,0,0,0.4)',
      }}>
        <div style={{ padding: '20px 24px 16px', borderBottom: '1px solid var(--border, #e2e8f0)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexShrink: 0 }}>
          <div>
            <div style={{ fontSize: 10, fontFamily: 'var(--font-mono, monospace)', color: 'var(--text-dim, #475569)', letterSpacing: '0.08em', marginBottom: 4 }}>EXECUÇÃO DIRECTA</div>
            <div style={{ fontSize: 15, fontWeight: 600, fontFamily: 'var(--font-display, Montserrat, sans-serif)', color: 'var(--text, #1e293b)' }}>Entra já na produção</div>
            <div style={{ fontSize: 12, color: 'var(--text-muted, #64748b)', marginTop: 4 }}>Um briefing aprovado, o mercado e os canais. A validação é a do quadro.</div>
          </div>
          <button onClick={onClose} style={{ background: 'none', border: 'none', color: 'var(--text-muted, #64748b)', fontSize: 18, cursor: 'pointer', lineHeight: 1, padding: 4 }}>✕</button>
        </div>
        {briefings && briefings.length > 0 && (() => {
          const brands = [{ slug: 'all', name: 'Todas', color: '#3859D0' },
            ...Object.values(briefings.reduce((acc, b) => {
              if (b.brand_slug && !acc[b.brand_slug]) acc[b.brand_slug] = { slug: b.brand_slug, name: b.brand_name || b.brand_slug, color: b.brand_color || '#3859D0' };
              return acc;
            }, {}))
          ];
          if (brands.length <= 2) return null;
          return (
            <div style={{ display: 'flex', gap: 6, padding: '10px 24px', borderBottom: '1px solid var(--border, #ECEFF5)', flexWrap: 'wrap', flexShrink: 0 }}>
              {brands.map(b => {
                const active = b.slug === brandFilter;
                return (
                  <button key={b.slug} onClick={() => { setBrandFilter(b.slug); setSelected(null); setPais(''); }} style={{
                    padding: '4px 12px', borderRadius: 99, border: `1px solid ${active ? b.color : 'var(--border, #ECEFF5)'}`,
                    background: active ? b.color + '15' : 'transparent',
                    color: active ? b.color : 'var(--text-muted, #94A4C4)',
                    fontSize: 12, fontWeight: active ? 600 : 400, cursor: 'pointer', fontFamily: 'inherit',
                  }}>{b.name}</button>
                );
              })}
            </div>
          );
        })()}
        <div className="scrollbar" style={{ flex: 1, overflowY: 'auto', padding: '12px 24px', minHeight: 160 }}>
          {briefings === null ? (
            <div style={{ padding: '32px 0', textAlign: 'center', color: 'var(--text-muted, #64748b)', fontSize: 13 }}>A carregar…</div>
          ) : briefings.length === 0 ? (
            <div style={{ padding: '32px 0', textAlign: 'center', fontSize: 13, color: 'var(--text-muted, #64748b)' }}>Não há briefings aprovados disponíveis.</div>
          ) : visiveis.length === 0 ? (
            <div style={{ padding: '32px 0', textAlign: 'center', fontSize: 13, color: 'var(--text-muted, #64748b)' }}>Esta marca não tem briefings aprovados.</div>
          ) : visiveis.map(b => {
            const isActive = selected?.id === b.id;
            const bColor = b.brand_color || '#3859D0';
            const n = Number(b.n_campanhas || 0);
            return (
              <button key={b.id} type="button" onClick={() => escolher(b)} style={{
                padding: '12px 14px', borderRadius: 8, cursor: 'pointer', marginBottom: 6, width: '100%',
                border: `1px solid ${isActive ? 'var(--ai-500, #3859D0)' : 'var(--border, #e2e8f0)'}`,
                background: isActive ? 'color-mix(in oklch, var(--ai-500, #3859D0) 8%, transparent)' : 'var(--bg, #f8fafc)',
                display: 'flex', alignItems: 'center', gap: 12, textAlign: 'left', fontFamily: 'inherit',
              }}>
                <div style={{ width: 4, borderRadius: 2, alignSelf: 'stretch', background: bColor, flexShrink: 0 }} />
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
                    <span style={{ fontSize: 11, fontWeight: 700, color: bColor, fontFamily: 'var(--font-display, Montserrat, sans-serif)', letterSpacing: '0.04em' }}>{b.brand_name}</span>
                    <span style={{ fontSize: 12, color: 'var(--text, #1e293b)', fontWeight: 500 }}>{b.commercial_name || b.product_name || `Briefing #${b.id}`}</span>
                  </div>
                  <div style={{ fontSize: 11, color: '#64748b', marginTop: 2 }}>
                    Aprovado
                    {n > 0 ? ` · ${n} campanha${n === 1 ? '' : 's'}` : ''}
                  </div>
                </div>
                {isActive && <div style={{ color: 'var(--ai-500, #3859D0)', fontSize: 16, flexShrink: 0 }}>✓</div>}
              </button>
            );
          })}
        </div>
        {selected && (
          <div style={{ borderTop: '1px solid var(--border, #e2e8f0)', padding: '14px 24px', flexShrink: 0, maxHeight: '46%', overflowY: 'auto' }}>
            <label style={{ display: 'block', fontSize: 12, color: '#4a5e7a' }}>
              Nome da campanha
              <input value={titulo} onChange={e => setTitulo(e.target.value)} placeholder={Number(selected.n_campanhas) > 0 ? 'Obrigatório nesta activação' : (selected.commercial_name || selected.product_name || '')} style={{ display: 'block', width: '100%', marginTop: 4, boxSizing: 'border-box', border: '1px solid #dde3ef', borderRadius: 8, padding: '8px 10px', font: 'inherit', fontSize: 13 }} />
            </label>
            {Number(selected.n_campanhas) > 0 && (
              <div style={{ fontSize: 12, color: '#64748b', marginTop: 6 }}>Este briefing já tem campanhas. Esta execução fica com a sua própria ficha.</div>
            )}
            <div style={{ fontSize: 12, color: '#4a5e7a', marginTop: 12, marginBottom: 6 }}>{mercadoDoBriefing ? 'Mercado do briefing' : 'Mercado desta execução'}</div>
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
              {mercados.map(m => {
                const on = pais === m.code;
                return (
                  <button key={m.code} type="button" onClick={() => setPais(m.code)} style={{
                    border: `1px solid ${on ? '#3859D0' : '#E3E6EB'}`, background: on ? '#EFF3FF' : '#fff',
                    color: on ? '#3859D0' : '#112954', borderRadius: 99, padding: '4px 10px', fontSize: 12, fontWeight: 700, cursor: 'pointer', fontFamily: 'inherit',
                  }}>{m.label}</button>
                );
              })}
            </div>
            <div style={{ fontSize: 12, color: '#4a5e7a', marginTop: 12, marginBottom: 6 }}>Canais</div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6 }}>
              {DIRECTA_CANAIS.map(c => {
                const on = canais.includes(c.id);
                return (
                  <button key={c.id} type="button" onClick={() => setCanais(prev => prev.includes(c.id) ? prev.filter(x => x !== c.id) : [...prev, c.id])} style={{
                    display: 'flex', alignItems: 'flex-start', gap: 8, textAlign: 'left',
                    border: `1px solid ${on ? '#3859D0' : '#E3E6EB'}`, background: on ? '#EFF3FF' : '#fff',
                    borderRadius: 8, padding: '8px 10px', cursor: 'pointer', fontFamily: 'inherit',
                  }}>
                    <span style={{ flex: 1, minWidth: 0 }}>
                      <span style={{ display: 'block', fontSize: 12, fontWeight: 700, color: on ? '#3859D0' : '#112954' }}>{c.label}</span>
                      <span style={{ display: 'block', fontSize: 11, color: '#64748b' }}>{c.sub}</span>
                    </span>
                    <span style={{ width: 14, height: 14, borderRadius: 3, border: `1.5px solid ${on ? '#3859D0' : '#CBD5E1'}`, background: on ? '#3859D0' : '#fff', color: '#fff', fontSize: 10, lineHeight: '12px', textAlign: 'center', flexShrink: 0 }}>{on ? '✓' : ''}</span>
                  </button>
                );
              })}
            </div>
            {error && <div style={{ marginTop: 8, fontSize: 12, color: 'var(--danger, #ef4444)' }}>{error}</div>}
          </div>
        )}
        {briefings && briefings.length > 0 && (
          <div style={{ padding: '14px 24px', borderTop: '1px solid var(--border, #e2e8f0)', display: 'flex', justifyContent: 'flex-end', gap: 8, flexShrink: 0 }}>
            <button onClick={onClose} className="btn">Cancelar</button>
            <button onClick={criar} disabled={!pronto || saving} className="btn btn-ai" style={{ opacity: (!pronto || saving) ? 0.5 : 1 }}>
              {saving ? 'A criar…' : 'Criar e abrir na produção'}
            </button>
          </div>
        )}
      </div>
    </div>
  );
};

const MktCampanhasScreen = ({ onOpenChat, userEmail, user, userTipo }) => {
  const podeArquivar = canActOnCampaign({ ...(user || {}), tipo: user?.tipo || user?.tipo_utilizador || userTipo });
  const [campanhas,     setCampanhas]     = React.useState(null);
  window.useMktZoneLoading(campanhas == null);
  const [allBrands,     setAllBrands]     = React.useState([]);
  const [filterBrand,    setFilterBrand]    = React.useState('all');
  const [filterCol,      setFilterCol]      = React.useState('all');
  const [filterProduto,  setFilterProduto]  = React.useState('all');
  const [filterPais,     setFilterPais]     = React.useState('all');
  const [filterCriador,  setFilterCriador]  = React.useState('all');
  const [filterMecanica, setFilterMecanica] = React.useState('all');

  const MECANICA_LABEL = { digirent:'Digirent', printplan:'PrintPlan', voucher:'Voucher on Demand', direct_discount:'Desc. Directo', trade_in:'Trade-In' };
  const MECANICA_COLOR = { digirent:'#7C3AED', printplan:'#D97706', voucher:'#0EA5E9', direct_discount:'#DC2626', trade_in:'#059669' };
  const today = new Date();
  const [dateRange,     setDateRange]     = React.useState({ preset: 'all', start: new Date(today.getFullYear(), 0, 1), end: today });
  const [search,        setSearch]        = React.useState('');
  const [busca,         setBusca]         = React.useState(false);
  const [briefingFiltro, setBriefingFiltro] = React.useState(() => {
    try { return localStorage.getItem('mkt-campanhas-briefing') || ''; } catch { return ''; }
  });
  React.useEffect(() => {
    try { localStorage.removeItem('mkt-campanhas-briefing'); } catch {}
  }, []);
  const [selected,      setSelected]      = React.useState(null);
  const [editing,       setEditing]       = React.useState(null);
  const [showModal,     setShowModal]     = React.useState(false);
  const [showDirecta,   setShowDirecta]   = React.useState(false);
  const [vista,         setVista]         = React.useState('quadro');
  const [pendingArquivar, setPendingArquivar] = React.useState(null);
  const [pendingRestaurar, setPendingRestaurar] = React.useState(null);
  const [pendingConcluir, setPendingConcluir] = React.useState(null);
  const [toast,         setToast]         = React.useState('');
  const [initialTab,    setInitialTab]    = React.useState('conceito');

  const BRAND_ORDER = ['mimaki', 'biond', 'decal', 'alldecor', 'sensek', 'netscreen', 'digidelta'];

  const load = React.useCallback(async () => {
    try {
      const live = await CampAPI.list();
      setCampanhas(Array.isArray(live) ? live : []);
    } catch {
      setCampanhas([]);
    }
  }, []);

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

  // Auto-abrir campanha vinda do briefing (via localStorage)
  React.useEffect(() => {
    if (!campanhas) return;
    try {
      const pendingId = localStorage.getItem('mkt-open-campanha-id');
      if (!pendingId) return;
      const tab = localStorage.getItem('mkt-open-campanha-tab');
      localStorage.removeItem('mkt-open-campanha-id');
      localStorage.removeItem('mkt-open-campanha-tab');
      const camp = campanhas.find(c => String(c.id) === pendingId);
      if (camp) {
        if (tab) setInitialTab(tab);
        setSelected(camp);
      }
    } catch {}
  }, [campanhas]);

  React.useEffect(() => {
    CampAPI.getBrands().then(d => {
      if (!Array.isArray(d)) return;
      const sorted = d.filter(b => b.active !== false && b.slug !== 'todas').sort((a, b) => {
        const ia = BRAND_ORDER.indexOf(a.slug), ib = BRAND_ORDER.indexOf(b.slug);
        return (ia === -1 ? 99 : ia) - (ib === -1 ? 99 : ib);
      });
      setAllBrands(sorted);
    }).catch(() => {});
  }, []);

  const [campUsageStats, setCampUsageStats] = React.useState(null);
  React.useEffect(() => {
    fetch('/api/marketing/campanhas/usage-stats')
      .then(r => r.ok ? r.json() : null)
      .then(data => { if (data) setCampUsageStats(data); })
      .catch(() => {});
  }, []);

  const toast_ = (msg) => { setToast(msg); setTimeout(() => setToast(''), 3200); };

  const [showDeleteModal, setShowDeleteModal] = React.useState(false);
  const [pendingDeleteCamp, setPendingDeleteCamp] = React.useState(null);

  const handleDelete = (campOrId) => {
    if (!podeArquivar) { toast_('Sem permissão para apagar campanhas.'); return; }
    const camp = typeof campOrId === 'object' && campOrId !== null ? campOrId : { id: campOrId, titulo: `Campanha #${campOrId}` };
    if (['arquivada', 'cancelada'].includes(camp.status)) {
      toast_('Uma campanha arquivada fica para consulta e para resultados.');
      return;
    }
    if (FINAL_STATUSES.includes(camp.status) || camp.status === 'concluida') {
      toast_(camp.status === 'concluida'
        ? 'Uma campanha concluída não se apaga daqui. Arquiva-a se quiseres tirá-la do quadro.'
        : 'Campanha em produção ou publicada não pode ser apagada. Contacta o admin.');
      return;
    }
    setPendingDeleteCamp(camp);
    setShowDeleteModal(true);
  };

  const handleDeleteConfirm = async () => {
    if (!pendingDeleteCamp) return;
    try {
      await CampAPI.remove(pendingDeleteCamp.id);
      await load();
      toast_('Campanha apagada.');
    } catch (e) { toast_(e.message || 'Erro ao apagar campanha.'); }
    setShowDeleteModal(false);
    setPendingDeleteCamp(null);
  };

  const handleArquivarDone = async () => {
    const titulo = pendingArquivar?.titulo;
    setPendingArquivar(null);
    await load();
    toast_(titulo ? `«${titulo}» foi para Campanhas Arquivadas.` : 'Campanha arquivada.');
  };

  const handleRestaurarDone = async (conhecido) => {
    const titulo = pendingRestaurar?.titulo;
    setPendingRestaurar(null);
    await load();
    toast_(conhecido
      ? (titulo ? `«${titulo}» voltou ao quadro.` : 'Campanha restaurada.')
      : (titulo ? `«${titulo}» voltou ao quadro, em Conceito.` : 'Campanha restaurada em Conceito.'));
  };

  const handleConcluirDone = async () => {
    const titulo = pendingConcluir?.titulo;
    setPendingConcluir(null);
    await load();
    toast_(titulo ? `«${titulo}» ficou em Concluídas.` : 'Campanha concluída.');
  };

  const handleReabrir = async (camp) => {
    if (!podeArquivar) { toast_('Sem permissão para reabrir campanhas.'); return; }
    try {
      await CampAPI.reabrir(camp.id);
      await load();
      toast_(`«${camp.titulo || 'Campanha'}» voltou a Activação.`);
    } catch (e) {
      toast_(e.message || 'Não reabri a campanha.');
    }
  };

  const COL_TAB_MAP = { campanha: 'campanha', estrategia: 'estrategia', conceito: 'conceito', orcamento: 'orcamento', segmentacao: 'segmentacao', planeamento: 'planeamento', funil: 'funil', sdr: 'sdr', aprovacao: 'aprovacao', producao: 'producao', activacao: 'briefing', concluidas: 'campanha' };
  const STATUS_TAB_MAP = {
    // Fluxo novo
    estrategia_pendente: 'campanha', estrategia_gerada: 'estrategia', estrategia_aprovada: 'estrategia',
    conceito_pendente:   'conceito',   conceito_gerado:   'conceito',   conceito: 'conceito',
    orcamento_pendente:  'orcamento',  orcamento_gerado:  'orcamento',  orcamento_aprovado:  'orcamento',
    segmentacao_pendente:'segmentacao',segmentacao_gerada:'segmentacao',segmentacao_aprovada:'segmentacao',
    planeamento_pendente:'planeamento',planeamento_gerado:'planeamento',planeamento_aprovado:'planeamento',
    funil_pendente:      'funil',      funil_gerado:      'funil',      funil_aprovado: 'funil',
    sdr_ai_pendente:     'sdr',        sdr_ai_aprovado:   'sdr',
    pending_executive:   'aprovacao',  em_aprovacao:      'aprovacao',
    // Fluxo legado
    canais_pendente: 'conceito', canais_gerado: 'conceito', canais_aprovado: 'conceito',
    copy_pendente:   'conceito', copy_gerado:   'conceito', copy_aprovado:   'conceito',
    idiomas_pendente:'conceito', idiomas_gerado:'conceito', idiomas_aprovado:'conceito',
    prompts_pendente:'conceito', prompts_gerado:'conceito', prompts_aprovado:'conceito',
    geracao: 'conceito', aprovacao: 'conceito',
    // Produção — campanha aprovada abre em Briefing (contexto). Botão global levas o utilizador à Produção de Conteúdos.
    em_producao: 'briefing', em_activacao: 'briefing', publicado: 'briefing', concluida: 'campanha',
  };
  const handleCardClick = (c, col) => {
    // Campanha em produção / activação / publicada → sempre abre em Briefing (contexto)
    if (['em_producao', 'em_activacao', 'publicado'].includes(c.status)) {
      setInitialTab('briefing');
      setSelected(c);
      return;
    }
    // Prioridade: coluna clicada (visual) > status (fallback)
    const tabFromCol = col?.id ? COL_TAB_MAP[col.id] : null;
    setInitialTab(tabFromCol || STATUS_TAB_MAP[c.status] || 'conceito');
    setSelected(c);
  };


  const campEstaArquivada = (c) => ['arquivada', 'cancelada'].includes(c.status);
  const activas = React.useMemo(() => (campanhas || []).filter(c => !campEstaArquivada(c)), [campanhas]);
  const arquivadas = React.useMemo(() => (campanhas || []).filter(campEstaArquivada), [campanhas]);
  const fonteFiltros = vista === 'arquivo' ? arquivadas : activas;

  const paisesDaCampanha = (c) => {
    const lista = Array.isArray(c.geo_markets) ? c.geo_markets : [];
    return lista.map(m => (m && typeof m === 'object') ? (m.country || m.name || '') : String(m || '')).map(s => s.trim()).filter(Boolean);
  };

  const produtos = React.useMemo(() => {
    const s = new Set(fonteFiltros.map(c => c.commercial_name || c.product_name).filter(Boolean));
    return [...s].sort().map(v => ({ value: v, label: v }));
  }, [fonteFiltros]);

  const paises = React.useMemo(() => {
    const s = new Set();
    fonteFiltros.forEach(c => paisesDaCampanha(c).forEach(p => s.add(p)));
    return [...s].sort((a, b) => a.localeCompare(b, 'pt')).map(v => ({ value: v, label: v }));
  }, [fonteFiltros]);

  const criadores = React.useMemo(() => {
    const s = new Set(fonteFiltros.map(c => c.created_by_name).filter(Boolean));
    return [...s].sort().map(v => ({ value: v, label: v }));
  }, [fonteFiltros]);

  const passaFiltrosBase = (c, dataCampo) => {
    if (briefingFiltro && String(c.briefing_id) !== String(briefingFiltro)) return false;
    if (filterBrand !== 'all' && c.brand_slug !== filterBrand) return false;
    if (filterProduto !== 'all' && (c.commercial_name || c.product_name) !== filterProduto) return false;
    if (filterPais !== 'all' && !paisesDaCampanha(c).includes(filterPais)) return false;
    if (filterCriador !== 'all' && c.created_by_name !== filterCriador) return false;
    if (dateRange.preset !== 'all' && dateRange.start && dateRange.end) {
      const d = new Date(dataCampo);
      if (Number.isNaN(d.getTime()) || d < dateRange.start || d > dateRange.end) return false;
    }
    if (search.trim()) {
      const q = search.toLowerCase();
      if (!(c.titulo || '').toLowerCase().includes(q) && !(c.commercial_name || c.product_name || '').toLowerCase().includes(q)) return false;
    }
    return true;
  };

  const filtered = React.useMemo(() => {
    return activas.filter(c => {
      if (!passaFiltrosBase(c, c.created_at)) return false;
      if (filterCol !== 'all') {
        const col = KANBAN_COLS.find(k => k.id === filterCol);
        if (col && !col.statuses.includes(c.status)) return false;
      }
      if (filterMecanica !== 'all') {
        const ofType = c.commercial_offer?.type || null;
        if (filterMecanica === 'none' ? ofType !== null : ofType !== filterMecanica) return false;
      }
      return true;
    });
  }, [activas, briefingFiltro, filterBrand, filterCol, filterProduto, filterPais, filterCriador, filterMecanica, dateRange, search]);

  const arquivoFiltrado = React.useMemo(() => {
    return arquivadas.filter(c => passaFiltrosBase(c, c.arquivada_em || c.updated_at));
  }, [arquivadas, briefingFiltro, filterBrand, filterProduto, filterPais, filterCriador, dateRange, search]);

  const kpi = React.useMemo(() => {
    const all = activas;
    const count = (id) => { const col = KANBAN_COLS.find(k => k.id === id); return col ? all.filter(c => col.statuses.includes(c.status)).length : 0; };
    return { total: all.filter(c => c.status !== 'concluida').length, campanha: count('campanha'), estrategia: count('estrategia'), conceito: count('conceito'), orcamento: count('orcamento'), segmentacao: count('segmentacao'), planeamento: count('planeamento'), funil: count('funil'), sdr: count('sdr'), aprovacao: count('aprovacao'), producao: count('producao'), activacao: count('activacao'), concluidas: count('concluidas') };
  }, [activas]);

  const kpiCards = [
    { id: 'campanha',    label: 'Campanha',    value: kpi.campanha,    accent: '#1e40af', fill: kpi.total ? kpi.campanha    / kpi.total : 0 },
    { id: 'estrategia',  label: 'Estratégia',  value: kpi.estrategia,  accent: '#5B43C5', fill: kpi.total ? kpi.estrategia  / kpi.total : 0 },
    { id: 'conceito',    label: 'Conceito',    value: kpi.conceito,    accent: '#94a3b8', fill: kpi.total ? kpi.conceito    / kpi.total : 0 },
    { id: 'orcamento',   label: 'Orçamento',   value: kpi.orcamento,   accent: '#0891b2', fill: kpi.total ? kpi.orcamento   / kpi.total : 0 },
    { id: 'segmentacao', label: 'Target', value: kpi.segmentacao, accent: '#7c3aed', fill: kpi.total ? kpi.segmentacao / kpi.total : 0 },
    { id: 'planeamento', label: 'Planeamento', value: kpi.planeamento, accent: '#0f766e', fill: kpi.total ? kpi.planeamento / kpi.total : 0 },
    { id: 'funil',       label: 'Funil',       value: kpi.funil,       accent: '#0369a1', fill: kpi.total ? kpi.funil       / kpi.total : 0 },
    { id: 'sdr',         label: 'SDR AI',      value: 0,               accent: '#7c3aed', fill: 0 },
    { id: 'aprovacao',   label: 'Aprovação',   value: kpi.aprovacao,   accent: '#ea580c', fill: kpi.total ? kpi.aprovacao   / kpi.total : 0 },
    { id: 'producao',    label: 'Produção',    value: kpi.producao,    accent: '#22c55e', fill: kpi.total ? kpi.producao    / kpi.total : 0 },
    { id: 'activacao',   label: 'Activação',   value: kpi.activacao,   accent: '#3859D0', fill: kpi.total ? kpi.activacao   / kpi.total : 0 },
    { id: 'concluidas',  label: 'Concluídas',  value: kpi.concluidas,  accent: '#0e7490', fill: kpi.total ? kpi.concluidas  / Math.max(kpi.total, 1) : 0 },
  ];

  // Auto-collapse: vazias colapsadas, com cards expandidas.
  // manualExpanded = colunas vazias que o user forçou a abrir (opt-in override).
  const [manualExpanded, setManualExpanded] = React.useState(() => new Set());
  const toggleCol = (colId) => setManualExpanded(prev => {
    const next = new Set(prev);
    next.has(colId) ? next.delete(colId) : next.add(colId);
    return next;
  });

  const colCards = KANBAN_COLS.map(col => ({
    ...col,
    cards: filtered.filter(c => col.statuses.includes(c.status)),
  }));

  const hasFilter = !!briefingFiltro || filterBrand !== 'all' || filterCol !== 'all' || filterProduto !== 'all' || filterPais !== 'all' || filterCriador !== 'all' || filterMecanica !== 'all' || dateRange.preset !== 'all' || search.trim();
  const limparFiltros = () => {
    setBriefingFiltro('');
    setFilterBrand('all'); setFilterCol('all'); setFilterProduto('all'); setFilterPais('all'); setFilterCriador('all'); setFilterMecanica('all');
    setDateRange({ preset: 'all', start: new Date(new Date().getFullYear(), 0, 1), end: new Date() });
    setSearch('');
  };

  // ── Vista de detalhe (sempre CampanhaDetail — stepper como navegação principal) ──
  if (selected) {
    return (
      <>
        <CampanhaDetail key={`${selected.id}-${initialTab}`} campanhaSummary={selected} onBack={() => setSelected(null)} onRefresh={load} initialTab={initialTab} userEmail={userEmail} />
        {editing && (
          <EditTituloModal campanha={editing} onSave={load} onClose={() => setEditing(null)} />
        )}
      </>
    );
  }

  // ── Vista Kanban ────────────────────────────────────────────────────────────
  return (
    <>
      {showModal && (
        <NovaCampanhaModal
          onClose={() => setShowModal(false)}
          onCreated={(camp) => { setShowModal(false); load(); toast_(`Campanha "${camp.titulo}" criada.`); }}
        />
      )}
      {showDirecta && (
        <ExecucaoDirectaModal
          userEmail={userEmail}
          initialBrand={filterBrand}
          onClose={() => setShowDirecta(false)}
          onCreated={(camp) => {
            setShowDirecta(false);
            try { localStorage.setItem('mkt-open-conteudo-id', String(camp.id)); } catch {}
            toast_(`«${camp.titulo}» entrou na Produção de Conteúdos.`);
            if (window.mktNavToSub) window.mktNavToSub('conteudos');
            else load();
          }}
        />
      )}
      <div data-theme="light" style={{ height: '100%', display: 'flex', flexDirection: 'column', background: '#f8fafc' }}>

        {/* ── Page header (fixo) ── */}
        <div style={{ background: 'var(--bg-elev, #ffffff)', borderBottom: '1px solid var(--border, #ECEFF5)', 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 · CAMPANHAS
          </div>
          <h1 style={{ margin: 0, fontSize: 21, fontWeight: 700, color: 'var(--text)', fontFamily: 'var(--font-display)', letterSpacing: '-0.01em', lineHeight: 1.2 }}>
            Campanhas
          </h1>
          <div style={{ height: 1, background: 'var(--border)', margin: '16px -40px 0' }} />
          <div style={{ display: 'flex', gap: 0, overflowX: 'auto' }}>
            {[{ slug: 'all', name: 'Todas' }, ...allBrands].map(b => {
              const active = b.slug === filterBrand;
              return (
                <button key={b.slug} onClick={() => setFilterBrand(b.slug === 'all' ? 'all' : b.slug)} style={{
                  background: 'none', border: 'none', outline: 'none',
                  borderBottom: `2px solid ${active ? 'var(--ai-500, #3859D0)' : 'transparent'}`,
                  color: active ? 'var(--text, #283252)' : 'var(--text-muted, #94A4C4)',
                  fontSize: 13, fontWeight: active ? 600 : 500,
                  fontFamily: 'var(--font-display, Montserrat, sans-serif)', letterSpacing: '.01em',
                  padding: '10px 18px 12px', cursor: 'pointer', whiteSpace: 'nowrap',
                  transition: 'color .15s, border-color .15s',
                }}>
                  {b.name}
                </button>
              );
            })}
          </div>
        </div>

        {/* ── Scrollable body — container único com padding uniforme ── */}
        <div className="scrollbar" style={{ flex: 1, minHeight: 0, overflowY: 'auto', overflowX: 'auto', background: '#f8fafc' }}>
          <div style={{ padding: '28px 40px 48px', minWidth: 640, display: 'flex', flexDirection: 'column', gap: 16 }}>
            {vista === 'quadro' && <CampBanner kpi={kpi} onFilter={setFilterCol} campanhas={filtered} userEmail={userEmail} />}
            {vista === 'arquivo' && (
              <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 14px', background: '#fff', borderRadius: 8, border: '1px solid var(--border, #dde3ef)' }}>
                <button type="button" onClick={() => setVista('quadro')} style={{ background: 'transparent', border: 0, cursor: 'pointer', padding: 0, fontSize: 12, color: 'var(--ai-500, #3859D0)', fontFamily: 'inherit' }}>Campanhas</button>
                <span style={{ color: 'var(--text-dim, #94a3b8)', fontSize: 12 }}>›</span>
                <span style={{ fontSize: 12, color: 'var(--text, #112954)', fontWeight: 700 }}>Campanhas Arquivadas</span>
                <span style={{ marginLeft: 'auto', fontSize: 12, color: 'var(--text-muted, #64748b)' }}>{arquivadas.length} arquivada{arquivadas.length !== 1 ? 's' : ''}</span>
              </div>
            )}

          {briefingFiltro && (
            <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 14px', background: '#fff', borderRadius: 8, border: '1px solid #dbe3f5' }}>
              <span style={{ fontSize: 13, color: '#112954' }}>
                Campanhas deste briefing · {(vista === 'arquivo' ? arquivoFiltrado : filtered).length}
              </span>
              <button type="button" onClick={() => setBriefingFiltro('')} style={{ marginLeft: 'auto', background: 'transparent', border: 0, cursor: 'pointer', padding: 0, fontSize: 12, color: 'var(--ai-500, #3859D0)', fontFamily: 'inherit' }}>Ver todas</button>
            </div>
          )}

          {/* Filter row */}
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 8 }}>
            <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>
              <CampDropdown label="Produto" value={filterProduto} onChange={setFilterProduto}
                options={[{ value: 'all', label: 'Todos' }, ...produtos]}
              />
              <CampDropdown label="País" value={filterPais} onChange={setFilterPais}
                options={[{ value: 'all', label: 'Todos' }, ...paises]}
              />
              {criadores.length > 0 && (
                <CampDropdown label="Criado por" value={filterCriador} onChange={setFilterCriador}
                  options={[{ value: 'all', label: 'Todos' }, ...criadores]}
                />
              )}
              {vista === 'quadro' && (
                <CampDropdown label="Estado" value={filterCol} onChange={setFilterCol}
                  options={[{ value: 'all', label: 'Todos' }, ...KANBAN_COLS.map(col => ({ value: col.id, label: col.label }))]}
                />
              )}
              {vista === 'quadro' && <CampDropdown label="Mecânica" value={filterMecanica} onChange={setFilterMecanica}
                options={[
                  { value: 'all',            label: 'Todas' },
                  { value: 'none',           label: 'Sem oferta' },
                  { value: 'digirent',       label: 'Digirent' },
                  { value: 'printplan',      label: 'PrintPlan' },
                  { value: 'voucher',        label: 'Voucher on Demand' },
                  { value: 'direct_discount',label: 'Desconto Directo' },
                  { value: 'trade_in',       label: 'Trade-In' },
                ]}
              />}
              {hasFilter && (
                <button onClick={limparFiltros}
                  style={{ background: 'none', border: 'none', color: 'var(--text-muted, #64748b)', fontSize: 11, cursor: 'pointer', padding: '4px 6px', fontFamily: 'var(--font-mono, monospace)' }}>
                  ✕ limpar
                </button>
              )}
              <CampDateRangePicker value={dateRange} onChange={setDateRange} />
              <span style={{ fontSize: 12, color: 'var(--text-muted, #64748b)', paddingLeft: 4 }}>
                {(vista === 'arquivo' ? arquivoFiltrado : filtered).length} resultado{(vista === 'arquivo' ? arquivoFiltrado : filtered).length !== 1 ? 's' : ''}
              </span>
            </div>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginLeft: 'auto' }}>
              {window.ResIcone && (
                <window.ResIcone label="Procurar campanhas" activo={busca} onClick={() => setBusca(v => !v)}>
                  {window.ResIconeLupa && <window.ResIconeLupa />}
                </window.ResIcone>
              )}
              {window.ResMenuAccoes && (
                <window.ResMenuAccoes icone align="right" passo="create_campaign" onOpen={() => setBusca(false)} items={vista === 'arquivo' ? [
                  { id: 'quadro', label: 'Quadro', sub: 'Campanhas em curso', onClick: () => setVista('quadro') },
                  { id: 'nova', label: 'Nova campanha', sub: 'Percorre a estratégia', onClick: () => { setVista('quadro'); setShowModal(true); } },
                  { id: 'directa', label: 'Execução directa', sub: 'Entra já na produção', onClick: () => { setVista('quadro'); setShowDirecta(true); } },
                ] : [
                  { id: 'nova', label: 'Nova campanha', sub: 'Percorre a estratégia', onClick: () => setShowModal(true) },
                  { id: 'directa', label: 'Execução directa', sub: 'Entra já na produção', onClick: () => setShowDirecta(true) },
                  { id: 'arquivo', label: 'Campanhas Arquivadas', sub: arquivadas.length ? `${arquivadas.length} arquivada${arquivadas.length === 1 ? '' : 's'}` : 'Consultar as que saíram do quadro', onClick: () => setVista('arquivo') },
                ]} />
              )}
            </div>
          </div>

          {vista === 'arquivo' ? (
            <CampArquivoLista
              linhas={arquivoFiltrado}
              canAct={podeArquivar}
              onOpen={(c) => { setInitialTab('campanha'); setSelected(c); }}
              onRestaurar={setPendingRestaurar}
            />
          ) : (
          /* KPI strip + Kanban — layout unificado por coluna com collapse */
          <div style={{ overflowX: 'auto', paddingBottom: 8 }}>
            {campanhas === null ? (
              <div style={{ display: 'flex', gap: 12, minWidth: 'max-content' }}>
                {KANBAN_COLS.map(col => (
                  <div key={col.id} style={{ width: 240, flexShrink: 0 }}>
                    <div style={{ height: 72, borderRadius: 8, background: '#ffffff', marginBottom: 12, opacity: 0.5 }} />
                    {[1, 2].map(i => <div key={i} style={{ height: 88, borderRadius: 8, background: '#ffffff', marginBottom: 8, opacity: 1 - i * 0.3 }} />)}
                  </div>
                ))}
              </div>
            ) : filtered.length === 0 && activas.length === 0 ? (
              <div style={{ padding: '48px 0', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 12 }}>
                <div style={{ fontSize: 13, color: 'var(--text-muted, #64748b)', textAlign: 'center' }}>
                  Ainda não há campanhas. Aprova um briefing e clica em "+ Criar Campanha" para começar.
                </div>
              </div>
            ) : filtered.length === 0 ? (
              <div style={{ padding: '48px 0', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 12 }}>
                <div style={{ fontSize: 13, color: 'var(--text-muted, #64748b)', textAlign: 'center' }}>Sem resultados para os filtros actuais.</div>
                <button onClick={limparFiltros} className="btn" style={{ fontSize: 12 }}>Limpar filtros</button>
              </div>
            ) : (
              <div style={{ display: 'flex', gap: 10, minWidth: 'max-content', alignItems: 'flex-start' }}>
                {colCards.map((col, i) => {
                  const k       = kpiCards.find(k => k.id === col.id) || { label: col.label, value: col.cards.length, accent: col.color, fill: 0 };
                  const collapsed = col.cards.length === 0 && !manualExpanded.has(col.id);
                  const colW    = collapsed ? 52 : 280;
                  return (
                    <div key={col.id} style={{ width: colW, minWidth: colW, flexShrink: 0, transition: 'width 0.22s ease',  }}>
                      {/* KPI card — header da coluna */}
                      {collapsed ? (
                        /* ── Collapsed strip ── */
                        <div onClick={() => toggleCol(col.id)} title={`Expandir ${k.label}`} style={{
                          width: 52, borderRadius: 8, cursor: 'pointer', overflow: 'hidden',
                          background: '#ffffff',
                          border: `1px solid #e2e8f0`,
                          display: 'flex', flexDirection: 'column', alignItems: 'center',
                          padding: '10px 0 8px', gap: 6, marginBottom: 10,
                          transition: 'background 0.15s',
                        }}>
                          {/* Count badge */}
                          <div style={{ width: 24, height: 24, borderRadius: '50%', background: k.value > 0 ? k.accent : 'transparent', border: `1.5px solid ${k.accent}`, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                            <span style={{ fontSize: 11, fontWeight: 700, color: k.value > 0 ? '#fff' : k.accent, fontFamily: 'monospace', lineHeight: 1 }}>{k.value}</span>
                          </div>
                          {/* Label vertical */}
                          <div style={{ writingMode: 'vertical-rl', textOrientation: 'mixed', transform: 'rotate(180deg)', fontSize: 10, fontWeight: 700, letterSpacing: '0.08em', textTransform: 'uppercase', color: k.accent, fontFamily: 'var(--font-mono, monospace)', userSelect: 'none' }}>
                            {k.label}
                          </div>
                          {/* Expand arrow */}
                          <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke={k.accent} strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
                        </div>
                      ) : (
                        /* ── Expanded card ── */
                        <div style={{
                          background: '#ffffff', border: '1px solid var(--border, #e2e8f0)',
                          borderRadius: 8, marginBottom: 10, overflow: 'hidden', cursor: 'pointer',
                          transition: 'border-color 150ms',
                        }} onClick={() => toggleCol(col.id)}>
                          <div style={{ padding: '10px 14px 0' }}>
                            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 4 }}>
                              <div style={{ fontSize: 9.5, fontWeight: 600, letterSpacing: '0.10em', textTransform: 'uppercase', color: 'var(--text-muted, #64748b)', fontFamily: 'var(--font-mono, monospace)', whiteSpace: 'nowrap' }}>{k.label}</div>
                              <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="var(--text-dim,#475569)" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><polyline points="15 18 9 12 15 6"/></svg>
                            </div>
                            <div style={{ fontSize: 22, fontWeight: 700, fontFamily: 'var(--font-display, Montserrat, sans-serif)', color: k.accent, lineHeight: 1, paddingBottom: 8 }}>{k.value}</div>
                            <div style={{ height: 3, background: 'var(--bg-sunken, #f1f5f9)', overflow: 'hidden' }}>
                              <div style={{ height: '100%', width: `${(k.fill || 0) * 100}%`, background: k.accent, transition: 'width .4s' }} />
                            </div>
                          </div>
                        </div>
                      )}

                      {/* Kanban column — só se expandido */}
                      {!collapsed && (
                        <KanbanColumn
                          col={col} cards={col.cards}
                          onCardClick={handleCardClick}
                          onEdit={setEditing}
                          onDelete={handleDelete}
                          onArchive={podeArquivar ? setPendingArquivar : null}
                          onConcluir={podeArquivar ? setPendingConcluir : null}
                          onReabrir={podeArquivar ? handleReabrir : null}
                          userEmail={userEmail}
                        />
                      )}
                    </div>
                  );
                })}
              </div>
            )}
          </div>
          )}
        </div>

        {showDeleteModal && (
          <CampDeleteConfirmModal
            campanha={pendingDeleteCamp}
            onConfirm={handleDeleteConfirm}
            onClose={() => { setShowDeleteModal(false); setPendingDeleteCamp(null); }}
          />
        )}
        {pendingArquivar && (
          <CampArquivarModal campanha={pendingArquivar} onClose={() => setPendingArquivar(null)} onDone={handleArquivarDone} />
        )}
        {pendingRestaurar && (
          <CampRestaurarModal campanha={pendingRestaurar} onClose={() => setPendingRestaurar(null)} onDone={handleRestaurarDone} />
        )}
        {pendingConcluir && (
          <CampConcluirModal campanha={pendingConcluir} onClose={() => setPendingConcluir(null)} onDone={handleConcluirDone} />
        )}

        <MktCampToast msg={toast} type="info" />
        </div> {/* fim scrollable body */}
        {campUsageStats && (() => {
          const fmtEur = (v) => v != null ? `€${parseFloat(v).toFixed(4).replace(/0+$/, '').replace(/\.$/, '')}` : '—';
          const fmtTok = (v) => Number(v || 0).toLocaleString('pt-PT');
          const sep = <span style={{ margin: '0 5px', opacity: 0.35 }}>·</span>;
          return (
            <div style={{
              flexShrink: 0, borderTop: '1px solid var(--border, #dde3ef)',
              background: '#ffffff', padding: '5px 24px',
              display: 'flex', alignItems: 'center', gap: 0,
              fontSize: 10.5, fontFamily: 'var(--font-mono)', color: 'var(--text-dim, #6b7fa3)',
            }}>
              <span style={{ fontWeight: 600, color: 'var(--text-muted, #4a5e7a)', marginRight: 10, fontSize: 10 }}>DIGI AI</span>
              <span>Custo total: <strong style={{ color: 'var(--text-muted)', fontWeight: 600 }}>{fmtEur(campUsageStats.custo_total)}</strong></span>
              {sep}<span>Média/campanha: <strong style={{ color: 'var(--text-muted)', fontWeight: 600 }}>{fmtEur(campUsageStats.media_por_campanha)}</strong></span>
              {sep}<span>Tokens: <strong style={{ color: 'var(--text-muted)', fontWeight: 600 }}>{fmtTok(campUsageStats.total_tokens)}</strong></span>
              {sep}<span>{Number(campUsageStats.total_chamadas || 0)} chamadas API</span>
            </div>
          );
        })()}
      </div>

      {editing && (
        <EditTituloModal campanha={editing} onSave={load} onClose={() => setEditing(null)} />
      )}

      {/* Tutorial — floating button + drawer */}
      {busca && window.ResBusca && (
        <window.ResBusca
          itens={(vista === 'arquivo' ? arquivadas : activas).map(c => {
            const col = KANBAN_COLS.find(k => k.statuses.includes(c.status));
            return {
              id: c.id,
              tipo: vista === 'arquivo' ? 'Arquivada' : 'Campanha',
              titulo: c.titulo || c.commercial_name || c.product_name || 'Campanha',
              detalhe: [c.brand_name, vista === 'arquivo' ? 'Arquivada' : col?.label].filter(Boolean).join(' · '),
              texto: `${c.commercial_name || ''} ${c.product_name || ''} ${c.created_by_name || ''}`,
            };
          })}
          placeholder={vista === 'arquivo' ? 'Procurar campanhas arquivadas' : 'Procurar campanhas'}
          vazio="Nenhuma campanha encontrada."
          onPick={(it) => {
            const lista = vista === 'arquivo' ? arquivadas : activas;
            const c = lista.find(x => String(x.id) === String(it.id));
            if (!c) return;
            if (vista === 'arquivo') { setInitialTab('campanha'); setSelected(c); return; }
            handleCardClick(c);
          }}
          onClose={() => setBusca(false)}
        />
      )}
      {window.MktTutorial && (() => {
        // Construir detail sintético a partir do selected (summary da lista)
        const CANAIS_STATUSES = ['canais_gerado','canais_aprovado','copy_pendente','copy_gerado','copy_aprovado','idiomas_pendente','idiomas_gerado','idiomas_aprovado','prompts_pendente','prompts_gerado','prompts_aprovado','pending_executive','em_producao','em_activacao','publicado'];
        const tutDetail = selected ? {
          id: selected.id,
          big_idea: selected.big_idea || null,
          canais_setup: CANAIS_STATUSES.includes(selected.status) ? true : null,
          copy: selected.num_copy_aprovado > 0
            ? Array(Number(selected.num_copy_aprovado)).fill({ status: 'aprovado' })
            : (selected.num_copy > 0 ? [{ status: 'pendente' }] : []),
          idiomas: selected.num_idiomas > 0 ? [{}] : [],
          prompts: selected.num_prompts_aprovado > 0
            ? Array(Number(selected.num_prompts_aprovado)).fill({ status: 'aprovado' })
            : (selected.num_prompts > 0 ? [{ status: 'pendente' }] : []),
          status: selected.status,
        } : null;
        return (
          <window.MktTutorial
            moduleName="campanhas"
            moduleLabel="Campanhas"
            moduleState={{ detail: tutDetail }}
            userEmail={userEmail}
          />
        );
      })()}
    </>
  );
};

window.MktCampanhasScreen = MktCampanhasScreen;
// ─── (fim) ───────────────────────────────────────────────────────────────────
