/* screen_marketing_resultados.jsx
   Marketing · Resultados v2 — campanhas no ar vs objectivos da Aprovação.
   Expõe window.MktPerformance360Screen e alias window.MktResultadosScreen.
*/

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

// 2014 é o primeiro ano com oportunidades nas sources de marketing do Gestor (WEB, ADS, NEWSLETTER, MIMAKI CONTACT).
const RES_ANO_INICIO = 2014;
const RES_ANO_CORRENTE = new Date().getFullYear();
const RES_ANOS = Array.from({ length: RES_ANO_CORRENTE - RES_ANO_INICIO + 1 }, (_, i) => RES_ANO_INICIO + i);
const RES_PERIODS = [
  { id: 'ytd', label: 'YTD' },
  { id: 'q1', label: 'Q1' },
  { id: 'q2', label: 'Q2' },
  { id: 'q3', label: 'Q3' },
  { id: 'q4', label: 'Q4' },
  { id: 'last_30d', label: '30d' },
];
function resPeriodsForYear(year) {
  if (Number(year) === RES_ANO_CORRENTE) return RES_PERIODS;
  // 30d noutro ano não é “últimos 30 dias de hoje” — esconde o botão.
  return RES_PERIODS.filter(p => p.id !== 'last_30d');
}
// Mesmas datas que parsePeriodo no servidor (calculators.js).
function resPeriodoDatas(id, year) {
  const hoje = new Date();
  const dia = new Date(hoje.getFullYear(), hoje.getMonth(), hoje.getDate());
  const y = Number(year) || hoje.getFullYear();
  const corrente = y === hoje.getFullYear();
  const q = { q1: [0, 2, 31], q2: [3, 5, 30], q3: [6, 8, 30], q4: [9, 11, 31] }[id];
  if (q) {
    const fim = new Date(y, q[1], q[2]);
    return { start: new Date(y, q[0], 1), end: corrente && fim > dia ? dia : fim };
  }
  if (id === 'last_30d') return { start: new Date(dia.getTime() - 29 * 86400000), end: dia };
  if (id === 'ytd') return { start: new Date(y, 0, 1), end: corrente ? dia : new Date(y, 11, 31) };
  return null;
}
// Períodos do filtro: id "periodo:ano" (ex.: q3:2026, ytd:2025 = ano completo), "homologo" e "custom".
function resHomologo() {
  const hoje = new Date();
  const y = hoje.getFullYear() - 1;
  const fimMes = new Date(y, hoje.getMonth() + 1, 0).getDate();
  return { start: new Date(y, 0, 1), end: new Date(y, hoje.getMonth(), Math.min(hoje.getDate(), fimMes)) };
}
function resPeriodoPresets() {
  const y = RES_ANO_CORRENTE;
  const trimestres = ['q4', 'q3', 'q2', 'q1']
    .filter(q => resPeriodoDatas(q, y).start <= new Date())
    .map(q => ({ id: `${q}:${y}`, label: `${q.toUpperCase()} ${y}` }));
  const anteriores = RES_ANOS.filter(a => a < y).sort((a, b) => b - a).map(a => ({ id: `ytd:${a}`, label: `Ano ${a}` }));
  return [
    { id: `ytd:${y}`, label: `YTD ${y}` },
    { id: 'custom', label: 'Personalizado' },
    { id: `last_30d:${y}`, label: 'Últimos 30 dias' },
    { id: 'homologo', label: `Mesmo período ${y - 1}` },
    ...trimestres,
    ...anteriores,
  ];
}
function resPeriodoResolver(id) {
  if (id === 'homologo') return resHomologo();
  const [p, a] = String(id).split(':');
  return a ? resPeriodoDatas(p, Number(a)) : null;
}
function resDataIso(d) {
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
}
const RES_ESTADOS = [
  { id: 'no_ar', label: 'No ar' },
  { id: 'agendada', label: 'Agendadas' },
  { id: 'terminada', label: 'Terminadas' },
  { id: 'todas', label: 'Todas' },
];
const RES_CANAIS = [
  { id: 'todos', label: 'Todos os canais' },
  { id: 'meta_ads', label: 'Meta Ads' },
  { id: 'email', label: 'Email' },
  { id: 'website', label: 'Website' },
  { id: 'whatsapp', label: 'WhatsApp' },
  { id: 'linkedin_ads', label: 'LinkedIn' },
  { id: 'google_ads', label: 'Google' },
  { id: 'outro', label: 'Outro' },
];
const RES_CANAL_LABEL = Object.fromEntries(RES_CANAIS.map(c => [c.id, c.label]));
const RES_SOURCES = [
  { id: 'G1006', label: 'ADS', grupo: 'MKT' },
  { id: 'G1005', label: 'WEB', grupo: 'MKT' },
  { id: 'G1007', label: 'NEWSLETTER', grupo: 'MKT' },
  { id: 'G1008', label: 'MIMAKI CONTACT', grupo: 'MKT' },
  { id: 'G1003', label: 'FEIRA', grupo: 'FEIRAS' },
  { id: 'G1004', label: 'OPENHOUSE', grupo: 'FEIRAS' },
  { id: 'G1000', label: 'COMERCIAL', grupo: 'RH' },
  { id: 'G1011', label: 'GESTORA CONTA', grupo: 'RH' },
  { id: 'G1001', label: 'PARTNER', grupo: 'RH' },
  { id: 'G1009', label: 'SDR', grupo: 'RH' },
  { id: 'G1002', label: 'DEP TECNICO', grupo: 'RH' },
  { id: 'G1010', label: 'COMERCIAL SHARE', grupo: 'RH' },
];
const RES_NAV_KEY = 'mkt-resultados-nav-v3';

const ResFiltro = ({ label, value, options, onChange, menuMinWidth = 180, menuMaxHeight = 280, marcado = false }) => {
  const [open, setOpen] = React.useState(false);
  const [over, setOver] = React.useState(false);
  const [overItem, setOverItem] = React.useState(null);
  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];
  const vazio = options[0]?.value;
  const marca = 'color-mix(in oklch, var(--ai-500, #3859D0) 12%, transparent)';
  const activoVisual = marcado || open;
  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <button type="button" onClick={() => setOpen(v => !v)}
        onMouseEnter={() => setOver(true)} onMouseLeave={() => setOver(false)}
        style={{
          background: activoVisual ? 'color-mix(in oklch, var(--ai-500, #3859D0) 8%, var(--bg-elev, #fff))' : (over ? 'var(--bg-hover, #f1f5f9)' : 'var(--bg-elev, #ffffff)'),
          color: 'var(--text, #112954)',
          border: `1px solid ${over || open || marcado ? 'var(--ai-500, #3859D0)' : 'var(--border, #dde3ef)'}`,
          borderRadius: 6,
          padding: '6px 10px', fontSize: 11.5,
          display: 'inline-flex', alignItems: 'center', gap: 8,
          cursor: 'pointer', fontFamily: 'inherit',
          transition: 'border-color .15s, background .15s', maxWidth: 360,
        }}>
        <span style={{ color: 'var(--text-dim, #475569)', fontSize: 10, textTransform: 'uppercase', letterSpacing: '.06em', fontFamily: 'var(--font-mono, monospace)', flexShrink: 0 }}>{label}</span>
        {current?.cor && <span style={{ width: 8, height: 8, borderRadius: 2, background: current.cor, flexShrink: 0 }} />}
        <span style={{ fontWeight: marcado ? 650 : 500, color: marcado ? 'var(--ai-500, #3859D0)' : 'var(--text, #112954)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{current?.label || '—'}</span>
        {marcado && (
          <span aria-hidden="true" title={`Limpar ${label}`}
            onMouseDown={(e) => { e.preventDefault(); e.stopPropagation(); }}
            onClick={(e) => { e.preventDefault(); e.stopPropagation(); onChange(vazio ?? ''); setOpen(false); }}
            style={{ color: 'var(--ai-500, #3859D0)', fontSize: 14, lineHeight: 1, flexShrink: 0, padding: '0 2px', cursor: 'pointer' }}>×</span>
        )}
        <span style={{ color: 'var(--text-muted, #64748b)', fontSize: 9, flexShrink: 0 }}>▾</span>
      </button>
      {open && (
        <div style={{
          position: 'absolute', top: 'calc(100% + 4px)', left: 0, zIndex: 50,
          background: 'var(--bg-elev, #ffffff)', border: '1px solid var(--border, #dde3ef)', borderRadius: 6,
          boxShadow: '0 8px 24px rgba(17,41,84,0.12)', minWidth: menuMinWidth, padding: 4, maxHeight: menuMaxHeight, overflowY: 'auto',
        }}>
          {options.map((o, i) => {
            const activo = o.value === value || overItem === o.value;
            const header = o.grupo && o.grupo !== options[i - 1]?.grupo;
            return (
              <React.Fragment key={String(o.value)}>
                {header && <div style={{ padding: '8px 10px 3px', fontSize: 10, fontWeight: 650, letterSpacing: '.06em', textTransform: 'uppercase', color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>{o.grupo}</div>}
                <button type="button"
                  onClick={() => { onChange(o.value); setOpen(false); }}
                  onMouseEnter={() => setOverItem(o.value)}
                  onMouseLeave={() => setOverItem(null)}
                  style={{
                    display: 'flex', alignItems: 'center', gap: 8, width: '100%', textAlign: 'left',
                    background: activo ? marca : 'transparent',
                    color: activo ? 'var(--ai-500, #3859D0)' : 'var(--text, #112954)',
                    border: 'none', padding: '7px 10px', borderRadius: 4,
                    fontSize: 12, fontWeight: o.value === value ? 600 : 500,
                    cursor: 'pointer', fontFamily: 'inherit',
                    transition: 'background .12s, color .12s',
                  }}>
                  {o.cor && <span style={{ width: 8, height: 8, borderRadius: 2, background: o.cor, flexShrink: 0 }} />}
                  <span style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{o.label}</span>
                  {o.n != null && <span style={{ color: 'var(--text-muted)', fontVariantNumeric: 'tabular-nums', fontWeight: 500, flexShrink: 0 }}>{Number(o.n).toLocaleString('en-GB')}</span>}
                </button>
              </React.Fragment>
            );
          })}
        </div>
      )}
    </div>
  );
};
function resEstado(v) {
  return String(v || '').replaceAll('_', ' ');
}
function resMetaEstado(s) {
  return { ACTIVE: 'activa', PAUSED: 'em pausa', ARCHIVED: 'arquivada', DELETED: 'apagada' }[s] || s || '—';
}
function ResSerie({ serie }) {
  const rows = Array.isArray(serie) ? serie : [];
  if (!rows.length) return null;
  const max = Math.max(1, ...rows.map(r => Number(r.leads) || 0));
  const w = 320;
  const h = 72;
  const pad = 6;
  const step = rows.length > 1 ? (w - pad * 2) / (rows.length - 1) : 0;
  const pts = rows.map((r, i) => {
    const x = pad + i * step;
    const y = h - pad - ((Number(r.leads) || 0) / max) * (h - pad * 2);
    return `${x},${y}`;
  }).join(' ');
  return (
    <div className="card" style={{ padding: 12, marginBottom: 12 }}>
      <div style={{ fontWeight: 650, fontSize: 13, marginBottom: 6 }}>Leads por semana</div>
      <svg width="100%" viewBox={`0 0 ${w} ${h}`} role="img" aria-label="Leads por semana" style={{ display: 'block' }}>
        <polyline fill="none" stroke="var(--ai-500)" strokeWidth="2" points={pts} />
      </svg>
      <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 10, color: 'var(--text-dim)', marginTop: 4 }}>
        <span>{String(rows[0].semana || '').slice(0, 10)}</span>
        <span>{String(rows[rows.length - 1].semana || '').slice(0, 10)}</span>
      </div>
    </div>
  );
}
function resLoadNav() {
  try { return JSON.parse(localStorage.getItem(RES_NAV_KEY) || '{}'); } catch { return {}; }
}
function resSaveNav(nav) {
  try { localStorage.setItem(RES_NAV_KEY, JSON.stringify(nav)); } catch {}
}

function resDecodeFile(buf) {
  const b = new Uint8Array(buf);
  if (b[0] === 0xFF && b[1] === 0xFE) return new TextDecoder('utf-16le').decode(buf);
  if (b[0] === 0xFE && b[1] === 0xFF) return new TextDecoder('utf-16be').decode(buf);
  return new TextDecoder('utf-8').decode(buf).replace(/^\uFEFF/, '');
}

function resParseCsv(text) {
  const first = text.split(/\r?\n/, 1)[0] || '';
  const count = (ch) => first.split(ch).length - 1;
  const sep = [['\t', count('\t')], [';', count(';')], [',', count(',')]].sort((a, b) => b[1] - a[1])[0][0];
  const rows = [];
  let row = [];
  let cell = '';
  let quoted = false;
  for (let i = 0; i < text.length; i += 1) {
    const ch = text[i];
    if (quoted) {
      if (ch === '"' && text[i + 1] === '"') { cell += '"'; i += 1; }
      else if (ch === '"') quoted = false;
      else cell += ch;
    } else if (ch === '"') quoted = true;
    else if (ch === sep) { row.push(cell); cell = ''; }
    else if (ch === '\n' || ch === '\r') {
      if (ch === '\r' && text[i + 1] === '\n') i += 1;
      row.push(cell); cell = '';
      if (row.some(v => v !== '')) rows.push(row);
      row = [];
    } else cell += ch;
  }
  row.push(cell);
  if (row.some(v => v !== '')) rows.push(row);
  const [header, ...body] = rows;
  if (!header) return [];
  const keys = header.map(h => h.trim().toLowerCase());
  return body.map(r => Object.fromEntries(keys.map((k, i) => [k, r[i] ?? ''])));
}

function ResLeadsMeta({ onDone }) {
  const [msg, setMsg] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const sincronizar = () => {
    setBusy(true); setMsg('A ler os formulários das contas Digidelta, Decal e BIOND…');
    campApiCall('/api/marketing/resultados/v2/meta-leads/sincronizar', { method: 'POST', body: '{}' })
      .then(d => { setMsg(`${d.lidas} leads lidos · ${d.gravadas} novos · ${d.campanhas} campanhas. Carrega em Recalcular para actualizar os resultados.`); onDone && onDone(); })
      .catch(e => setMsg(e.message))
      .finally(() => setBusy(false));
  };
  const importar = async (file) => {
    if (!file) return;
    setBusy(true); setMsg('A ler o ficheiro…');
    try {
      const linhas = resParseCsv(resDecodeFile(await file.arrayBuffer()));
      if (!linhas.length || !('id' in linhas[0] || 'lead_id' in linhas[0])) throw new Error('O ficheiro não tem a coluna id. Usa a exportação de leads do Ads Manager (CSV).');
      let lidas = 0; let gravadas = 0; let ignoradas = 0;
      for (let i = 0; i < linhas.length; i += 1000) {
        setMsg(`A importar ${Math.min(i + 1000, linhas.length)} de ${linhas.length}…`);
        const d = await campApiCall('/api/marketing/resultados/v2/meta-leads/importar', { method: 'POST', body: JSON.stringify({ linhas: linhas.slice(i, i + 1000) }) });
        lidas += d.lidas || 0; gravadas += d.gravadas || 0; ignoradas += d.ignoradas || 0;
      }
      setMsg(`${lidas} leads lidos · ${gravadas} novos${ignoradas ? ` · ${ignoradas} sem id` : ''}. Carrega em Recalcular para actualizar os resultados.`);
      onDone && onDone();
    } catch (e) {
      setMsg(e.message);
    } finally {
      setBusy(false);
    }
  };
  return (
    <div className="card" style={{ padding: 12, marginBottom: 14, fontSize: 12 }}>
      <div style={{ fontWeight: 650, marginBottom: 6 }}>Leads Meta</div>
      <div style={{ color: 'var(--text-muted)', marginBottom: 8 }}>
        A sincronização lê os formulários das três contas, mas a Meta só devolve os últimos 90 dias. Para leads mais antigos, exporta o CSV de leads no Ads Manager e importa aqui.
      </div>
      <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' }}>
        <button type="button" className="btn btn-sm" disabled={busy} onClick={sincronizar}>Sincronizar agora</button>
        <label className="btn btn-sm" style={{ cursor: busy ? 'default' : 'pointer', opacity: busy ? 0.6 : 1 }}>
          Importar CSV
          <input type="file" accept=".csv,.tsv,text/csv" disabled={busy} style={{ display: 'none' }} onChange={e => { importar(e.target.files?.[0]); e.target.value = ''; }} />
        </label>
        {msg && <span style={{ color: 'var(--text-muted)' }}>{msg}</span>}
      </div>
    </div>
  );
}

function ResCampanhaCard({ c, onOpen }) {
  const etapas = (c.etapas || []).filter(e => e.key !== 'reach').slice(0, 4);
  const meta = c.tipo === 'meta_externa';
  const crm = c.tipo === 'crm';
  return (
    <button type="button" onClick={onOpen} className="card" style={{ textAlign: 'left', padding: 14, cursor: 'pointer', width: '100%' }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', gap: 10, alignItems: 'flex-start' }}>
        <div style={{ minWidth: 0 }}>
          <div style={{ fontWeight: 650, fontSize: 14, lineHeight: 1.3 }}>{c.titulo}</div>
          <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 3 }}>
            {c.marca_nome} · {crm ? `Gestor · ${c.crm_fonte}` : meta ? 'Meta' : 'Portal'} · {crm ? 'sem campanha Meta' : meta ? resMetaEstado(c.meta_status) : resEstado(c.estado)}
            {' · '}{c.janela_inicio || '—'} → {c.janela_fim || (c.estado === 'no_ar' || c.meta_active ? 'activa' : '—')}
          </div>
        </div>
        <div style={{ textAlign: 'right', flexShrink: 0 }}>
          <div style={{ fontWeight: 650, fontSize: 16 }}>{resFmtEur(crm ? c.fechos_eur : c.spend_eur)}</div>
          <div style={{ fontSize: 10, color: 'var(--text-dim)' }}>{crm ? "WIN's" : 'spend'}</div>
        </div>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: `repeat(${Math.max(etapas.length, 1)}, 1fr)`, gap: 8, marginTop: 12 }}>
        {etapas.map(e => (
          <div key={e.key}>
            <div style={{ fontSize: 10, color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '.03em' }}>{e.label}</div>
            <div style={{ fontSize: 15, fontWeight: 650, marginTop: 2 }}>
              {resFmtN(e.real)}
              {e.objectivo != null && <span style={{ fontWeight: 450, color: 'var(--text-muted)', fontSize: 12 }}> / {resFmtN(e.objectivo)}</span>}
            </div>
          </div>
        ))}
      </div>
      {c.leads_plataforma > 0 && (
        <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 10 }}>{resFmtN(c.leads_plataforma)} leads reportados pela Meta</div>
      )}
      {(c.pedidos?.amostras || c.pedidos?.reuniao) ? (
        <div style={{ fontSize: 11, color: '#9a3412', marginTop: 8 }}>{[c.pedidos.amostras ? `${resFmtN(c.pedidos.amostras)} pedidos de amostras` : '', c.pedidos.reuniao ? `${resFmtN(c.pedidos.reuniao)} pedidos de reunião` : ''].filter(Boolean).join(' · ')}</div>
      ) : null}
    </button>
  );
}



function resKpiNum(v) {
  const n = Number(v);
  return Number.isFinite(n) ? n : 0;
}

function resKpisCampanha(c) {
  const et = (key) => (c.etapas || []).find(e => e.key === key) || null;
  const pack = (e, extra) => ({
    real: resKpiNum(extra?.real != null ? extra.real : e?.real),
    objectivo: resKpiNum(e?.objectivo) > 0 ? resKpiNum(e.objectivo) : null,
    real_com_objectivo: resKpiNum(e?.objectivo) > 0 ? resKpiNum(e?.real) : 0,
    pct: e?.pct ?? null,
    light: e?.light || null,
    ...(extra || {}),
  });
  const fechos = et('fechos') || et('wins');
  const ops = resKpiNum(c.oportunidades ?? et('oportunidades')?.real);
  const reach = et('reach');
  return {
    spend_eur: resKpiNum(c.spend_eur),
    investimento_eur: resKpiNum(c.investimento_eur),
    campanhas_no_ar: c.estado === 'no_ar' ? 1 : 0,
    leads: pack(et('leads')),
    qualificados: pack(et('qualificados')),
    oportunidades: pack(et('oportunidades'), { real: ops }),
    fechos: pack(fechos, { valor_eur: resKpiNum(c.fechos_eur) }),
    demos: pack(et('demos')),
    testing_rolls: pack(et('testing_rolls')),
    primeira_encomenda: pack(et('primeira_encomenda')),
    reach: pack(reach, { real: resKpiNum(reach?.real) || resKpiNum(c.reach) }),
  };
}

function resFmtRoas(faturado, invest) {
  if (!(invest > 0)) return '—';
  const r = Number(faturado) / invest;
  if (!Number.isFinite(r)) return '—';
  const digits = r < 10 ? 2 : 1;
  return r.toLocaleString('pt-PT', { minimumFractionDigits: digits, maximumFractionDigits: digits }) + '×';
}

function resRoasNum(faturado, invest) {
  if (!(invest > 0)) return null;
  const r = Number(faturado) / invest;
  return Number.isFinite(r) ? r : null;
}

function resCustoUnit(spend, n) {
  if (!(spend > 0) || !(n > 0)) return null;
  const c = Number(spend) / n;
  return Number.isFinite(c) ? c : null;
}

const RES_CMP_FUNIL_CORES = ['#3859D0', '#F59E0B', '#E11D48', '#16A34A'];

function resCmpBanda(pts) {
  let d = `M ${pts[0][0]} ${pts[0][1]}`;
  for (let i = 0; i < pts.length - 1; i++) {
    const p0 = pts[Math.max(0, i - 1)];
    const p1 = pts[i];
    const p2 = pts[i + 1];
    const p3 = pts[Math.min(pts.length - 1, i + 2)];
    const c1x = p1[0] + (p2[0] - p0[0]) / 6;
    const c1y = p1[1] + (p2[1] - p0[1]) / 6;
    const c2x = p2[0] - (p3[0] - p1[0]) / 6;
    const c2y = p2[1] - (p3[1] - p1[1]) / 6;
    d += ` C ${c1x} ${c1y}, ${c2x} ${c2y}, ${p2[0]} ${p2[1]}`;
  }
  return d;
}

function resCmpFunilPath(values, w, h, escala) {
  const n = values.length || 1;
  const max = Math.max(values[0] || 0, 1);
  const col = w / n;
  const knots = [{ x: 0, v: values[0] || 0 }];
  for (let i = 0; i < n; i++) {
    const v = values[i] || 0;
    knots.push({ x: i * col + col * 0.22, v });
    knots.push({ x: (i + 1) * col - col * 0.22, v });
  }
  knots.push({ x: w, v: values[n - 1] || 0 });
  const yOf = (v, lado) => {
    const half = Math.max(7, (v / max) * h * 0.42) * (escala || 1);
    return h / 2 + lado * half;
  };
  const top = knots.map(k => [k.x, yOf(k.v, -1)]);
  const bot = knots.map(k => [k.x, yOf(k.v, 1)]);
  const volta = resCmpBanda([...bot].reverse()).replace(/^M\s*[-\d.eE]+\s+[-\d.eE]+\s*/, '');
  return `${resCmpBanda(top)} L ${bot[bot.length - 1][0]} ${bot[bot.length - 1][1]} ${volta} Z`;
}

function resCmpPct(part, whole) {
  const p = whole ? (Number(part) || 0) / whole * 100 : 0;
  if (p > 0 && p < 10) return p.toFixed(1).replace('.', ',') + '%';
  return Math.round(p) + '%';
}

function resCmpEtapasDeDados(d) {
  return [
    { key: 'leads', label: 'Leads', real: resKpiNum(d?.leads) },
    { key: 'qualificados', label: 'MQL', real: resKpiNum(d?.mql) },
    { key: 'oportunidades', label: 'Oportunidades', real: resKpiNum(d?.oportunidades) },
    { key: 'fechos', label: "WIN's", real: resKpiNum(d?.fechos), valor: resKpiNum(d?.fechos_eur) },
  ];
}

/** Funil acumulado com split por ano em cada etapa. */
function ResCompararFunil({ anoA, anoB, dadosA, dadosB, gidPrefix }) {
  const etapasA = resCmpEtapasDeDados(dadosA);
  const etapasB = resCmpEtapasDeDados(dadosB);
  const etapas = etapasA.map((e, i) => ({
    key: e.key,
    label: e.label,
    real: e.real + etapasB[i].real,
    valor: (e.valor || 0) + (etapasB[i].valor || 0) || undefined,
    a: e.real,
    b: etapasB[i].real,
  }));
  const primeiro = etapas[0]?.real || 0;
  const ultimo = etapas[etapas.length - 1];
  const taxa = primeiro ? (ultimo.real / primeiro) * 100 : 0;
  const taxaTxt = primeiro
    ? (taxa > 0 && taxa < 10 ? taxa.toFixed(1).replace('.', ',') : String(Math.round(taxa))) + '%'
    : '0%';
  const fraseUltima = ultimo.real === 1 ? "1 WIN" : `${resFmtN(ultimo.real)} WIN's`;
  const sub = primeiro
    ? `${fraseUltima} de ${resFmtN(primeiro)} leads (soma) · ${anoA} + ${anoB}${ultimo.valor ? ` · ${resFmtEur(ultimo.valor)}` : ''}`
    : `Sem leads · ${anoA} + ${anoB}`;
  const n = Math.max(etapas.length, 1);
  const H = 180;
  const gid = (gidPrefix || 'cmp-funil-') + 'acum';
  const Corpo = window.ResFunilCorpo;
  const Contagem = window.ResContagem;
  const TipLinha = window.AnTipLinha;

  return (
    <div style={{ marginBottom: 12 }}>
      <div className="font-display" style={{ fontSize: 14, fontWeight: 650, marginBottom: 4 }}>
        {anoA} + {anoB} acumulado
      </div>
      <div style={{ fontSize: 11, color: 'var(--text-dim)', marginBottom: 8 }}>
        Número grande = soma dos dois YTD · linhas por baixo = cada ano
      </div>

      <div className="card an-card" style={{ padding: '18px 18px 8px', marginBottom: 4 }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', gap: 16, alignItems: 'flex-end' }}>
          <div>
            <div className="font-display" style={{ fontSize: 40, fontWeight: 650, letterSpacing: '-0.03em', lineHeight: 1, fontVariantNumeric: 'tabular-nums' }}>
              {primeiro && Contagem ? <Contagem valor={taxa} fmt={window.resFmtTaxa} /> : taxaTxt}
            </div>
            <div style={{ fontSize: 13, color: 'var(--text-muted)', marginTop: 6 }}>{sub}</div>
          </div>
        </div>
        {Corpo ? (
          <Corpo etapas={etapas} H={H} gid={gid} cores={RES_CMP_FUNIL_CORES} pathFn={resCmpFunilPath} pctFn={resCmpPct}
            ariaLabel={`Funil acumulado ${anoA} e ${anoB}`} linhaNum={24} padCelula="0 6px 10px"
            extraCelula={e => (
              <div style={{ fontSize: 11, color: 'var(--text-muted)', lineHeight: 1.35, marginTop: 2 }}>
                <div>{anoA} · {resFmtN(e.a)}</div>
                <div>{anoB} · {resFmtN(e.b)}</div>
              </div>
            )}
            tipExtra={(e, i) => (TipLinha ? (
              <>
                <TipLinha cor={RES_CMP_FUNIL_CORES[i]} contorno label={String(anoA)} valor={resFmtN(e.a)} />
                <TipLinha cor={RES_CMP_FUNIL_CORES[i]} contorno label={String(anoB)} valor={resFmtN(e.b)} />
              </>
            ) : null)} />
        ) : (
        <div style={{ position: 'relative', marginTop: 16, width: '100%' }}>
          <svg viewBox={`0 0 800 ${H}`} preserveAspectRatio="none" width="100%" height={H} role="img" aria-label={`Funil acumulado ${anoA} e ${anoB}`} style={{ display: 'block', position: 'absolute', left: 0, top: 28, width: '100%', height: H }}>
            <defs>
              <linearGradient id={gid} x1="0" y1="0" x2="1" y2="0" gradientUnits="objectBoundingBox">
                {RES_CMP_FUNIL_CORES.map((c, i) => (
                  <stop key={c} offset={n <= 1 ? 0 : i / (n - 1)} stopColor={c} />
                ))}
              </linearGradient>
            </defs>
            <path d={resCmpFunilPath(etapas.map(e => e.real), 800, H, 1.18)} fill="rgba(17,41,84,.18)" />
            <path d={resCmpFunilPath(etapas.map(e => e.real), 800, H, 1.08)} fill="rgba(26,53,104,.22)" />
            <path d={resCmpFunilPath(etapas.map(e => e.real), 800, H, 1)} fill={`url(#${gid})`} />
          </svg>
          <div style={{ position: 'relative', display: 'grid', gridTemplateColumns: `repeat(${etapas.length}, 1fr)`, width: '100%' }}>
            {etapas.map((e, i) => (
              <div key={e.key} style={{ textAlign: 'center', minWidth: 0, borderLeft: i ? '1px solid var(--border)' : 'none', padding: '0 6px 10px' }}>
                <div className="font-display" style={{ fontWeight: 650, fontSize: 20, lineHeight: '24px' }}>{resFmtN(e.real)}</div>
                <div style={{ fontSize: 11, color: 'var(--text-muted)', lineHeight: 1.35, marginTop: 2 }}>
                  <div>{anoA} · {resFmtN(e.a)}</div>
                  <div>{anoB} · {resFmtN(e.b)}</div>
                </div>
                <div style={{ height: H, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                  <div style={{ background: '#fff', color: '#112954', borderRadius: 999, padding: '4px 12px', fontSize: 13, fontWeight: 700, boxShadow: '0 0 0 1px rgba(17,41,84,.12)' }}>{resCmpPct(e.real, primeiro)}</div>
                </div>
                <div style={{ fontSize: 13, color: 'var(--text-muted)' }}>{e.label}</div>
              </div>
            ))}
          </div>
        </div>
        )}
      </div>
    </div>
  );
}

function ResInvestimentoRetorno({ kpis, fatura, year, isMobile }) {
  const k = kpis || {};
  const spend = resKpiNum(k.spend_eur);
  const orcamento = k.investimento_eur != null ? resKpiNum(k.investimento_eur) : null;
  const faturado = resKpiNum(k.fechos?.valor_eur);
  const nFechos = resKpiNum(k.fechos?.real);
  const leads = resKpiNum(k.leads?.real);
  const ops = resKpiNum(k.oportunidades?.real);
  const cpl = resCustoUnit(spend, leads);
  const cpo = resCustoUnit(spend, ops);
  const cards = [
    {
      label: 'Investimento',
      value: resFmtEur(spend),
      sub: orcamento != null && orcamento > 0
        ? `${year} · orçamento portal ${resFmtEur(orcamento)}`
        : String(year),
    },
    {
      label: 'Faturado',
      value: resFmtEur(faturado),
      sub: fatura?.conjunto?.wins
        ? `Gestor e fatura Primavera · ${resFmtN(nFechos)} WIN's`
        : `Produto_Valor no Gestor · ${resFmtN(nFechos)} WIN's`,
    },
    {
      label: 'ROAS',
      value: resFmtRoas(faturado, spend),
      sub: spend > 0 ? 'faturado / investimento' : 'sem investimento',
    },
    {
      label: 'Custo por lead',
      value: cpl != null ? resFmtEur(cpl, true) : '—',
      sub: leads > 0 ? `${resFmtN(leads)} leads` : 'sem leads',
    },
    {
      label: 'Custo por oportunidade',
      value: cpo != null ? resFmtEur(cpo, true) : '—',
      sub: ops > 0 ? `${resFmtN(ops)} oportunidades` : 'sem oportunidades',
    },
  ];
  return (
    <div style={{ marginBottom: 18 }}>
      <div className="font-display" style={{ fontSize: 14, fontWeight: 650, marginBottom: 8 }}>
        Investimento e retorno · {year}
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr 1fr' : 'repeat(5, 1fr)', gap: 10 }}>
        {cards.map(c => (
          <div key={c.label} style={{ padding: 14, border: '1px solid var(--border)', borderRadius: 10, background: 'var(--bg-elev)', minWidth: 0 }}>
            <div style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', letterSpacing: '.04em', textTransform: 'uppercase' }}>{c.label}</div>
            <div className="font-display" style={{ fontSize: 22, fontWeight: 650, marginTop: 4, lineHeight: 1.15 }}>{c.value}</div>
            <div style={{ fontSize: 11, marginTop: 4, color: 'var(--text-muted)' }}>{c.sub}</div>
          </div>
        ))}
      </div>
      {fatura?.conjunto?.wins > 0 && (
        <div style={{ marginTop: 10, padding: '12px 14px', border: '1px solid var(--border)', borderRadius: 10, background: 'var(--bg-elev)', fontSize: 12, color: 'var(--text-muted)', lineHeight: 1.5 }}>
          <div style={{ color: 'var(--text)', fontWeight: 650, marginBottom: 4 }}>Wins a 0 ou 1 € · fatura Primavera</div>
          <div>Neste período: {resFmtEur(fatura.periodo?.euros)} em {resFmtN(fatura.periodo?.com_fatura)} de {resFmtN(fatura.periodo?.consideradas)} wins Decal e Mimaki.</div>
          <div>Em todos os anos: {resFmtEur(fatura.conjunto.euros)} em {resFmtN(fatura.conjunto.com_fatura)} de {resFmtN(fatura.conjunto.wins)} wins.</div>
          <ul style={{ margin: '8px 0 0', paddingLeft: 18 }}>
            <li>Entram WEB, ADS e Newsletter. O valor é o total das faturas do NIF nos 12 meses a seguir ao início, em todos os artigos.</li>
            <li>Cada fatura conta numa só win, a mais recente desse cliente.</li>
            <li>Um valor do Gestor acima de 1 € mantém-se. A UJ330H híbrida mantém 34.900 €.</li>
            <li>Sem NIF nesta cópia do Primavera, ou sem fatura nesses 12 meses, a win fica a 0 €. O 1 € do Gestor não entra no faturado.</li>
            <li>Espanha, Andorra e a source Mimaki Contact ficam de fora.</li>
          </ul>
        </div>
      )}
    </div>
  );
}

function resReachFromOverview(ov) {
  const fromKpi = resKpiNum(ov?.kpis?.reach?.real);
  if (fromKpi > 0) return fromKpi;
  let sum = 0;
  for (const c of ov?.campanhas || []) {
    const e = (c.etapas || []).find(x => x.key === 'reach');
    if (e) sum += resKpiNum(e.real);
    else if (c.reach != null) sum += resKpiNum(c.reach);
  }
  return sum;
}

function resFunilDeCampanha(c) {
  if (c?.funil) return c.funil;
  const b = c?.brand_slug;
  if (b === 'mimaki' || b === 'alldecor') return 'demo';
  if (b === 'biond' || b === 'decal') return 'roll';
  if (b === 'sensek') return 'sensek';
  return 'comum';
}

function resFunilAgg(campanhas, funis, keys) {
  const set = new Set(funis);
  const out = {};
  for (const key of keys) out[key] = 0;
  for (const c of campanhas || []) {
    if (!set.has(resFunilDeCampanha(c))) continue;
    for (const key of keys) {
      const e = (c.etapas || []).find(x => x.key === key || (key === 'fechos' && x.key === 'wins'));
      if (e) out[key] += resKpiNum(e.real);
      else if (key === 'fechos') out[key] += resKpiNum(c._fact?.fechos);
      else if (key === 'oportunidades') out[key] += resKpiNum(c.oportunidades ?? c._fact?.oportunidades);
    }
  }
  return out;
}

function resExtractComparar(ov) {
  const k = ov?.kpis || {};
  const camps = ov?.campanhas || [];
  return {
    leads: resKpiNum(k.leads?.real),
    mql: resKpiNum(k.qualificados?.real),
    oportunidades: resKpiNum(k.oportunidades?.real),
    fechos: resKpiNum(k.fechos?.real),
    fechos_eur: resKpiNum(k.fechos?.valor_eur),
    spend: resKpiNum(k.spend_eur),
    reach: resReachFromOverview(ov),
    demo: resFunilAgg(camps, ['demo'], ['oportunidades', 'demos', 'fechos']),
    roll: resFunilAgg(camps, ['roll'], ['oportunidades', 'testing_rolls', 'primeira_encomenda']),
    sensek: resFunilAgg(camps, ['sensek'], ['qualificados', 'testing_rolls', 'primeira_encomenda']),
    comum: resFunilAgg(camps, ['comum'], ['oportunidades', 'fechos']),
  };
}

function resDiffPct(a, b) {
  if (!(a > 0)) return null;
  return ((b - a) / a) * 100;
}

function resFmtDiff(a, b, { eur } = {}) {
  const d = b - a;
  const pct = resDiffPct(a, b);
  const abs = eur ? resFmtEur(d) : resFmtN(d);
  const sign = d > 0 ? '+' : '';
  if (pct == null) return `${sign}${abs}`;
  const pctStr = (pct > 0 ? '+' : '') + pct.toFixed(1).replace('.', ',') + '%';
  return `${sign}${abs} (${pctStr})`;
}

function resAnosCompararInit(yearDefault) {
  const y = RES_ANOS.includes(Number(yearDefault)) ? Number(yearDefault) : RES_ANO_CORRENTE;
  if (y === RES_ANOS[0]) return { a: RES_ANOS[0], b: RES_ANOS[1] };
  return { a: y - 1, b: y };
}

function ResCompararModal({ yearDefault, estado, source, action, marca, onClose }) {
  const init = resAnosCompararInit(yearDefault);
  const [anoA, setAnoA] = React.useState(init.a);
  const [anoB, setAnoB] = React.useState(init.b);
  const [loading, setLoading] = React.useState(true);
  const [err, setErr] = React.useState('');
  const [dadosA, setDadosA] = React.useState(null);
  const [dadosB, setDadosB] = React.useState(null);
  const mesmoAno = anoA === anoB;
  const yLo = Math.min(anoA, anoB);
  const yHi = Math.max(anoA, anoB);

  React.useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);

  React.useEffect(() => {
    if (mesmoAno) {
      setLoading(false);
      setDadosA(null);
      setDadosB(null);
      setErr('');
      return undefined;
    }
    let cancel = false;
    setLoading(true); setErr('');
    const marcaQs = marca ? `&marca=${encodeURIComponent(marca)}` : '';
    const base = `periodo=ytd&estado=${encodeURIComponent(estado || 'todas')}&source=${encodeURIComponent(source || 'todas')}&action=${encodeURIComponent(action || 'todas')}${marcaQs}`;
    Promise.all([
      campApiCall(`/api/marketing/resultados/v2/overview?${base}&year=${anoA}`),
      campApiCall(`/api/marketing/resultados/v2/overview?${base}&year=${anoB}`),
    ]).then(([a, b]) => {
      if (cancel) return;
      setDadosA(resExtractComparar(a));
      setDadosB(resExtractComparar(b));
      setLoading(false);
    }).catch(e => {
      if (cancel) return;
      setErr(e.message);
      setLoading(false);
    });
    return () => { cancel = true; };
  }, [anoA, anoB, estado, source, action, marca, mesmoAno]);

  const roasA = dadosA ? resRoasNum(dadosA.fechos_eur, dadosA.spend) : null;
  const roasB = dadosB ? resRoasNum(dadosB.fechos_eur, dadosB.spend) : null;
  const cplA = dadosA ? resCustoUnit(dadosA.spend, dadosA.leads) : null;
  const cplB = dadosB ? resCustoUnit(dadosB.spend, dadosB.leads) : null;
  const cpoA = dadosA ? resCustoUnit(dadosA.spend, dadosA.oportunidades) : null;
  const cpoB = dadosB ? resCustoUnit(dadosB.spend, dadosB.oportunidades) : null;

  const linhas = dadosA && dadosB ? [
    { label: 'Leads', a: dadosA.leads, b: dadosB.leads },
    { label: 'MQL', a: dadosA.mql, b: dadosB.mql },
    { label: 'Oportunidades', a: dadosA.oportunidades, b: dadosB.oportunidades },
    { label: "WIN's", a: dadosA.fechos, b: dadosB.fechos },
    { label: 'Investimento', a: dadosA.spend, b: dadosB.spend, eur: true },
    { label: 'Faturado', a: dadosA.fechos_eur, b: dadosB.fechos_eur, eur: true },
    {
      label: 'ROAS',
      a: roasA,
      b: roasB,
      fmtA: () => resFmtRoas(dadosA.fechos_eur, dadosA.spend),
      fmtB: () => resFmtRoas(dadosB.fechos_eur, dadosB.spend),
      fmtDiff: () => (roasA == null || roasB == null ? '—' : resFmtDiff(roasA, roasB)),
    },
    {
      label: 'Custo por lead',
      a: cplA,
      b: cplB,
      fmtA: () => (cplA != null ? resFmtEur(cplA, true) : '—'),
      fmtB: () => (cplB != null ? resFmtEur(cplB, true) : '—'),
      fmtDiff: () => (cplA == null || cplB == null ? '—' : resFmtDiff(cplA, cplB, { eur: true })),
    },
    {
      label: 'Custo por oportunidade',
      a: cpoA,
      b: cpoB,
      fmtA: () => (cpoA != null ? resFmtEur(cpoA, true) : '—'),
      fmtB: () => (cpoB != null ? resFmtEur(cpoB, true) : '—'),
      fmtDiff: () => (cpoA == null || cpoB == null ? '—' : resFmtDiff(cpoA, cpoB, { eur: true })),
    },
    ...(dadosA.reach > 0 || dadosB.reach > 0 ? [{ label: 'Reach', a: dadosA.reach, b: dadosB.reach }] : []),
  ] : [];

  const blocosSector = dadosA && dadosB ? [
    {
      titulo: 'Mimaki e AllDecor',
      rows: [
        { label: 'Oportunidades', a: dadosA.demo.oportunidades, b: dadosB.demo.oportunidades },
        { label: 'Demos', a: dadosA.demo.demos, b: dadosB.demo.demos },
        { label: "WIN's", a: dadosA.demo.fechos, b: dadosB.demo.fechos },
      ],
    },
    {
      titulo: 'Biond e Decal',
      rows: [
        { label: 'Oportunidades', a: dadosA.roll.oportunidades, b: dadosB.roll.oportunidades },
        { label: 'Testing Rolls', a: dadosA.roll.testing_rolls, b: dadosB.roll.testing_rolls },
        { label: '1ª Encomenda', a: dadosA.roll.primeira_encomenda, b: dadosB.roll.primeira_encomenda },
      ],
    },
    {
      titulo: 'Sensek',
      rows: [
        { label: 'MQL', a: dadosA.sensek.qualificados, b: dadosB.sensek.qualificados },
        { label: 'Testing Rolls', a: dadosA.sensek.testing_rolls, b: dadosB.sensek.testing_rolls },
        { label: '1ª Encomenda', a: dadosA.sensek.primeira_encomenda, b: dadosB.sensek.primeira_encomenda },
      ],
    },
    {
      titulo: 'NetScreen',
      rows: [
        { label: 'Oportunidades', a: dadosA.comum.oportunidades, b: dadosB.comum.oportunidades },
        { label: "WIN's", a: dadosA.comum.fechos, b: dadosB.comum.fechos },
      ],
    },
  ] : [];

  const th = { textAlign: 'right', padding: '8px 10px', fontSize: 11, color: 'var(--text-dim)', fontWeight: 600, borderBottom: '1px solid var(--border)' };
  const tdL = { padding: '8px 10px', fontSize: 13, borderBottom: '1px solid var(--border)' };
  const tdR = { ...tdL, textAlign: 'right', fontVariantNumeric: 'tabular-nums' };

  const tabela = (rows) => (
    <table style={{ width: '100%', borderCollapse: 'collapse', marginBottom: 16 }}>
      <thead>
        <tr>
          <th style={{ ...th, textAlign: 'left' }}>KPI</th>
          <th style={th}>{anoA}</th>
          <th style={th}>{anoB}</th>
          <th style={th}>Diferença (base {anoA})</th>
        </tr>
      </thead>
      <tbody>
        {rows.map(r => {
          const va = r.fmtA ? r.fmtA() : (r.eur ? resFmtEur(r.a) : resFmtN(r.a));
          const vb = r.fmtB ? r.fmtB() : (r.eur ? resFmtEur(r.b) : resFmtN(r.b));
          const na = r.a == null ? 0 : Number(r.a);
          const nb = r.b == null ? 0 : Number(r.b);
          const diff = r.fmtDiff ? r.fmtDiff() : resFmtDiff(r.a, r.b, { eur: r.eur });
          const delta = (r.a == null || r.b == null) ? 0 : (nb - na);
          return (
            <tr key={r.label}>
              <td style={tdL}>{r.label}</td>
              <td style={tdR}>{va}</td>
              <td style={tdR}>{vb}</td>
              <td style={{
                ...tdR,
                color: delta > 0 ? 'var(--success)' : delta < 0 ? 'var(--danger)' : 'var(--text-muted)',
              }}>{diff}</td>
            </tr>
          );
        })}
      </tbody>
    </table>
  );

  return (
    <div
      role="dialog"
      aria-modal="true"
      aria-label="Comparar anos"
      onClick={e => { if (e.target === e.currentTarget) onClose(); }}
      style={{
        position: 'fixed', inset: 0, zIndex: 80, background: 'rgba(15, 23, 32, 0.45)',
        display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24,
      }}
    >
      <div className="card" style={{
        width: 'min(920px, 100%)', maxHeight: '90vh', overflow: 'auto', padding: 20,
        background: 'var(--bg-elev)', border: '1px solid var(--border)', borderRadius: 12,
        boxShadow: '0 18px 48px rgba(0,0,0,.18)',
      }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12, marginBottom: 14 }}>
          <div>
            <div style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', letterSpacing: '.06em' }}>RESULTADOS · COMPARAR</div>
            <h2 className="font-display" style={{ margin: '4px 0 0', fontSize: 20, fontWeight: 650 }}>Dois anos lado a lado</h2>
            <div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 4 }}>YTD de cada ano · mesmos filtros de estado, marca, source e action</div>
          </div>
          <button type="button" className="btn btn-sm" onClick={onClose}>Fechar</button>
        </div>

        <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', marginBottom: 12, alignItems: 'center' }}>
          <label style={{ fontSize: 12, color: 'var(--text-muted)' }}>
            Ano A{' '}
            <select value={anoA} onChange={e => setAnoA(Number(e.target.value))} style={{ marginLeft: 6, padding: '6px 8px', borderRadius: 8, border: '1px solid var(--border)', background: 'var(--bg)', color: 'var(--text)' }}>
              {RES_ANOS.map(y => <option key={y} value={y}>{y}</option>)}
            </select>
          </label>
          <label style={{ fontSize: 12, color: 'var(--text-muted)' }}>
            Ano B{' '}
            <select value={anoB} onChange={e => setAnoB(Number(e.target.value))} style={{ marginLeft: 6, padding: '6px 8px', borderRadius: 8, border: '1px solid var(--border)', background: 'var(--bg)', color: 'var(--text)' }}>
              {RES_ANOS.map(y => <option key={y} value={y}>{y}</option>)}
            </select>
          </label>
        </div>

        {mesmoAno && (
          <div style={{ fontSize: 12, color: 'var(--warning, #b45309)', marginBottom: 12, padding: '8px 10px', borderRadius: 8, background: 'rgba(245,158,11,.12)', border: '1px solid rgba(245,158,11,.35)' }}>
            Escolhe dois anos diferentes para comparar.
          </div>
        )}

        {err && <div style={{ color: 'var(--danger)', fontSize: 12, marginBottom: 12 }}>{err}</div>}
        {!mesmoAno && loading && <div style={{ color: 'var(--text-muted)', fontSize: 12, marginBottom: 12 }}>A carregar {anoA} e {anoB}…</div>}

        {!mesmoAno && !loading && dadosA && dadosB && (
          <>
            <ResCompararFunil
              anoA={anoA}
              anoB={anoB}
              dadosA={dadosA}
              dadosB={dadosB}
              gidPrefix={`cmp-funil-${yLo}-${yHi}-`}
            />
            <div className="font-display" style={{ fontSize: 14, fontWeight: 650, margin: '8px 0' }}>KPIs principais · {anoA} vs {anoB}</div>
            {tabela(linhas)}
            {blocosSector.map(bl => (
              <div key={bl.titulo}>
                <div className="font-display" style={{ fontSize: 13, fontWeight: 650, marginBottom: 6 }}>{bl.titulo}</div>
                {tabela(bl.rows)}
              </div>
            ))}
          </>
        )}
      </div>
    </div>
  );
}

const RES_FUNIL_VOLTA_KEY = 'mkt-resultados-funil-volta';

function resFunilLinks(l) {
  const pessoa = l.tipo === 'lead' ? (l.ref || l.pessoa_chave) : '';
  const ficha = pessoa ? `screen=marketing&sub=leads-outbound&pessoa=${encodeURIComponent(pessoa)}&tab=resumo` : '';
  let crm = '';
  let crmTitulo = '';
  if (l.op_fm_id) {
    crm = `screen=marketing&sub=audiencias&op=${encodeURIComponent(l.op_fm_id)}`;
    crmTitulo = 'Abrir a oportunidade no CRM';
  } else if (l.entidade_fm_id) {
    crm = `screen=marketing&sub=audiencias&entidade=${encodeURIComponent(l.entidade_fm_id)}${l.tenant ? `&tenant=${encodeURIComponent(l.tenant)}` : ''}`;
    crmTitulo = 'Abrir a entidade no CRM';
  }
  return { ficha, crm, crmTitulo, principal: ficha || crm };
}

function resFunilIr(destino, fase, campanha) {
  try { sessionStorage.setItem(RES_FUNIL_VOLTA_KEY, JSON.stringify({ fase: { key: fase.key, label: fase.label, real: fase.real }, campanha: campanha || '' })); } catch {}
  try { window.history.replaceState(null, '', `#screen=marketing&sub=resultados&funil=${encodeURIComponent(fase.key)}`); } catch {}
  window.location.hash = destino;
}

function resFunilVolta() {
  try {
    const marca = new URLSearchParams((window.location.hash || '').replace(/^#/, '')).get('funil');
    if (!marca) return null;
    const v = JSON.parse(sessionStorage.getItem(RES_FUNIL_VOLTA_KEY) || 'null');
    return v?.fase?.key === marca ? v : null;
  } catch { return null; }
}

function resFunilLimparVolta() {
  try {
    const p = new URLSearchParams((window.location.hash || '').replace(/^#/, ''));
    if (!p.has('funil')) return;
    p.delete('funil');
    window.history.replaceState(null, '', '#' + p.toString());
  } catch {}
}

function ResFunilModal({ fase, qs, campanha, totais, onClose }) {
  const [linhas, setLinhas] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [err, setErr] = React.useState('');
  const [q, setQ] = React.useState('');
  const [truncado, setTruncado] = React.useState(false);
  React.useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);
  React.useEffect(() => {
    let cancel = false;
    setLoading(true);
    setErr('');
    const extra = campanha ? `&campanha=${encodeURIComponent(campanha)}` : '';
    campApiCall(`/api/marketing/resultados/v2/funil?fase=${encodeURIComponent(fase.key)}&${qs}${extra}`)
      .then(d => {
        if (cancel) return;
        setLinhas(d.linhas || []);
        setTruncado(!!d.truncado);
        setLoading(false);
      })
      .catch(e => { if (!cancel) { setErr(e.message); setLoading(false); } });
    return () => { cancel = true; };
  }, [fase.key, qs, campanha]);
  const termo = q.trim().toLowerCase();
  const visiveis = termo
    ? linhas.filter(l => `${l.nome} ${l.empresa} ${l.campanha} ${l.marca} ${l.detalhe} ${l.estado}`.toLowerCase().includes(termo))
    : linhas;
  const th = { textAlign: 'left', padding: '8px 10px', fontSize: 10, color: 'var(--text-dim)', fontWeight: 600, borderBottom: '1px solid var(--border)', position: 'sticky', top: 0, background: 'var(--bg-elev)' };
  const td = { padding: '7px 10px', borderBottom: '1px solid var(--border)', verticalAlign: 'top' };
  const tdAbrir = { position: 'sticky', right: 0, textAlign: 'right', whiteSpace: 'nowrap', background: 'var(--bg-elev)', boxShadow: '-1px 0 0 var(--border)' };
  return (
    <div role="dialog" aria-modal="true" aria-label={fase.label} onClick={e => { if (e.target === e.currentTarget) onClose(); }} style={{ position: 'fixed', inset: 0, zIndex: 80, background: 'rgba(15, 23, 32, 0.45)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24 }}>
      <div className="card" style={{ width: 'min(1040px, 100%)', maxHeight: '90vh', display: 'flex', flexDirection: 'column', padding: 20, background: 'var(--bg-elev)', border: '1px solid var(--border)', borderRadius: 12, boxShadow: '0 18px 48px rgba(0,0,0,.18)' }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12, marginBottom: 12 }}>
          <div>
            <div style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', letterSpacing: '.06em' }}>FUNIL · {String(fase.label || '').toUpperCase()}</div>
            <h2 className="font-display" style={{ margin: '4px 0 0', fontSize: 20, fontWeight: 650 }}>{fase.label} · {resFmtN(fase.real)}</h2>
            <div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 4 }}>{loading ? 'A carregar…' : `${resFmtN(visiveis.length)} registos`}{truncado ? ' · lista limitada a 2000' : ''}</div>
            <div style={{ display: 'flex', gap: 22, marginTop: 12 }}>
              <div>
                <div style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', letterSpacing: '.05em' }}>OPORTUNIDADES</div>
                <div className="font-display" style={{ fontSize: 18, fontWeight: 650 }}>{resFmtN(totais?.oportunidades)}</div>
              </div>
              <div>
                <div style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', letterSpacing: '.05em' }}>WIN'S</div>
                <div className="font-display" style={{ fontSize: 18, fontWeight: 650 }}>{resFmtN(totais?.wins)}{totais?.winsEur != null ? ` · ${resFmtEur(totais.winsEur)}` : ''}</div>
              </div>
            </div>
          </div>
          <button type="button" className="btn btn-sm" onClick={onClose}>Fechar</button>
        </div>
        <input value={q} onChange={e => setQ(e.target.value)} placeholder="Procurar nome, empresa ou campanha" style={{ marginBottom: 10, padding: '8px 10px', borderRadius: 8, border: '1px solid var(--border)', background: 'var(--bg)', color: 'var(--text)', fontSize: 13 }} />
        {err && <div style={{ color: 'var(--danger)', fontSize: 12, marginBottom: 8 }}>{err}</div>}
        <div style={{ overflow: 'auto', minHeight: 0 }}>
          <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12 }}>
            <thead>
              <tr>
                {['Nome', 'Campanha', 'Origem', 'Detalhe', 'Data', 'Estado', 'Valor', 'Abrir'].map(h => <th key={h} style={h === 'Abrir' ? { ...th, ...tdAbrir, zIndex: 1 } : th}>{h}</th>)}
              </tr>
            </thead>
            <tbody>
              {visiveis.map((l, i) => {
                const lk = resFunilLinks(l);
                const ir = (destino) => (ev) => { ev.stopPropagation(); resFunilIr(destino, fase, campanha); };
                return (
                  <tr key={`${l.origem}-${l.campanha}-${l.nome}-${l.data}-${i}`} className="an-tr"
                    onClick={lk.principal ? ir(lk.principal) : undefined}
                    title={lk.principal ? (lk.ficha ? 'Abrir a ficha da lead' : lk.crmTitulo) : undefined}
                    style={{ cursor: lk.principal ? 'pointer' : 'default' }}>
                    <td style={td}>
                      <div style={{ fontWeight: lk.principal ? 600 : 400, color: lk.principal ? 'var(--text)' : 'var(--text-muted)' }}>{l.nome || '—'}</div>
                      {l.empresa ? <div style={{ color: 'var(--text-muted)', fontSize: 11 }}>{l.empresa}</div> : null}
                    </td>
                    <td style={td}>{l.campanha}</td>
                    <td style={td}>{l.origem}{l.marca ? ` · ${l.marca}` : ''}</td>
                    <td style={td}>{l.detalhe || '—'}</td>
                    <td style={td}>{l.data || '—'}</td>
                    <td style={td}>{l.estado || '—'}</td>
                    <td style={td}>{l.valor != null ? resFmtEur(l.valor) : '—'}</td>
                    <td style={{ ...td, ...tdAbrir }}>
                      {lk.ficha && <button type="button" className="res-funil-link" onClick={ir(lk.ficha)} title="Abrir a ficha da lead">Lead</button>}
                      {lk.crm && <button type="button" className="res-funil-link" onClick={ir(lk.crm)} title={lk.crmTitulo} style={{ marginLeft: lk.ficha ? 6 : 0 }}>CRM</button>}
                      {!lk.ficha && !lk.crm && <span style={{ color: 'var(--text-dim)' }}>—</span>}
                    </td>
                  </tr>
                );
              })}
              {!loading && !visiveis.length && <tr><td colSpan={8} style={{ padding: 16, color: 'var(--text-dim)' }}>Sem registos nesta fase.</td></tr>}
            </tbody>
          </table>
        </div>
      </div>
    </div>
  );
}

const MktPerformance360Screen = ({ setSub }) => {
  const brandApi = window.useMktBrand ? window.useMktBrand() : { brand: 'todas', isAll: true, setBrand: () => {} };
  const { brand, isAll, setBrand, userMarcas } = brandApi;
  const bp = window.useBreakpoint ? window.useBreakpoint() : { isMobile: false };
  const saved = resLoadNav();
  const yearInit = RES_ANOS.includes(Number(saved.year)) ? Number(saved.year) : RES_ANO_CORRENTE;
  const periodoInit = (saved.periodo === 'last_30d' && yearInit !== RES_ANO_CORRENTE) ? 'ytd' : (saved.periodo || 'ytd');
  const [year, setYear] = React.useState(yearInit);
  const [periodo, setPeriodo] = React.useState(periodoInit);
  const [inicio, setInicio] = React.useState(saved.inicio || '');
  const [fim, setFim] = React.useState(saved.fim || '');
  const [estado, setEstado] = React.useState(saved.estado || 'todas');
  const [source, setSource] = React.useState(RES_SOURCES.some(s => s.id === saved.source) ? saved.source : 'todas');
  const [action, setAction] = React.useState(saved.action || 'todas');
  const [accoes, setAccoes] = React.useState([]);
  const [volta] = React.useState(resFunilVolta);
  const [campanhaFiltro, setCampanhaFiltro] = React.useState(volta?.campanha || '');
  const [campanhaId, setCampanhaId] = React.useState(saved.campanhaId || '');
  const [overview, setOverview] = React.useState(null);
  const [fonteMarca, setFonteMarca] = React.useState('todas');
  const [detalhe, setDetalhe] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [err, setErr] = React.useState('');
  window.useMktZoneLoading(loading && !overview);
  const [mais, setMais] = React.useState(false);
  const [leadsMeta, setLeadsMeta] = React.useState(false);
  const [compararAberto, setCompararAberto] = React.useState(false);
  const [busca, setBusca] = React.useState(false);
  const [analiseInfo, setAnaliseInfo] = React.useState(null);
  const [faseFunil, setFaseFunil] = React.useState(volta?.fase || null);
  const [rvo, setRvo] = React.useState(null);
  const [rels, setRels] = React.useState([]);

  React.useEffect(() => { resSaveNav({ year, periodo, inicio, fim, estado, source, action, campanhaId }); }, [year, periodo, inicio, fim, estado, source, action, campanhaId]);
  React.useEffect(() => {
    const lista = overview?.filtros_gestor?.actions;
    if (Array.isArray(lista) && lista.length) setAccoes(lista);
  }, [overview]);

  const qsBase = `periodo=${periodo}&year=${year}&estado=${estado}&source=${encodeURIComponent(source)}&action=${encodeURIComponent(action)}${periodo === 'custom' && inicio && fim ? `&inicio=${inicio}&fim=${fim}` : ''}${isAll ? '' : `&marca=${encodeURIComponent(brand)}`}`;
  const qs = qsBase;
  const loadGen = React.useRef(0);
  const load = React.useCallback(() => {
    const gen = ++loadGen.current;
    const marcaVista = isAll ? 'todas' : brand;
    setLoading(true); setErr('');
    campApiCall('/api/marketing/resultados/v2/overview?' + qs)
      .then(d => { if (gen !== loadGen.current) return; setOverview(d); setFonteMarca(marcaVista); setLoading(false); })
      .catch(e => { if (gen !== loadGen.current) return; setErr(e.message); setLoading(false); });
  }, [qs, isAll, brand]);
  React.useEffect(() => { load(); }, [load]);

  React.useEffect(() => {
    if (!campanhaFiltro || !overview) return;
    const lista = overview.campanhas || [];
    if (!lista.some(c => c.id === campanhaFiltro)) setCampanhaFiltro('');
  }, [overview, campanhaFiltro]);

  const detalheQs = qsBase;
  React.useEffect(() => {
    if (!campanhaId) { setDetalhe(null); return; }
    let cancel = false;
    campApiCall('/api/marketing/resultados/v2/campanha/' + encodeURIComponent(campanhaId) + '?' + detalheQs)
      .then(d => { if (!cancel) { setDetalhe(d); setErr(''); } })
      .catch(e => { if (!cancel) setErr(e.message); });
    return () => { cancel = true; };
  }, [campanhaId, detalheQs]);

  const recalcular = () => {
    setErr('');
    setLoading(true);
    campApiCall('/api/marketing/resultados/v2/recalcular', { method: 'POST', body: '{}' })
      .then(() => {
        load();
        if (campanhaId) {
          return campApiCall('/api/marketing/resultados/v2/campanha/' + encodeURIComponent(campanhaId) + '?' + detalheQs).then(setDetalhe);
        }
      })
      .catch(e => { setErr(e.message); setLoading(false); });
  };

  const csv = () => {
    const rows = overview?.campanhas || [];
    const header = ['Campanha', 'Origem', 'Marca', 'Sector', 'Estado', 'Janela', 'Spend', 'Leads Meta', 'Leads', 'Leads obj', 'MQL', 'Oportunidades', "WIN's €", 'Semáforo'];
    const lines = [header.join(';')];
    for (const c of rows) {
      const leads = (c.etapas || []).find(e => e.key === 'leads') || {};
      const qual = (c.etapas || []).find(e => e.key === 'qualificados') || {};
      lines.push([
        c.titulo, c.tipo === 'crm' ? 'Gestor' : c.tipo === 'meta_externa' ? 'Meta' : 'Portal', c.marca_nome, c.sector, c.estado,
        `${c.janela_inicio || ''} ${c.janela_fim || ''}`, c.spend_eur, c.leads_plataforma || 0,
        leads.real, leads.objectivo ?? '', qual.real, c.oportunidades || 0, c.fechos_eur || 0, c.light || '',
      ].join(';'));
    }
    const blob = new Blob(['\uFEFF' + lines.join('\n')], { type: 'text/csv;charset=utf-8' });
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = 'resultados-campanhas.csv';
    a.click();
  };

  const k = overview?.kpis || {};
  const campanhasLista = overview?.campanhas || [];
  const escolhida = campanhaFiltro ? campanhasLista.find(c => c.id === campanhaFiltro) : null;
  const kVisiveis = escolhida ? resKpisCampanha(escolhida) : k;
  const sectorFunil = escolhida
    ? window.resSectorFunil(escolhida.brand_slug, false)
    : window.resSectorFunil(fonteMarca, fonteMarca === 'todas');
  const brandCards = escolhida ? (escolhida.brand_slug || fonteMarca) : fonteMarca;
  const FunilMain = window.ResFunil;
  const ObjCards = window.ResObjectivoCards;
  const LinhasCmp = window.ResLinhas;
  const marcasNav = [{ id: 'todas', nome: 'Todas' }, ...((window.MKT_BRANDS || []).filter(b => b.id !== 'todas' && (!userMarcas?.length || userMarcas.includes(b.id))))];
  const acaoBtn = { height: 30, padding: '0 14px', fontSize: 12 };
  const MenuAccoes = window.ResMenuAccoes;
  const Icone = window.ResIcone;
  const Lupa = window.ResIconeLupa;
  const Trilho = window.ResTrilho;
  const Busca = window.ResBusca;
  const AnaliseResumo = window.ResAnaliseResumo;
  const abrirAnalise = () => {
    try { localStorage.setItem('mkt-analise-marca', isAll ? 'todas' : brand); } catch {}
    setSub && setSub('resultados-analise');
  };
  const filtrosAnalise = { periodo, ano: year, inicio: periodo === 'custom' ? inicio : '', fim: periodo === 'custom' ? fim : '', source, action, campanha: campanhaFiltro };
  const filtrosActivos = [
    estado !== 'todas' && (RES_ESTADOS.find(e => e.id === estado)?.label || estado),
    source !== 'todas' && (RES_SOURCES.find(s => s.id === source)?.label || source),
    action !== 'todas' && (accoes.find(a => a.id === action)?.label || action),
    campanhaFiltro && (campanhasLista.find(c => c.id === campanhaFiltro)?.titulo || 'campanha'),
  ].filter(Boolean);
  const limparFiltros = () => { setSource('todas'); setAction('todas'); setCampanhaFiltro(''); setEstado('todas'); };
  const mudarSource = (v) => {
    setSource(v);
    if (v !== 'todas' && action !== 'todas') {
      const a = accoes.find(x => x.id === action);
      if (!a || a.source !== v) setAction('todas');
    }
  };
  const rankSource = (id) => {
    const i = RES_SOURCES.findIndex(s => s.id === id);
    return i < 0 ? 99 : i;
  };
  const accoesVisiveis = accoes
    .filter(a => source === 'todas' || a.source === source)
    .slice()
    .sort((a, b) => rankSource(a.source) - rankSource(b.source) || String(a.label).localeCompare(String(b.label), 'pt'));
  const actionOptions = [
    { value: 'todas', label: 'Todas' },
    ...accoesVisiveis.map(a => ({
      value: a.id,
      label: a.label,
      grupo: source === 'todas' ? (RES_SOURCES.find(s => s.id === a.source)?.label || 'Outras') : undefined,
    })),
  ];
  if (action !== 'todas' && !actionOptions.some(o => o.value === action)) actionOptions.push({ value: action, label: action });
  const Periodo = window.BriefingDateRangePicker;
  const periodoValor = (() => {
    if (periodo === 'custom') {
      const r = inicio && fim ? { start: new Date(`${inicio}T00:00:00`), end: new Date(`${fim}T00:00:00`) } : resPeriodoDatas('ytd', year);
      const h = resHomologo();
      const homologo = inicio === resDataIso(h.start) && fim === resDataIso(h.end);
      return { preset: homologo ? 'homologo' : 'custom', start: r.start, end: r.end };
    }
    const r = resPeriodoDatas(periodo, year) || resPeriodoDatas('ytd', year);
    return { preset: `${periodo}:${year}`, start: r.start, end: r.end };
  })();
  const mudarPeriodo = (v) => {
    if (v.preset === 'custom' || v.preset === 'homologo') {
      setInicio(resDataIso(v.start)); setFim(resDataIso(v.end));
      if (RES_ANOS.includes(v.end.getFullYear())) setYear(v.end.getFullYear());
      setPeriodo('custom');
      return;
    }
    const [p, a] = String(v.preset).split(':');
    setYear(Number(a) || RES_ANO_CORRENTE);
    setPeriodo(p || 'ytd');
  };
  const actualizadoTxt = overview?.atualizado_em ? `Actualizado ${new Date(overview.atualizado_em).toLocaleString('pt-PT', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' })}` : '';
  const menuItens = [
    { id: 'resultados', label: 'Resultados', sub: 'Funil, objectivos e campanhas', checked: !campanhaId, onClick: () => setCampanhaId('') },
    {
      id: 'analise', label: 'Análise da Digi AI',
      ponto: analiseInfo?.analise?.tom ? (window.RES_LIGHT || {})[analiseInfo.analise.tom] : 'var(--text-dim)',
      sub: analiseInfo?.gerado_em ? `Última análise ${new Date(analiseInfo.gerado_em).toLocaleString('pt-PT', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' })}` : 'Leitura, BP e campanhas a fazer',
      onClick: abrirAnalise,
    },
    { id: 'faturado', label: 'Faturado e investimento', sub: 'Total e série anual', onClick: () => setSub && setSub('resultados-faturado') },
    { id: 'mimaki', label: 'Report Mimaki', sub: 'Pipeline das equipas Mimaki em ranks Mimaki', onClick: () => setSub && setSub('resultados-mimaki') },
    { separador: true },
    ...(filtrosActivos.length ? [{ id: 'limpar', label: 'Limpar filtros', sub: filtrosActivos.join(' · '), onClick: limparFiltros }] : []),
    { id: 'comparar', label: 'Comparar anos', sub: 'Dois YTD lado a lado', onClick: () => setCompararAberto(true) },
    { id: 'leads-meta', label: 'Leads Meta', sub: 'Leads dos formulários Meta', checked: leadsMeta, onClick: () => setLeadsMeta(v => !v) },
    { id: 'recalcular', label: loading ? 'A recalcular…' : 'Recalcular', sub: [actualizadoTxt, `Primavera ${overview?.fontes?.primavera ? 'ligado' : 'não ligado'}`].filter(Boolean).join(' · '), disabled: loading, onClick: recalcular },
    { id: 'csv', label: 'Exportar CSV', sub: 'Campanhas deste filtro', onClick: csv },
  ];
  const menu = MenuAccoes ? <MenuAccoes icone items={menuItens} onOpen={() => setBusca(false)} /> : (
    <>
      <button type="button" className="btn" style={acaoBtn} onClick={() => setCompararAberto(true)}>Comparar</button>
      <button type="button" className="btn btn-ai" style={acaoBtn} onClick={csv}>CSV</button>
    </>
  );
  const origemCampanha = (c) => (c.tipo === 'crm' ? 'Gestor' : c.tipo === 'meta_externa' ? 'Meta' : 'Campanha');
  const itensBusca = campanhasLista.map(c => ({
    id: c.id, tipo: origemCampanha(c), titulo: c.titulo || '—',
    detalhe: [c.marca_nome, resEstado(c.estado), c.janela_inicio].filter(Boolean).join(' · '),
    texto: [c.sector, c.crm_fonte].filter(Boolean).join(' '),
  }));
  const fecharBusca = React.useCallback(() => setBusca(false), []);
  const campanhaAberta = campanhaId && detalhe?.campanha ? detalhe.campanha : null;

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%', minHeight: 0, background: 'var(--bg)' }}>
      <div style={{ background: 'var(--bg-elev)', borderBottom: '1px solid var(--border)', padding: '20px 40px 0', flexShrink: 0 }}>
        <div style={{ fontSize: 11, fontWeight: 600, letterSpacing: '0.07em', color: 'var(--text-muted)', textTransform: 'uppercase', marginBottom: 6, fontFamily: 'var(--font-mono)' }}>
          MARKETING · RESULTADOS
        </div>
        <h1 style={{ margin: 0, fontSize: 21, fontWeight: 700, color: 'var(--text)', fontFamily: 'var(--font-display)', letterSpacing: '-0.01em', lineHeight: 1.2 }}>
          Resultados
        </h1>
        <div style={{ height: 1, background: 'var(--border)', margin: '16px -40px 0' }} />
        <div style={{ display: 'flex', gap: 0, overflowX: 'auto' }}>
          {marcasNav.map(b => {
            const active = b.id === brand;
            return (
              <button key={b.id} type="button" onClick={() => setBrand && setBrand(b.id)}
                style={{
                  background: 'none', border: 'none', outline: 'none',
                  borderBottom: `2px solid ${active ? 'var(--ai-500)' : 'transparent'}`,
                  color: active ? 'var(--text)' : 'var(--text-muted)',
                  fontSize: 13, fontWeight: active ? 600 : 500,
                  fontFamily: 'var(--font-display)', letterSpacing: '.01em',
                  padding: '10px 18px 12px',
                  cursor: 'pointer', whiteSpace: 'nowrap',
                  transition: 'color .15s, border-color .15s',
                }}>
                {b.nome}
              </button>
            );
          })}
        </div>
      </div>

      <div className="scrollbar" style={{ overflowY: 'auto', flex: 1, minHeight: 0, padding: '28px 40px 64px', background: 'var(--bg)' }}>
        {compararAberto && (
          <ResCompararModal
            yearDefault={year}
            estado={estado}
            source={source}
            action={action}
            marca={isAll ? '' : brand}
            onClose={() => setCompararAberto(false)}
          />
        )}
        {busca && Busca && (
          <Busca itens={itensBusca} placeholder="Procurar campanhas" vazio="Nenhuma campanha neste filtro."
            onPick={(it) => setCampanhaId(it.id)} onClose={fecharBusca} />
        )}

        {campanhaId && Trilho && (
          <Trilho segmentos={[{ label: 'Resultados', onClick: () => setCampanhaId('') }, { label: campanhaAberta?.titulo || 'A carregar…' }]} extra={menu} nota={campanhaAberta && (
            <span style={{ display: 'inline-flex', alignItems: 'center', gap: 8, fontSize: 12, color: 'var(--text-muted)' }}>
              {campanhaAberta.marca_nome && <span style={{ color: 'var(--ai-500)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.06em', fontSize: 10, fontFamily: 'var(--font-mono)' }}>{campanhaAberta.marca_nome}</span>}
              <span>{origemCampanha(campanhaAberta)}</span>
              {campanhaAberta.tipo !== 'crm' && campanhaAberta.estado && <span>{resEstado(campanhaAberta.estado)}</span>}
              <span>{overview?.periodo?.label || 'YTD'}</span>
            </span>
          )} />
        )}

        {!campanhaId && (
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 8, marginBottom: 18 }}>
          <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>
            <ResFiltro label="Estado" value={estado} onChange={setEstado}
              options={RES_ESTADOS.map(p => ({ value: p.id, label: p.label }))} />
            <ResFiltro label="Source Gestor" value={source} onChange={mudarSource} menuMinWidth={220} marcado={source !== 'todas'}
              options={[{ value: 'todas', label: 'Todas' }, ...RES_SOURCES.map(s => ({ value: s.id, label: s.label, grupo: s.grupo }))]} />
            <ResFiltro label="Action Gestor" value={action} onChange={setAction} menuMinWidth={260} menuMaxHeight={360} marcado={action !== 'todas'}
              options={actionOptions} />
            <ResFiltro label="Campanha" value={campanhaFiltro} onChange={setCampanhaFiltro} menuMinWidth={280} marcado={!!campanhaFiltro}
              options={[{ value: '', label: 'Todas' }, ...[...campanhasLista].sort((a, b) => String(a.titulo || '').localeCompare(String(b.titulo || ''), 'pt')).map(c => ({ value: c.id, label: c.titulo }))]} />
            {Periodo ? (
              <Periodo value={periodoValor} onChange={mudarPeriodo} presets={resPeriodoPresets()} resolver={resPeriodoResolver} />
            ) : (
              <ResFiltro label="Período" value={periodoValor.preset} onChange={id => mudarPeriodo({ preset: id, ...(resPeriodoResolver(id) || {}) })}
                options={resPeriodoPresets().filter(p => p.id !== 'custom').map(p => ({ value: p.id, label: p.label }))} />
            )}
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginLeft: 'auto' }}>
            {Icone && Busca && (
              <Icone label="Procurar campanhas" activo={busca} onClick={() => setBusca(v => !v)}><Lupa /></Icone>
            )}
            {menu}
          </div>
        </div>
        )}

        {leadsMeta && <ResLeadsMeta />}
        {err && <div style={{ color: 'var(--danger)', fontSize: 12, marginBottom: 12 }}>{err}</div>}
        {loading && !overview && <div style={{ color: 'var(--text-muted)', fontSize: 12, marginBottom: 12 }}>A consolidar…</div>}

        {campanhaId && detalhe?.campanha
          ? <ResCampanha detalhe={detalhe} onBack={() => setCampanhaId('')} setSub={setSub} onReload={() => campApiCall('/api/marketing/resultados/v2/campanha/' + encodeURIComponent(campanhaId) + '?' + detalheQs).then(setDetalhe)} />
          : (
            <>
              {overview && (
                <>
                  {FunilMain ? <FunilMain
                    kpis={kVisiveis}
                    sector={sectorFunil}
                    marca={isAll ? 'todas' : brand}
                    pronto={fonteMarca === (isAll ? 'todas' : brand)}
                    periodo={escolhida ? `${overview.periodo?.label || ''} · ${escolhida.titulo}` : overview.periodo?.label}
                    campanhasNoAr={escolhida ? (escolhida.estado === 'no_ar' ? 1 : null) : k.campanhas_no_ar}
                    altura={220}
                    onFase={setFaseFunil}
                  /> : null}
                  {faseFunil && (
                    <ResFunilModal
                      fase={faseFunil}
                      qs={qsBase}
                      campanha={escolhida ? escolhida.id : ''}
                      totais={{
                        oportunidades: resKpiNum(kVisiveis?.oportunidades?.real),
                        wins: resKpiNum(kVisiveis?.fechos?.real),
                        winsEur: resKpiNum(kVisiveis?.fechos?.valor_eur),
                      }}
                      onClose={() => { setFaseFunil(null); resFunilLimparVolta(); }}
                    />
                  )}
                  {AnaliseResumo ? <AnaliseResumo marca={isAll ? 'todas' : brand} filtros={filtrosAnalise} setSub={setSub} onInfo={setAnaliseInfo} /> : null}
                  {ObjCards ? <ObjCards
                    kpis={kVisiveis}
                    campanhas={escolhida ? [escolhida] : campanhasLista}
                    brand={brandCards}
                    isAll={!escolhida && fonteMarca === 'todas'}
                    isMobile={bp.isMobile}
                  /> : null}
                  <ResInvestimentoRetorno
                    kpis={kVisiveis}
                    fatura={escolhida
                      ? { periodo: escolhida.fatura_wins || { consideradas: 0, com_fatura: 0, euros: 0 }, conjunto: overview?.fatura_wins?.conjunto }
                      : overview?.fatura_wins}
                    year={year}
                    isMobile={bp.isMobile}
                  />
                </>
              )}

              <div style={{ display: 'flex', alignItems: 'baseline', gap: 8, marginBottom: 8 }}>
                <div className="font-display" style={{ fontSize: 14, fontWeight: 650 }}>Campanhas</div>
                {campanhasLista.length > 0 && <span style={{ fontSize: 12, color: 'var(--text-muted)' }}>{campanhasLista.length} campanha{campanhasLista.length !== 1 ? 's' : ''}</span>}
              </div>
              {(overview?.campanhas || []).length === 0 && (
                <div className="card" style={{ padding: 16, marginBottom: 16, fontSize: 13, color: 'var(--text-muted)', display: 'flex', gap: 12, alignItems: 'center', flexWrap: 'wrap' }}>
                  <span>{estado === 'no_ar' ? 'Não há campanhas no ar neste momento.' : 'Sem campanhas neste filtro.'}</span>
                  {estado !== 'todas' && <button type="button" className="btn btn-sm" onClick={() => setEstado('todas')}>Ver as campanhas do período</button>}
                </div>
              )}
              <div style={{ display: 'grid', gridTemplateColumns: bp.isMobile ? '1fr' : 'repeat(auto-fill, minmax(340px, 1fr))', gap: 10, marginBottom: 18 }}>
                {(overview?.campanhas || []).map(c => (
                  <ResCampanhaCard key={c.id} c={c} onOpen={() => setCampanhaId(c.id)} />
                ))}
              </div>

              <div style={{ display: 'grid', gridTemplateColumns: bp.isMobile ? '1fr' : '1fr 1fr', gap: 12, marginBottom: 18 }}>
                <div>
                  <div className="font-display" style={{ fontSize: 14, fontWeight: 650, marginBottom: 8 }}>Por marca</div>
                  {LinhasCmp ? <LinhasCmp
                    linhas={(overview?.por_marca || []).map(m => ({ id: m.brand_slug, nome: m.marca_nome, spend: m.spend_eur, leads: m.leads, mql: m.qualificados, ops: m.oportunidades, fechos: m.fechos, fechos_eur: m.fechos_eur }))}
                    onPick={l => setBrand && setBrand(l.id)}
                  /> : null}
                </div>
                <div>
                  <div className="font-display" style={{ fontSize: 14, fontWeight: 650, marginBottom: 8 }}>Por canal</div>
                  {LinhasCmp ? <LinhasCmp
                    linhas={(overview?.por_canal || []).map(c => ({ id: c.canal || c.id, nome: c.label, spend: c.spend_eur, leads: c.leads, mql: c.qualificados, ops: c.oportunidades, fechos: c.fechos, fechos_eur: c.fechos_eur }))}
                    onPick={l => { const s = { meta_ads: 'G1006', website: 'G1005', email: 'G1007' }[l.id]; if (s) mudarSource(s); }}
                  /> : null}
                </div>
              </div>

              <details style={{ marginBottom: 12 }}>
                <summary style={{ cursor: 'pointer', fontSize: 13, fontWeight: 650 }}>Leads sem campanha · website, WhatsApp e orgânico</summary>
                <div className="card" style={{ padding: 12, marginTop: 8, fontSize: 12 }}>
                  <div>{resFmtN(overview?.sem_campanha?.leads)} leads · {resFmtN(overview?.sem_campanha?.oportunidades)} com oportunidade · {resFmtN(overview?.sem_campanha?.fechos)} WIN's ({resFmtEur(overview?.sem_campanha?.fechos_eur)})</div>
                  {(overview?.sem_campanha?.por_canal || []).map(c => <div key={c.canal} style={{ marginTop: 4, color: 'var(--text-muted)' }}>{RES_CANAL_LABEL[c.canal] || c.canal}: {c.leads} leads · {c.oportunidades} oportunidades</div>)}
                  {(overview?.sem_campanha?.gestor_mkt || []).map(g => <div key={g.source} style={{ marginTop: 4 }}>{g.source}: {g.ops} OPs · {g.wins} wins · {resFmtEur(g.wins_eur)}</div>)}
                </div>
              </details>

              <details open={mais} onToggle={e => setMais(e.target.open)}>
                <summary style={{ cursor: 'pointer', fontSize: 13, fontWeight: 650 }}>Mais · Business Plan e relatórios</summary>
                <ResMais aberto={mais} brand={brand} isAll={isAll} rvo={rvo} setRvo={setRvo} rels={rels} setRels={setRels} />
              </details>
            </>
          )}
      </div>
    </div>
  );
};

function ResCampanha({ detalhe, onBack, setSub, onReload }) {
  const c = detalhe.campanha;
  const externa = c.tipo === 'meta_externa';
  const crm = c.tipo === 'crm';
  const fora = externa || crm;
  const [mes, setMes] = React.useState(new Date().toISOString().slice(0, 7));
  const [canalSpend, setCanalSpend] = React.useState('linkedin_ads');
  const [valor, setValor] = React.useState('');
  const [notas, setNotas] = React.useState('');
  const gravarSpend = () => {
    campApiCall('/api/marketing/resultados/v2/spend-manual', {
      method: 'POST',
      body: JSON.stringify({ campanha_id: c.id, canal: canalSpend, mes, spend_eur: Number(valor), notas }),
    }).then(() => { setValor(''); onReload(); }).catch(() => {});
  };
  const abrir = () => {
    try { localStorage.setItem('mkt-open-campanha-id', String(c.id)); } catch {}
    if (setSub) setSub('campanhas');
    else window.location.hash = 'screen=marketing&sub=campanhas';
  };
  const abrirLead = (l) => {
    const pessoa = l.ref || l.pessoa_chave;
    window.location.hash = 'screen=marketing&sub=leads-outbound&pessoa=' + encodeURIComponent(pessoa);
  };
  return (
    <div>
      {!window.ResTrilho && <button type="button" className="btn btn-sm btn-ghost" onClick={onBack}>← Voltar</button>}
      <div style={{ display: 'flex', justifyContent: 'space-between', gap: 12, alignItems: 'flex-start', margin: '10px 0 14px', flexWrap: 'wrap' }}>
        <div>
          <h2 className="font-display" style={{ margin: 0, fontSize: 22 }}>{c.titulo}</h2>
          <div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 4 }}>
            {c.marca_nome} · {crm ? `Gestor · ${c.crm_fonte}` : externa ? 'Meta, fora do portal' : c.sector} · {crm ? 'sem campanha Meta' : resEstado(c.estado)} · {c.janela_inicio || '—'} → {c.janela_fim || (c.estado === 'no_ar' ? 'activa' : '—')}
          </div>
          <div style={{ fontSize: 11, color: 'var(--text-dim)', marginTop: 4 }}>
            {crm
              ? `Oportunidades ${c.marca_nome} com source ${c.crm_fonte} no Gestor, fora de uma campanha Meta e do portal.`
              : externa
              ? `Campanha criada directamente na Meta${c.objective ? ` · objectivo ${c.objective}` : ''} · sem Investment Case, por isso sem objectivos`
              : <>Objectivos {c.objectivos_origem || 'por calcular'}{c.congelado_em ? ` · ${new Date(c.congelado_em).toLocaleDateString('pt-PT')}` : ''}{c.congelado_por ? ` · ${c.congelado_por}` : ''}</>}
            {!fora && !detalhe.fontes?.primavera && (c.funil === 'roll' || c.brand_slug === 'biond' || c.brand_slug === 'decal') ? ' · 1ª Encomenda: fatura Primavera ou WIN no Gestor' : ''}
            {!fora && !detalhe.fontes?.primavera && (c.funil === 'sensek' || c.brand_slug === 'sensek') ? ' · 1ª Encomenda: Primavera não ligado' : ''}
          </div>
        </div>
        {!fora && <button type="button" className="btn btn-sm" onClick={abrir}>Abrir campanha</button>}
      </div>

      <div className="card" style={{ padding: 14, marginBottom: 12 }}>
        {(detalhe.etapas || []).map((e, i, arr) => {
          const prev = i > 0 ? arr[i - 1].real : null;
          const taxaRaw = prev ? e.real / prev * 100 : null;
          const taxa = taxaRaw == null ? null : (taxaRaw > 0 && taxaRaw < 1 ? taxaRaw.toFixed(1).replace('.', ',') : Math.round(taxaRaw));
          return (
            <div key={e.key} style={{ display: 'grid', gridTemplateColumns: '140px 1fr 90px', gap: 8, alignItems: 'center', marginBottom: 8 }}>
              <div style={{ fontSize: 12 }}>{e.label}{taxa != null ? <span style={{ color: 'var(--text-dim)' }}> · {taxa}%</span> : null}</div>
              <div>
                <div style={{ fontSize: 12 }}>{resFmtN(e.real)}{e.objectivo != null ? ` / ${resFmtN(e.objectivo)}` : (fora ? '' : ' · sem objectivo')}{e.esperado != null ? ` · esperado ${resFmtN(e.esperado)}` : ''}</div>
                {!fora && window.resBar && window.resBar(e.pct, e.light)}
              </div>
              <div style={{ fontSize: 11, color: e.light ? (window.RES_LIGHT || {})[e.light] : 'var(--text-dim)' }}>{e.light || (e.objectivo == null ? '' : 'cedo')}</div>
            </div>
          );
        })}
      </div>

      <div className="card" style={{ padding: 0, overflow: 'auto', marginBottom: 12 }}>
        <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12 }}>
          <thead><tr>{['Canal', 'Spend', 'Leads', 'Obj.', 'MQL', 'OPs', "WIN's", 'CPL'].map(h => <th key={h} style={{ textAlign: 'left', padding: '8px 10px', fontSize: 10, color: 'var(--text-dim)', borderBottom: '1px solid var(--border)' }}>{h}</th>)}</tr></thead>
          <tbody>
            {(detalhe.por_canal || []).map(r => (
              <tr key={r.canal}>
                <td style={{ padding: '7px 10px', borderBottom: '1px solid var(--border)' }}>{r.label}</td>
                <td style={{ padding: '7px 10px', borderBottom: '1px solid var(--border)' }}>{resFmtEur(r.spend_eur)}</td>
                <td style={{ padding: '7px 10px', borderBottom: '1px solid var(--border)' }}>{resFmtN(r.leads)}</td>
                <td style={{ padding: '7px 10px', borderBottom: '1px solid var(--border)' }}>{r.leads_obj != null ? resFmtN(r.leads_obj) : '—'}</td>
                <td style={{ padding: '7px 10px', borderBottom: '1px solid var(--border)' }}>{resFmtN(r.qualificados)}</td>
                <td style={{ padding: '7px 10px', borderBottom: '1px solid var(--border)' }}>{resFmtN(r.oportunidades)}</td>
                <td style={{ padding: '7px 10px', borderBottom: '1px solid var(--border)' }}>{resFmtN(r.fechos)}</td>
                <td style={{ padding: '7px 10px', borderBottom: '1px solid var(--border)' }}>{r.cpl_eur != null ? resFmtEur(r.cpl_eur, true) : '—'}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      <ResSerie serie={detalhe.serie_semanal} />

      {(detalhe.meta?.campanhas || []).length > 0 && (
        <div className="card" style={{ padding: 12, marginBottom: 12, fontSize: 12 }}>
          <div style={{ fontWeight: 650, marginBottom: 6 }}>Meta Ads</div>
          {detalhe.meta.campanhas.map(m => (
            <div key={m.meta_campaign_id} style={{ marginTop: 4 }}>{m.campaign_name || m.meta_campaign_id} · {resMetaEstado(m.effective_status)} · reach {resFmtN(m.reach_lifetime)} · spend {resFmtEur(m.spend_eur)} · {resFmtN(m.impressoes)} imp · {resFmtN(m.cliques)} cliques · {resFmtN(m.leads_plataforma)} leads na plataforma</div>
          ))}
          <div style={{ marginTop: 6, color: 'var(--text-muted)' }}>{resFmtN((detalhe.por_canal || []).find(r => r.canal === 'meta_ads')?.leads)} leads Meta no portal</div>
        </div>
      )}

      {!fora && <div className="card" style={{ padding: 12, marginBottom: 12 }}>
        <div style={{ fontWeight: 650, fontSize: 13, marginBottom: 8 }}>Investimento manual · LinkedIn, Google, Feiras</div>
        <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
          <select value={canalSpend} onChange={e => setCanalSpend(e.target.value)} style={{ padding: '6px 8px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg-elev)', color: 'var(--text)' }}>
            <option value="linkedin_ads">LinkedIn Ads</option>
            <option value="google_ads">Google Ads</option>
            <option value="feiras">Feiras</option>
          </select>
          <input type="month" value={mes} onChange={e => setMes(e.target.value)} style={{ padding: '6px 8px', borderRadius: 6, border: '1px solid var(--border)' }} />
          <input type="number" min="0" placeholder="€" value={valor} onChange={e => setValor(e.target.value)} style={{ width: 100, padding: '6px 8px', borderRadius: 6, border: '1px solid var(--border)' }} />
          <input placeholder="Notas" value={notas} onChange={e => setNotas(e.target.value)} style={{ padding: '6px 8px', borderRadius: 6, border: '1px solid var(--border)' }} />
          <button type="button" className="btn btn-sm" onClick={gravarSpend}>Gravar</button>
        </div>
        {(detalhe.spend_manual || []).map((s, i) => (
          <div key={i} style={{ fontSize: 12, marginTop: 6, color: 'var(--text-muted)', display: 'flex', gap: 8 }}>
            <span>{RES_CANAL_LABEL[s.canal] || s.canal} · {String(s.mes).slice(0, 7)} · {resFmtEur(s.spend_eur)} {s.notas || ''}</span>
            <button type="button" className="btn btn-sm btn-ghost" onClick={() => campApiCall('/api/marketing/resultados/v2/spend-manual', { method: 'DELETE', body: JSON.stringify({ campanha_id: c.id, canal: s.canal, mes: String(s.mes).slice(0, 7) }) }).then(onReload).catch(() => {})}>Remover</button>
          </div>
        ))}
      </div>}

      {(detalhe.crm_ops || []).length > 0 && (
        <div className="card" style={{ padding: 12, marginBottom: 12, fontSize: 12 }}>
          <div style={{ fontWeight: 650, marginBottom: 4 }}>Oportunidades no CRM</div>
          <div style={{ color: 'var(--text-muted)', marginBottom: 8 }}>{crm ? `Source ${c.crm_fonte} no Gestor, marca pela equipa ou pelo produto. ` : 'Ligadas pelo formulário à entidade, ou por source ADS na data da campanha e na equipa da marca. '}MQL é stage Interesse ou à frente. Oportunidade é Demo, decisão ou financeiro. Fecho é ganha. Um win Mimaki a 1 € usa o PVP do catálogo quando é superior a 100 €.</div>
          {detalhe.crm_ops.map(o => (
            <div key={o.fm_id} style={{ marginTop: 4 }}>{o.entidade || 'Sem entidade'} · {resEstado(o.stage) || '—'} · {o.status} · {o.start}{Number(o.valor) > 1 ? ` · ${resFmtEur(o.valor)}` : ''}</div>
          ))}
        </div>
      )}

      {!crm && <div className="font-display" style={{ fontSize: 14, fontWeight: 650, marginBottom: 8 }}>Leads</div>}
      {!crm && (
      <div className="card" style={{ padding: 0, overflow: 'auto' }}>
        <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12 }}>
          <tbody>
            {(detalhe.leads || []).map(l => (
              <tr key={l.pessoa_chave + l.canal} onClick={() => abrirLead(l)} style={{ cursor: 'pointer' }}>
                <td style={{ padding: '7px 10px', borderBottom: '1px solid var(--border)' }}>
                  <div>{l.nome || 'Sem nome'}</div>
                  <div style={{ color: 'var(--text-muted)', fontSize: 11 }}>{l.empresa || ''}</div>
                </td>
                <td style={{ padding: '7px 10px', borderBottom: '1px solid var(--border)' }}>{l.canal}</td>
                <td style={{ padding: '7px 10px', borderBottom: '1px solid var(--border)' }}>{l.lead_em ? String(l.lead_em).slice(0, 10) : ''}</td>
                <td style={{ padding: '7px 10px', borderBottom: '1px solid var(--border)', fontSize: 11 }}>
                  {l.qualificado ? 'MQL · ' : ''}
                  {(l.ops || []).length ? `${l.ops.length} OP · ` : ''}
                  {l.demo ? 'Demo · ' : ''}
                  {l.testing_roll_em ? 'Testing Roll · ' : ''}
                  {l.primeira_fatura_em ? '1ª Encomenda · ' : ''}
                  {Number(l.fecho_eur) > 1 ? resFmtEur(l.fecho_eur) + ' · ' : ''}
                  {l.confianca}
                </td>
              </tr>
            ))}
            {!(detalhe.leads || []).length && <tr><td style={{ padding: 14, color: 'var(--text-dim)' }}>Sem leads atribuídos. Recalcula depois de haver leads no módulo.</td></tr>}
          </tbody>
        </table>
      </div>
      )}
    </div>
  );
}

function ResMais({ aberto, brand, isAll, rvo, setRvo, rels, setRels }) {
  const [year, setYear] = React.useState(2026);
  React.useEffect(() => {
    if (!aberto) return;
    const bq = isAll ? '' : `&brand_id=${encodeURIComponent(brand)}`;
    campApiCall(`/api/marketing/resultados/real-vs-objectivo?year=${year}${bq}`).then(setRvo).catch(() => setRvo(null));
    campApiCall(`/api/marketing/resultados/relatorios?periodo=ytd${isAll ? '' : `&brand=${brand}`}`).then(d => setRels(d.rows || [])).catch(() => setRels([]));
  }, [aberto, year, brand, isAll]);
  if (!aberto) return null;
  return (
    <div style={{ marginTop: 10 }}>
      <div style={{ marginBottom: 8 }}>
        <select value={year} onChange={e => setYear(Number(e.target.value))} style={{ padding: '6px 8px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg-elev)', color: 'var(--text)' }}>
          {[2025, 2026, 2027].map(y => <option key={y} value={y}>{y}</option>)}
        </select>
        <span style={{ marginLeft: 8, fontSize: 12, color: 'var(--text-muted)' }}>Real vs objectivo do Business Plan (módulo anterior)</span>
      </div>
      {rvo?.ytd && <div style={{ fontSize: 13, marginBottom: 8 }}>YTD actual {resFmtEur(rvo.ytd.actual_revenue)} · target {resFmtEur(rvo.ytd.target_revenue)}</div>}
      <div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 8 }}>{(rvo?.linhas || []).length} linhas de BP</div>
      <div style={{ fontSize: 13, fontWeight: 650, margin: '8px 0' }}>Relatórios AI</div>
      {(rels || []).slice(0, 5).map(r => <div key={r.id} style={{ fontSize: 12, marginBottom: 6 }}>{r.resumo_executivo}</div>)}
      {!(rels || []).length && <div style={{ fontSize: 12, color: 'var(--text-dim)' }}>Sem relatórios.</div>}
    </div>
  );
}

window.MktPerformance360Screen = MktPerformance360Screen;
window.MktResultadosScreen = MktPerformance360Screen;
window.ResFiltro = ResFiltro;
