/* Primitivas visuais de Resultados — funil, KPIs e barras.
   Partilhadas por Resultados e Início. Nomes globais Res* (já usados no módulo). */

const RES_LIGHT = { verde: 'var(--success)', amarelo: 'var(--warning)', vermelho: 'var(--danger)' };
const RES_FUNIL_CORES = ['#3859D0', '#F59E0B', '#E11D48', '#16A34A'];

function resFmtEur(v, cents) {
  if (v == null || !Number.isFinite(Number(v))) return '—';
  const n = Number(v);
  if (!cents && Math.abs(n) >= 1e6) return '€' + (n / 1e6).toFixed(1).replace('.', ',') + 'M';
  if (!cents && Math.abs(n) >= 1000) return '€' + Math.round(n).toLocaleString('pt-PT');
  return '€' + n.toLocaleString('pt-PT', { minimumFractionDigits: cents ? 2 : 0, maximumFractionDigits: cents ? 2 : 0 });
}
function resFmtN(v) {
  if (v == null || !Number.isFinite(Number(v))) return '—';
  return Math.round(Number(v)).toLocaleString('pt-PT');
}

const ResKpi = ({ label, value, sub, light }) => (
  <div style={{ padding: 14, border: '1px solid var(--border)', borderRadius: 10, background: 'var(--bg-elev)' }}>
    <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 }}>{value}</div>
    {sub && <div style={{ fontSize: 11, color: light ? RES_LIGHT[light] : 'var(--text-muted)', marginTop: 2 }}>{sub}</div>}
  </div>
);

function resSectorFunil(brand, isAll) {
  if (isAll || !brand || brand === 'todas' || brand === 'all') return 'todas';
  if (brand === 'mimaki' || brand === 'alldecor') return 'demo';
  if (brand === 'biond' || brand === 'decal') return 'roll';
  if (brand === 'sensek') return 'sensek';
  return 'comum';
}

function resFunilCampanha(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 resFunilTaxaFace(etapas, i, pctFace) {
  const real = Number(etapas[i]?.real) || 0;
  const base = pctFace === 'anterior' && i > 0 ? (Number(etapas[i - 1]?.real) || 0) : (Number(etapas[0]?.real) || 0);
  return base ? real / base * 100 : 0;
}

function resPctFunil(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 resFunilEtapas(k, sector) {
  const leads = Number(k.leads?.real) || 0;
  const etapas = [{ key: 'leads', label: 'Leads', real: leads }];
  const op = { key: 'oportunidades', label: 'Oportunidades', real: Number(k.oportunidades?.real) || 0 };
  const demos = { key: 'demos', label: 'Demos', real: Number(k.demos?.real) || 0 };
  const rolls = { key: 'testing_rolls', label: 'Testing Rolls', real: Number(k.testing_rolls?.real) || 0 };
  const wins = { key: 'fechos', label: "WIN's", real: Number(k.fechos?.real) || 0, valor: k.fechos?.valor_eur };
  const enc = { key: 'primeira_encomenda', label: '1ª Encomenda', real: Number(k.primeira_encomenda?.real) || 0, valor: k.primeira_encomenda?.valor_eur };
  const mql = { key: 'qualificados', label: 'MQL', real: Number(k.qualificados?.real) || 0 };
  if (sector === 'demo') etapas.push(op, demos, wins);
  else if (sector === 'roll') etapas.push(op, rolls, enc);
  else if (sector === 'sensek') etapas.push(mql, rolls, enc);
  else if (sector === 'todas') {
    etapas.push(op, {
      key: 'demo',
      label: 'Demo',
      real: (Number(k.demos?.real) || 0) + (Number(k.testing_rolls?.real) || 0),
    }, wins);
  }
  else etapas.push(mql, op, wins);
  return etapas;
}

function resBanda(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 resFunilPath(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 = resBanda([...bot].reverse()).replace(/^M\s*[-\d.eE]+\s+[-\d.eE]+\s*/, '');
  return `${resBanda(top)} L ${bot[bot.length - 1][0]} ${bot[bot.length - 1][1]} ${volta} Z`;
}

const RES_FUNIL_CSS = `
@keyframes res-funil-reveal { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0 0 0 0); } }
@keyframes res-funil-esvazia { from { clip-path: inset(0 0 0 0); } to { clip-path: inset(0 100% 0 0); } }
@keyframes res-funil-pop { from { opacity: 0; transform: scale(.4); } to { opacity: 1; transform: scale(1); } }
@keyframes res-funil-fade { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
.res-funil-svg { animation: res-funil-reveal 1.1s cubic-bezier(.65,0,.35,1) backwards; }
.res-funil-path { transition: d .7s cubic-bezier(.22,1,.36,1); }
.res-funil-seg { transition: opacity .2s ease; pointer-events: none; }
.res-funil-dim { transition: opacity .18s ease, color .18s ease; }
.res-funil-num { animation: res-funil-fade .5s cubic-bezier(.22,1,.36,1) backwards; animation-delay: calc(150ms + var(--i, 0) * 90ms); }
.res-funil-pill { animation: res-funil-pop .5s cubic-bezier(.34,1.56,.64,1) backwards; animation-delay: calc(380ms + var(--i, 0) * 110ms); transition: transform .2s cubic-bezier(.22,1,.36,1), background-color .2s ease, color .2s ease, box-shadow .2s ease; }
.res-funil-esvazia > .res-funil-palco { animation: res-funil-esvazia 1s cubic-bezier(.65,0,.35,1) forwards; }
.res-funil-esvazia > .res-funil-palco .res-funil-svg,
.res-funil-esvazia > .res-funil-palco .res-funil-num,
.res-funil-esvazia > .res-funil-palco .res-funil-pill { animation: none !important; }
.res-funil-esvazia > .res-funil-palco .res-funil-path { transition: none; }
.res-funil-link { height: 24px; padding: 0 9px; border-radius: 6px; border: 1px solid var(--border); background: var(--bg-elev); color: var(--text-muted); font: inherit; font-size: 11px; font-weight: 600; cursor: pointer; transition: background-color .15s ease, color .15s ease, border-color .15s ease; }
.res-funil-link:hover { background: var(--bg-hover, var(--bg-sunken)); color: var(--ai-500); border-color: color-mix(in oklch, var(--ai-500) 40%, var(--border)); }
.res-funil-link:focus-visible { outline: 2px solid var(--ai-500); outline-offset: 1px; }
@media (prefers-reduced-motion: reduce) {
  .res-funil-svg, .res-funil-num, .res-funil-pill, .res-funil-esvazia > .res-funil-palco { animation: none !important; }
  .res-funil-path { transition: none; }
}
`;
(function resInjectFunilCss() {
  if (typeof document === 'undefined') return;
  let s = document.getElementById('res-funil-css');
  if (!s) {
    s = document.createElement('style');
    s.id = 'res-funil-css';
    document.head.appendChild(s);
  }
  s.textContent = RES_FUNIL_CSS;
})();

// Conta do valor anterior até ao novo (0 na primeira montagem).
function useResContagem(alvo, dur = 900) {
  const fim = Number(alvo) || 0;
  const [v, setV] = React.useState(0);
  const atual = React.useRef(0);
  React.useEffect(() => {
    const reduz = typeof window !== 'undefined' && window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    if (reduz || typeof requestAnimationFrame === 'undefined') { atual.current = fim; setV(fim); return undefined; }
    const ini = atual.current;
    const t0 = performance.now();
    let raf = 0;
    const passo = (t) => {
      const p = Math.min(1, (t - t0) / dur);
      const x = ini + (fim - ini) * (1 - Math.pow(1 - p, 3));
      atual.current = x;
      setV(x);
      if (p < 1) raf = requestAnimationFrame(passo);
    };
    raf = requestAnimationFrame(passo);
    return () => cancelAnimationFrame(raf);
  }, [fim, dur]);
  return v;
}
const ResContagem = ({ valor, fmt }) => {
  const v = useResContagem(valor);
  return <>{(fmt || resFmtN)(v)}</>;
};
function resFmtTaxa(t) {
  const x = Number(t) || 0;
  return (x > 0 && x < 10 ? x.toFixed(1).replace('.', ',') : String(Math.round(x))) + '%';
}

const resTipVazio = () => ({ ref: null, tip: null, show() {}, hide() {}, activo: null });

// Forma do funil e colunas por etapa: entrada, hover por etapa e tooltip com as conversões.
const RES_FUNIL_ROTULOS = { total: 'Total', anterior: 'Da etapa anterior', primeira: 'Das leads', valor: 'Valor', clica: 'Clica para ver o detalhe' };
function ResFunilCorpo({ etapas, H, gid, cores, pathFn, pctFn, ariaLabel, onFase, extraCelula, tipExtra, topSvg = 28, linhaNum = 28, padCelula = '0 8px 10px', rotulos, pctFace = 'primeira' }) {
  const rt = { ...RES_FUNIL_ROTULOS, ...rotulos };
  const t = (window.useAnTip || resTipVazio)();
  const Tip = window.AnTipBox; const TT = window.AnTipTitulo; const TL = window.AnTipLinha; const TN = window.AnTipNota;
  const hi = t.activo;
  const n = Math.max(etapas.length, 1);
  const col = 800 / n;
  const primeiro = etapas[0]?.real || 0;
  const valores = etapas.map(e => e.real);
  const dExt = pathFn(valores, 800, H, 1.18);
  const dMeio = pathFn(valores, 800, H, 1.08);
  const dInt = pathFn(valores, 800, H, 1);
  const forma = (d) => ({ d: `path('${d}')` });
  const tipDe = (e, i) => (TT && TL ? (
    <>
      <TT>{e.label}</TT>
      <TL cor={cores[i % cores.length]} label={rt.total} valor={resFmtN(e.real)} />
      {tipExtra && tipExtra(e, i)}
      {i > 0 && <TL label={rt.anterior} valor={pctFn(e.real, etapas[i - 1].real)} />}
      {i > 1 && <TL label={rt.primeira} valor={pctFn(e.real, primeiro)} />}
      {e.valor ? <TL label={rt.valor} valor={rt.fmtValor ? rt.fmtValor(e.valor) : resFmtEur(e.valor)} /> : null}
      {onFase && TN && <TN>{rt.clica}</TN>}
    </>
  ) : null);
  return (
    <div ref={t.ref} style={{ position: 'relative', marginTop: 16, width: '100%' }}>
      <svg className="res-funil-svg" viewBox={`0 0 800 ${H}`} preserveAspectRatio="none" width="100%" height={H} role="img" aria-label={ariaLabel} style={{ display: 'block', position: 'absolute', left: 0, top: topSvg, width: '100%', height: H }}>
        <defs>
          <linearGradient id={gid} x1="0" y1="0" x2="1" y2="0" gradientUnits="objectBoundingBox">
            {cores.map((c, i) => (
              <stop key={i} offset={n <= 1 ? 0 : i / (n - 1)} stopColor={c} />
            ))}
          </linearGradient>
          <clipPath id={`${gid}-clip`}>
            <path className="res-funil-path" d={dExt} style={forma(dExt)} />
          </clipPath>
        </defs>
        <path className="res-funil-path" d={dExt} style={forma(dExt)} fill="rgba(17,41,84,.18)" />
        <path className="res-funil-path" d={dMeio} style={forma(dMeio)} fill="rgba(26,53,104,.22)" />
        <path className="res-funil-path" d={dInt} style={forma(dInt)} fill={`url(#${gid})`} />
        <g clipPath={`url(#${gid}-clip)`}>
          {etapas.map((e, i) => (
            <rect key={e.key} className="res-funil-seg" x={i * col} y={0} width={col} height={H}
              style={{ fill: 'var(--bg-elev, #fff)', opacity: hi == null || hi === i ? 0 : 0.5 }} />
          ))}
        </g>
      </svg>
      <div style={{ position: 'relative', display: 'grid', gridTemplateColumns: `repeat(${etapas.length}, 1fr)`, width: '100%' }}>
        {etapas.map((e, i) => {
          const cor = cores[i % cores.length];
          const on = hi === i;
          const fora = hi != null && !on;
          const celula = (
            <>
              <div className="font-display res-funil-num res-funil-dim" style={{ '--i': i, fontWeight: 650, fontSize: 20, lineHeight: `${linhaNum}px`, opacity: fora ? 0.5 : 1 }}>
                <ResContagem valor={e.real} />
              </div>
              {extraCelula && <div className="res-funil-dim" style={{ opacity: fora ? 0.5 : 1 }}>{extraCelula(e, i)}</div>}
              <div style={{ height: H, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                <div className="res-funil-pill" style={{ '--i': i, background: on ? cor : '#fff', color: on ? '#fff' : '#112954', borderRadius: 999, padding: '4px 12px', fontSize: 13, fontWeight: 700, fontVariantNumeric: 'tabular-nums', boxShadow: `0 0 0 1px ${on ? cor : 'rgba(17,41,84,.12)'}`, transform: on ? 'scale(1.1)' : 'none' }}>
                  <ResContagem valor={resFunilTaxaFace(etapas, i, pctFace)} fmt={resFmtTaxa} />
                </div>
              </div>
              <div className="res-funil-dim" style={{ fontSize: 13, color: on ? 'var(--text)' : 'var(--text-muted)', opacity: fora ? 0.5 : 1 }}>{e.label}</div>
            </>
          );
          const estilo = { textAlign: 'center', minWidth: 0, borderLeft: i ? '1px solid var(--border)' : 'none', padding: padCelula };
          const hover = { onMouseMove: ev => t.show(ev, tipDe(e, i), i), onMouseLeave: t.hide };
          if (!onFase) return <div key={e.key} style={estilo} {...hover}>{celula}</div>;
          return (
            <button key={e.key} type="button" onClick={() => onFase(e)} {...hover} style={{ ...estilo, background: 'transparent', borderTop: 'none', borderRight: 'none', borderBottom: 'none', color: 'inherit', cursor: 'pointer', font: 'inherit' }}>
              {celula}
            </button>
          );
        })}
      </div>
      {Tip && <Tip tip={t.tip} />}
    </div>
  );
}

function resFunilNota(sector) {
  if (sector === 'demo') return 'Oportunidades a partir do stage 10. Demos a partir do stage 65.';
  if (sector === 'roll') return 'Oportunidades a partir do stage 10. Testing Rolls a partir do stage 65. 1ª Encomenda: fatura Primavera ou WIN no Gestor.';
  if (sector === 'todas') return 'Demo é o stage 65. Mimaki e AllDecor contam a demo; Biond e Decal contam o testing roll.';
  return '';
}

function resFunilAssinatura(kpis, sector, periodo, campanhasNoAr) {
  const etapas = resFunilEtapas(kpis || {}, sector);
  return [sector, periodo || '', campanhasNoAr == null ? '' : campanhasNoAr, ...etapas.map(e => `${e.key}:${e.real}:${e.valor || 0}`)].join('|');
}

function resFunilCabecaDados(kpis, sector, periodo, campanhasNoAr) {
  const etapas = resFunilEtapas(kpis || {}, sector);
  const primeiro = Number(etapas[0]?.real) || 0;
  const ultimo = etapas[etapas.length - 1] || { key: '', real: 0, label: '' };
  const taxa = primeiro ? (Number(ultimo.real) || 0) / primeiro * 100 : 0;
  let rotulo = String(ultimo.label || '').toLowerCase();
  if (ultimo.key === 'primeira_encomenda') rotulo = Number(ultimo.real) === 1 ? 'primeira encomenda' : 'primeiras encomendas';
  else if (ultimo.key === 'fechos') rotulo = Number(ultimo.real) === 1 ? 'WIN' : "WIN's";
  return { primeiro, ultimo, taxa, rotulo, periodo: periodo || 'YTD', campanhasNoAr };
}

function ResFunilEuro({ valor }) {
  const v = useResContagem(Number(valor) || 0);
  if (!Number(valor) && Math.round(v) === 0) return null;
  return <> · {resFmtEur(v)}</>;
}

function ResFunilCabeca({ kpis, sector, periodo, campanhasNoAr }) {
  const d = resFunilCabecaDados(kpis, sector, periodo, campanhasNoAr);
  const noAr = d.campanhasNoAr != null;
  return (
    <div className="res-funil-cabeca" 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' }}>
          {d.primeiro ? <ResContagem valor={d.taxa} fmt={resFmtTaxa} /> : '0%'}
        </div>
        <div style={{ fontSize: 13, color: 'var(--text-muted)', marginTop: 6, fontVariantNumeric: 'tabular-nums' }}>
          {d.primeiro ? (
            <>
              <ResContagem valor={d.ultimo.real} /> {d.rotulo} de <ResContagem valor={d.primeiro} /> leads
              <ResFunilEuro valor={d.ultimo.valor} />
              {' · '}{d.periodo}
              {noAr ? <> · <ResContagem valor={d.campanhasNoAr} /> no ar</> : null}
            </>
          ) : (
            <>Sem leads neste período · {d.periodo}{noAr ? <> · <ResContagem valor={d.campanhasNoAr} /> no ar</> : null}</>
          )}
        </div>
      </div>
    </div>
  );
}

function resMovimentoReduzido() {
  return typeof window !== 'undefined' && window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
}

function ResFunil({ kpis, sector, periodo, campanhasNoAr, gidPrefix, altura, onFase, marca, pronto = true }) {
  const formaLive = resFunilEtapas(kpis || {}, sector).map(e => e.key).join('|');
  const liveRef = React.useRef({});
  liveRef.current = { kpis, sector, periodo, campanhasNoAr, forma: formaLive };
  const [vista, setVista] = React.useState(() => liveRef.current);
  const [cabeca, setCabeca] = React.useState(() => liveRef.current);
  const [modo, setModo] = React.useState('entrada');
  const [tick, setTick] = React.useState(0);
  const marcaVista = React.useRef(marca);
  const formaVista = React.useRef(formaLive);
  const cabecaAssinatura = React.useRef(resFunilAssinatura(kpis, sector, periodo, campanhasNoAr));

  React.useEffect(() => {
    if (modo === 'esvazia' || modo === 'espera') return;
    if (marca != null && marca !== marcaVista.current) return;
    if (formaLive !== formaVista.current) return;
    setVista(liveRef.current);
  }, [kpis, sector, periodo, campanhasNoAr, formaLive, marca, modo]);

  React.useEffect(() => {
    if (marca == null) return;
    const mudaMarca = marca !== marcaVista.current;
    const mudaForma = !mudaMarca && formaLive !== formaVista.current;
    if (!mudaMarca && !mudaForma) return;
    if (resMovimentoReduzido()) {
      if (mudaMarca && !pronto) return;
      marcaVista.current = marca;
      formaVista.current = liveRef.current.forma;
      setVista(liveRef.current);
      return;
    }
    if (modo !== 'esvazia' && modo !== 'espera') setModo('esvazia');
  }, [marca, formaLive, pronto, modo]);

  React.useEffect(() => {
    if (modo !== 'esvazia') return undefined;
    const t = setTimeout(() => setModo('espera'), 1000);
    return () => clearTimeout(t);
  }, [modo]);

  React.useEffect(() => {
    if (modo !== 'espera') return;
    if (marca != null && marca !== marcaVista.current && !pronto) return;
    marcaVista.current = marca;
    formaVista.current = liveRef.current.forma;
    setVista(liveRef.current);
    setTick(n => n + 1);
    setModo('enche');
  }, [modo, pronto, marca, formaLive, kpis, sector, periodo, campanhasNoAr]);

  React.useEffect(() => {
    if (marca != null && marca !== marcaVista.current && !pronto) return;
    const assinatura = resFunilAssinatura(kpis, sector, periodo, campanhasNoAr);
    if (assinatura === cabecaAssinatura.current) return;
    cabecaAssinatura.current = assinatura;
    setCabeca(liveRef.current);
  }, [kpis, sector, periodo, campanhasNoAr, marca, pronto]);

  const etapas = resFunilEtapas(vista.kpis || {}, vista.sector);
  const nota = resFunilNota(vista.sector);
  const gid = (gidPrefix || 'res-funil-') + (vista.sector || '') + '-' + tick;
  const H = Number(altura) > 80 ? Number(altura) : 200;
  const aEsvaziar = modo === 'esvazia' || modo === 'espera';
  return (
    <div className={'card an-card' + (aEsvaziar ? ' res-funil-esvazia' : '')} style={{ padding: '18px 18px 8px', marginBottom: 12 }}>
      <ResFunilCabeca kpis={cabeca.kpis} sector={cabeca.sector} periodo={cabeca.periodo} campanhasNoAr={cabeca.campanhasNoAr} />
      <div key={tick} className="res-funil-palco">
        <ResFunilCorpo etapas={etapas} H={H} gid={gid} cores={RES_FUNIL_CORES} pathFn={resFunilPath} pctFn={resPctFunil}
          ariaLabel="Funil de resultados" onFase={aEsvaziar ? undefined : onFase} />
        {nota && <div style={{ fontSize: 11, color: 'var(--text-dim)', margin: '2px 0 8px' }}>{nota}</div>}
      </div>
    </div>
  );
}

function ResLinhas({ linhas, onPick }) {
  const max = Math.max(1, ...linhas.map(l => Number(l.leads) || 0));
  if (!linhas.length) return <div className="card" style={{ padding: 12, fontSize: 12, color: 'var(--text-dim)' }}>Sem dados neste filtro.</div>;
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
      {linhas.map(l => (
        <button key={l.id} type="button" onClick={() => onPick && onPick(l)} className="card" style={{ textAlign: 'left', padding: 12, cursor: onPick ? 'pointer' : 'default', width: '100%' }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', gap: 8 }}>
            <strong>{l.nome}</strong>
            <span style={{ fontSize: 12, color: 'var(--text-muted)' }}>{resFmtEur(l.spend)}</span>
          </div>
          <div style={{ height: 8, borderRadius: 99, background: 'var(--surface-muted)', marginTop: 8, overflow: 'hidden' }}>
            <div style={{ width: `${Math.max(((Number(l.leads) || 0) / max) * 100, l.leads ? 3 : 0)}%`, height: '100%', borderRadius: 99, background: 'linear-gradient(90deg, #d6e2f0, #3859D0)' }} />
          </div>
          <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', marginTop: 6, fontSize: 11, color: 'var(--text-muted)' }}>
            <span>{resFmtN(l.leads)} leads</span>
            <span>{resFmtN(l.mql)} MQL</span>
            <span>{resFmtN(l.ops)} OPs</span>
            <span>{Number(l.fechos) === 1 ? "1 WIN" : `${resFmtN(l.fechos)} WIN's`}{l.fechos_eur ? ` · ${resFmtEur(l.fechos_eur)}` : ''}</span>
          </div>
        </button>
      ))}
    </div>
  );
}

const RES_ETAPA_LABEL = {
  reach: 'Reach',
  leads: 'Leads',
  qualificados: 'MQL',
  demos: 'Demos',
  testing_rolls: 'Testing Rolls',
  primeira_encomenda: '1ª Encomenda',
  oportunidades: 'Oportunidades',
  fechos: "WIN's",
  wins: "WIN's",
};

function resBar(pct, light) {
  const w = Math.max(0, Math.min(100, Number(pct) || 0));
  return (
    <div style={{ height: 4, borderRadius: 2, background: 'var(--border)', marginTop: 4 }}>
      <div style={{ width: w + '%', height: '100%', borderRadius: 2, background: light ? RES_LIGHT[light] : 'var(--ai-500)' }} />
    </div>
  );
}

function resEtapaKeys(key) {
  if (key === 'fechos' || key === 'wins') return ['fechos', 'wins'];
  return [key];
}

/** Agrega objectivo só em campanhas com objectivo > 0 (não dilui com Meta/CRM). */
function resAggEtapa(campanhas, key, realOverride) {
  const keys = resEtapaKeys(key);
  let real = 0;
  let objectivo = 0;
  let realObj = 0;
  let light = null;
  const lightRank = { vermelho: 0, amarelo: 1, verde: 2 };
  for (const c of campanhas || []) {
    const e = (c.etapas || []).find(x => keys.includes(x.key));
    if (!e) continue;
    real += Number(e.real) || 0;
    if (Number(e.objectivo) > 0) {
      objectivo += Number(e.objectivo) || 0;
      realObj += Number(e.real) || 0;
      if (e.light && (light == null || (lightRank[e.light] ?? 9) < (lightRank[light] ?? 9))) light = e.light;
    }
  }
  const obj = objectivo > 0 ? objectivo : null;
  return {
    real: realOverride != null ? realOverride : real,
    real_com_objectivo: realObj,
    objectivo: obj,
    pct: obj ? Math.round(realObj / obj * 100) : null,
    light,
  };
}

function resKpiOuAgg(kpis, campanhas, key, realOverride) {
  const k = kpis?.[key];
  const agg = resAggEtapa(campanhas, key, realOverride);
  if (k && (k.objectivo != null || k.real_com_objectivo != null || k.pct != null)) {
    return {
      real: realOverride != null ? realOverride : (Number(k.real) || 0),
      real_com_objectivo: Number(k.real_com_objectivo) || 0,
      objectivo: k.objectivo != null && Number(k.objectivo) > 0 ? Number(k.objectivo) : null,
      pct: k.pct,
      light: k.light || agg.light,
    };
  }
  if (k && realOverride == null) {
    return { ...agg, real: Number(k.real) || 0, light: k.light || agg.light };
  }
  return agg;
}

function resCardEtapa(key, data) {
  return { key, label: RES_ETAPA_LABEL[key] || key, ...data };
}

function ResObjCard({ card }) {
  const temObj = card.objectivo != null && Number(card.objectivo) > 0;
  const pct = temObj
    ? (card.pct != null ? card.pct : Math.round((Number(card.real_com_objectivo) || 0) / card.objectivo * 100))
    : null;
  const lightColor = card.light ? RES_LIGHT[card.light] : null;
  return (
    <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' }}>{card.label}</div>
      <div className="font-display" style={{ fontSize: 22, fontWeight: 650, marginTop: 4, lineHeight: 1.15 }}>
        {resFmtN(card.real)}
        {temObj && <span style={{ fontWeight: 450, color: 'var(--text-muted)', fontSize: 15 }}> / {resFmtN(card.objectivo)}</span>}
      </div>
      <div style={{ fontSize: 11, marginTop: 4, color: lightColor || 'var(--text-muted)' }}>
        {temObj ? `${pct}% do objectivo` : 'sem objectivo'}
      </div>
      {temObj && resBar(pct, card.light)}
    </div>
  );
}

function ResObjCta({ label, title, sub, onClick }) {
  return (
    <button
      type="button"
      onClick={onClick}
      style={{
        padding: 14, border: '1px solid var(--border)', borderRadius: 10, background: 'var(--bg-elev)',
        minWidth: 0, width: '100%', height: '100%', textAlign: 'left', cursor: 'pointer', color: 'inherit',
        display: 'flex', flexDirection: 'column', justifyContent: 'space-between',
      }}
    >
      <div>
        <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, display: 'flex', alignItems: 'baseline', gap: 8 }}>
          {title}
          <span style={{ fontWeight: 450, color: 'var(--text-muted)', fontSize: 15 }}>→</span>
        </div>
      </div>
      <div style={{ fontSize: 11, marginTop: 4, color: 'var(--text-muted)' }}>{sub}</div>
    </button>
  );
}

function ResObjectivoCards({ kpis, campanhas, brand, isAll, isMobile, cta }) {
  const k = kpis || {};
  const lista = campanhas || [];
  const sector = resSectorFunil(brand, isAll);
  const reach = resAggEtapa(lista, 'reach');
  const leads = resKpiOuAgg(k, lista, 'leads');
  const mql = resKpiOuAgg(k, lista, 'qualificados');
  const ctaCard = cta && cta.onClick ? (
    <ResObjCta
      label={cta.label || 'Resultados'}
      title={cta.title || 'Ver todos'}
      sub={cta.sub || 'Campanhas, canais e marcas'}
      onClick={cta.onClick}
    />
  ) : null;

  if (isAll || brand === 'todas') {
    const comuns = [
      resCardEtapa('reach', reach),
      resCardEtapa('leads', leads),
      resCardEtapa('oportunidades', resKpiOuAgg(k, lista, 'oportunidades')),
    ];
    const demo = lista.filter(c => resFunilCampanha(c) === 'demo');
    const roll = lista.filter(c => resFunilCampanha(c) === 'roll');
    const sensek = lista.filter(c => resFunilCampanha(c) === 'sensek');
    const com = lista.filter(c => resFunilCampanha(c) === 'comum');
    const grupos = [
      {
        titulo: 'Mimaki e AllDecor',
        cards: [
          resCardEtapa('demos', resAggEtapa(demo, 'demos')),
          resCardEtapa('fechos', resAggEtapa(demo, 'fechos')),
        ],
      },
      {
        titulo: 'Biond e Decal',
        cards: [
          resCardEtapa('testing_rolls', resAggEtapa(roll, 'testing_rolls')),
          resCardEtapa('primeira_encomenda', resAggEtapa(roll, 'primeira_encomenda')),
        ],
      },
      {
        titulo: 'Sensek',
        cards: [
          resCardEtapa('testing_rolls', resAggEtapa(sensek, 'testing_rolls')),
          resCardEtapa('primeira_encomenda', resAggEtapa(sensek, 'primeira_encomenda')),
        ],
      },
      {
        titulo: 'NetScreen',
        cards: [
          resCardEtapa('oportunidades', resAggEtapa(com, 'oportunidades')),
          resCardEtapa('fechos', resAggEtapa(com, 'fechos')),
        ],
      },
    ];
    return (
      <div style={{ marginBottom: 18 }}>
        <div style={{ display: 'grid', gridTemplateColumns: `repeat(${isMobile ? 1 : (ctaCard ? 4 : 3)}, 1fr)`, gap: 10, marginBottom: 12 }}>
          {comuns.map(card => <ResObjCard key={'c-' + card.key} card={card} />)}
          {ctaCard}
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : 'repeat(4, 1fr)', gap: 12 }}>
          {grupos.map(g => (
            <div key={g.titulo}>
              <div style={{ fontSize: 11, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', letterSpacing: '.04em', textTransform: 'uppercase', marginBottom: 6 }}>{g.titulo}</div>
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
                {g.cards.map(card => <ResObjCard key={g.titulo + '-' + card.key} card={card} />)}
              </div>
            </div>
          ))}
        </div>
      </div>
    );
  }

  let cards;
  if (sector === 'demo') {
    cards = [
      resCardEtapa('reach', reach),
      resCardEtapa('leads', leads),
      resCardEtapa('oportunidades', resKpiOuAgg(k, lista, 'oportunidades')),
      resCardEtapa('demos', resKpiOuAgg(k, lista, 'demos')),
      resCardEtapa('fechos', resKpiOuAgg(k, lista, 'fechos')),
    ];
  } else if (sector === 'roll') {
    cards = [
      resCardEtapa('reach', reach),
      resCardEtapa('leads', leads),
      resCardEtapa('oportunidades', resKpiOuAgg(k, lista, 'oportunidades')),
      resCardEtapa('testing_rolls', resKpiOuAgg(k, lista, 'testing_rolls')),
      resCardEtapa('primeira_encomenda', resKpiOuAgg(k, lista, 'primeira_encomenda')),
    ];
  } else if (sector === 'sensek') {
    cards = [
      resCardEtapa('reach', reach),
      resCardEtapa('leads', leads),
      resCardEtapa('qualificados', mql),
      resCardEtapa('testing_rolls', resKpiOuAgg(k, lista, 'testing_rolls')),
      resCardEtapa('primeira_encomenda', resKpiOuAgg(k, lista, 'primeira_encomenda')),
    ];
  } else {
    cards = [
      resCardEtapa('reach', reach),
      resCardEtapa('leads', leads),
      resCardEtapa('qualificados', mql),
      resCardEtapa('oportunidades', resKpiOuAgg(k, lista, 'oportunidades')),
      resCardEtapa('fechos', resKpiOuAgg(k, lista, 'fechos')),
    ];
  }

  const nCols = cards.length + (ctaCard ? 1 : 0);
  return (
    <div style={{ display: 'grid', gridTemplateColumns: `repeat(${isMobile ? 2 : nCols}, 1fr)`, gap: 10, marginBottom: 18 }}>
      {cards.map(card => <ResObjCard key={card.key} card={card} />)}
      {ctaCard}
    </div>
  );
}

// ── Menu dropdown "Acções" (mesmo estilo do ResFiltro) ──────────────────────
// Botão quadrado de 30 px da barra de filtros (lupa, menu), como em Prospeção.
const ResIcone = ({ children, onClick, activo, label, ...resto }) => (
  <button type="button" aria-label={label} title={label} onMouseDown={e => e.stopPropagation()} onClick={onClick} {...resto} style={{
    width: 30, height: 30, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
    border: `1px solid ${activo ? 'var(--ai-500, #3859D0)' : 'var(--border, #dde3ef)'}`, borderRadius: 6,
    background: activo ? 'color-mix(in oklch, var(--ai-500, #3859D0) 10%, var(--bg-elev, #fff))' : 'var(--bg-elev, #fff)',
    color: activo ? 'var(--ai-500, #3859D0)' : 'var(--text, #112954)', cursor: 'pointer', padding: 0,
    transition: 'border-color .15s, background-color .15s, color .15s',
  }}>{children}</button>
);
const ResIconeLupa = () => <svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"><circle cx="7" cy="7" r="4.25" /><path d="M10.5 10.5L13.5 13.5" /></svg>;
const ResIconeMenu = () => <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>;

// Trilho "Resultados › …" das sub-vistas, com o menu à direita.
const ResTrilho = ({ segmentos, nota, extra }) => (
  <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 14px', background: 'var(--bg-elev, #fff)', borderRadius: 8, border: '1px solid var(--border)', boxShadow: '0 1px 4px rgba(40,41,61,0.04), 0 4px 16px rgba(96,97,112,0.08)', flexWrap: 'wrap', marginBottom: 18 }}>
    {segmentos.map((seg, i) => {
      const ultimo = i === segmentos.length - 1;
      return (
        <React.Fragment key={i}>
          {seg.onClick && !ultimo ? (
            <button type="button" onClick={seg.onClick} style={{ background: 'transparent', border: 0, cursor: 'pointer', padding: 0, fontSize: 12, color: 'var(--ai-500, #3859D0)', fontFamily: 'inherit' }}>{seg.label}</button>
          ) : (
            <span style={{ fontSize: 12, color: 'var(--text)', fontWeight: ultimo ? 700 : 400 }}>{seg.label}</span>
          )}
          {!ultimo && <span style={{ color: 'var(--text-dim)', fontSize: 12 }}>›</span>}
        </React.Fragment>
      );
    })}
    {nota}
    {extra && <div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 8 }}>{extra}</div>}
  </div>
);

// Procura em ecrã inteiro, como em Prospeção. itens: [{ id, tipo, titulo, detalhe, texto }].
const ResBusca = ({ itens, onPick, onClose, placeholder = 'Procurar', vazio = 'Nada encontrado.' }) => {
  const [q, setQ] = React.useState('');
  const [foco, setFoco] = React.useState(0);
  const inputRef = React.useRef(null);
  React.useEffect(() => { inputRef.current?.focus(); }, []);
  React.useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, [onClose]);
  const norm = (s) => String(s || '').normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
  const termo = norm(q.trim());
  const rows = termo ? (itens || []).filter(it => norm(`${it.titulo} ${it.detalhe || ''} ${it.texto || ''}`).includes(termo)).slice(0, 12) : [];
  React.useEffect(() => { setFoco(0); }, [termo]);
  const abrir = (it) => { if (!it) return; onPick(it); onClose(); };
  const onKeyDown = (e) => {
    if (e.key === 'ArrowDown') { e.preventDefault(); setFoco(i => Math.min(rows.length - 1, i + 1)); }
    if (e.key === 'ArrowUp') { e.preventDefault(); setFoco(i => Math.max(0, i - 1)); }
    if (e.key === 'Enter') { e.preventDefault(); abrir(rows[foco]); }
  };
  return (
    <div role="dialog" aria-label={placeholder} onMouseDown={onClose} style={{
      position: 'fixed', inset: 0, zIndex: 9999, background: 'rgba(0,0,0,.78)',
      display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24,
    }}>
      <button type="button" aria-label="Fechar" onMouseDown={e => e.stopPropagation()} onClick={onClose} style={{
        position: 'absolute', top: 18, right: 22, width: 36, height: 36, border: 'none', background: 'transparent',
        color: '#fff', fontSize: 26, lineHeight: 1, cursor: 'pointer', fontFamily: 'inherit',
      }}>×</button>
      <div onMouseDown={e => e.stopPropagation()} style={{ width: 'min(920px, 100%)' }}>
        <style>{'.res-procurar::placeholder{color:rgba(255,255,255,.65)}'}</style>
        <div style={{ display: 'flex', alignItems: 'center', gap: 16, borderBottom: '1px solid rgba(255,255,255,.85)', paddingBottom: 8 }}>
          <input ref={inputRef} className="res-procurar" value={q} onChange={e => setQ(e.target.value)} onKeyDown={onKeyDown}
            placeholder={placeholder} aria-label={placeholder}
            style={{ flex: 1, height: 48, border: 'none', outline: 'none', background: 'transparent', color: '#fff', fontSize: 22, fontFamily: 'inherit', fontWeight: 400 }} />
          <svg width="22" height="22" viewBox="0 0 16 16" fill="none" stroke="#fff" strokeWidth="1.6" strokeLinecap="round" aria-hidden="true"><circle cx="7" cy="7" r="4.25" /><path d="M10.5 10.5L13.5 13.5" /></svg>
        </div>
        {termo && (
          <div style={{ marginTop: 8, maxHeight: '60vh', overflowY: 'auto' }}>
            {!rows.length && <div style={{ padding: '12px 4px', fontSize: 14, color: 'rgba(255,255,255,.75)' }}>{vazio}</div>}
            {rows.map((r, i) => (
              <button key={r.id} type="button" onMouseEnter={() => setFoco(i)} onClick={() => abrir(r)} style={{
                display: 'flex', width: '100%', textAlign: 'left', gap: 12, alignItems: 'baseline',
                background: i === foco ? 'rgba(255,255,255,.14)' : 'transparent',
                border: 'none', borderRadius: 6, padding: '10px 8px', cursor: 'pointer', fontFamily: 'inherit', color: '#fff',
              }}>
                <span style={{ fontSize: 11, fontWeight: 650, letterSpacing: '.04em', textTransform: 'uppercase', color: 'rgba(255,255,255,.6)', minWidth: 72 }}>{r.tipo}</span>
                <span style={{ fontSize: 16, fontWeight: 600 }}>{r.titulo}</span>
                {r.detalhe && <span style={{ fontSize: 13, color: 'rgba(255,255,255,.72)' }}>{r.detalhe}</span>}
              </button>
            ))}
          </div>
        )}
      </div>
    </div>
  );
};

// icone: botão ☰ de 30 px (barra de filtros); sem icone: botão "Acções ▾".
const ResMenuAccoes = ({ items, label = 'Acções', align = 'right', icone = false, onOpen, passo }) => {
  const [open, setOpen] = React.useState(false);
  const [over, setOver] = React.useState(false);
  const [foco, setFoco] = React.useState(-1);
  const ref = React.useRef(null);
  const accionaveis = (items || []).map((it, i) => (!it.separador && !it.disabled ? i : null)).filter(i => i != null);
  React.useEffect(() => {
    if (!open) return;
    const onClick = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    const onKey = (e) => {
      if (e.key === 'Escape') { setOpen(false); return; }
      if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
        e.preventDefault();
        setFoco(f => {
          const pos = accionaveis.indexOf(f);
          const next = e.key === 'ArrowDown' ? (pos + 1) % accionaveis.length : (pos <= 0 ? accionaveis.length - 1 : pos - 1);
          return accionaveis[next] ?? -1;
        });
      }
      if (e.key === 'Enter' && foco >= 0) {
        e.preventDefault();
        const it = items[foco];
        if (it && !it.disabled && it.onClick) { setOpen(false); it.onClick(); }
      }
    };
    document.addEventListener('mousedown', onClick);
    document.addEventListener('keydown', onKey);
    return () => { document.removeEventListener('mousedown', onClick); document.removeEventListener('keydown', onKey); };
  }, [open, foco, items]);
  const hover = 'color-mix(in oklch, var(--ai-500, #3859D0) 12%, transparent)';
  return (
    <div ref={ref} style={{ position: 'relative' }}>
      {icone ? (
        <ResIcone label="Menu" activo={open} aria-haspopup="menu" aria-expanded={open} data-tutorial-step={passo || undefined}
          onClick={() => { setOpen(v => !v); setFoco(-1); onOpen && onOpen(); }}>
          <ResIconeMenu />
        </ResIcone>
      ) : (
        <button type="button" className="btn btn-ai" aria-haspopup="menu" aria-expanded={open}
          onClick={() => { setOpen(v => !v); setFoco(-1); onOpen && onOpen(); }}
          onMouseEnter={() => setOver(true)} onMouseLeave={() => setOver(false)}
          style={{ height: 30, padding: '0 14px', fontSize: 12, display: 'inline-flex', alignItems: 'center', gap: 8, filter: over ? 'brightness(1.08)' : 'none', boxShadow: open ? '0 0 0 3px color-mix(in oklch, var(--ai-500, #3859D0) 22%, transparent)' : 'none', transition: 'filter .15s, box-shadow .15s' }}>
          {label}<span style={{ fontSize: 9 }}>▾</span>
        </button>
      )}
      {open && (
        <div role="menu" style={{
          position: 'absolute', top: `calc(100% + ${icone ? 6 : 4}px)`, [align === 'left' ? 'left' : 'right']: 0, zIndex: 70,
          background: 'var(--bg-elev, #ffffff)', border: '1px solid var(--border, #dde3ef)', borderRadius: icone ? 8 : 6,
          boxShadow: icone ? '0 12px 32px rgba(17,41,84,0.14)' : '0 8px 24px rgba(17,41,84,0.12)', minWidth: icone ? 260 : 240, padding: 4,
        }}>
          {(items || []).map((it, i) => {
            if (it.separador) return <div key={`sep-${i}`} style={{ height: 1, background: 'var(--border)', margin: '4px 2px' }} />;
            const activo = foco === i;
            return (
              <button key={it.id || i} type="button" role="menuitem" disabled={it.disabled}
                onClick={() => { if (it.disabled) return; setOpen(false); it.onClick && it.onClick(); }}
                onMouseEnter={() => setFoco(i)} onMouseLeave={() => setFoco(-1)}
                style={{
                  display: 'flex', alignItems: 'center', gap: 8, width: '100%', textAlign: 'left',
                  background: activo && !it.disabled ? hover : 'transparent',
                  color: it.disabled ? 'var(--text-dim)' : activo || it.destaque ? 'var(--ai-500, #3859D0)' : 'var(--text, #112954)',
                  border: 'none', padding: '8px 10px', borderRadius: icone ? 6 : 4, fontSize: icone ? 13 : 12, fontWeight: icone || it.destaque ? 650 : 500,
                  cursor: it.disabled ? 'default' : 'pointer', fontFamily: 'inherit', transition: 'background .12s, color .12s',
                }}>
                {it.ponto && <span style={{ width: 8, height: 8, borderRadius: '50%', background: it.ponto, flexShrink: 0 }} />}
                <span style={{ flex: 1, minWidth: 0 }}>
                  <span style={{ display: 'block', whiteSpace: 'nowrap' }}>{it.label}</span>
                  {it.sub && <span style={{ display: 'block', fontSize: icone ? 11 : 10.5, color: 'var(--text-muted)', fontWeight: 400, marginTop: 1 }}>{it.sub}</span>}
                </span>
                {it.checked && <span style={{ fontSize: 12, color: 'var(--ai-500, #3859D0)' }}>✓</span>}
              </button>
            );
          })}
        </div>
      )}
    </div>
  );
};

// ── Cartão "Análise do dia" da Digi AI, por baixo do funil ──────────────────
const RES_TOM_LABEL = { verde: 'No caminho', amarelo: 'Atenção', vermelho: 'Acção urgente' };

function resAnaliseQs(marca, filtros) {
  const f = filtros || {};
  const p = new URLSearchParams({ marca: marca || 'todas' });
  if (f.periodo) p.set('periodo', f.periodo);
  if (f.ano) p.set('ano', f.ano);
  if (f.inicio) p.set('inicio', f.inicio);
  if (f.fim) p.set('fim', f.fim);
  if (f.canal) p.set('canal', f.canal);
  if (f.source) p.set('source', f.source);
  if (f.action) p.set('action', f.action);
  if (f.campanha) p.set('campanha', f.campanha);
  return p.toString();
}

function resPrimeirasFrases(texto, n = 3) {
  const partes = String(texto || '').match(/[^.!?]+[.!?]+(\s|$)/g) || [String(texto || '')];
  return partes.slice(0, n).join('').trim();
}

const ResAnaliseResumo = ({ marca = 'todas', filtros, compacto = false, setSub, onInfo }) => {
  const [estado, setEstado] = React.useState({ loading: true });
  const [gerando, setGerando] = React.useState(false);
  const qs = resAnaliseQs(marca, filtros);
  const carregar = React.useCallback((gerar) => {
    const url = `/api/marketing/resultados/v2/analise/leitura?${qs}${gerar ? '&gerar=1' : ''}`;
    return fetch(url).then(r => r.json()).then(d => {
      setEstado({ loading: false, ...d });
      onInfo && onInfo(d?.analise || null);
    }).catch(e => setEstado({ loading: false, erro: e.message }));
  }, [qs]);
  React.useEffect(() => { setEstado({ loading: true }); carregar(false); }, [carregar]);
  const gerar = () => { setGerando(true); carregar(true).finally(() => setGerando(false)); };
  const verCompleta = () => {
    try { localStorage.setItem('mkt-analise-marca', marca || 'todas'); } catch {}
    setSub && setSub('resultados-analise');
  };

  const card = { padding: compacto ? 14 : 16, borderRadius: 10, border: '1px solid var(--border)', background: 'var(--bg-elev)', marginBottom: 18, position: 'relative' };
  const cabecalho = (extra) => (
    <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8, flexWrap: 'wrap' }}>
      <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="var(--ai-500, #3859D0)" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" /></svg>
      <span className="font-display" style={{ fontSize: 13, fontWeight: 650 }}>{estado.tipo === 'ecra' ? 'Leitura da Digi AI destes filtros' : 'Análise do dia · Digi AI'}</span>
      {extra}
    </div>
  );

  if (estado.loading) return <div style={card}>{cabecalho(<span style={{ fontSize: 11, color: 'var(--text-muted)' }}>a carregar…</span>)}</div>;
  if (estado.erro) return <div style={card}>{cabecalho(<span style={{ fontSize: 11, color: 'var(--danger)' }}>{estado.erro}</span>)}</div>;
  if (estado.pendente) {
    return (
      <div style={card}>
        {cabecalho(null)}
        <div style={{ display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap', fontSize: 12.5, color: 'var(--text-muted)' }}>
          <span style={{ flex: 1, minWidth: 200 }}>Estes filtros ainda têm leitura por fazer hoje. A análise diária cobre o YTD de cada marca.</span>
          <button type="button" className="btn btn-sm" disabled={gerando} onClick={gerar}>{gerando ? 'A Digi AI está a ler os filtros…' : 'Gerar leitura'}</button>
          <button type="button" className="btn btn-sm" onClick={verCompleta}>Ver análise do dia</button>
        </div>
      </div>
    );
  }
  const a = estado.analise;
  if (!a) {
    return (
      <div style={card}>
        {cabecalho(null)}
        <div style={{ fontSize: 12.5, color: 'var(--text-muted)' }}>A análise do dia corre às 06:30 e fica aqui com a leitura, os KPIs e as campanhas a fazer.</div>
      </div>
    );
  }
  const an = a.analise || {};
  const kpis = a.kpis || [];
  const kpi = (id) => kpis.find(k => k.id === id);
  const tresKpis = [kpi('leads_vs_nec'), kpi('bp'), kpi('sdr')].filter(Boolean);
  const campanhas = (an.campanhas || []).filter(c => c.estado !== 'descartada').slice(0, 2);
  const tomCor = RES_LIGHT[an.tom] || 'var(--text-muted)';
  const hora = a.gerado_em ? new Date(a.gerado_em).toLocaleString('pt-PT', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }) : '';
  const TIPO = { captacao: 'Captação', reactivacao: 'Reactivação', familia: 'Família', equipamento: 'Equipamento', cross_sell: 'Cross-sell', corrigir: 'Corrigir' };

  return (
    <div className="an-card" data-acento="1" style={{ ...card, borderLeft: `3px solid ${tomCor}` }}>
      {cabecalho(<>
        <span style={{ fontSize: 10.5, fontWeight: 600, color: tomCor, background: `color-mix(in oklch, ${tomCor} 12%, transparent)`, padding: '2px 8px', borderRadius: 99 }}>{RES_TOM_LABEL[an.tom] || an.tom}</span>
        <span style={{ fontSize: 10.5, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>{String(a.dia || '').slice(0, 10)} · {hora}{a.origem === 'regras' ? ' · regras' : ''}</span>
        <span style={{ flex: 1 }} />
        <button type="button" className="btn btn-sm" onClick={verCompleta}>Ver análise completa</button>
      </>)}
      {an.titulo && <div style={{ fontSize: 13.5, fontWeight: 600, color: 'var(--text)', marginBottom: 6, lineHeight: 1.4 }}>{an.titulo}</div>}
      <div style={{ display: 'grid', gridTemplateColumns: compacto ? 'repeat(3, minmax(0, 1fr))' : 'repeat(3, minmax(0, 1fr))', gap: 8, marginBottom: 10 }}>
        {tresKpis.map(k => (
          <div key={k.id} className="an-kpi" style={{ padding: '8px 10px', borderRadius: 8, background: 'var(--bg)', border: '1px solid var(--border)', minWidth: 0 }}>
            <div style={{ fontSize: 9.5, color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '.04em', fontFamily: 'var(--font-mono)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{k.label}</div>
            <div style={{ display: 'flex', alignItems: 'baseline', gap: 6, marginTop: 2 }}>
              <span className="font-display" style={{ fontSize: 17, fontWeight: 700, color: RES_LIGHT[k.luz] || 'var(--text)' }}>{k.valor_txt}</span>
              {k.alvo_txt && k.alvo_txt !== '—' && <span style={{ fontSize: 10.5, color: 'var(--text-muted)' }}>de {k.alvo_txt}</span>}
            </div>
          </div>
        ))}
      </div>
      {!compacto && an.leitura && <div style={{ fontSize: 12.5, lineHeight: 1.55, color: 'var(--text)', marginBottom: campanhas.length ? 10 : 0 }}>{resPrimeirasFrases(an.leitura, 3)}</div>}
      {compacto && an.leitura && <div style={{ fontSize: 12, lineHeight: 1.5, color: 'var(--text-muted)', marginBottom: 6 }}>{resPrimeirasFrases(an.leitura, 2)}</div>}
      {!compacto && campanhas.length > 0 && (
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 1fr))', gap: 8 }}>
          {campanhas.map(c => (
            <div key={c.gatilho_id} className="an-kpi" style={{ padding: '8px 10px', borderRadius: 8, border: '1px dashed var(--border-strong, #bec9de)', fontSize: 12, minWidth: 0 }}>
              <div style={{ fontSize: 9.5, color: 'var(--ai-500, #3859D0)', fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '.05em' }}>{TIPO[c.tipo] || c.tipo}{c.equipa_label ? ` · ${c.equipa_label}` : ''}{c.estado === 'briefing_criado' ? ' · briefing criado' : ''}</div>
              <div style={{ fontWeight: 600, marginTop: 2, color: 'var(--text)' }}>{c.titulo}</div>
              <div style={{ color: 'var(--text-muted)', fontSize: 11, marginTop: 2 }}>
                {c.leads_alvo ? `${resFmtN(c.leads_alvo)} leads/mês` : 'Correcção'}{c.orcamento_ref_eur ? ` · ${resFmtEur(c.orcamento_ref_eur)}/mês ref.` : ''}{c.prazo ? ` · ${c.prazo}` : ''}
              </div>
            </div>
          ))}
        </div>
      )}
    </div>
  );
};

window.ResMenuAccoes = ResMenuAccoes;
window.ResIcone = ResIcone;
window.ResIconeLupa = ResIconeLupa;
window.ResTrilho = ResTrilho;
window.ResBusca = ResBusca;
window.ResAnaliseResumo = ResAnaliseResumo;
window.ResFunil = ResFunil;
window.ResFunilCorpo = ResFunilCorpo;
window.ResContagem = ResContagem;
window.resFmtTaxa = resFmtTaxa;
window.ResKpi = ResKpi;
window.ResLinhas = ResLinhas;
window.ResObjCard = ResObjCard;
window.ResObjCta = ResObjCta;
window.ResObjectivoCards = ResObjectivoCards;
window.resFmtEur = resFmtEur;
window.resFmtN = resFmtN;
window.resBar = resBar;
window.resAggEtapa = resAggEtapa;
window.resSectorFunil = resSectorFunil;
window.RES_LIGHT = RES_LIGHT;
window.RES_ETAPA_LABEL = RES_ETAPA_LABEL;
