/* MARKETING > Budget — envelope de Paid Media. Header no padrão de Resultados / Activação. */

const BUDGET_TAB = (label, active, onClick) => (
  <button type="button" onClick={onClick} style={{
    background: 'none', border: 'none', outline: 'none',
    borderBottom: `2px solid ${active ? 'var(--ai-500, #3859D0)' : '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',
  }}>{label}</button>
);

function BudgetKpi({ label, value, sub, over, dica, align }) {
  const [on, setOn] = React.useState(false);
  return (
    <div
      onMouseEnter={() => setOn(true)}
      onMouseLeave={() => setOn(false)}
      style={{ position: 'relative', padding: 14, border: '1px solid var(--border)', borderRadius: 10, background: 'var(--bg-elev)', minWidth: 0, cursor: 'help' }}
    >
      <div style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', letterSpacing: '.04em', textTransform: 'uppercase' }}>{label}</div>
      <div className="font-display" style={{ fontSize: 22, fontWeight: 650, marginTop: 4, lineHeight: 1.15, color: over ? 'var(--danger, #b42318)' : 'var(--text)' }}>{value}</div>
      <div style={{ fontSize: 11, marginTop: 4, color: 'var(--text-muted)' }}>{sub}</div>
      {on && dica && (
        <div style={{
          position: 'absolute', top: 'calc(100% + 6px)', zIndex: 30, width: 240,
          ...(align === 'right' ? { right: 0 } : { left: 0 }),
          background: 'var(--text, #112954)', color: '#fff', borderRadius: 8, padding: '8px 10px',
          fontSize: 12, lineHeight: 1.4, fontWeight: 450, boxShadow: '0 8px 24px rgba(17,41,84,0.18)',
        }}>{dica}</div>
      )}
    </div>
  );
}

function BudgetFiltro({ label, value, options, onChange }) {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => {
    if (!open) return undefined;
    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 type="button" onClick={() => setOpen(v => !v)} style={{
        background: 'var(--bg-elev, #fff)', color: 'var(--text)',
        border: `1px solid ${open ? 'var(--ai-500, #3859D0)' : 'var(--border)'}`,
        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)', fontSize: 10, textTransform: 'uppercase', letterSpacing: '.06em', fontFamily: 'var(--font-mono)' }}>{label}</span>
        <span style={{ fontWeight: 500 }}>{current?.label || '—'}</span>
        <span style={{ color: 'var(--text-muted)', fontSize: 9 }}>▾</span>
      </button>
      {open && (
        <div style={{
          position: 'absolute', top: 'calc(100% + 4px)', left: 0, zIndex: 50,
          background: 'var(--bg-elev)', border: '1px solid var(--border)', borderRadius: 6,
          boxShadow: '0 8px 24px rgba(17,41,84,0.12)', minWidth: 160, maxHeight: 320, overflowY: 'auto', padding: 4,
        }}>
          {options.map(o => (
            <button key={String(o.value)} type="button" onClick={() => { onChange(o.value); setOpen(false); }} style={{
              display: 'block', width: '100%', textAlign: 'left', border: 'none',
              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)',
              padding: '7px 10px', borderRadius: 4, fontSize: 12, fontWeight: o.value === value ? 600 : 500,
              cursor: 'pointer', fontFamily: 'inherit',
            }}>{o.label}</button>
          ))}
        </div>
      )}
    </div>
  );
}

function BudgetIcone({ children, onClick, activo, label, disabled }) {
  return (
    <button type="button" aria-label={label} title={label} disabled={disabled} onMouseDown={e => e.stopPropagation()} onClick={onClick} style={{
      width: 30, height: 30, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
      border: `1px solid ${activo ? 'var(--ai-500)' : 'var(--border)'}`, borderRadius: 6,
      background: activo ? 'color-mix(in oklch, var(--ai-500) 10%, #fff)' : '#fff',
      color: activo ? 'var(--ai-500)' : 'var(--text)', cursor: disabled ? 'default' : 'pointer', padding: 0,
      opacity: disabled ? 0.55 : 1,
    }}>{children}</button>
  );
}

function BudgetMenu({ items, activo }) {
  const [open, setOpen] = React.useState(false);
  const [foco, setFoco] = React.useState(-1);
  const ref = React.useRef(null);
  React.useEffect(() => {
    if (!open) return undefined;
    const onClick = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    const onKey = (e) => { if (e.key === 'Escape') setOpen(false); };
    document.addEventListener('mousedown', onClick);
    document.addEventListener('keydown', onKey);
    return () => { document.removeEventListener('mousedown', onClick); document.removeEventListener('keydown', onKey); };
  }, [open]);
  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <BudgetIcone label="Vistas" activo={open} onClick={() => { setOpen(v => !v); setFoco(-1); }}>
        <svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"><path d="M2.5 4h11M2.5 8h11M2.5 12h11" /></svg>
      </BudgetIcone>
      {open && (
        <div role="menu" style={{
          position: 'absolute', top: 'calc(100% + 6px)', right: 0, zIndex: 70,
          background: '#fff', border: '1px solid var(--border)', borderRadius: 8,
          boxShadow: '0 12px 32px rgba(17,41,84,0.14)', minWidth: 260, padding: 4,
        }}>
          {items.map((it, i) => (
            <button key={it.id} type="button" role="menuitem" disabled={it.disabled}
              onClick={() => { if (it.disabled) return; setOpen(false); it.onClick(); }}
              onMouseEnter={() => setFoco(i)} onMouseLeave={() => setFoco(-1)}
              style={{
                display: 'flex', alignItems: 'center', gap: 8, width: '100%', textAlign: 'left',
                background: foco === i ? 'color-mix(in oklch, var(--ai-500) 10%, #fff)' : 'transparent',
                color: activo === it.id || foco === i ? 'var(--ai-500)' : 'var(--text)',
                border: 'none', padding: '8px 10px', borderRadius: 6, cursor: it.disabled ? 'default' : 'pointer', fontFamily: 'inherit',
                opacity: it.disabled ? 0.55 : 1,
              }}>
              <span style={{ flex: 1, minWidth: 0 }}>
                <span style={{ display: 'block', fontSize: 13, fontWeight: 650 }}>{it.label}</span>
                {it.sub && <span style={{ display: 'block', fontSize: 11, color: 'var(--text-muted)', fontWeight: 400, marginTop: 1 }}>{it.sub}</span>}
              </span>
              {activo === it.id && <span style={{ color: 'var(--ai-500)' }}>✓</span>}
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

function BudgetCelula({ value, readOnly, onSave }) {
  const [txt, setTxt] = React.useState(value == null ? '' : String(value));
  React.useEffect(() => { setTxt(value == null ? '' : String(value)); }, [value]);
  if (readOnly) {
    return <span style={{ fontFamily: 'var(--font-mono)', fontSize: 12 }}>{window.budgetFmtEur(value, value % 1 ? 2 : 0)}</span>;
  }
  return (
    <input
      value={txt}
      inputMode="decimal"
      aria-label="Valor em euros"
      onChange={e => setTxt(e.target.value)}
      onBlur={() => {
        const n = Number(String(txt).replace(',', '.'));
        if (!Number.isFinite(n) || n < 0 || n === Number(value)) return;
        onSave(n);
      }}
      style={{
        width: 72, textAlign: 'right', fontFamily: 'var(--font-mono)', fontSize: 12,
        border: '1px solid var(--border)', borderRadius: 4, padding: '3px 6px',
        background: 'var(--bg)', color: 'var(--text)',
      }}
    />
  );
}

const BUDGET_CORES_CANAL = {
  meta_ads: 'var(--ai-500, #3859D0)',
  linkedin_ads: '#0A66C2',
  google_ads: '#E37400',
  muppi_led: '#0F766E',
};

function budgetMesCorrente(ano) {
  const hoje = new Date();
  const y = hoje.getFullYear();
  if (ano < y) return 12;
  if (ano > y) return 1;
  return hoje.getMonth() + 1;
}

const BUDGET_FONTES = [
  { value: 'todas', label: 'Todas' },
  { value: 'api', label: 'Meta (API)' },
  { value: 'manual', label: 'Manual' },
];

const BUDGET_SITUACOES = [
  { value: 'todas', label: 'Todas' },
  { value: 'com', label: 'Com orçamento' },
  { value: 'sem', label: 'Sem orçamento' },
  { value: 'acima', label: 'Acima do ritmo' },
  { value: 'abaixo', label: 'Abaixo do ritmo' },
];

const BUDGET_MERCADOS = { pt: 'Portugal', es: 'Espanha', global: 'Global' };

// Mesmas datas que o filtro de Resultados (resPeriodoDatas / resPeriodoPresets).
const BUDGET_ANOS_PERIODO = [2022, 2023, 2024, 2025, 2026];

function budgetDia(d) {
  return new Date(d.getFullYear(), d.getMonth(), d.getDate());
}

function budgetDias(a, b) {
  return Math.round((budgetDia(b) - budgetDia(a)) / 86400000) + 1;
}

function budgetPeriodoDatas(id, year) {
  const hoje = new Date();
  const dia = budgetDia(hoje);
  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;
}

function budgetHomologo() {
  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 budgetPeriodoPresets() {
  if (typeof resPeriodoPresets === 'function') return resPeriodoPresets();
  const y = new Date().getFullYear();
  const trimestres = ['q4', 'q3', 'q2', 'q1']
    .filter(q => budgetPeriodoDatas(q, y).start <= new Date())
    .map(q => ({ id: `${q}:${y}`, label: `${q.toUpperCase()} ${y}` }));
  const anteriores = BUDGET_ANOS_PERIODO.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 budgetPeriodoResolver(id) {
  if (typeof resPeriodoResolver === 'function') return resPeriodoResolver(id);
  if (id === 'homologo') return budgetHomologo();
  const [p, a] = String(id).split(':');
  return a ? budgetPeriodoDatas(p, Number(a)) : null;
}

function budgetIntervaloInicial() {
  const y = new Date().getFullYear();
  const id = `ytd:${y}`;
  const r = budgetPeriodoResolver(id) || budgetPeriodoDatas('ytd', y);
  return { preset: id, start: r.start, end: r.end };
}

function budgetAnosEntre(start, end) {
  const out = [];
  for (let y = start.getFullYear(); y <= end.getFullYear(); y += 1) out.push(y);
  return out;
}

function budgetIntervaloCivil(v) {
  const s = budgetDia(v.start);
  const e = budgetDia(v.end);
  return s.getFullYear() === e.getFullYear()
    && s.getMonth() === 0 && s.getDate() === 1
    && e.getMonth() === 11 && e.getDate() === 31;
}

function budgetGraficoAnoCheio(v) {
  const s = budgetDia(v.start);
  const e = budgetDia(v.end);
  const hoje = budgetDia(new Date());
  if (s.getFullYear() !== e.getFullYear() || s.getMonth() !== 0 || s.getDate() !== 1) return false;
  if (e.getTime() === new Date(s.getFullYear(), 11, 31).getTime()) return true;
  return e.getTime() === hoje.getTime() && s.getFullYear() === hoje.getFullYear();
}

function budgetMesDentro(ano, mes, start, end) {
  const ini = new Date(ano, mes - 1, 1);
  const fim = new Date(ano, mes, 0);
  return budgetDia(end) >= ini && budgetDia(start) <= fim;
}

function budgetRecorteMes(ano, mes, orcamento, executado, start, end, hoje) {
  const ini = new Date(ano, mes - 1, 1);
  const fim = new Date(ano, mes, 0);
  const a = budgetDia(start) > ini ? budgetDia(start) : ini;
  const b0 = budgetDia(end) < fim ? budgetDia(end) : fim;
  if (b0 < a) return { orcamento: 0, executado: 0, ritmo: 0, executadoAte: 0, dentro: false };
  const diasMes = fim.getDate();
  const o = Number(orcamento) || 0;
  const e = Number(executado) || 0;
  const orc = o * (budgetDias(a, b0) / diasMes);
  const hojeD = budgetDia(hoje);
  let exe = 0;
  if (ini <= hojeD) {
    const fimGasto = fim < hojeD ? fim : hojeD;
    const bE = b0 < fimGasto ? b0 : fimGasto;
    const diasBalde = fim < hojeD ? diasMes : hojeD.getDate();
    if (bE >= a && diasBalde > 0) exe = e * (budgetDias(a, bE) / diasBalde);
  }
  let ritmo = 0;
  if (hojeD >= a) {
    const ate = b0 < hojeD ? b0 : hojeD;
    if (ate >= a) ritmo = o * (budgetDias(a, ate) / diasMes);
  }
  return { orcamento: orc, executado: exe, ritmo, executadoAte: exe, dentro: true };
}

function budgetRound(v) {
  return Math.round((Number(v) || 0) * 100) / 100;
}

function budgetAplicarIntervalo(linha, ano, intervalo) {
  const hoje = new Date();
  let orcamento = 0;
  let executado = 0;
  let executadoAte = 0;
  let ritmo = 0;
  const meses = (linha.meses || []).map(row => {
    const r = budgetRecorteMes(ano, row.mes, row.orcamento, row.executado, intervalo.start, intervalo.end, hoje);
    orcamento += r.orcamento;
    executado += r.executado;
    executadoAte += r.executadoAte;
    ritmo += r.ritmo;
    return { ...row, ano, orcamento: budgetRound(r.orcamento), executado: budgetRound(r.executado), dentro: r.dentro };
  });
  const desvio = budgetRound(executadoAte - ritmo);
  return {
    ...linha,
    ano,
    meses,
    mesesOrigem: (linha.meses || []).map(m => ({ ...m, ano })),
    orcamento_ano: budgetRound(orcamento),
    executado: budgetRound(executado),
    executado_ate: budgetRound(executadoAte),
    ritmo: budgetRound(ritmo),
    desvio,
    desvio_pct: ritmo > 0 ? budgetRound((desvio / ritmo) * 100) : null,
    restante: budgetRound(orcamento - executado),
  };
}

function budgetJuntar(partes) {
  const map = new Map();
  for (const linha of partes) {
    const k = `${linha.brand_slug}|${linha.canal}|${linha.item || ''}`;
    const prev = map.get(k);
    if (!prev) {
      map.set(k, { ...linha, meses: [...linha.meses], mesesOrigem: [...(linha.mesesOrigem || [])] });
      continue;
    }
    prev.orcamento_ano = budgetRound(prev.orcamento_ano + linha.orcamento_ano);
    prev.executado = budgetRound(prev.executado + linha.executado);
    prev.executado_ate = budgetRound(prev.executado_ate + linha.executado_ate);
    prev.ritmo = budgetRound(prev.ritmo + linha.ritmo);
    prev.desvio = budgetRound(prev.executado_ate - prev.ritmo);
    prev.desvio_pct = prev.ritmo > 0 ? budgetRound((prev.desvio / prev.ritmo) * 100) : null;
    prev.restante = budgetRound(prev.orcamento_ano - prev.executado);
    if ((linha.ano || 0) >= (prev.ano || 0)) prev.alocado = linha.alocado;
    prev.meses = prev.meses.concat(linha.meses);
    prev.mesesOrigem = prev.mesesOrigem.concat(linha.mesesOrigem || []);
  }
  return [...map.values()];
}

function budgetSituacao(linha, original, situacao) {
  if (situacao === 'com') return original.orcamento_ano > 0;
  if (situacao === 'sem') return !(original.orcamento_ano > 0) && original.executado > 0;
  if (situacao === 'acima') return linha.desvio > 0;
  if (situacao === 'abaixo') return linha.desvio < 0;
  return true;
}

function budgetSomar(linhas) {
  const orcamento_ano = budgetRound(linhas.reduce((s, l) => s + Number(l.orcamento_ano || 0), 0));
  const orcamento_ate = budgetRound(linhas.reduce((s, l) => s + Number(l.ritmo || 0), 0));
  const executado = budgetRound(linhas.reduce((s, l) => s + Number(l.executado_ate || 0), 0));
  const executadoTotal = budgetRound(linhas.reduce((s, l) => s + Number(l.executado || 0), 0));
  const desvio = budgetRound(executado - orcamento_ate);
  return {
    orcamento_ano,
    orcamento_ate,
    executado,
    desvio,
    desvio_pct: orcamento_ate > 0 ? budgetRound((desvio / orcamento_ate) * 100) : null,
    restante: budgetRound(orcamento_ano - executadoTotal),
  };
}

function budgetCorMes(pct) {
  if (pct <= 100) return 'var(--ai-500, #3859D0)';
  if (pct <= 110) return 'var(--warning, #f59e0b)';
  return 'var(--danger, #dc2626)';
}

function BudgetGraficos({ linhas, linhasAno, ano, cheio, civil, onMarca }) {
  const Mes = window.AnMesBars;
  const Div = window.AnDivergente;
  const Emp = window.AnEmpilhada;
  const Bullet = window.AnBullet;
  const Heat = window.AnHeatmap;
  const Cartao = window.AnCartao;
  const fmt = window.anFmtEur || window.budgetFmtEur;
  if (!Mes || !Cartao || !linhas.length) return null;

  const mes = cheio ? budgetMesCorrente(ano) : 12;
  const hojeY = new Date().getFullYear();
  const serie = linhasAno?.length ? linhasAno : linhas;
  const bp = Array.from({ length: 12 }, (_, i) => serie.reduce((s, l) => s + (Number(l.meses?.[i]?.orcamento) || 0), 0));
  const real = Array.from({ length: 12 }, (_, i) => serie.reduce((s, l) => s + (Number(l.meses?.[i]?.executado) || 0), 0));
  const desvios = linhas
    .filter(l => Number(l.desvio) !== 0)
    .slice()
    .sort((a, b) => Math.abs(Number(b.desvio)) - Math.abs(Number(a.desvio)))
    .map(l => ({
      label: `${l.brand_nome} · ${l.canal_nome}`,
      sub: l.orcamento_ano > 0 ? null : 'sem orçamento',
      valor: l.desvio,
      pct: l.desvio_pct,
      onClick: () => onMarca && onMarca(l.brand_slug),
    }));
  const porCanal = [];
  const accCanal = new Map();
  for (const l of linhas) {
    if (!(Number(l.executado) > 0)) continue;
    accCanal.set(l.canal, (accCanal.get(l.canal) || 0) + Number(l.executado));
  }
  for (const [slug, valor] of accCanal) {
    const nome = linhas.find(l => l.canal === slug)?.canal_nome || slug;
    porCanal.push({ label: nome, valor, cor: BUDGET_CORES_CANAL[slug] || 'var(--text-dim)' });
  }
  const comOrc = linhas.filter(l => l.orcamento_ano > 0).reduce((s, l) => s + Number(l.executado || 0), 0);
  const semOrc = linhas.filter(l => !(l.orcamento_ano > 0)).reduce((s, l) => s + Number(l.executado || 0), 0);
  const mapa = serie.map(l => ({
    equipa: `${l.brand_slug}|${l.canal}`,
    label: `${l.brand_nome} · ${l.canal_nome}`,
    meses: (l.meses || []).map(m => {
      const futuro = ano > hojeY || (ano === hojeY && m.mes > mes);
      if (futuro) return { pct: null, bp: m.orcamento, real: null, nota: 'Mês futuro' };
      if (!(m.orcamento > 0) && !(m.executado > 0)) return { pct: null, bp: 0, real: 0, nota: 'Sem movimento' };
      if (!(m.orcamento > 0)) {
        return { pct: null, bp: 0, real: m.executado, nota: 'Sem orçamento neste mês', cor: 'var(--warning, #f59e0b)', texto: '!' };
      }
      const pct = m.executado / m.orcamento * 100;
      return { pct, bp: m.orcamento, real: m.executado, cor: budgetCorMes(pct) };
    }),
  }));
  const grelha = { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))', gap: 14, marginBottom: 14 };

  return (
    <>
      <div style={grelha}>
        <Cartao titulo="Orçamento contra executado, por mês" sub={cheio ? 'Barras do mês. Linhas, o acumulado. Pontilhado, se o ritmo actual chegar a Dezembro.' : 'Meses dentro do período. Um mês cortado entra pela parte dos dias.'}>
          <Mes bp={bp} real={real} mes={mes} fmt={fmt} nomes={{ a: 'Orçamento', b: 'Executado' }} />
        </Cartao>
        <Cartao titulo="Desvio face ao ritmo" sub="Esquerda, abaixo do ritmo. Direita, acima.">
          {Div ? <Div modo="custo" itens={desvios} fmt={fmt} /> : null}
        </Cartao>
      </div>
      <div style={grelha}>
        <Cartao titulo="De onde veio o executado" sub="Canal, e quanto desse gasto tem linha de orçamento.">
          <div style={{ fontSize: 11, color: 'var(--text-muted)', marginBottom: 4 }}>Por canal</div>
          {Emp ? <Emp partes={porCanal} fmt={fmt} altura={14} /> : null}
          <div style={{ fontSize: 11, color: 'var(--text-muted)', margin: '14px 0 4px' }}>Com linha e sem linha</div>
          {Emp ? <Emp partes={[{ label: 'Com orçamento', valor: comOrc, cor: 'var(--ai-500, #3859D0)' }, { label: 'Sem orçamento', valor: semOrc, cor: 'var(--warning, #f59e0b)' }]} fmt={fmt} altura={14} /> : null}
        </Cartao>
        <Cartao titulo="Campanhas dentro do envelope" sub={civil ? 'Preenchimento, executado. Traço preto, aprovado e ainda activo. Traço cinzento, orçamento do ano.' : 'No período escolhido. O aprovado das campanhas é do ano e não entra neste recorte.'}>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            {linhas.map(l => (Bullet ? (
              <Bullet
                key={`${l.brand_slug}|${l.canal}`}
                real={l.executado}
                bpData={civil ? l.alocado : 0}
                bpAno={l.orcamento_ano}
                label={`${l.brand_nome} · ${l.canal_nome}`}
                fmt={fmt}
                escala="custo"
                nomes={{ real: 'Executado', marca: 'Aprovado activo', fim: 'Orçamento do ano', nota: '% do aprovado' }}
                sub={l.orcamento_ano > 0 ? null : 'sem orçamento'}
                onClick={() => onMarca && onMarca(l.brand_slug)}
              />
            ) : null))}
          </div>
        </Cartao>
      </div>
      {linhas.length > 1 && Heat && (
        <div style={{ marginBottom: 18 }}>
          <Cartao titulo="Mês a mês" sub="Percentagem do orçamento desse mês. Azul até 100 %, âmbar até 110 %, vermelho acima. ! é gasto sem orçamento.">
            <Heat
              linhas={mapa}
              larguraRotulo={168}
              nomes={{ plano: 'Orçamento', real: 'Executado', pct: 'do mês', vazio: 'sem movimento' }}
              onPick={id => onMarca && onMarca(String(id).split('|')[0])}
            />
          </Cartao>
        </div>
      )}
    </>
  );
}

function budgetGastoNoIntervalo(blocos, intervalo) {
  let total = 0;
  for (const bloco of blocos || []) {
    const linha = {
      meses: Array.from({ length: 12 }, (_, i) => ({
        mes: i + 1,
        orcamento: 0,
        executado: Number(bloco.meses?.find(m => m.mes === i + 1)?.executado) || 0,
      })),
    };
    total += budgetAplicarIntervalo(linha, bloco.ano, intervalo).executado;
  }
  return budgetRound(total);
}

const BUDGET_ESTADO_CAMP = {
  em_producao: 'Em produção',
  em_activacao: 'Em activação',
  arquivada: 'Arquivada',
  concluida: 'Concluída',
  cancelada: 'Cancelada',
};

function BudgetCampanhas({ aprovadas, soltas, intervalo }) {
  const fmt = window.budgetFmtEur;
  const th = { textAlign: 'left', padding: '8px 12px', fontSize: 10, fontFamily: 'var(--font-mono)', letterSpacing: '.06em', textTransform: 'uppercase', color: 'var(--text-dim)', borderBottom: '1px solid var(--border)', whiteSpace: 'nowrap' };
  const td = { padding: '10px 12px', borderBottom: '1px solid var(--border)', fontSize: 13, verticalAlign: 'middle' };
  const aprovadoActivo = budgetRound(aprovadas.filter(l => !l.fechada).reduce((s, l) => s + l.aprovado, 0));
  const gastoAprovado = budgetRound(aprovadas.reduce((s, l) => s + l.gasto, 0));
  const gastoSolto = budgetRound(soltas.reduce((s, l) => s + l.gasto, 0));
  const cards = [
    { label: 'Aprovado activo', value: fmt(aprovadoActivo), sub: 'sem as arquivadas' },
    { label: 'Gasto nas aprovadas', value: fmt(gastoAprovado), sub: 'ligado à campanha do portal' },
    { label: 'Meta sem campanha', value: fmt(gastoSolto), sub: `${soltas.length} campanhas no período` },
    { label: 'Por ligar', value: fmt(gastoSolto), sub: 'não entra no aprovado' },
  ];
  return (
    <>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, minmax(0, 1fr))', gap: 10, marginBottom: 18 }}>
        {cards.slice(0, 3).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 }}>{c.value}</div>
            <div style={{ fontSize: 11, marginTop: 4, color: 'var(--text-muted)' }}>{c.sub}</div>
          </div>
        ))}
        <div 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' }}>Restante do aprovado</div>
          <div className="font-display" style={{ fontSize: 22, fontWeight: 650, marginTop: 4 }}>{fmt(budgetRound(aprovadoActivo - gastoAprovado))}</div>
          <div style={{ fontSize: 11, marginTop: 4, color: 'var(--text-muted)' }}>activo − gasto no período</div>
        </div>
      </div>
      <div className="font-display" style={{ fontSize: 14, fontWeight: 650, marginBottom: 8 }}>Aprovadas no portal</div>
      <div style={{ border: '1px solid var(--border)', borderRadius: 10, background: 'var(--bg-elev)', overflow: 'auto', marginBottom: 18 }}>
        <table style={{ width: '100%', borderCollapse: 'collapse' }}>
          <thead>
            <tr>{['Campanha', 'Marca', 'Canal', 'Estado', 'Aprovado', 'Gasto', 'Restante'].map(h => <th key={h} style={th}>{h}</th>)}</tr>
          </thead>
          <tbody>
            {aprovadas.map(l => (
              <tr key={`${l.campanha_id}|${l.canal}`} style={{ opacity: l.fechada ? 0.62 : 1 }}>
                <td style={td}>{l.titulo}</td>
                <td style={td}>{l.brand_nome}</td>
                <td style={td}>{l.canal_nome}</td>
                <td style={td}>{BUDGET_ESTADO_CAMP[l.status] || l.status}</td>
                <td style={{ ...td, fontFamily: 'var(--font-mono)' }}>{fmt(l.aprovado)}</td>
                <td style={{ ...td, fontFamily: 'var(--font-mono)' }}>{fmt(l.gasto)}</td>
                <td style={{ ...td, fontFamily: 'var(--font-mono)' }}>{fmt(budgetRound(l.aprovado - l.gasto))}</td>
              </tr>
            ))}
            {!aprovadas.length && <tr><td colSpan={7} style={{ ...td, color: 'var(--text-muted)', textAlign: 'center', padding: 28 }}>Sem campanhas aprovadas neste filtro.</td></tr>}
          </tbody>
        </table>
      </div>
      <div className="font-display" style={{ fontSize: 14, fontWeight: 650, marginBottom: 8 }}>Meta sem campanha do portal</div>
      <div style={{ border: '1px solid var(--border)', borderRadius: 10, background: 'var(--bg-elev)', overflow: 'auto' }}>
        <table style={{ width: '100%', borderCollapse: 'collapse' }}>
          <thead>
            <tr>{['Campanha Meta', 'Marca', 'Gasto no período'].map(h => <th key={h} style={th}>{h}</th>)}</tr>
          </thead>
          <tbody>
            {soltas.map(l => (
              <tr key={`${l.chave}|${l.brand_slug}`}>
                <td style={td}>{l.nome}</td>
                <td style={td}>{l.brand_nome}</td>
                <td style={{ ...td, fontFamily: 'var(--font-mono)' }}>{fmt(l.gasto)}</td>
              </tr>
            ))}
            {!soltas.length && <tr><td colSpan={3} style={{ ...td, color: 'var(--text-muted)', textAlign: 'center', padding: 28 }}>Nada por ligar neste período.</td></tr>}
          </tbody>
        </table>
      </div>
      <div style={{ fontSize: 12, color: 'var(--text-muted)', lineHeight: 1.45, marginTop: 12 }}>
        O aprovado é o orçamento da campanha com aprovação. O gasto lê resultados_spend no período escolhido. Uma campanha Meta só entra na primeira tabela quando está ligada a uma campanha do portal.
      </div>
    </>
  );
}

function BudgetPlataformas({ linhas, conferencia, catalogo, cartoes, intervalo, ano, contaInicial, onSave, onAdd }) {
  const fmt = window.budgetFmtEur;
  const [aberta, setAberta] = React.useState(null);
  const [nova, setNova] = React.useState(null);
  const th = { textAlign: 'left', padding: '8px 12px', fontSize: 10, fontFamily: 'var(--font-mono)', letterSpacing: '.06em', textTransform: 'uppercase', color: 'var(--text-dim)', borderBottom: '1px solid var(--border)', whiteSpace: 'nowrap' };
  const td = { padding: '10px 12px', borderBottom: '1px solid var(--border)', fontSize: 13, verticalAlign: 'middle' };
  const orc = budgetRound(linhas.reduce((s, l) => s + Number(l.orcamento_ano || 0), 0));
  const exe = budgetRound(linhas.reduce((s, l) => s + Number(l.executado || 0), 0));
  const desvio = budgetRound(exe - orc);
  const cards = [
    { label: 'Orçamento', value: fmt(orc), sub: 'destas plataformas' },
    { label: 'Executado', value: fmt(exe), sub: 'lançado à mão, por cartão' },
    { label: 'Face ao orçamento', value: fmt(desvio), sub: orc > 0 ? 'executado − orçamento' : 'ainda sem envelope', over: desvio > 0 },
  ];
  const campo = { border: '1px solid var(--border)', borderRadius: 6, padding: '6px 8px', fontSize: 12, fontFamily: 'inherit', background: 'var(--bg)', color: 'var(--text)' };
  return (
    <>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, minmax(0, 1fr))', gap: 10, marginBottom: 18 }}>
        {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, color: c.over ? 'var(--danger, #b42318)' : 'var(--text)' }}>{c.value}</div>
            <div style={{ fontSize: 11, marginTop: 4, color: 'var(--text-muted)' }}>{c.sub}</div>
          </div>
        ))}
      </div>
      <div style={{ border: '1px solid var(--border)', borderRadius: 10, background: 'var(--bg-elev)', overflow: 'auto' }}>
        <table style={{ width: '100%', borderCollapse: 'collapse' }}>
          <thead>
            <tr>{['Plataforma', 'Categoria', 'Conta', 'Cartão', 'Orçamento', 'Executado', 'Restante'].map(h => <th key={h} style={th}>{h}</th>)}</tr>
          </thead>
          <tbody>
            {linhas.map(l => {
              const id = `${l.categoria}|${l.canal}|${l.conta}|${l.cartao}`;
              const open = aberta === id;
              return (
                <React.Fragment key={id}>
                  <tr onClick={() => setAberta(open ? null : id)} style={{ cursor: 'pointer' }}>
                    <td style={td}>{l.canal_nome}</td>
                    <td style={td}>{l.categoria_nome}</td>
                    <td style={td}>{l.conta_nome}</td>
                    <td style={td}>{l.cartao_rotulo}</td>
                    <td style={{ ...td, fontFamily: 'var(--font-mono)' }}>{fmt(l.orcamento_ano)}</td>
                    <td style={{ ...td, fontFamily: 'var(--font-mono)' }}>{fmt(l.executado)}</td>
                    <td style={{ ...td, fontFamily: 'var(--font-mono)' }}>{fmt(l.restante)}</td>
                  </tr>
                  {open && (
                    <tr>
                      <td colSpan={7} style={{ padding: '8px 12px 16px', background: 'var(--bg)' }}>
                        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(6, minmax(0, 1fr))', gap: 8 }}>
                          {(l.mesesOrigem || l.meses).map(m => (
                            <div key={`${m.ano || ano}-${m.mes}`} style={{ border: '1px solid var(--border)', borderRadius: 8, padding: 8, background: 'var(--bg-elev)', opacity: budgetMesDentro(m.ano || ano, m.mes, intervalo.start, intervalo.end) ? 1 : 0.4 }}>
                              <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-muted)', marginBottom: 6 }}>{window.BUDGET_MESES[m.mes - 1]}</div>
                              <div style={{ fontSize: 10, color: 'var(--text-dim)', marginBottom: 2 }}>Orçamento</div>
                              <BudgetCelula value={m.orcamento} onSave={v => onSave(l, m.mes, v, 'orcamento', m.ano || ano)} />
                              <div style={{ fontSize: 10, color: 'var(--text-dim)', margin: '6px 0 2px' }}>Executado</div>
                              <BudgetCelula value={m.executado} onSave={v => onSave(l, m.mes, v, 'executado', m.ano || ano)} />
                            </div>
                          ))}
                        </div>
                      </td>
                    </tr>
                  )}
                </React.Fragment>
              );
            })}
            {!linhas.length && <tr><td colSpan={7} style={{ ...td, color: 'var(--text-muted)', textAlign: 'center', padding: 28 }}>Sem plataformas neste filtro.</td></tr>}
          </tbody>
        </table>
      </div>
      <div style={{ marginTop: 14 }}>
        {!nova ? (
          <button type="button" onClick={() => setNova({ categoria: catalogo[0]?.categoria_slug || '', canal: catalogo[0]?.slug || '', conta: contaInicial || 'dgd', cartao: contaInicial === 'netscreen' ? '' : '3340', mes: new Date().getMonth() + 1, executado: '', orcamento: '' })} style={{ border: '1px solid var(--border)', background: '#fff', borderRadius: 6, padding: '6px 10px', fontSize: 12, cursor: 'pointer', fontFamily: 'inherit' }}>Adicionar lançamento</button>
        ) : (
          <form onSubmit={(e) => { e.preventDefault(); onAdd(nova); setNova(null); }} style={{ display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'end' }}>
            <label style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>PLATAFORMA
              <select value={nova.canal} onChange={e => {
                const plat = catalogo.find(p => p.slug === e.target.value);
                const ai = plat?.categoria_slug === 'ai';
                setNova({ ...nova, canal: e.target.value, categoria: plat?.categoria_slug || '', cartao: ai ? '8231' : nova.cartao });
              }} style={{ ...campo, display: 'block', marginTop: 4 }}>
                {catalogo.map(p => <option key={p.slug} value={p.slug}>{p.nome}</option>)}
              </select>
            </label>
            <label style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>CONTA
              <select value={nova.conta} onChange={e => {
                const conta = e.target.value;
                const cards = cartoes.filter(c => c.conta === conta);
                setNova({ ...nova, conta, cartao: cards[0]?.cartao || '' });
              }} style={{ ...campo, display: 'block', marginTop: 4 }}>
                {[...new Map(cartoes.map(c => [c.conta, c])).values()].map(c => <option key={c.conta} value={c.conta}>{c.conta_nome}</option>)}
              </select>
            </label>
            <label style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>CARTÃO
              <select value={nova.cartao} onChange={e => setNova({ ...nova, cartao: e.target.value })} style={{ ...campo, display: 'block', marginTop: 4 }}>
                {cartoes.filter(c => c.conta === nova.conta).map(c => <option key={c.cartao || 'sem'} value={c.cartao}>{c.rotulo}</option>)}
              </select>
            </label>
            <label style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>MÊS
              <select value={nova.mes} onChange={e => setNova({ ...nova, mes: Number(e.target.value) })} style={{ ...campo, display: 'block', marginTop: 4 }}>
                {(window.BUDGET_MESES || []).map((nome, i) => <option key={nome} value={i + 1}>{nome}</option>)}
              </select>
            </label>
            <label style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>EXECUTADO
              <input value={nova.executado} onChange={e => setNova({ ...nova, executado: e.target.value })} inputMode="decimal" style={{ ...campo, display: 'block', marginTop: 4, width: 90 }} />
            </label>
            <label style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>ORÇAMENTO
              <input value={nova.orcamento} onChange={e => setNova({ ...nova, orcamento: e.target.value })} inputMode="decimal" style={{ ...campo, display: 'block', marginTop: 4, width: 90 }} />
            </label>
            <button type="submit" style={{ height: 32, padding: '0 12px', borderRadius: 6, border: '1px solid var(--ai-500)', background: 'var(--ai-500)', color: '#fff', fontSize: 12, fontWeight: 600, cursor: 'pointer' }}>Guardar</button>
            <button type="button" onClick={() => setNova(null)} style={{ height: 32, padding: '0 12px', borderRadius: 6, border: '1px solid var(--border)', background: '#fff', fontSize: 12, cursor: 'pointer' }}>Cancelar</button>
          </form>
        )}
      </div>
      {!!conferencia.length && (
        <div style={{ marginTop: 18, padding: 14, border: '1px solid var(--border)', borderRadius: 10, background: 'var(--bg-elev)' }}>
          <div className="font-display" style={{ fontSize: 14, fontWeight: 650, marginBottom: 6 }}>Conferência de anúncios</div>
          {conferencia.map(c => (
            <div key={`${c.ano}-${c.mes}-${c.cartao}`} style={{ fontSize: 13, padding: '6px 0', borderTop: '1px solid var(--border)' }}>
              {window.BUDGET_MESES[c.mes - 1]} {c.ano} · cartão {c.cartao || 'NetScreen'} · Facebk {fmt(c.amount_eur)} no extrato. A API de anúncios, em todas as marcas, regista {fmt(c.api_eur)} nesse mês.
            </div>
          ))}
          <div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 8, lineHeight: 1.45 }}>Este débito não entra no executado. O ciclo do cartão não é o mês de veiculação, e a NetScreen pode estar noutra conta.</div>
        </div>
      )}
      <div style={{ fontSize: 12, color: 'var(--text-muted)', lineHeight: 1.45, marginTop: 12 }}>
        A categoria segue a plataforma, não o cartão. AI no 3340 continua em AI. OVH e Amazon Prime ficam de fora.
      </div>
    </>
  );
}

function MktBudgetScreen() {
  const brandApi = window.useMktBrand ? window.useMktBrand() : { brand: 'todas', isAll: true, setBrand: () => {}, userMarcas: null };
  const { brand, isAll, setBrand, userMarcas } = brandApi;
  const [vista, setVista] = React.useState('paid');
  const [intervalo, setIntervalo] = React.useState(budgetIntervaloInicial);
  const [canal, setCanal] = React.useState('todos');
  const [fonte, setFonte] = React.useState('todas');
  const [situacao, setSituacao] = React.useState('todas');
  const [estadoCamp, setEstadoCamp] = React.useState('todas');
  const [catPlat, setCatPlat] = React.useState('todas');
  const [mercado, setMercado] = React.useState('todos');
  const [pacote, setPacote] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [syncing, setSyncing] = React.useState(false);
  window.useMktZoneLoading(loading && !pacote);
  const [err, setErr] = React.useState('');
  const [aberta, setAberta] = React.useState(null);

  const marcasNav = [{ id: 'todas', nome: 'Todas' }, ...((window.MKT_BRANDS || []).filter(b => b.id !== 'todas' && (!userMarcas?.length || userMarcas.includes(b.id))))];

  const anosKey = budgetAnosEntre(intervalo.start, intervalo.end).join(',');
  const ano = intervalo.end.getFullYear();

  const load = React.useCallback(async () => {
    setLoading(true);
    setErr('');
    try {
      const anos = anosKey.split(',').map(Number).filter(Boolean);
      const partes = await Promise.all(anos.map(async (y) => {
        const q = `?ano=${y}&marca=${encodeURIComponent(brand || 'todas')}`;
        const [g, c, camp, plat] = await Promise.all([
          window.budgetFetch(q),
          window.budgetFetch(`/contas${q}`),
          window.budgetFetch(`/campanhas${q}`),
          window.budgetFetch(`/plataformas${q}`),
        ]);
        return { ano: y, g, c, camp, plat };
      }));
      setPacote(partes);
    } catch (e) {
      setErr(e.message);
    } finally {
      setLoading(false);
    }
  }, [anosKey, brand]);

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

  const sync = async () => {
    setSyncing(true);
    setErr('');
    try {
      await window.budgetFetch('/sync', {
        method: 'POST',
        body: JSON.stringify({ ano, marca: brand || 'todas' }),
      });
      await load();
    } catch (e) {
      setErr(e.message);
    } finally {
      setSyncing(false);
    }
  };

  const gravarOrcamento = async (linha, mes, amount, anoMes) => {
    try {
      await window.budgetFetch('/orcamento', {
        method: 'PUT',
        body: JSON.stringify({
          ano: anoMes || ano, mes, brand_slug: linha.brand_slug, canal_slug: linha.canal,
          amount_eur: amount, item: linha.item || '',
        }),
      });
      await load();
    } catch (e) { setErr(e.message); }
  };

  const gravarPlataforma = async (linha, mes, amount, campo, anoMes) => {
    try {
      await window.budgetFetch('/plataformas', {
        method: 'PUT',
        body: JSON.stringify({
          ano: anoMes || ano, mes, campo,
          categoria_slug: linha.categoria, canal_slug: linha.canal,
          conta: linha.conta, cartao: linha.cartao, amount_eur: amount,
        }),
      });
      await load();
    } catch (e) { setErr(e.message); }
  };

  const adicionarPlataforma = async (nova) => {
    const exec = Number(String(nova.executado).replace(',', '.'));
    const orcN = Number(String(nova.orcamento).replace(',', '.'));
    if (!(exec > 0) && !(orcN > 0)) { setErr('Indica o executado ou o orçamento.'); return; }
    try {
      if (exec > 0) await gravarPlataforma({ categoria: nova.categoria, canal: nova.canal, conta: nova.conta, cartao: nova.cartao }, nova.mes, exec, 'executado', ano);
      if (orcN > 0) await gravarPlataforma({ categoria: nova.categoria, canal: nova.canal, conta: nova.conta, cartao: nova.cartao }, nova.mes, orcN, 'orcamento', ano);
    } catch (e) { setErr(e.message); }
  };

  const gravarExecutado = async (linha, mes, amount, anoMes) => {
    try {
      await window.budgetFetch('/executado', {
        method: 'PUT',
        body: JSON.stringify({
          ano: anoMes || ano, mes, brand_slug: linha.brand_slug, canal_slug: linha.canal, amount_eur: amount,
        }),
      });
      await load();
    } catch (e) { setErr(e.message); }
  };

  const passa = (l) => (canal === 'todos' || l.canal === canal) && (fonte === 'todas' || l.fonte === fonte);
  const porChave = new Map();
  for (const p of pacote || []) {
    for (const l of (p.g?.linhas || [])) {
      if (!passa(l)) continue;
      const k = `${l.brand_slug}|${l.canal}|${l.item || ''}`;
      const slot = porChave.get(k) || { originais: [], recortes: [] };
      slot.originais.push(l);
      slot.recortes.push(budgetAplicarIntervalo(l, p.ano, intervalo));
      porChave.set(k, slot);
    }
  }
  const situadas = [...porChave.values()].map(slot => ({
    linha: budgetJuntar(slot.recortes)[0],
    original: {
      orcamento_ano: slot.originais.reduce((s, l) => s + Number(l.orcamento_ano || 0), 0),
      executado: slot.originais.reduce((s, l) => s + Number(l.executado || 0), 0),
    },
  })).filter(({ linha, original }) => budgetSituacao(linha, original, situacao));
  const sem = situacao === 'todas'
    ? situadas.filter(({ original, linha }) => !(original.orcamento_ano > 0) && original.executado > 0 && linha.executado > 0).map(({ linha }) => linha)
    : [];
  const semKeys = new Set(sem.map(l => `${l.brand_slug}|${l.canal}`));
  const visivel = (l) => l.orcamento_ano > 0 || l.executado > 0 || l.alocado > 0 || l.ritmo > 0;
  const linhas = situadas
    .map(({ linha }) => linha)
    .filter(visivel)
    .filter(l => situacao !== 'todas' || !isAll || !semKeys.has(`${l.brand_slug}|${l.canal}`));
  const linhasGraf = situadas.map(({ linha }) => linha).filter(visivel);
  const chavesGraf = new Set(linhasGraf.map(l => `${l.brand_slug}|${l.canal}`));
  const pacoteAno = (pacote || []).find(p => p.ano === ano);
  const data = pacoteAno?.g || null;
  const contas = pacoteAno?.c || null;
  const baseAno = (data?.linhas || []).filter(passa);
  const linhasAno = baseAno.filter(l => chavesGraf.has(`${l.brand_slug}|${l.canal}`) && (l.orcamento_ano > 0 || l.executado > 0 || l.alocado > 0));
  const serieJanela = [...porChave.values()].flatMap(slot => slot.recortes)
    .filter(l => l.ano === ano && chavesGraf.has(`${l.brand_slug}|${l.canal}`));
  const k = linhasGraf.length ? budgetSomar(linhasGraf) : (data?.kpis || {});
  const canaisOpts = [{ value: 'todos', label: 'Todos' }, ...((data?.canais || []).map(c => ({ value: c.slug, label: c.nome })))];
  const mercadosOpts = [{ value: 'todos', label: 'Todos' }, ...[...new Set((contas?.contas || []).map(c => c.mercado).filter(Boolean))].map(id => ({ value: id, label: BUDGET_MERCADOS[id] || id }))];
  const quando = data?.atualizado_em ? new Date(data.atualizado_em).toLocaleString('pt-PT') : 'ainda sem sync';
  const vistasMenu = [
    { id: 'paid', label: 'Paid Media', sub: 'Orçamento e executado por marca e canal', onClick: () => setVista('paid') },
    { id: 'campanhas', label: 'Campanhas', sub: 'Aprovado no portal e gasto Meta por ligar', onClick: () => setVista('campanhas') },
    { id: 'contas', label: 'Contas', sub: 'Contas Meta e spend do ano', onClick: () => setVista('contas') },
    { id: 'plataformas', label: 'Plataformas', sub: 'Email, catálogos, AI e o resto fora dos anúncios', onClick: () => setVista('plataformas') },
    { id: 'sync', label: syncing ? 'A actualizar…' : 'Actualizar Meta', sub: `Última actualização ${quando}`, disabled: syncing, onClick: sync },
  ];
  const th = { textAlign: 'left', padding: '8px 12px', fontSize: 10, fontFamily: 'var(--font-mono)', letterSpacing: '.06em', textTransform: 'uppercase', color: 'var(--text-dim)', borderBottom: '1px solid var(--border)', whiteSpace: 'nowrap' };
  const td = { padding: '10px 12px', borderBottom: '1px solid var(--border)', fontSize: 13, verticalAlign: 'middle' };
  const fmt = window.budgetFmtEur;

  const civil = budgetIntervaloCivil(intervalo);
  const cheio = budgetGraficoAnoCheio(intervalo);
  const periodoLabel = (budgetPeriodoPresets().find(p => p.id === intervalo.preset) || {}).label || 'Personalizado';
  const definido = budgetRound(situadas.reduce((s, x) => s + Number(x.original.orcamento_ano || 0), 0));
  const anosDef = [...new Set((pacote || []).map(p => p.ano))];
  const definidoLabel = anosDef.length > 1 ? `${anosDef[0]}–${anosDef[anosDef.length - 1]}` : String(ano);
  const kpis = [
    {
      label: 'Orçamento definido',
      value: fmt(definido),
      sub: civil ? definidoLabel : `${definidoLabel} · no período ${fmt(k.orcamento_ano)}`,
      dica: `Soma das linhas de Paid Media definidas para ${definidoLabel}. O filtro de datas não mexe neste total.${civil ? '' : ` No período escolhido entram ${fmt(k.orcamento_ano)}.`}`,
    },
    civil ? {
      label: 'Até ao mês',
      value: fmt(k.orcamento_ate),
      sub: 'meses fechados + parte deste mês',
      dica: 'Meses já fechados, mais a parte deste mês até hoje. É o ritmo a que o orçamento do ano devia estar a ser gasto.',
    } : {
      label: 'Ritmo',
      value: fmt(k.orcamento_ate),
      sub: 'esperado até hoje no período',
      dica: 'Quanto do período devia estar gasto até hoje. Um mês fechado entra inteiro. O mês corrente entra pelos dias já passados.',
    },
    {
      label: 'Executado',
      value: fmt(k.executado),
      sub: civil ? 'Meta via contas · resto manual' : periodoLabel,
      dica: 'Gasto real nas datas do filtro. O Meta vem das contas de anúncios. O resto entra quando é lançado à mão. Marcas sem linha de orçamento também contam.',
    },
    {
      label: 'Desvio',
      value: fmt(k.desvio),
      sub: k.desvio_pct == null ? 'sem ritmo' : `${k.desvio_pct > 0 ? '+' : ''}${k.desvio_pct}% face ao ritmo`,
      over: k.desvio > 0,
      dica: 'Executado menos o ritmo. Abaixo de zero, gastou-se menos do que estava previsto até hoje. A percentagem é face a esse ritmo.',
    },
    civil ? {
      label: 'Restante no ano',
      value: fmt(k.restante),
      sub: 'orçamento − executado',
      dica: 'Orçamento definido menos o executado do ano. É o que ainda cabe até Dezembro.',
    } : {
      label: 'Restante',
      value: fmt(k.restante),
      sub: 'orçamento do período − executado',
      dica: 'Orçamento das datas escolhidas menos o executado dessas datas. Não é o que falta até ao fim do ano.',
    },
  ];
  const Periodo = window.BriefingDateRangePicker;
  const aprovadasMap = new Map();
  const soltasMap = new Map();
  for (const p of pacote || []) {
    for (const l of p.camp?.aprovadas || []) {
      if (!isAll && l.brand_slug !== brand) continue;
      const k = `${l.campanha_id}|${l.canal}`;
      const prev = aprovadasMap.get(k) || { ...l, blocos: [] };
      prev.blocos.push({ ano: p.ano, meses: l.meses });
      aprovadasMap.set(k, prev);
    }
    for (const l of p.camp?.meta_sem_portal || []) {
      if (!isAll && l.brand_slug !== brand) continue;
      const k = `${l.chave}|${l.brand_slug}`;
      const prev = soltasMap.get(k) || { ...l, blocos: [] };
      prev.blocos.push({ ano: p.ano, meses: l.meses });
      soltasMap.set(k, prev);
    }
  }
  const aprovadas = [...aprovadasMap.values()]
    .map(l => ({ ...l, gasto: budgetGastoNoIntervalo(l.blocos, intervalo) }))
    .filter(l => estadoCamp === 'todas' || (estadoCamp === 'fechadas' ? l.fechada : !l.fechada));
  const soltas = [...soltasMap.values()]
    .map(l => ({ ...l, gasto: budgetGastoNoIntervalo(l.blocos, intervalo) }))
    .filter(l => l.gasto > 0);
  const ambitoConta = !isAll && brand !== 'netscreen';
  const platMap = new Map();
  for (const p of pacote || []) {
    for (const l of p.plat?.linhas || []) {
      if (ambitoConta) continue;
      if (!isAll && l.conta !== 'netscreen') continue;
      if (catPlat !== 'todas' && l.categoria !== catPlat) continue;
      const k = `${l.categoria}|${l.canal}|${l.conta}|${l.cartao}`;
      const slot = platMap.get(k) || [];
      slot.push(budgetAplicarIntervalo({ ...l, brand_slug: `${l.conta}|${l.cartao}`, item: '' }, p.ano, intervalo));
      platMap.set(k, slot);
    }
  }
  const linhasPlat = [...platMap.values()].map(recs => budgetJuntar(recs)[0]).filter(l => l.orcamento_ano > 0 || l.executado > 0);
  const conferencia = [];
  if (isAll) {
    for (const p of pacote || []) {
      for (const c of p.plat?.conferencia || []) {
        const rec = budgetRecorteMes(p.ano, c.mes, 0, c.amount_eur, intervalo.start, intervalo.end, new Date());
        const api = budgetRecorteMes(p.ano, c.mes, 0, c.api_eur, intervalo.start, intervalo.end, new Date());
        if (rec.executado > 0) conferencia.push({ ...c, ano: p.ano, amount_eur: rec.executado, api_eur: api.executado });
      }
    }
  }
  const platRef = (pacote || []).find(p => p.ano === ano)?.plat;
  const catsPlat = [{ value: 'todas', label: 'Todas' }, ...((platRef?.categorias || []).map(c => ({ value: c.slug, label: c.nome })))];
  const marcaNome = marcasNav.find(b => b.id === brand)?.nome || brand;

  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 · BUDGET
        </div>
        <h1 style={{ margin: 0, fontSize: 21, fontWeight: 700, color: 'var(--text)', fontFamily: 'var(--font-display)', letterSpacing: '-0.01em', lineHeight: 1.2 }}>
          Budget
        </h1>
        <div style={{ height: 1, background: 'var(--border)', margin: '16px -40px 0' }} />
        <div style={{ display: 'flex', gap: 0, overflowX: 'auto' }}>
          {marcasNav.map(b => BUDGET_TAB(b.nome, b.id === brand, () => setBrand && setBrand(b.id)))}
        </div>
      </div>

      <div className="scrollbar" style={{ overflowY: 'auto', flex: 1, minHeight: 0, padding: '28px 40px 64px', background: 'var(--bg)' }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8, flexWrap: 'wrap', marginBottom: 18 }}>
          <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>
            {Periodo ? (
              <Periodo value={intervalo} onChange={setIntervalo} presets={budgetPeriodoPresets()} resolver={budgetPeriodoResolver} align="left" />
            ) : null}
            {vista === 'paid' && <BudgetFiltro label="Canal" value={canal} onChange={setCanal} options={canaisOpts} />}
            {vista === 'paid' && <BudgetFiltro label="Fonte" value={fonte} onChange={setFonte} options={BUDGET_FONTES} />}
            {vista === 'paid' && <BudgetFiltro label="Situação" value={situacao} onChange={setSituacao} options={BUDGET_SITUACOES} />}
            {vista === 'campanhas' && <BudgetFiltro label="Estado" value={estadoCamp} onChange={setEstadoCamp} options={[{ value: 'todas', label: 'Todas' }, { value: 'activas', label: 'Activas' }, { value: 'fechadas', label: 'Arquivadas' }]} />}
            {vista === 'plataformas' && !ambitoConta && <BudgetFiltro label="Categoria" value={catPlat} onChange={setCatPlat} options={catsPlat} />}
            {vista === 'contas' && <BudgetFiltro label="Mercado" value={mercado} onChange={setMercado} options={mercadosOpts} />}
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginLeft: 'auto' }}>
            <span style={{ fontSize: 11, color: 'var(--text-muted)', whiteSpace: 'nowrap' }}>Meta actualizado {quando}</span>
            <BudgetIcone label={syncing ? 'A actualizar a Meta' : 'Actualizar Meta'} disabled={syncing} onClick={sync}>
              <svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M13.5 8a5.5 5.5 0 1 1-1.4-3.7" /><path d="M13.5 2.5V5H11" /></svg>
            </BudgetIcone>
            <BudgetMenu activo={vista} items={vistasMenu} />
          </div>
        </div>
        {err && <div style={{ color: 'var(--danger)', fontSize: 12, marginBottom: 12 }}>{err}</div>}
        {loading && !pacote && <div style={{ color: 'var(--text-muted)', fontSize: 12 }}>A carregar o envelope…</div>}

        {vista === 'paid' && pacote && (
          <>
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, minmax(0, 1fr))', gap: 10, marginBottom: 18 }}>
              {kpis.map((c, i) => <BudgetKpi key={c.label} {...c} align={i >= 3 ? 'right' : 'left'} />)}
            </div>

            <BudgetGraficos linhas={linhasGraf} linhasAno={cheio ? linhasAno : serieJanela} ano={ano} cheio={cheio} civil={civil} onMarca={setBrand} />

            <div style={{ border: '1px solid var(--border)', borderRadius: 10, background: 'var(--bg-elev)', overflow: 'auto' }}>
              <table style={{ width: '100%', borderCollapse: 'collapse' }}>
                <thead>
                  <tr>
                    {['Marca', 'Canal', 'Orçamento', 'Executado', 'Ritmo', 'Desvio', 'Restante', 'Alocado'].map(h => <th key={h} style={th}>{h}</th>)}
                  </tr>
                </thead>
                <tbody>
                  {linhas.map(l => {
                    const id = `${l.brand_slug}|${l.canal}`;
                    const open = aberta === id;
                    return (
                      <React.Fragment key={id}>
                        <tr onClick={() => setAberta(open ? null : id)} style={{ cursor: 'pointer' }}>
                          <td style={td}>{l.brand_nome}{l.item ? <div style={{ fontSize: 11, color: 'var(--text-muted)' }}>{l.item}</div> : null}</td>
                          <td style={td}>{l.canal_nome}</td>
                          <td style={{ ...td, fontFamily: 'var(--font-mono)' }}>{fmt(l.orcamento_ano)}</td>
                          <td style={{ ...td, fontFamily: 'var(--font-mono)' }}>{fmt(l.executado)}</td>
                          <td style={{ ...td, fontFamily: 'var(--font-mono)' }}>{fmt(l.ritmo)}</td>
                          <td style={{ ...td, fontFamily: 'var(--font-mono)', color: l.desvio > 0 ? 'var(--danger, #b42318)' : 'var(--text)' }}>{fmt(l.desvio)}</td>
                          <td style={{ ...td, fontFamily: 'var(--font-mono)' }}>{fmt(l.restante)}</td>
                          <td style={{ ...td, fontFamily: 'var(--font-mono)' }}>{fmt(l.alocado)}</td>
                        </tr>
                        {open && (
                          <tr>
                            <td colSpan={8} style={{ padding: '8px 12px 16px', background: 'var(--bg)' }}>
                              <div style={{ display: 'grid', gridTemplateColumns: 'repeat(6, minmax(0, 1fr))', gap: 8 }}>
                                {(l.mesesOrigem || l.meses).map(m => (
                                  <div key={`${m.ano || ano}-${m.mes}`} style={{ border: '1px solid var(--border)', borderRadius: 8, padding: 8, background: 'var(--bg-elev)', opacity: budgetMesDentro(m.ano || ano, m.mes, intervalo.start, intervalo.end) ? 1 : 0.4 }}>
                                    <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-muted)', marginBottom: 6 }}>{window.BUDGET_MESES[m.mes - 1]}{(l.mesesOrigem || []).some(x => x.ano && x.ano !== (m.ano || ano)) ? ` ${m.ano || ano}` : ''}</div>
                                    <div style={{ fontSize: 10, color: 'var(--text-dim)', marginBottom: 2 }}>Orçamento</div>
                                    <BudgetCelula value={m.orcamento} onSave={v => gravarOrcamento(l, m.mes, v, m.ano || ano)} />
                                    <div style={{ fontSize: 10, color: 'var(--text-dim)', margin: '6px 0 2px' }}>Executado</div>
                                    <BudgetCelula value={m.executado} readOnly={l.fonte !== 'manual'} onSave={v => gravarExecutado(l, m.mes, v, m.ano || ano)} />
                                  </div>
                                ))}
                              </div>
                            </td>
                          </tr>
                        )}
                      </React.Fragment>
                    );
                  })}
                  {!linhas.length && (
                    <tr><td colSpan={8} style={{ ...td, color: 'var(--text-muted)', textAlign: 'center', padding: 28 }}>Sem linhas de Paid Media para este filtro.</td></tr>
                  )}
                </tbody>
              </table>
            </div>

            {!!sem.filter(l => !linhas.some(x => x.brand_slug === l.brand_slug && x.canal === l.canal)).length && (
              <div style={{ marginTop: 18 }}>
                <div className="font-display" style={{ fontSize: 14, fontWeight: 650, marginBottom: 8 }}>Gasto sem linha de orçamento</div>
                <div style={{ border: '1px solid var(--border)', borderRadius: 10, background: 'var(--bg-elev)' }}>
                  {sem.filter(l => !linhas.some(x => x.brand_slug === l.brand_slug && x.canal === l.canal)).map(l => (
                    <div key={`${l.brand_slug}|${l.canal}|sem`} style={{ display: 'flex', justifyContent: 'space-between', padding: '10px 14px', borderBottom: '1px solid var(--border)', fontSize: 13 }}>
                      <span>{l.brand_nome} · {l.canal_nome}</span>
                      <span style={{ fontFamily: 'var(--font-mono)' }}>{fmt(l.executado)}</span>
                    </div>
                  ))}
                </div>
              </div>
            )}
          </>
        )}

        {vista === 'campanhas' && pacote && (
          <BudgetCampanhas aprovadas={aprovadas} soltas={soltas} intervalo={intervalo} />
        )}

        {vista === 'plataformas' && pacote && ambitoConta && (
          <div style={{ padding: 18, border: '1px solid var(--border)', borderRadius: 10, background: 'var(--bg-elev)', fontSize: 13, color: 'var(--text)', lineHeight: 1.5 }}>
            Estas plataformas são da conta Digidelta, ou da conta NetScreen. Não se repartem por {marcaNome}. Abre Todas, ou a tab NetScreen para a conta dela.
          </div>
        )}

        {vista === 'plataformas' && pacote && !ambitoConta && (
          <BudgetPlataformas
            linhas={linhasPlat}
            conferencia={conferencia}
            catalogo={platRef?.plataformas || []}
            cartoes={platRef?.cartoes || []}
            intervalo={intervalo}
            contaInicial={isAll ? 'dgd' : 'netscreen'}
            ano={ano}
            onSave={gravarPlataforma}
            onAdd={adicionarPlataforma}
          />
        )}

        {vista === 'contas' && (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
            <div style={{ border: '1px solid var(--border)', borderRadius: 10, background: 'var(--bg-elev)', overflow: 'auto' }}>
              <table style={{ width: '100%', borderCollapse: 'collapse' }}>
                <thead>
                  <tr>
                    {['Marca', 'Conta', 'Mercado', 'ID', 'Spend da marca'].map(h => <th key={h} style={th}>{h}</th>)}
                  </tr>
                </thead>
                <tbody>
                  {(contas?.contas || []).filter(c => (isAll || c.marca_slug === brand) && (mercado === 'todos' || c.mercado === mercado)).map(c => (
                    <tr key={`${c.account_id}|${c.mercado}`}>
                      <td style={td}>{c.marca_slug}</td>
                      <td style={td}>{c.account_name}</td>
                      <td style={td}>{c.mercado}</td>
                      <td style={{ ...td, fontFamily: 'var(--font-mono)', fontSize: 12 }}>{c.account_id}</td>
                      <td style={{ ...td, fontFamily: 'var(--font-mono)' }}>{fmt(c.spend_marca_eur)}</td>
                    </tr>
                  ))}
                  {!(contas?.contas || []).length && (
                    <tr><td colSpan={5} style={{ ...td, color: 'var(--text-muted)' }}>Sem contas Meta activas.</td></tr>
                  )}
                </tbody>
              </table>
            </div>
            <div style={{ fontSize: 12, color: 'var(--text-muted)', lineHeight: 1.45 }}>
              O spend é o da marca no ano, lido de resultados_spend. A conta de Espanha da Mimaki e a conta ibérica somam na mesma marca; AllDecor separa-se pelo nome da campanha. LinkedIn, Google e Muppi editam-se na vista Paid Media, no mês da linha.
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

window.MktBudgetScreen = MktBudgetScreen;
