/* Report Mimaki — pipeline das equipas Mimaki (PT, GC, ES, PA, AD) na linguagem de ranks da Mimaki EMEA.
   Dados do módulo Comercial (Gestor, snapshot Primavera, BP, F9, encomendas RPC). Corpo em inglês para a Mimaki.
   Expõe window.MktResultadosMimakiScreen. */

const MKR_API = '/api/marketing/resultados/v2/mimaki';
const MKR_FILTROS_KEY = 'mkt-mimaki-filtros';
const MKR_TAB_KEY = 'mkt-mimaki-tab';
const MKR_TABS = [
  { id: 'overview', label: 'Overview' },
  { id: 'teams', label: 'Teams' },
  { id: 'segments', label: 'Segments' },
  { id: 'contact', label: 'Mimaki Contact' },
  { id: 'ops', label: 'Opportunities' },
  { id: 'map', label: 'Ranking map' },
];
const MKR_RANK_COR = { D: '#94A3B8', C: '#F59E0B', B: '#3859D0', A: '#7B61FF', S: '#16A34A', Lost: '#DC2626' };
const MKR_MESES = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

function mkrEur(v) {
  if (v == null || !Number.isFinite(Number(v))) return '—';
  const n = Number(v);
  if (Math.abs(n) >= 1e6) return `€${(n / 1e6).toFixed(2)}M`;
  if (Math.abs(n) >= 1e4) return `€${Math.round(n / 1000).toLocaleString('en-GB')}k`;
  return `€${Math.round(n).toLocaleString('en-GB')}`;
}
function mkrN(v) {
  if (v == null || !Number.isFinite(Number(v))) return '—';
  return Math.round(Number(v)).toLocaleString('en-GB');
}
function mkrPct(v) {
  return v == null || !Number.isFinite(Number(v)) ? '—' : `${Number(v).toFixed(1)}%`;
}
function mkrQs(extra = {}, email) {
  const p = new URLSearchParams();
  const e = email || window.currentUser?.email;
  if (e) p.set('email', e);
  Object.entries(extra).forEach(([k, v]) => { if (v != null && v !== '') p.set(k, v); });
  return p.toString();
}
function mkrEmImpressao() {
  try { return new URLSearchParams(window.location.search).get('mkrprint') === '1'; } catch { return false; }
}
const MKR_PRINT_CSS = 'html.mkr-print aside,html.mkr-print header,html.mkr-print .mkr-no-print{display:none!important}html.mkr-print,html.mkr-print body{height:auto!important;overflow:visible!important}html.mkr-print #mkr-root,html.mkr-print #mkr-scroll{height:auto!important;max-height:none!important;overflow:visible!important}html.mkr-print *{animation:none!important;transition:none!important}';
function mkrEquipaCurta(t) {
  return String(t || '');
}
function mkrMesLabel(chave) {
  const [y, m] = String(chave).split('-').map(Number);
  return `${MKR_MESES[m - 1]} ${String(y).slice(2)}`;
}
const MKR_MESES_PT = ['jan', 'fev', 'mar', 'abr', 'mai', 'jun', 'jul', 'ago', 'set', 'out', 'nov', 'dez'];
function mkrIntervalo(inicio, fim) {
  const parte = (iso) => {
    const [y, m, d] = String(iso || '').split('-').map(Number);
    if (!y || !m || !d) return null;
    return { y, txt: `${d} ${MKR_MESES_PT[m - 1]}` };
  };
  const a = parte(inicio);
  const b = parte(fim);
  if (!a || !b) return '';
  return a.y === b.y ? `${a.txt} – ${b.txt} ${b.y}` : `${a.txt} ${a.y} – ${b.txt} ${b.y}`;
}
function mkrIntervaloEn(inicio, fim) {
  const parte = (iso) => {
    const [y, m, d] = String(iso || '').split('-').map(Number);
    if (!y || !m || !d) return null;
    return { y, txt: `${d} ${MKR_MESES[m - 1]}` };
  };
  const a = parte(inicio);
  const b = parte(fim);
  if (!a || !b) return '';
  return a.y === b.y ? `${a.txt} – ${b.txt} ${b.y}` : `${a.txt} ${a.y} – ${b.txt} ${b.y}`;
}
function mkrQuando(iso) {
  if (!iso) return '—';
  const d = new Date(iso);
  if (Number.isNaN(d.getTime())) return '—';
  return d.toLocaleString('pt-PT', { timeZone: 'Europe/Lisbon', day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' });
}

const MkrTh = ({ children, right, style, colSpan }) => (
  <th colSpan={colSpan} style={{ textAlign: right ? 'right' : 'left', padding: '8px 10px', fontSize: 10, fontWeight: 600, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', letterSpacing: '.04em', textTransform: 'uppercase', whiteSpace: 'nowrap', borderBottom: '1px solid var(--border)', background: 'var(--bg-elev)', position: 'sticky', top: 0, zIndex: 1, ...style }}>{children}</th>
);
const MkrTd = ({ children, right, mono, muted, style, title, rowSpan }) => (
  <td title={title} rowSpan={rowSpan} style={{ textAlign: right ? 'right' : 'left', padding: '7px 10px', fontSize: 12, borderTop: '1px solid var(--border)', whiteSpace: 'nowrap', color: muted ? 'var(--text-muted)' : 'var(--text)', fontFamily: mono ? 'var(--font-mono)' : undefined, fontVariantNumeric: 'tabular-nums', ...style }}>{children}</td>
);
const MkrTabela = ({ children, maxHeight }) => (
  <div className="scrollbar" style={{ overflow: 'auto', maxHeight, border: '1px solid var(--border)', borderRadius: 10, background: 'var(--bg-elev)' }}>
    <table style={{ width: '100%', borderCollapse: 'collapse' }}>{children}</table>
  </div>
);
const MKR_RANK_SIGNIFICADO = {
  D: { nome: 'Lead', detalhe: 'Gestor 10, 20 or 50', title: 'Lead. Gestor 10 Lead, 20 Interesse, or 50 Evento when the company has no demo yet.' },
  C1: { nome: 'Event with a demo', detalhe: 'Gestor stage 50', title: 'Opportunity. Gestor stage 50 Evento at a company that already has a demo or a later stage.' },
  C2: { nome: 'Demo planned', detalhe: 'Gestor stage 65', title: 'Opportunity. Gestor stage 65 Demo: demo or sample planned.' },
  C3: { nome: 'Demo completed', detalhe: 'No Gestor stage yet', title: 'Opportunity. Sample or demo completed. The Gestor has no stage for this step.' },
  B: { nome: 'Final offer', detalhe: 'Gestor 80 or 90', title: 'Final agreement. Gestor 80 or 90: waiting for a decision, final offer sent.' },
  A: { nome: 'Verbal order', detalhe: 'Gestor stage 92', title: 'Verbal order. Gestor stage 92: financing approved, credit check completed.' },
};
const MkrRank = ({ rank, sub }) => (
  <span style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', minWidth: 26, height: 20, padding: '0 6px', borderRadius: 5, fontSize: 11, fontWeight: 700, color: '#fff', background: MKR_RANK_COR[rank] || 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>{sub || rank}</span>
);
const MkrBarra = ({ valor, max, cor, alvo, altura = 8 }) => {
  const m = Math.max(Number(max) || 0, Number(valor) || 0, Number(alvo) || 0, 0.0001);
  return (
    <div style={{ position: 'relative', height: altura, background: 'var(--bg-sunken, #e8edf5)', borderRadius: altura / 2 }}>
      <div style={{ position: 'absolute', left: 0, top: 0, bottom: 0, width: `${Math.min(100, (Number(valor) || 0) / m * 100)}%`, background: cor, borderRadius: altura / 2, transition: 'width .4s' }} />
      {alvo != null && <div title="Target" style={{ position: 'absolute', left: `${Math.min(100, alvo / m * 100)}%`, top: -3, bottom: -3, width: 2, background: 'var(--text)' }} />}
    </div>
  );
};

// ── Overview ────────────────────────────────────────────────────────────────
const MkrCelula = ({ n, valor, forte }) => (
  n ? (
    <span style={{ display: 'inline-flex', flexDirection: 'column', alignItems: 'flex-end', lineHeight: 1.25 }}>
      <span style={{ fontWeight: forte ? 700 : 600 }}>{mkrN(n)}</span>
      <span style={{ fontSize: 10.5, color: 'var(--text-muted)' }}>{mkrEur(valor)}</span>
    </span>
  ) : <span style={{ color: 'var(--text-dim)' }}>—</span>
);

const MkrPipeline = ({ data, isMobile }) => {
  const p = data.pipeline;
  const a = data.alvos;
  const maxValor = Math.max(...p.escada.map(e => e.valor), 1);
  const alvoC = a.bp_restante != null ? a.bp_restante * a.cobertura_c : null;
  const okC = alvoC != null && a.pipeline_c_eur >= alvoC;
  const cols = ['D', 'C', 'B', 'A'];
  const linhaTotal = { segmento: 'Total', ...p };
  return (
    <AnCartao titulo="Open pipeline · Mimaki ranking" sub={`All open Mimaki opportunities today in Mimaki CRM ranks · ${mkrN(p.n)} opportunities · ${mkrN(p.pt)} Portugal · ${mkrN(p.es)} Spain · ${mkrN(p.direct)} Direct · ${mkrN(p.dealer)} Dealer`}
      direita={
        <span style={{ textAlign: 'right', whiteSpace: 'nowrap' }}>
          <span className="font-display" style={{ fontSize: 18, fontWeight: 700 }}>{mkrEur(p.valor)}</span>
          <div style={{ fontSize: 11, color: 'var(--text-muted)' }}>weighted {mkrEur(p.ponderado)}</div>
        </span>
      }>
      <div style={{ display: 'grid', gridTemplateColumns: isMobile ? 'repeat(3, 1fr)' : 'repeat(6, 1fr)', gap: 8 }}>
        {p.escada.map(e => {
          const sig = MKR_RANK_SIGNIFICADO[e.key] || { nome: e.key, detalhe: '', title: e.key };
          return (
          <div key={e.key} title={sig.title} style={{ border: '1px solid var(--border)', borderTop: `3px solid ${MKR_RANK_COR[e.rank]}`, borderRadius: 8, padding: '10px 10px 9px', opacity: e.n ? 1 : 0.5 }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
              <MkrRank rank={e.rank} sub={e.key} />
              <span style={{ fontSize: 10.5, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>{Math.round(e.prob * 100)}%</span>
            </div>
            <div style={{ fontSize: 12, fontWeight: 650, marginTop: 8, lineHeight: 1.25 }}>{sig.nome}</div>
            <div style={{ fontSize: 10.5, color: 'var(--text-muted)', lineHeight: 1.35, minHeight: 28, marginTop: 2 }}>{sig.detalhe}</div>
            <div className="font-display" style={{ fontSize: 20, fontWeight: 700, marginTop: 6 }}>{mkrN(e.n)}</div>
            <div style={{ fontSize: 11.5, fontWeight: 600, fontVariantNumeric: 'tabular-nums' }}>{mkrEur(e.valor)}</div>
            <div style={{ fontSize: 10.5, color: 'var(--text-muted)', marginBottom: 6 }}>weighted {mkrEur(e.ponderado)}</div>
            <MkrBarra valor={e.valor} max={maxValor} cor={MKR_RANK_COR[e.rank]} altura={5} />
          </div>
          );
        })}
      </div>
      <div style={{ display: 'flex', height: 10, borderRadius: 5, overflow: 'hidden', margin: '12px 0 4px', background: 'var(--bg-sunken, #e8edf5)' }}>
        {p.escada.filter(e => e.valor > 0).map(e => (
          <div key={e.key} title={`${e.key} · ${MKR_RANK_SIGNIFICADO[e.key]?.nome || ''} · ${mkrEur(e.valor)}`} style={{ width: `${e.valor / Math.max(p.valor, 1) * 100}%`, background: MKR_RANK_COR[e.rank] }} />
        ))}
      </div>
      <div style={{ fontSize: 10.5, color: 'var(--text-dim)', marginBottom: 12 }}>Share of open value by rank. The percentage on each card is the Mimaki probability used in the weighted value.</div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap', padding: '10px 12px', borderRadius: 8, marginBottom: 12, background: `color-mix(in oklch, ${okC ? 'var(--success, #16a34a)' : 'var(--danger, #dc2626)'} 7%, transparent)` }}>
        <span style={{ fontSize: 10, fontWeight: 700, letterSpacing: '.06em', textTransform: 'uppercase', fontFamily: 'var(--font-mono)', color: okC ? 'var(--success, #16a34a)' : 'var(--danger, #dc2626)' }}>5× rule</span>
        <span style={{ fontSize: 12 }}>
          Rank C pipeline <b>{mkrEur(a.pipeline_c_eur)}</b>
          {a.bp_restante != null
            ? <> = <b>{a.cobertura_c_real != null ? `${a.cobertura_c_real}×` : '—'}</b> the remaining budget {mkrEur(a.bp_restante)} ({a.bp_restante_equipas.join(', ')}) · target {mkrEur(alvoC)} ({a.cobertura_c}×) · expected at 20% {mkrEur(a.pipeline_c_eur * a.conv_c)}</>
            : <> · no BP in comercial_bp_targets for this selection</>}
        </span>
        <span style={{ fontSize: 12, marginLeft: isMobile ? 0 : 'auto' }}>A–C expected at 30%: <b>{mkrEur(a.esperado_ac_eur)}</b></span>
      </div>

      <MkrTabela>
        <thead>
          <tr>
            <MkrTh>Segment</MkrTh>
            {cols.map(k => <MkrTh key={k} right><MkrRank rank={k} /></MkrTh>)}
            <MkrTh right>Open</MkrTh><MkrTh right>Weighted</MkrTh>
            <MkrTh right>Portugal</MkrTh><MkrTh right>Spain</MkrTh><MkrTh right>Direct</MkrTh><MkrTh right>Dealer</MkrTh>
          </tr>
        </thead>
        <tbody>
          {[...p.segmentos, linhaTotal].map(s => {
            const total = s.segmento === 'Total';
            const st = total ? { fontWeight: 700, background: 'var(--bg-sunken, #f1f5f9)' } : undefined;
            return (
              <tr key={s.segmento}>
                <MkrTd style={{ fontWeight: total ? 700 : 600, ...st }}>{s.segmento}</MkrTd>
                {cols.map(k => <MkrTd key={k} right style={st}><MkrCelula n={s.por_rank[k].n} valor={s.por_rank[k].valor} forte={total} /></MkrTd>)}
                <MkrTd right style={st}><MkrCelula n={s.n} valor={s.valor} forte /></MkrTd>
                <MkrTd right style={{ fontWeight: 600, ...st }}>{mkrEur(s.ponderado)}</MkrTd>
                <MkrTd right muted style={st}>{mkrN(s.pt)}</MkrTd><MkrTd right muted style={st}>{mkrN(s.es)}</MkrTd>
                <MkrTd right muted style={st}>{mkrN(s.direct)}</MkrTd><MkrTd right muted style={st}>{mkrN(s.dealer)}</MkrTd>
              </tr>
            );
          })}
        </tbody>
      </MkrTabela>
      <div style={{ fontSize: 10.5, color: 'var(--text-dim)', marginTop: 8, lineHeight: 1.5 }}>
        Weighted = value × Mimaki rank probability (D 30%, C1 50%, C2 60%, C3 70%, B 80%, A 90%). Dealer = Mimaki PA team.
        SG 3.2m+ is only SWJ-320EA and UJV55-320. TS330-3200DS is TA. UJV300DTF-75 is Signage 1.8m.
        {p.antigas.n > 0 && ` ${mkrN(p.antigas.n)} open opportunities (${mkrEur(p.antigas.valor)}) started more than 12 months ago.`}
        {p.sem_valor > 0 && ` ${mkrN(p.sem_valor)} have no value in the Gestor.`}
      </div>
    </AnCartao>
  );
};

const MKR_FUNIL_CORES = ['#64748B', '#F59E0B', '#3859D0', '#7B61FF', '#16A34A'];
function mkrPctFunil(part, whole) {
  const p = whole ? (Number(part) || 0) / whole * 100 : 0;
  return p > 0 && p < 10 ? `${p.toFixed(1)}%` : `${Math.round(p)}%`;
}

const MkrModelos = ({ data }) => {
  const grupos = data.modelos_abertos || [];
  if (!grupos.length) return null;
  const cols = ['D', 'C', 'B', 'A'];
  const total = grupos.reduce((acc, g) => {
    acc.n += g.n;
    for (const k of cols) acc.por_rank[k] += g.por_rank[k];
    return acc;
  }, { n: 0, por_rank: { D: 0, C: 0, B: 0, A: 0 } });
  const n0 = (v) => (v ? mkrN(v) : '—');
  const celulaC = (m) => {
    const sub = m.por_sub;
    const nota = sub ? [`C1 ${mkrN(sub.C1)}`, `C2 ${mkrN(sub.C2)}`, `C3 ${mkrN(sub.C3)}`].filter(x => !x.endsWith(' 0')).join(' · ') : '';
    return <MkrTd right title={nota || undefined}>{n0(m.por_rank.C)}</MkrTd>;
  };
  return (
    <AnCartao titulo="Open pipeline by model" sub="Open opportunities today, one row per model. Each opportunity is one machine. D, C, B and A are the Mimaki ranks.">
      <MkrTabela>
        <thead>
          <tr>
            <MkrTh>Model</MkrTh>
            {cols.map(k => <MkrTh key={k} right><MkrRank rank={k} /></MkrTh>)}
            <MkrTh right>Open</MkrTh>
          </tr>
        </thead>
        <tbody>
          {grupos.map(g => (
            <React.Fragment key={g.segmento}>
              <tr>
                <td colSpan={6} style={{ padding: '8px 10px', fontSize: 12, fontWeight: 700, borderTop: '1px solid var(--border)', background: 'var(--bg-sunken, #f1f5f9)' }}>
                  {g.segmento}
                  <span style={{ fontWeight: 500, color: 'var(--text-muted)', marginLeft: 8 }}>{mkrN(g.n)} open</span>
                </td>
              </tr>
              {g.modelos.map(m => (
                <tr key={`${g.segmento}:${m.modelo}`}>
                  <MkrTd style={{ fontWeight: 600 }}>{m.modelo}</MkrTd>
                  <MkrTd right>{n0(m.por_rank.D)}</MkrTd>
                  {celulaC(m)}
                  <MkrTd right>{n0(m.por_rank.B)}</MkrTd>
                  <MkrTd right>{n0(m.por_rank.A)}</MkrTd>
                  <MkrTd right style={{ fontWeight: 700 }}>{mkrN(m.n)}</MkrTd>
                </tr>
              ))}
            </React.Fragment>
          ))}
          <tr>
            <MkrTd style={{ fontWeight: 700, background: 'var(--bg-sunken, #f1f5f9)' }}>Total</MkrTd>
            {cols.map(k => <MkrTd key={k} right style={{ fontWeight: 700, background: 'var(--bg-sunken, #f1f5f9)' }}>{mkrN(total.por_rank[k])}</MkrTd>)}
            <MkrTd right style={{ fontWeight: 700, background: 'var(--bg-sunken, #f1f5f9)' }}>{mkrN(total.n)}</MkrTd>
          </tr>
        </tbody>
      </MkrTabela>
      <div style={{ fontSize: 10.5, color: 'var(--text-dim)', marginTop: 8, lineHeight: 1.5 }}>
        One open opportunity counts as one machine. The C column adds C1, C2 and C3. Rank D is a lead.
      </div>
    </AnCartao>
  );
};

const MkrFunilMimaki = ({ data, isMobile }) => {
  const Corpo = window.ResFunilCorpo;
  const f = data.funil_mimaki || [];
  if (!Corpo || f.length < 2 || typeof resFunilPath !== 'function') return null;
  const r = data.resumo;
  const a = data.alvos;
  const d = f[0];
  const c = f[1];
  const s = f[f.length - 1];
  const taxa = d.real ? s.real / d.real * 100 : 0;
  const convC = c.real ? s.real / c.real * 100 : null;
  const okC = convC != null && convC >= a.conv_c * 100;
  const etapas = f.map(e => ({ key: e.key, label: `${e.key} · ${e.label.replace(/^\w+ · /, '')}`, real: e.real, valor: e.valor, _e: e }));
  const chip = (cor, titulo, valor, nota) => (
    <div style={{ border: '1px solid var(--border)', borderLeft: `3px solid ${cor}`, borderRadius: 8, padding: '7px 10px', minWidth: 170 }}>
      <div style={{ fontSize: 9.5, fontWeight: 600, letterSpacing: '.05em', textTransform: 'uppercase', color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>{titulo}</div>
      <div style={{ fontSize: 15, fontWeight: 700, color: cor, fontVariantNumeric: 'tabular-nums', marginTop: 1 }}>{valor}</div>
      <div style={{ fontSize: 10.5, color: 'var(--text-muted)' }}>{nota}</div>
    </div>
  );
  return (
    <div className="card an-card" style={{ padding: '18px 18px 10px', borderRadius: 10, border: '1px solid var(--border)', background: 'var(--bg-elev)' }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', gap: 16, alignItems: 'flex-end', flexWrap: 'wrap' }}>
        <div>
          <div style={{ fontSize: 10, fontWeight: 600, letterSpacing: '.06em', textTransform: 'uppercase', color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', marginBottom: 6 }}>Mimaki leads ranking funnel</div>
          <div className="font-display" style={{ fontSize: 40, fontWeight: 650, letterSpacing: '-0.03em', lineHeight: 1, fontVariantNumeric: 'tabular-nums' }}>{mkrPctFunil(s.real, d.real)}</div>
          <div style={{ fontSize: 13, color: 'var(--text-muted)', marginTop: 6 }}>
            {mkrN(s.real)} sold of {mkrN(d.real)} opportunities started · {mkrEur(s.valor)} · {data.periodo.label} · {mkrN(r.open)} open · {mkrN(r.lost)} lost
          </div>
        </div>
        <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
          {chip(okC ? 'var(--success, #16a34a)' : 'var(--danger, #dc2626)', 'Rank C → Sold', convC == null ? '—' : mkrPct(convC), `Mimaki target ${Math.round(a.conv_c * 100)}%`)}
          {chip('var(--ai-500, #3859D0)', 'Open A–C × 30%', mkrEur(a.esperado_ac_eur), `${mkrN(a.pipeline_ac_n)} open · ${mkrEur(a.pipeline_ac_eur)}`)}
          {chip(a.cobertura_c_real != null && a.cobertura_c_real >= a.cobertura_c ? 'var(--success, #16a34a)' : 'var(--danger, #dc2626)', 'C pipeline vs budget', a.cobertura_c_real != null ? `${a.cobertura_c_real}×` : '—', `Mimaki rule ${a.cobertura_c}×`)}
        </div>
      </div>
      <Corpo etapas={etapas} H={isMobile ? 150 : 190} gid="mkr-funil" cores={MKR_FUNIL_CORES} pathFn={resFunilPath} pctFn={mkrPctFunil} pctFace="anterior"
        ariaLabel="Mimaki leads ranking funnel" topSvg={62}
        rotulos={{ total: 'Reached', anterior: 'From previous rank', primeira: 'From D', valor: 'Value', fmtValor: mkrEur }}
        extraCelula={(e) => (
          <div style={{ fontSize: 10.5, color: 'var(--text-muted)', lineHeight: 1.35, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
            <div style={{ overflow: 'hidden', textOverflow: 'ellipsis' }} title={`Value reached ${mkrEur(e.valor)} · Mimaki probability ${e._e.prob}`}>{mkrEur(e.valor)} · {e._e.prob}</div>
            <div style={{ overflow: 'hidden', textOverflow: 'ellipsis' }}>{e.key === 'S' ? `${mkrN(e._e.aqui_n)} sold` : `${mkrN(e._e.aqui_n)} open · ${mkrEur(e._e.aqui_valor)}`}</div>
          </div>
        )} />
      <div style={{ fontSize: 10.5, color: 'var(--text-dim)', margin: '4px 0 6px', lineHeight: 1.5 }}>
        Each rank counts the opportunities started in the period that are at that rank or higher today. Lost opportunities count in D only, because the Gestor does not keep the highest stage they reached, so conversion from C onwards reads high.
      </div>
    </div>
  );
};

const MkrMeses = ({ meses }) => {
  const [over, setOver] = React.useState(null);
  const max = Math.max(...meses.map(m => Math.max(m.vendas, m.bp)), 1);
  const maxN = Math.max(...meses.map(m => m.novas), 1);
  const h = 140;
  return (
    <AnCartao titulo="Monthly trend" sub="Sales vs BP (bars) · new opportunities and wins per month"
      direita={<span style={{ display: 'flex', gap: 12, fontSize: 10.5, color: 'var(--text-muted)' }}>
        <span><span style={{ display: 'inline-block', width: 9, height: 9, borderRadius: 2, background: 'var(--ai-500, #3859D0)', marginRight: 4 }} />Sales</span>
        <span><span style={{ display: 'inline-block', width: 9, height: 9, borderRadius: 2, border: '1.5px solid var(--text)', marginRight: 4 }} />BP</span>
      </span>}>
      <div style={{ display: 'grid', gridTemplateColumns: `repeat(${meses.length}, 1fr)`, gap: 6, alignItems: 'end' }}>
        {meses.map(m => (
          <div key={m.mes} onMouseEnter={() => setOver(m.mes)} onMouseLeave={() => setOver(null)} style={{ display: 'flex', flexDirection: 'column', alignItems: 'stretch', gap: 4, cursor: 'default' }}>
            <div style={{ position: 'relative', height: h, display: 'flex', alignItems: 'flex-end', justifyContent: 'center' }}>
              {over === m.mes && (
                <div style={{ position: 'absolute', bottom: h + 4, left: '50%', transform: 'translateX(-50%)', background: 'var(--bg-elev)', border: '1px solid var(--border)', borderRadius: 6, padding: '6px 8px', fontSize: 11, whiteSpace: 'nowrap', boxShadow: '0 4px 14px rgba(0,0,0,.12)', zIndex: 2 }}>
                  <div style={{ fontWeight: 650 }}>{mkrMesLabel(m.mes)}</div>
                  <div>Sales {mkrEur(m.vendas)} · BP {mkrEur(m.bp)}</div>
                  <div>{m.novas} new · {m.won} won ({mkrEur(m.won_eur)})</div>
                </div>
              )}
              <div style={{ width: '62%', height: `${m.vendas / max * 100}%`, background: 'var(--ai-500, #3859D0)', borderRadius: '3px 3px 0 0', opacity: over && over !== m.mes ? 0.5 : 1 }} />
              {m.bp > 0 && <div style={{ position: 'absolute', left: '12%', right: '12%', bottom: `${m.bp / max * 100}%`, height: 0, borderTop: '2px solid var(--text)' }} />}
            </div>
            <div style={{ height: 4, background: 'var(--bg-sunken, #e8edf5)', borderRadius: 2 }}>
              <div style={{ width: `${m.novas / maxN * 100}%`, height: '100%', background: '#94A3B8', borderRadius: 2 }} />
            </div>
            <div style={{ textAlign: 'center', fontSize: 10, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>{mkrMesLabel(m.mes)}</div>
            <div style={{ textAlign: 'center', fontSize: 10, color: 'var(--text-dim)' }}>{m.novas}/{m.won}</div>
          </div>
        ))}
      </div>
      <div style={{ fontSize: 10.5, color: 'var(--text-dim)', marginTop: 8 }}>Grey bar and numbers under each month: new opportunities / wins. BP exists for Mimaki PT and GC only. The current month is still open.</div>
    </AnCartao>
  );
};

const MkrOrigens = ({ data, isMobile, mudarF }) => {
  const os = data.origens_equipa || [];
  const total = os.reduce((s, o) => s + o.n, 0);
  const novas = os.flatMap(o => (o.equipas || []).filter(e => e.nova).map(e => `${o.label} · ${e.label}`));
  if (!os.length) return null;
  const pickOrigem = (k) => mudarF({ origem: data.origem === k ? '' : k });
  const pickEquipa = (k) => mudarF({ equipa: data.equipa === k ? '' : k });
  const sep = { borderLeft: '1px solid var(--border)' };
  const corWr = (v) => (v == null ? undefined : v >= ALVO_WR_MKR ? 'var(--success, #16a34a)' : v >= 14 ? 'var(--warning, #f59e0b)' : 'var(--danger, #dc2626)');
  const metricas = (o, forte) => {
    const delta = o.n_ant ? (o.n - o.n_ant) / o.n_ant * 100 : null;
    return (
      <>
        <MkrTd right style={sep}><b>{mkrN(o.n)}</b> <span style={{ fontSize: 10.5, color: 'var(--text-muted)' }}>{mkrPct(o.share)}</span></MkrTd>
        <MkrTd right muted>{mkrN(o.n_ant)}{delta != null && <span style={{ fontSize: 10.5, marginLeft: 4, color: delta >= 0 ? 'var(--success, #16a34a)' : 'var(--danger, #dc2626)' }}>{delta >= 0 ? '+' : '−'}{Math.abs(Math.round(delta))}%</span>}</MkrTd>
        <MkrTd right style={{ fontWeight: forte ? 700 : 600 }}>{mkrN(o.won)}</MkrTd>
        <MkrTd right muted>{mkrN(o.lost)}</MkrTd>
        <MkrTd right style={{ color: corWr(o.win_rate), fontWeight: 600 }} title={`${mkrN(o.won)} won ÷ ${mkrN(o.won + o.lost)} closed · ${mkrN(o.open)} still open`}>{mkrPct(o.win_rate)}</MkrTd>
        <MkrTd right muted title={`${mkrN(o.chegou_c)} of ${mkrN(o.n)} are today at rank C or above, or won`}>{mkrPct(o.pct_c_mais)}</MkrTd>
        <MkrTd right style={sep}><MkrCelula n={o.won_periodo_n} valor={o.won_periodo_eur} forte /></MkrTd>
        <MkrTd right style={sep}><MkrCelula n={o.aberto.n} valor={o.aberto.valor} /></MkrTd>
        <MkrTd right muted>{mkrEur(o.aberto.ponderado)}</MkrTd>
        {!isMobile && (
          <MkrTd style={{ minWidth: 110 }}>
            <div style={{ display: 'flex', gap: 1, height: 8, borderRadius: 3, overflow: 'hidden', background: 'var(--bg-sunken, #e8edf5)' }}>
              {['D', 'C', 'B', 'A'].filter(k => o.aberto.por_rank[k].valor > 0).map(k => (
                <div key={k} title={`${k} · ${mkrN(o.aberto.por_rank[k].n)} · ${mkrEur(o.aberto.por_rank[k].valor)}`} style={{ width: `${o.aberto.por_rank[k].valor / Math.max(o.aberto.valor, 1) * 100}%`, background: MKR_RANK_COR[k] }} />
              ))}
            </div>
          </MkrTd>
        )}
      </>
    );
  };
  return (
    <AnCartao titulo="Generated by"
      sub={`Opportunities started in ${data.periodo.label}. The source is who generated the opportunity. The team is the Gestor team it is assigned to. Click a source or a team to filter the report.`}>
      <div style={{ display: 'flex', gap: 2, height: 12, borderRadius: 5, overflow: 'hidden', marginBottom: 6 }}>
        {os.filter(o => o.n > 0).map(o => (
          <div key={o.key} title={`${o.label} · ${mkrN(o.n)} (${mkrPct(o.share)})`} onClick={() => pickOrigem(o.key)}
            style={{ width: `${o.n / Math.max(total, 1) * 100}%`, background: o.cor, cursor: 'pointer', opacity: data.origem && data.origem !== o.key ? 0.3 : 1 }} />
        ))}
      </div>
      <div style={{ display: 'flex', gap: '2px 12px', flexWrap: 'wrap', fontSize: 10.5, color: 'var(--text-muted)', marginBottom: 12 }}>
        {os.map(o => <span key={o.key}><span style={{ display: 'inline-block', width: 8, height: 8, borderRadius: 2, background: o.cor, marginRight: 4 }} />{o.label} {mkrPct(o.share)}</span>)}
      </div>
      <MkrTabela>
        <thead>
          <tr>
            <MkrTh style={{ borderBottom: 'none' }} />
            <MkrTh style={{ borderBottom: 'none' }} />
            <MkrTh colSpan={6} style={{ textAlign: 'center', borderLeft: '1px solid var(--border)' }}>{`Started in ${data.periodo.label}`}</MkrTh>
            <MkrTh style={{ textAlign: 'center', borderLeft: '1px solid var(--border)' }}>Closed in period</MkrTh>
            <MkrTh colSpan={isMobile ? 2 : 3} style={{ textAlign: 'center', borderLeft: '1px solid var(--border)' }}>Open today</MkrTh>
          </tr>
          <tr>
            <MkrTh>Source</MkrTh>
            <MkrTh>Team</MkrTh>
            <MkrTh right style={{ borderLeft: '1px solid var(--border)' }}>Opportunities</MkrTh><MkrTh right>Same period LY</MkrTh>
            <MkrTh right>Won</MkrTh><MkrTh right>Lost</MkrTh><MkrTh right>Win rate</MkrTh><MkrTh right>At C+ or won</MkrTh>
            <MkrTh right style={{ borderLeft: '1px solid var(--border)' }}>Won (any start)</MkrTh>
            <MkrTh right style={{ borderLeft: '1px solid var(--border)' }}>Open</MkrTh><MkrTh right>Weighted</MkrTh>
            {!isMobile && <MkrTh style={{ minWidth: 110 }}>By rank</MkrTh>}
          </tr>
        </thead>
        <tbody>
          {os.map(o => {
            const origemActiva = data.origem === o.key;
            const origemApagada = !!data.origem && !origemActiva;
            const nomesEquipa = new Set((o.equipas || []).map(e => e.label));
            const dets = (o.detalhe || []).filter(d => d.det !== o.label && !nomesEquipa.has(d.det) && (o.detalhe.length > 1 || d.nova));
            return (
              <React.Fragment key={o.key}>
                <tr className="mkr-linha" onClick={() => pickOrigem(o.key)} title={origemActiva ? 'Click again to show all sources' : `Filter the report by ${o.label}`}
                  style={{ cursor: 'pointer', opacity: origemApagada ? 0.4 : 1, background: `color-mix(in oklch, ${o.cor} ${origemActiva ? 12 : 6}%, transparent)` }}>
                  <MkrTd style={{ fontWeight: 700, verticalAlign: 'top', whiteSpace: 'normal', minWidth: 168 }}>
                    <span style={{ display: 'inline-block', width: 9, height: 9, borderRadius: 2, background: o.cor, marginRight: 7 }} />{o.label}
                    {dets.length > 0 && (
                      <div style={{ fontSize: 10.5, fontWeight: 500, color: 'var(--text-muted)', marginTop: 3, lineHeight: 1.4 }}>
                        {dets.map((d, i) => <span key={d.det}>{i ? ' · ' : ''}{d.det} {mkrN(d.n)}{d.nova && <span title="No opportunities with this source in the same period last year" style={{ marginLeft: 3, fontSize: 9, fontWeight: 700, color: 'var(--ai-500, #3859D0)' }}>NEW</span>}</span>)}
                      </div>
                    )}
                  </MkrTd>
                  <MkrTd muted style={{ verticalAlign: 'top' }} />
                  {metricas(o, true)}
                </tr>
                {(o.equipas || []).map(e => {
                  const equipaActiva = data.equipa === e.key;
                  const equipaApagada = !!data.equipa && !equipaActiva;
                  return (
                    <tr key={`${o.key}:${e.key}`} className="mkr-linha" onClick={() => pickEquipa(e.key)} title={equipaActiva ? 'Click again to show all teams' : `Filter the report by ${e.label}`}
                      style={{ cursor: 'pointer', opacity: origemApagada || equipaApagada ? 0.4 : 1, background: equipaActiva && !origemApagada ? `color-mix(in oklch, ${e.cor} 8%, transparent)` : undefined }}>
                      <MkrTd />
                      <MkrTd style={{ fontWeight: 600 }}>
                        <span style={{ display: 'inline-block', width: 8, height: 8, borderRadius: 2, background: e.cor, marginRight: 7 }} />{e.label}
                        {e.nova && <span title="No opportunities from this source for this team in the same period last year" style={{ marginLeft: 6, fontSize: 9, fontWeight: 700, color: 'var(--ai-500, #3859D0)' }}>NEW</span>}
                      </MkrTd>
                      {metricas(e, false)}
                    </tr>
                  );
                })}
              </React.Fragment>
            );
          })}
        </tbody>
      </MkrTabela>
      <div style={{ fontSize: 10.5, color: 'var(--text-dim)', marginTop: 8, lineHeight: 1.55 }}>
        <b>Started in the period</b>: opportunities by start date. The percentage on a source is its share of all starts. The percentage on a team is its share of that source. Win rate = won ÷ (won + lost) of those starts; the open ones are still pending. At C+ or won = share that is today at rank C or above or won; lost opportunities count as D because the Gestor does not keep the highest stage they reached.
        {' '}<b>Closed in the period</b>: Closed Won by close date, whatever the start date, so it can include opportunities started last year.
        {' '}The line under a source is the Gestor source inside that group.
        {novas.length > 0 && <>{' '}NEW on a team = that team had no opportunities from this source in the same period last year ({novas.join(', ')}).</>}
      </div>
    </AnCartao>
  );
};

const MkrOverview = ({ data, isMobile, mudarF }) => {
  const Kpi = window.ResKpi;
  const r = data.resumo;
  const eqs = data.equipa ? data.equipas.filter(e => e.key === data.equipa) : data.equipas;
  const vendas = eqs.reduce((s, e) => s + (Number(e.vendas) || 0), 0);
  const comBp = eqs.filter(e => e.bp_periodo != null);
  const vendasBp = comBp.reduce((s, e) => s + (Number(e.vendas) || 0), 0);
  const bp = comBp.reduce((s, e) => s + (Number(e.bp_periodo) || 0), 0);
  const pipelineAberto = data.pipeline.valor;
  return (
    <>
      <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr 1fr' : 'repeat(5, 1fr)', gap: 10, marginBottom: 14 }}>
        <Kpi label="Opportunities" value={mkrN(r.n)} sub={`${mkrN(r.pt)} Portugal · ${mkrN(r.es)} Spain`} />
        <Kpi label="Won" value={mkrN(r.won)} sub={`${mkrEur(r.won_eur)} · ${mkrN(r.lost)} lost`} />
        <Kpi label="Win rate" value={mkrPct(r.win_rate)} sub="Won ÷ (won + lost)" light={r.win_rate == null ? null : r.win_rate >= 20 ? 'verde' : r.win_rate >= 14 ? 'amarelo' : 'vermelho'} />
        <Kpi label="Open pipeline" value={mkrEur(pipelineAberto)} sub={`${mkrN(data.pipeline.n)} open today · weighted ${mkrEur(data.pipeline.ponderado)}`} />
        <Kpi label="Sales in period" value={mkrEur(vendas)} sub={bp > 0 ? `${mkrPct(vendasBp / bp * 100)} of BP ${mkrEur(bp)} (${comBp.map(e => e.label.replace('Mimaki ', '')).join('+')})` : 'No BP for this selection'} light={bp > 0 ? (vendasBp / bp >= 1 ? 'verde' : vendasBp / bp >= 0.85 ? 'amarelo' : 'vermelho') : null} />
      </div>
      <div style={{ marginBottom: 14 }}><MkrFunilMimaki data={data} isMobile={isMobile} /></div>
      <div style={{ marginBottom: 14 }}><MkrOrigens data={data} isMobile={isMobile} mudarF={mudarF} /></div>
      <div style={{ marginBottom: 14 }}><MkrPipeline data={data} isMobile={isMobile} /></div>
      <div style={{ marginBottom: 14 }}><MkrModelos data={data} /></div>
      <div style={{ marginBottom: 14 }}><MkrLeadsMimaki data={data} isMobile={isMobile} /></div>
      <MkrMeses meses={data.meses} />
    </>
  );
};

// ── Teams ───────────────────────────────────────────────────────────────────
const MKR_MESBARS_ROTULOS = { corrente: 'current month', mensal: 'monthly', acumulado: 'cumulative', ritmo: 'At current pace', ritmoLegenda: 'current pace' };
const MKR_KPI_ROTULOS = { actual: 'Actual', obj: 'Target' };
const MKR_PAISES = ['Iberia', 'Portugal', 'Spain'];
const ALVO_WR_MKR = 20;
function mkrUn(v) {
  if (v == null || !Number.isFinite(Number(v))) return '—';
  const n = Number(v);
  return n.toLocaleString('en-GB', { maximumFractionDigits: Math.abs(n) < 100 && n % 1 ? 1 : 0 });
}
const mkrX = (v) => (v == null ? '—' : `${Number(v).toFixed(1)}×`);

const MkrToggle = ({ value, onChange, opcoes }) => (
  <span style={{ display: 'inline-flex', border: '1px solid var(--border)', borderRadius: 7, overflow: 'hidden', flexShrink: 0 }}>
    {opcoes.map(o => (
      <button key={o} type="button" onClick={() => onChange(o)}
        style={{ border: 'none', padding: '4px 10px', fontSize: 11.5, fontWeight: value === o ? 650 : 500, cursor: 'pointer', fontFamily: 'inherit', background: value === o ? 'var(--ai-500, #3859D0)' : 'transparent', color: value === o ? '#fff' : 'var(--text-muted)' }}>{o}</button>
    ))}
  </span>
);

const MkrMiniStat = ({ label, valor, sub }) => (
  <div style={{ minWidth: 0 }}>
    <div style={{ fontSize: 9.5, color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '.04em', fontFamily: 'var(--font-mono)' }}>{label}</div>
    <div style={{ fontSize: 13, fontWeight: 650, fontVariantNumeric: 'tabular-nums', whiteSpace: 'nowrap' }}>{valor}</div>
    {sub && <div style={{ fontSize: 10.5, color: 'var(--text-muted)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{sub}</div>}
  </div>
);

const MkrCartaoEquipa = ({ e, activo, apagado, onClick }) => {
  const temBp = Number(e.bp_periodo) > 0;
  return (
    <button type="button" onClick={onClick} aria-pressed={activo} title={activo ? 'Click again to show all teams' : `Filter the report by ${e.label}`}
      style={{
        textAlign: 'left', cursor: 'pointer', font: 'inherit', color: 'inherit', background: 'var(--bg-elev)', minWidth: 0,
        border: `1px solid ${activo ? e.cor : 'var(--border)'}`, borderTop: `3px solid ${e.cor}`, borderRadius: 10, padding: '12px 12px 10px',
        opacity: apagado ? 0.5 : 1, boxShadow: activo ? `0 0 0 3px color-mix(in oklch, ${e.cor} 22%, transparent)` : 'none', transition: 'opacity .15s, box-shadow .15s',
      }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 6 }}>
        <span className="font-display" style={{ fontSize: 14, fontWeight: 700 }}>{e.label}</span>
        <span style={{ fontSize: 9.5, fontWeight: 600, fontFamily: 'var(--font-mono)', color: e.cor, textTransform: 'uppercase', letterSpacing: '.04em' }}>{e.canal}</span>
      </div>
      <div style={{ fontSize: 10.5, color: 'var(--text-muted)', marginBottom: 4 }}>{e.sub}</div>
      <AnGauge size={128}
        pct={temBp ? e.atingido_pct : (e.win_rate == null ? null : e.win_rate / (ALVO_WR_MKR) * 100)}
        valor={temBp ? mkrPct(e.atingido_pct) : mkrPct(e.win_rate)}
        label={temBp ? 'of BP in period' : 'Win rate'}
        sub={temBp ? `${mkrEur(e.vendas)} of ${mkrEur(e.bp_periodo)}` : `target ${ALVO_WR_MKR}% · no € BP`} />
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8px 10px', marginTop: 10, paddingTop: 8, borderTop: '1px solid var(--border)' }}>
        <MkrMiniStat label="Sales" valor={mkrEur(e.vendas)} sub={`LY ${mkrEur(e.vendas_ant)}`} />
        <MkrMiniStat label="Open" valor={mkrEur(e.pipeline_aberto_eur)} sub={`weighted ${mkrEur(e.ponderado_eur)}`} />
        <MkrMiniStat label="Won / opps" valor={`${mkrN(e.won)} / ${mkrN(e.n)}`} sub={temBp ? `win rate ${mkrPct(e.win_rate)}` : `${mkrN(e.lost)} lost`} />
        <MkrMiniStat label="Rank C → S" valor={mkrPct(e.conv_c_s)} sub={`C coverage ${mkrX(e.cobertura_c_real)}`} />
      </div>
    </button>
  );
};

const MkrPilhaRank = ({ ranks }) => {
  const ks = ['D', 'C', 'B', 'A'];
  const total = ks.reduce((s, k) => s + (Number(ranks?.[k]?.valor) || 0), 0);
  if (!total) return <span style={{ fontSize: 11, color: 'var(--text-dim)' }}>No open value</span>;
  return (
    <div style={{ minWidth: 0 }}>
      <div style={{ display: 'flex', gap: 2, height: 10, borderRadius: 4, overflow: 'hidden' }}>
        {ks.filter(k => ranks[k].valor > 0).map(k => (
          <div key={k} title={`Rank ${k} · ${mkrN(ranks[k].n)} open · ${mkrEur(ranks[k].valor)}`} style={{ width: `${ranks[k].valor / total * 100}%`, background: MKR_RANK_COR[k] }} />
        ))}
      </div>
      <div style={{ display: 'flex', gap: '2px 9px', flexWrap: 'wrap', fontSize: 10.5, color: 'var(--text-muted)', marginTop: 4 }}>
        {ks.map(k => (
          <span key={k} style={{ whiteSpace: 'nowrap', opacity: ranks[k].n ? 1 : 0.5 }}>
            <span style={{ display: 'inline-block', width: 8, height: 8, borderRadius: 2, background: MKR_RANK_COR[k], marginRight: 4 }} />{k} {mkrN(ranks[k].n)} · {mkrEur(ranks[k].valor)}
          </span>
        ))}
      </div>
    </div>
  );
};

const MkrVendasBp = ({ data }) => {
  const eqs = data.equipa ? data.equipas.filter(e => e.key === data.equipa) : data.equipas;
  const comBp = eqs.filter(e => Array.isArray(e.bp_mensal));
  const lista = comBp.length ? comBp : eqs;
  const soma = (k) => Array.from({ length: 12 }, (_, i) => lista.reduce((s, e) => s + (Number(e[k]?.[i]) || 0), 0));
  const snapMes = data.fontes?.snapshot_primavera?.mes_max;
  const fimMes = Number(String(data.periodo.fim).slice(5, 7));
  const mes = lista.every(e => e.fonte_vendas === 'primavera') && snapMes ? Math.min(Number(snapMes), fimMes) : fimMes;
  const nomes = lista.map(e => e.label.replace('Mimaki ', '')).join(' + ');
  return (
    <AnCartao titulo={comBp.length ? 'Sales vs BP by month' : 'Sales by month'}
      sub={comBp.length
        ? `${nomes} · Primavera snapshot (comercial_ytd_snap) against comercial_bp_targets`
        : `${nomes} · Gestor Closed Won value by close date · no € BP for this team`}>
      <AnMesBars bp={comBp.length ? soma('bp_mensal') : new Array(12).fill(0)} real={soma('vendas_mensal')} mes={mes} altura={200} fmt={mkrEur}
        nomes={{ a: 'BP', b: 'Sales' }} rotulos={MKR_MESBARS_ROTULOS} />
      {comBp.length > 0 && comBp.length < eqs.length && (
        <div style={{ fontSize: 10.5, color: 'var(--text-dim)', marginTop: 6 }}>€ BP exists for {nomes} only. Spain, Partners and AllDecor follow the units BP in the machines chart.</div>
      )}
    </AnCartao>
  );
};

const MkrMaquinasMes = ({ eq, pais, setPais, mes }) => {
  const t = eq.paises[pais].TOTAL;
  return (
    <AnCartao titulo="Machines sold vs equipment BP by month" sub={`${pais} · units · BP Studio FY${String(eq.ano).slice(2)} against Gestor Closed Won`}
      direita={<MkrToggle value={pais} onChange={setPais} opcoes={MKR_PAISES} />}>
      <AnMesBars bp={t.bp_mensal} real={t.won_mensal} mes={mes} altura={200} fmt={mkrUn} nomes={{ a: 'BP units', b: 'Sold' }} rotulos={MKR_MESBARS_ROTULOS} />
    </AnCartao>
  );
};

const MkrEquipamentosSeg = ({ eq, pais, setPais, isMobile }) => {
  const p = eq.paises[pais];
  const t = p.TOTAL;
  const q = eq.trimestre_seguinte;
  const modelos = (() => {
    const l = eq.modelos.filter(m => pais === 'Iberia' || m.pais === pais);
    if (pais !== 'Iberia') return l;
    const by = new Map();
    for (const m of l) {
      const k = m.modelo.toUpperCase();
      const a = by.get(k) || { ...m, bp_qty_ano: 0, bp_qty: 0, won_qty: 0, won_eur: 0 };
      a.bp_qty_ano += m.bp_qty_ano; a.bp_qty += m.bp_qty; a.won_qty += m.won_qty; a.won_eur += m.won_eur;
      by.set(k, a);
    }
    return [...by.values()].map(m => ({ ...m, gap_qty: m.won_qty - m.bp_qty })).sort((a, b) => a.gap_qty - b.gap_qty);
  })();
  const pctCor = (v) => (v == null ? 'var(--text)' : v >= 100 ? 'var(--success, #16a34a)' : v >= 85 ? 'var(--warning, #f59e0b)' : 'var(--danger, #dc2626)');
  return (
    <AnCartao titulo="Mimaki machines vs equipment BP by segment"
      sub={`${pais} · BP Studio units (bp_objectivos, active version) · € BP = units × Iberia ASP · company level, all Mimaki teams`}
      direita={<MkrToggle value={pais} onChange={setPais} opcoes={MKR_PAISES} />}>
      <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr 1fr' : 'repeat(4, 1fr)', gap: 10, marginBottom: 14 }}>
        <MkrMiniStat label="Sold vs BP to date" valor={<span style={{ color: pctCor(t.pct_qty) }}>{mkrUn(t.won_qty)} / {mkrUn(t.bp_qty)} · {mkrPct(t.pct_qty)}</span>} sub={`year BP ${mkrUn(t.bp_qty_ano)} units`} />
        <MkrMiniStat label="Value vs € BP to date" valor={<span style={{ color: pctCor(t.pct_eur) }}>{mkrEur(t.won_eur)} / {mkrEur(t.bp_eur)}</span>} sub={`year BP ${mkrEur(t.bp_eur_ano)}`} />
        <MkrMiniStat label="Same period last year" valor={`${mkrUn(t.won_ant_qty)} units`} sub={`${t.won_qty >= t.won_ant_qty ? '+' : '−'}${mkrUn(Math.abs(t.won_qty - t.won_ant_qty))} units · ${mkrEur(t.won_ant_eur)}`} />
        <MkrMiniStat label={`Q${q} rank B+ needed`} valor={<span style={{ color: t.open_b_n >= t.b_necessarios ? 'var(--success, #16a34a)' : 'var(--danger, #dc2626)' }}>{mkrN(t.open_b_n)} / {mkrN(t.b_necessarios)}</span>} sub="open B+A vs Q budget ÷ 20%" />
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1.5fr 1fr', gap: 18 }}>
        <div style={{ display: 'grid', gap: 12, alignContent: 'start' }}>
          {eq.segmentos.map(s => {
            const x = p[s.key];
            if (!x.bp_qty_ano && !x.won_qty) return null;
            return (
              <div key={s.key} style={{ display: 'grid', gridTemplateColumns: '1fr 112px', gap: 12, alignItems: 'center' }}>
                <AnBullet real={x.won_qty} bpData={x.bp_qty} bpAno={x.bp_qty_ano} fmt={mkrUn} label={`${s.key} · ${s.label}`}
                  nomes={{ real: 'Sold', marca: 'BP to date', fim: 'BP year', nota: '% of BP to date' }}
                  sub={`${mkrUn(x.won_qty)} sold · BP ${mkrUn(x.bp_qty)} to date, ${mkrUn(x.bp_qty_ano)} year · ${mkrEur(x.won_eur)} vs ${mkrEur(x.bp_eur)} · LY ${mkrUn(x.won_ant_qty)}`} />
                <div title={`BP Studio rule: Q${q} budget units ÷ 20% conversion = rank B+ (stage 80+) opportunities needed`}
                  style={{ border: '1px solid var(--border)', borderRadius: 7, padding: '5px 8px', textAlign: 'right' }}>
                  <div style={{ fontSize: 9, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', textTransform: 'uppercase' }}>Q{q} B+ open / need</div>
                  <div style={{ fontSize: 13, fontWeight: 700, fontVariantNumeric: 'tabular-nums', color: !x.b_necessarios ? 'var(--text-muted)' : x.open_b_n >= x.b_necessarios ? 'var(--success, #16a34a)' : 'var(--danger, #dc2626)' }}>{mkrN(x.open_b_n)} / {mkrN(x.b_necessarios)}</div>
                </div>
              </div>
            );
          })}
        </div>
        <div style={{ minWidth: 0 }}>
          <div style={{ fontSize: 11.5, fontWeight: 650, marginBottom: 6 }}>Largest gaps by model</div>
          <MkrTabela maxHeight={300}>
            <thead><tr><MkrTh>Model</MkrTh><MkrTh right>Sold</MkrTh><MkrTh right>BP to date</MkrTh><MkrTh right>Year</MkrTh><MkrTh right>Gap</MkrTh></tr></thead>
            <tbody>
              {modelos.slice(0, 10).map(m => (
                <tr key={`${m.pais}${m.modelo}`}>
                  <MkrTd><b>{m.modelo}</b> <span style={{ fontSize: 10.5, color: 'var(--text-muted)' }}>{m.segmento}</span></MkrTd>
                  <MkrTd right>{mkrUn(m.won_qty)}</MkrTd><MkrTd right muted>{mkrUn(m.bp_qty)}</MkrTd><MkrTd right muted>{mkrUn(m.bp_qty_ano)}</MkrTd>
                  <MkrTd right style={{ fontWeight: 650, color: m.gap_qty >= 0 ? 'var(--success, #16a34a)' : 'var(--danger, #dc2626)' }}>{m.gap_qty >= 0 ? '+' : '−'}{mkrUn(Math.abs(m.gap_qty))}</MkrTd>
                </tr>
              ))}
            </tbody>
          </MkrTabela>
        </div>
      </div>
      <div style={{ fontSize: 10.5, color: 'var(--text-dim)', marginTop: 10, lineHeight: 1.5 }}>
        Segments follow the BP Studio of the Comercial module (SG, IP, TA, CP, 3D). Sold = Mimaki machines Closed Won in the Gestor by close date, Portugal from the PTDIG company and Spain from DIGES. BP to date prorates the current month by days.
        Q{q} need = BP units of Q{q} ÷ 20% conversion, the same rule the BP Studio uses for stage 80+.
      </div>
    </AnCartao>
  );
};

const MkrScorecards = ({ data, onPick, isMobile }) => (
  <AnCartao titulo="Team scorecards" sub="Sales against BP (or last year when the team has no € BP) · Mimaki targets · open pipeline by rank today. Click a team to filter the report.">
    <div style={{ display: 'grid', gap: 4 }}>
      {data.equipas.map(e => {
        const temBp = Number(e.bp_periodo) > 0;
        const activo = data.equipa === e.key;
        return (
          <div key={e.key} className="mkr-linha-eq" onClick={() => onPick(e.key)}
            style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '150px 1.2fr 1.3fr 1.3fr', gap: 16, alignItems: 'center', padding: '10px 8px', borderRadius: 8, cursor: 'pointer', borderTop: '1px solid var(--border)', opacity: data.equipa && !activo ? 0.45 : 1, background: activo ? `color-mix(in oklch, ${e.cor} 6%, transparent)` : undefined }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 }}>
              <span style={{ width: 9, height: 9, borderRadius: '50%', background: e.cor, flexShrink: 0 }} />
              <span style={{ minWidth: 0 }}>
                <b style={{ fontSize: 12.5 }}>{e.label}</b>
                <div style={{ fontSize: 10.5, color: 'var(--text-muted)' }}>{e.fonte_vendas === 'primavera' ? 'Primavera snapshot' : 'Gestor Closed Won'}</div>
              </span>
            </div>
            <AnBullet fmt={mkrEur}
              real={e.vendas} bpData={temBp ? e.bp_periodo : e.vendas_ant} bpAno={temBp ? e.bp_ano : 0}
              label={temBp ? 'Sales vs BP' : 'Sales vs last year'}
              nomes={temBp ? { real: 'Sales', marca: 'BP to date', fim: 'BP year', nota: '% of BP to date' } : { real: 'Sales', marca: 'Same period last year', fim: '—', nota: '% of last year' }}
              sub={temBp ? `${mkrEur(e.vendas)} of ${mkrEur(e.bp_periodo)} · year ${mkrEur(e.bp_ano)} · LY ${mkrEur(e.vendas_ant)}` : `${mkrEur(e.vendas)} vs ${mkrEur(e.vendas_ant)} last year`} />
            <AnMiniKpis rotulos={MKR_KPI_ROTULOS} itens={[
              { label: 'Win rate', valor: e.win_rate, obj: ALVO_WR_MKR, amarelo: 14, fmt: mkrPct },
              { label: 'C → Sold', valor: e.conv_c_s, obj: 20, amarelo: 14, fmt: mkrPct },
              { label: 'C coverage', valor: e.cobertura_c_real, obj: 5, amarelo: 3, fmt: mkrX },
            ]} />
            <div style={{ minWidth: 0 }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 11, marginBottom: 3 }}>
                <span>Open {mkrN(e.pipeline_aberto_n)} · <b>{mkrEur(e.pipeline_aberto_eur)}</b></span>
                <span style={{ color: 'var(--text-muted)' }}>weighted {mkrEur(e.ponderado_eur)}</span>
              </div>
              <MkrPilhaRank ranks={e.abertas_rank} />
            </div>
          </div>
        );
      })}
    </div>
    <div style={{ fontSize: 10.5, color: 'var(--text-dim)', marginTop: 8, lineHeight: 1.5 }}>
      C coverage = open rank C value ÷ BP still to sell this year (Mimaki rule 5×), only for teams with € BP. C → Sold = opportunities started in the period that reached rank C and were sold.
    </div>
  </AnCartao>
);

const MkrEquipas = ({ data, mudarF, isMobile }) => {
  const paisDaEquipa = { 'MIMAKI PT': 'Portugal', 'MIMAKI ES': 'Spain' }[data.equipa] || 'Iberia';
  const [pais, setPais] = React.useState(paisDaEquipa);
  React.useEffect(() => { setPais(paisDaEquipa); }, [data.equipa]);
  const eq = data.equipamentos;
  const pick = (k) => mudarF({ equipa: data.equipa === k ? '' : k });
  const fim = String(data.periodo.fim);
  const mesEq = eq && Number(fim.slice(0, 4)) === eq.ano ? Number(fim.slice(5, 7)) : 12;
  return (
    <>
      <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr 1fr' : 'repeat(5, 1fr)', gap: 10, marginBottom: 14 }}>
        {data.equipas.map(e => <MkrCartaoEquipa key={e.key} e={e} activo={data.equipa === e.key} apagado={!!data.equipa && data.equipa !== e.key} onClick={() => pick(e.key)} />)}
      </div>
      <div style={{ marginBottom: 14 }}><MkrFunilMimaki data={data} isMobile={isMobile} /></div>
      <div style={{ marginBottom: 14 }}><MkrScorecards data={data} onPick={pick} isMobile={isMobile} /></div>
      <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: 14, marginBottom: 14 }}>
        <MkrVendasBp data={data} />
        {eq ? <MkrMaquinasMes eq={eq} pais={pais} setPais={setPais} mes={mesEq} /> : <AnCartao titulo="Machines sold vs equipment BP"><AnVazio texto="No equipment BP in bp_objectivos for this year" /></AnCartao>}
      </div>
      {eq && <div style={{ marginBottom: 14 }}><MkrEquipamentosSeg eq={eq} pais={pais} setPais={setPais} isMobile={isMobile} /></div>}
      <MkrTabelaEquipas data={data} />
    </>
  );
};

const MkrTabelaEquipas = ({ data }) => (
  <>
    <MkrTabela>
      <thead>
        <tr>
          <MkrTh>Team</MkrTh><MkrTh right>Opps</MkrTh><MkrTh right>Won</MkrTh><MkrTh right>Lost</MkrTh><MkrTh right>Win rate</MkrTh>
          <MkrTh right>Open pipeline</MkrTh><MkrTh right>Weighted</MkrTh><MkrTh right>C coverage</MkrTh><MkrTh right>Mimaki Contact</MkrTh><MkrTh right>Sales</MkrTh><MkrTh right>Last year</MkrTh><MkrTh right>BP period</MkrTh><MkrTh right>Achieved</MkrTh>
          <MkrTh right>To invoice</MkrTh><MkrTh right>Forecast</MkrTh><MkrTh>Sales source</MkrTh>
        </tr>
      </thead>
      <tbody>
        {data.equipas.map(e => (
          <tr key={e.key} style={{ opacity: data.equipa && data.equipa !== e.key ? 0.45 : 1 }}>
            <MkrTd><span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}><span style={{ width: 8, height: 8, borderRadius: '50%', background: e.cor }} /><span><b>{e.label}</b><div style={{ fontSize: 10.5, color: 'var(--text-muted)' }}>{e.sub}</div></span></span></MkrTd>
            <MkrTd right>{mkrN(e.n)}</MkrTd><MkrTd right>{mkrN(e.won)}</MkrTd><MkrTd right muted>{mkrN(e.lost)}</MkrTd>
            <MkrTd right>{mkrPct(e.win_rate)}</MkrTd><MkrTd right>{mkrEur(e.pipeline_aberto_eur)}</MkrTd>
            <MkrTd right muted>{mkrEur(e.ponderado_eur)}</MkrTd><MkrTd right muted>{mkrX(e.cobertura_c_real)}</MkrTd><MkrTd right muted>{mkrN(e.mimaki_contact_n)}</MkrTd>
            <MkrTd right style={{ fontWeight: 650 }}>{mkrEur(e.vendas)}</MkrTd><MkrTd right muted>{mkrEur(e.vendas_ant)}</MkrTd>
            <MkrTd right>{mkrEur(e.bp_periodo)}</MkrTd>
            <MkrTd right style={{ color: e.atingido_pct == null ? undefined : e.atingido_pct >= 100 ? 'var(--success)' : e.atingido_pct >= 85 ? 'var(--warning)' : 'var(--danger)', fontWeight: 650 }}>{mkrPct(e.atingido_pct)}</MkrTd>
            <MkrTd right muted>{mkrEur(e.por_faturar)}</MkrTd><MkrTd right muted>{mkrEur(e.forecast)}</MkrTd>
            <MkrTd muted title={e.fonte_label} style={{ fontSize: 11 }}>{e.fonte_vendas === 'primavera' ? 'Primavera snapshot' : 'Gestor Closed Won'}</MkrTd>
          </tr>
        ))}
      </tbody>
    </MkrTabela>
    <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 10, lineHeight: 1.55 }}>
      Sales for Mimaki PT and GC come from the Primavera snapshot (comercial_ytd_snap) by team code. Spain, Partners and AllDecor have no Primavera ES feed, so sales use the value of Closed Won opportunities by close date, as the Comercial RPC does.
      BP comes from comercial_bp_targets, to invoice from the ECW backlog (primavera.f9_snap) and forecast from the RPC orders (rpc_encomendas). Opportunity counts use the start date inside the period.
    </div>
  </>
);

// ── Segments ────────────────────────────────────────────────────────────────
const MkrSegmentos = ({ data }) => {
  const max = Math.max(...data.segmentos.map(s => s.n), 1);
  return (
    <>
      <MkrTabela>
        <thead>
          <tr>
            <MkrTh>Segment</MkrTh><MkrTh style={{ width: '22%' }}>Opportunities</MkrTh><MkrTh right>Open</MkrTh><MkrTh right>Won</MkrTh><MkrTh right>Win rate</MkrTh>
            <MkrTh right>PT / ES</MkrTh><MkrTh right>Direct / Dealer</MkrTh><MkrTh right>Open value</MkrTh><MkrTh right>Won in period</MkrTh><MkrTh right>Last year</MkrTh>
          </tr>
        </thead>
        <tbody>
          {data.segmentos.map(s => (
            <tr key={s.segmento}>
              <MkrTd><b>{s.segmento}</b></MkrTd>
              <MkrTd><span style={{ display: 'grid', gridTemplateColumns: '1fr 40px', gap: 8, alignItems: 'center' }}><MkrBarra valor={s.n} max={max} cor="var(--ai-500, #3859D0)" /><span style={{ textAlign: 'right' }}>{mkrN(s.n)}</span></span></MkrTd>
              <MkrTd right>{mkrN(s.open)}</MkrTd><MkrTd right>{mkrN(s.won)}</MkrTd><MkrTd right>{mkrPct(s.win_rate)}</MkrTd>
              <MkrTd right muted>{mkrN(s.pt)} / {mkrN(s.es)}</MkrTd><MkrTd right muted>{mkrN(s.direct)} / {mkrN(s.dealer)}</MkrTd>
              <MkrTd right>{mkrEur(s.open_eur)}</MkrTd>
              <MkrTd right style={{ fontWeight: 650, color: s.won_periodo_eur >= s.won_ant_eur ? 'var(--success)' : 'var(--danger)' }}>{mkrEur(s.won_periodo_eur)}</MkrTd>
              <MkrTd right muted>{mkrEur(s.won_ant_eur)}</MkrTd>
            </tr>
          ))}
        </tbody>
      </MkrTabela>
      <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 10, lineHeight: 1.55 }}>
        Segments: TA-DTF (DTF, except UJV300DTF-75), TA (TxF, TS200, TS330 including TS330-3200DS, TX300P, Tiger), SG 3.2m+ (SWJ-320EA and UJV55-320 only), IP Small (UJF), IP Large (JFX), Signage 1.8m (CJV, UCJV, UJV55 except UJV55-320, UJV100, UJ330H, JV330, JV200, UJV300DTF-75).
        Dealer is the Mimaki PA team; all other teams sell direct.
      </div>
    </>
  );
};

const MkrLeadsMimaki = ({ data, isMobile }) => {
  const c = data.mimaki_contact;
  if (!c) return null;
  const ordem = { A: 0, B: 1, C: 2, D: 3 };
  const hoje = Date.now();
  const abertas = (data.linhas || [])
    .filter(l => l.source_group === 'Mimaki Contact' && l.rank !== 'S' && l.rank !== 'Lost')
    .map(l => ({ ...l, dias: l.start_date ? Math.floor((hoje - Date.parse(`${l.start_date}T00:00:00`)) / 86400000) : null }))
    .sort((a, b) => (ordem[a.rank] ?? 9) - (ordem[b.rank] ?? 9) || (b.dias || 0) - (a.dias || 0));
  return (
    <AnCartao titulo="Leads from Mimaki"
      sub={`Mimaki Contact opportunities started in ${data.periodo.label}. These are the leads Mimaki sends and Digidelta updates in the Gestor. The table is every one of them still open, and the rank is where it sits today.`}>
      <div style={{ display: 'flex', gap: 16, flexWrap: 'wrap', marginBottom: 12, fontSize: 12.5 }}>
        <span><b>{mkrN(c.n)}</b> started</span>
        <span><b>{mkrN(c.open)}</b> open</span>
        <span><b>{mkrN(c.won)}</b> won</span>
        <span><b>{mkrN(c.lost)}</b> lost · win rate {mkrPct(c.win_rate)}</span>
        <span style={{ color: c.sem_avanco_14d > 0 ? 'var(--danger, #dc2626)' : 'var(--text-muted)' }}><b>{mkrN(c.sem_avanco_14d)}</b> still at stage 10 after 14 days</span>
      </div>
      <div style={{ display: 'flex', gap: 14, flexWrap: 'wrap', marginBottom: 12, fontSize: 12 }}>
        {(c.por_rank || []).filter(x => x.n > 0).map(x => (
          <span key={x.key} style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
            <MkrRank rank={x.key === 'Lost' ? 'Lost' : x.key.charAt(0)} sub={x.key} /> {mkrN(x.n)}
          </span>
        ))}
        <span style={{ color: 'var(--text-muted)' }}>{(c.por_equipa || []).map(e => `${e.label} ${mkrN(e.n)}`).join(' · ')}</span>
      </div>
      {abertas.length > 0 ? (
        <MkrTabela>
          <thead>
            <tr>
              <MkrTh>Company</MkrTh><MkrTh>Country</MkrTh><MkrTh>Team</MkrTh><MkrTh>Model</MkrTh>
              <MkrTh>Rank</MkrTh><MkrTh>Gestor stage</MkrTh><MkrTh right>Days</MkrTh>
            </tr>
          </thead>
          <tbody>
            {abertas.map(l => (
              <tr key={`${l.tenant}:${l.id}`}>
                <MkrTd style={{ fontWeight: 600, maxWidth: 240, overflow: 'hidden', textOverflow: 'ellipsis' }} title={l.company}>{l.company || '—'}</MkrTd>
                <MkrTd muted>{l.country || '—'}</MkrTd>
                <MkrTd>{l.team || '—'}</MkrTd>
                <MkrTd>{l.equipment || '—'}</MkrTd>
                <MkrTd><MkrRank rank={l.rank} /></MkrTd>
                <MkrTd muted>{l.gestor_stage || '—'}</MkrTd>
                <MkrTd right style={{ fontWeight: 650, color: l.rank === 'D' && l.dias > 14 ? 'var(--danger, #dc2626)' : undefined }}>{l.dias == null ? '—' : mkrN(l.dias)}</MkrTd>
              </tr>
            ))}
          </tbody>
        </MkrTabela>
      ) : <div style={{ fontSize: 12.5, color: 'var(--text-muted)' }}>No open Mimaki Contact leads in this period.</div>}
    </AnCartao>
  );
};

// ── Mimaki Contact (G1008) ──────────────────────────────────────────────────
const MkrContacto = ({ data, isMobile }) => {
  const Kpi = window.ResKpi;
  const c = data.mimaki_contact;
  const max = Math.max(...c.por_rank.map(x => x.n), 1);
  const hoje = Date.now();
  const parados = data.linhas
    .filter(l => l.source_group === 'Mimaki Contact' && l.rank === 'D' && /^10\b/.test(l.gestor_stage) && l.start_date)
    .map(l => ({ ...l, dias: Math.floor((hoje - Date.parse(`${l.start_date}T00:00:00`)) / 86400000) }))
    .filter(l => l.dias > 14)
    .sort((a, b) => b.dias - a.dias);
  return (
    <>
      <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr 1fr' : 'repeat(5, 1fr)', gap: 10, marginBottom: 14 }}>
        <Kpi label="Leads from Mimaki" value={mkrN(c.n)} sub={`${mkrN(c.pt)} Portugal · ${mkrN(c.es)} Spain`} />
        <Kpi label="In progress" value={mkrN(c.open)} sub={mkrEur(c.open_eur)} />
        <Kpi label="Won" value={mkrN(c.won)} sub={mkrEur(c.won_eur)} />
        <Kpi label="Lost" value={mkrN(c.lost)} sub={`Win rate ${mkrPct(c.win_rate)}`} />
        <Kpi label="Untouched > 14 days" value={mkrN(c.sem_avanco_14d)} sub="Still at Gestor stage 10" light={c.sem_avanco_14d > 0 ? 'vermelho' : 'verde'} />
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: 14 }}>
        <AnCartao titulo="Follow-up by rank" sub="Current rank of the leads Mimaki sent (source G1008 Mimaki Contact)">
          <div style={{ display: 'grid', gap: 8 }}>
            {c.por_rank.map(x => (
              <div key={x.key} style={{ display: 'grid', gridTemplateColumns: '40px 1fr 40px', gap: 10, alignItems: 'center', fontSize: 12 }}>
                <MkrRank rank={x.key} /><MkrBarra valor={x.n} max={max} cor={MKR_RANK_COR[x.key]} altura={10} /><span style={{ textAlign: 'right', fontWeight: 650 }}>{mkrN(x.n)}</span>
              </div>
            ))}
          </div>
        </AnCartao>
        <AnCartao titulo="By team" sub="Who follows the Mimaki leads">
          {c.por_equipa.length ? (
            <div style={{ display: 'grid', gap: 8 }}>
              {c.por_equipa.map(x => (
                <div key={x.key} style={{ display: 'grid', gridTemplateColumns: '110px 1fr 40px', gap: 10, alignItems: 'center', fontSize: 12 }}>
                  <span>{x.label}</span><MkrBarra valor={x.n} max={c.n} cor={(data.equipas_def.find(e => e.key === x.key) || {}).cor} altura={10} /><span style={{ textAlign: 'right', fontWeight: 650 }}>{mkrN(x.n)}</span>
                </div>
              ))}
            </div>
          ) : <AnVazio texto="no Mimaki Contact leads in this period" />}
        </AnCartao>
      </div>
      {parados.length > 0 && (
        <AnCartao titulo="Leads to follow up now" sub="Mimaki Contact leads still at Gestor stage 10 after 14 days" acento="var(--danger, #dc2626)" style={{ marginTop: 14 }}>
          <MkrTabela>
            <thead><tr><MkrTh>Company</MkrTh><MkrTh>Team</MkrTh><MkrTh>Salesperson</MkrTh><MkrTh>Equipment</MkrTh><MkrTh right>Days</MkrTh><MkrTh>Start</MkrTh></tr></thead>
            <tbody>
              {parados.map(l => (
                <tr key={`${l.tenant}:${l.id}`} className="mkr-linha" onClick={() => mkrAbrirCrm(l, 'contact')} title="Open the opportunity in the CRM" style={{ cursor: 'pointer' }}>
                  <MkrTd style={{ fontWeight: 600 }}>{l.company || '—'}</MkrTd><MkrTd muted>{mkrEquipaCurta(l.team)}</MkrTd>
                  <MkrTd>{l.salesperson || '—'}</MkrTd><MkrTd>{l.equipment}</MkrTd>
                  <MkrTd right style={{ color: 'var(--danger)', fontWeight: 650 }}>{l.dias}</MkrTd><MkrTd mono muted>{l.start_date}</MkrTd>
                </tr>
              ))}
            </tbody>
          </MkrTabela>
        </AnCartao>
      )}
    </>
  );
};

// ── Opportunities ───────────────────────────────────────────────────────────
function mkrAbrirCrm(l, tab) {
  try { history.replaceState(null, '', `#screen=marketing&sub=resultados-mimaki&tab=${tab}`); } catch {}
  window.location.hash = `screen=marketing&sub=audiencias&op=${encodeURIComponent(l.id)}`;
}

const MkrOps = ({ data }) => {
  const Filtro = window.ResFiltro;
  const [q, setQ] = React.useState('');
  const [rank, setRank] = React.useState('');
  const [fonte, setFonte] = React.useState('');
  const [gerado, setGerado] = React.useState('');
  const [limite, setLimite] = React.useState(150);
  const norm = (s) => String(s || '').normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
  const termo = norm(q.trim());
  const fontes = Object.keys(data.por_fonte).sort();
  const gerados = (data.origens || []).filter(o => o.n > 0);
  const lista = data.linhas.filter(l => (!rank || l.rank === rank) && (!fonte || l.source_group === fonte) && (!gerado || l.generated_by === gerado)
    && (!termo || norm(`${l.company} ${l.equipment} ${l.salesperson} ${l.source} ${l.id}`).includes(termo)));
  React.useEffect(() => { setLimite(150); }, [termo, rank, fonte, gerado]);
  return (
    <>
      <div style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap', marginBottom: 10 }}>
        <input value={q} onChange={e => setQ(e.target.value)} placeholder="Search company, equipment, salesperson" aria-label="Search opportunities"
          style={{ height: 30, minWidth: 240, flex: '0 1 320px', padding: '0 10px', border: '1px solid var(--border)', borderRadius: 7, background: 'var(--bg-elev)', color: 'var(--text)', fontSize: 12, fontFamily: 'inherit' }} />
        {Filtro && <Filtro label="Rank" value={rank} onChange={setRank} options={[{ value: '', label: 'All' }, ...['D', 'C', 'B', 'A', 'S', 'Lost'].map(r => ({ value: r, label: r }))]} menuMinWidth={120} />}
        {Filtro && gerados.length > 0 && <Filtro label="Generated by" value={gerado} onChange={setGerado} options={[{ value: '', label: 'All' }, ...gerados.map(o => ({ value: o.label, label: `${o.label} (${o.n})` }))]} menuMinWidth={200} />}
        {Filtro && <Filtro label="Source" value={fonte} onChange={setFonte} options={[{ value: '', label: 'All' }, ...fontes.map(f => ({ value: f, label: `${f} (${data.por_fonte[f]})` }))]} />}
        <span style={{ fontSize: 11.5, color: 'var(--text-muted)', marginLeft: 'auto' }}>{mkrN(lista.length)} of {mkrN(data.linhas.length)} opportunities</span>
      </div>
      <MkrTabela maxHeight="62vh">
        <thead>
          <tr>
            <MkrTh>Company</MkrTh><MkrTh>Country</MkrTh><MkrTh>Team</MkrTh><MkrTh>Salesperson</MkrTh><MkrTh>Generated by</MkrTh><MkrTh>Source</MkrTh>
            <MkrTh>Equipment</MkrTh><MkrTh>Segment</MkrTh><MkrTh>Gestor stage</MkrTh><MkrTh>Rank</MkrTh><MkrTh>Mimaki CRM step</MkrTh>
            <MkrTh right>Value</MkrTh><MkrTh>Start</MkrTh><MkrTh>Close</MkrTh>
          </tr>
        </thead>
        <tbody>
          {lista.slice(0, limite).map(l => (
            <tr key={`${l.tenant}:${l.id}`} className="mkr-linha" onClick={() => mkrAbrirCrm(l, 'ops')} title="Open the opportunity in the CRM" style={{ cursor: 'pointer' }}>
              <MkrTd style={{ maxWidth: 260, overflow: 'hidden', textOverflow: 'ellipsis', fontWeight: 600 }} title={l.company}>{l.company || '—'}</MkrTd>
              <MkrTd muted>{l.country}</MkrTd><MkrTd muted title={l.team_inferred ? 'No team in the Gestor · team of the salesperson' : undefined}>{mkrEquipaCurta(l.team)}{l.team_inferred ? '*' : ''}</MkrTd>
              <MkrTd>{l.salesperson || <span style={{ color: 'var(--text-dim)' }}>—</span>}</MkrTd>
              <MkrTd title={l.generated_by_detail}>{l.generated_by}</MkrTd>
              <MkrTd muted style={{ maxWidth: 160, overflow: 'hidden', textOverflow: 'ellipsis' }} title={l.source_group}>{l.source}</MkrTd>
              <MkrTd style={{ maxWidth: 220, overflow: 'hidden', textOverflow: 'ellipsis' }} title={l.equipment}>{l.equipment}</MkrTd>
              <MkrTd muted>{l.segment}</MkrTd><MkrTd muted>{l.gestor_stage}</MkrTd>
              <MkrTd><MkrRank rank={l.rank} sub={l.rank_sub} /></MkrTd>
              <MkrTd muted>{l.mimaki_status ? `${l.mimaki_status} · ${l.mimaki_status_label}` : '—'}</MkrTd>
              <MkrTd right>{mkrEur(l.value_eur)}</MkrTd>
              <MkrTd mono muted>{l.start_date || '—'}</MkrTd><MkrTd mono muted>{l.close_date || '—'}</MkrTd>
            </tr>
          ))}
        </tbody>
      </MkrTabela>
      {lista.length > limite && (
        <div style={{ textAlign: 'center', marginTop: 10 }}>
          <button type="button" className="btn btn-sm" onClick={() => setLimite(n => n + 300)}>Show {mkrN(Math.min(300, lista.length - limite))} more</button>
        </div>
      )}
      {!lista.length && <div style={{ marginTop: 10 }}><AnVazio texto="no opportunities for these filters" /></div>}
    </>
  );
};

// ── Ranking map ─────────────────────────────────────────────────────────────
const MKR_GRUPOS_MIMAKI = [['D', '30%', [1, 2, 3]], ['C', '50–70%', [4, 5, 6]], ['B', '80%', [7]], ['A', '90%', [8, 9, 10]], ['S', '100%', [11]]];
const MKR_MAPA_LINHA = 50;

// As duas tabelas usam as mesmas linhas (um passo Mimaki por linha) para ficarem alinhadas lado a lado.
const MkrMapa = ({ data, isMobile }) => {
  const linhas = [];
  MKR_GRUPOS_MIMAKI.forEach(([rank, prob, estados]) => estados.forEach((estado, i) => {
    linhas.push({ id: `e${estado}`, rank, prob, estado, primeiro: i === 0, n: estados.length, gestor: data.mapeamento.filter(m => m.estado === estado) });
  }));
  linhas.push({ id: 'lost', rank: 'Lost', prob: '', estado: null, primeiro: true, n: 1, gestor: data.mapeamento.filter(m => m.estado == null) });
  const td = (l, extra) => ({
    height: MKR_MAPA_LINHA, verticalAlign: 'middle', whiteSpace: 'normal',
    borderTop: l.primeiro ? '2px solid var(--border)' : '1px solid var(--border)', ...extra,
  });
  return (
    <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: 14, alignItems: 'start' }}>
      <AnCartao titulo="Gestor → Mimaki rank" sub="How each Gestor stage is reported to Mimaki">
        <MkrTabela>
          <thead><tr><MkrTh>Gestor stage</MkrTh><MkrTh style={{ width: 110 }}>Reported as</MkrTh></tr></thead>
          <tbody>
            {linhas.map(l => (
              <tr key={l.id}>
                <MkrTd style={td(l)}>
                  {l.gestor.length
                    ? l.gestor.map(m => <div key={m.gestor} style={{ lineHeight: 1.35 }}>{m.gestor}{m.nota && <sup style={{ color: 'var(--text-muted)', marginLeft: 2 }}>1</sup>}</div>)
                    : <span style={{ color: 'var(--text-dim)', fontStyle: 'italic' }}>No Gestor stage</span>}
                </MkrTd>
                <MkrTd style={td(l)}>
                  {l.gestor.length ? (
                    <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
                      <MkrRank rank={l.gestor[0].rank.replace(/\d$/, '')} sub={l.gestor[0].rank} />
                      {l.gestor[0].nota && <span style={{ fontSize: 10.5, color: 'var(--text-muted)' }}>or C1<sup>1</sup></span>}
                    </span>
                  ) : <span style={{ color: 'var(--text-dim)' }}>—</span>}
                </MkrTd>
              </tr>
            ))}
          </tbody>
        </MkrTabela>
        {data.mapeamento.filter(m => m.nota).map(m => (
          <div key={m.gestor} style={{ fontSize: 10.5, color: 'var(--text-muted)', marginTop: 8 }}><sup>1</sup> {m.gestor}: {m.nota}.</div>
        ))}
      </AnCartao>
      <AnCartao titulo="Mimaki leads ranking chart" sub="Ranks and CRM steps used by Mimaki EMEA">
        <MkrTabela>
          <thead><tr><MkrTh style={{ width: 110 }}>Mimaki rank</MkrTh><MkrTh>Mimaki CRM step</MkrTh></tr></thead>
          <tbody>
            {linhas.map(l => (
              <tr key={l.id}>
                {l.primeiro && (
                  <MkrTd style={td(l, { borderRight: '1px solid var(--border)' })} rowSpan={l.n}>
                    <span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
                      <MkrRank rank={l.rank} />
                      {l.prob && <span style={{ color: 'var(--text-muted)', fontVariantNumeric: 'tabular-nums' }}>{l.prob}</span>}
                    </span>
                  </MkrTd>
                )}
                <MkrTd style={td(l, { color: l.gestor.length ? 'var(--text)' : 'var(--text-muted)' })}>
                  {l.estado ? `${l.estado} · ${data.estados_mimaki[l.estado]}` : <span style={{ color: 'var(--text-dim)', fontStyle: 'italic' }}>Closed lost · not a Mimaki step</span>}
                </MkrTd>
              </tr>
            ))}
          </tbody>
        </MkrTabela>
      </AnCartao>
    </div>
  );
};

// ── Envio ───────────────────────────────────────────────────────────────────
const MKR_DIAS = ['Segunda', 'Terça', 'Quarta', 'Quinta', 'Sexta', 'Sábado', 'Domingo'];
const MKR_EMAIL_RE = /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/i;

function mkrAgendaTexto(e) {
  const h = `${String(e.hora).padStart(2, '0')}:${String(e.minuto).padStart(2, '0')}`;
  if (e.cadencia === 'mes') return `Dia ${e.dia_mes} de cada mês às ${h}`;
  const nomes = ['segundas', 'terças', 'quartas', 'quintas', 'sextas', 'sábados', 'domingos'];
  const nome = nomes[(Number(e.dia_semana) || 1) - 1] || 'semanas';
  return `${Number(e.dia_semana) >= 6 ? 'Todos os' : 'Todas as'} ${nome} às ${h}`;
}

const MkrEnvioModal = ({ onClose, email }) => {
  const [escolhidos, setEscolhidos] = React.useState({});
  const [externos, setExternos] = React.useState([]);
  const [externoInput, setExternoInput] = React.useState('');
  const [busca, setBusca] = React.useState('');
  const [nota, setNota] = React.useState('');
  const [cadencia, setCadencia] = React.useState('agora');
  const [diaSemana, setDiaSemana] = React.useState(1);
  const [diaMes, setDiaMes] = React.useState(2);
  const [hora, setHora] = React.useState('09:00');
  const [html, setHtml] = React.useState('');
  const [confirmar, setConfirmar] = React.useState(false);
  const [aTrabalhar, setATrabalhar] = React.useState(false);
  const [erro, setErro] = React.useState('');
  const [ok, setOk] = React.useState('');
  const [lista, setLista] = React.useState(null);
  const [aApagar, setAApagar] = React.useState(null);

  const carregar = React.useCallback(() => {
    fetch(`${MKR_API}/envios?${mkrQs({}, email)}`)
      .then(async r => { const b = await r.json().catch(() => ({})); if (!r.ok) throw new Error(b.error || `HTTP ${r.status}`); return b; })
      .then(setLista)
      .catch(e => setErro(e.message));
  }, [email]);
  React.useEffect(() => { carregar(); }, [carregar]);
  React.useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape' && !aTrabalhar) onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose, aTrabalhar]);

  const pessoas = lista?.utilizadores || [];
  const destinatarios = () => [...pessoas.filter(p => escolhidos[p.email]).map(p => p.email), ...externos];
  const foraDigidelta = (email) => {
    const host = String(email || '').toLowerCase().split('@')[1] || '';
    return host !== 'digidelta.pt' && !host.endsWith('.digidelta.pt');
  };
  const adicionarExterno = () => {
    const partes = externoInput.split(/[,;\s]+/).map(s => s.trim().toLowerCase()).filter(Boolean);
    if (!partes.length) return;
    const invalidos = partes.filter(e => !MKR_EMAIL_RE.test(e));
    if (invalidos.length) { setErro('Há um email externo inválido.'); return; }
    const doPortal = new Set(pessoas.map(p => p.email));
    setEscolhidos(prev => {
      const next = { ...prev };
      partes.filter(e => doPortal.has(e)).forEach(e => { next[e] = true; });
      return next;
    });
    setExternos(prev => [...new Set([...prev, ...partes.filter(e => !doPortal.has(e))])]);
    setExternoInput('');
    setErro('');
    setConfirmar(false);
  };
  const pedir = async (path, opts) => {
    const r = await fetch(`${MKR_API}${path}${path.includes('?') ? '&' : '?'}${mkrQs({}, email)}`, {
      headers: { 'Content-Type': 'application/json' },
      ...opts,
    });
    const b = await r.json().catch(() => ({}));
    if (!r.ok) throw new Error(b.error || `HTTP ${r.status}`);
    return b;
  };
  const previsualizar = async () => {
    setATrabalhar(true); setErro(''); setOk('');
    try {
      const b = await pedir('/envios/preview', { method: 'POST', body: JSON.stringify({ nota, destinatarios: destinatarios() }) });
      setHtml(b.html || '');
    } catch (e) { setErro(e.message); }
    finally { setATrabalhar(false); }
  };
  const enviar = async () => {
    setATrabalhar(true); setErro(''); setOk('');
    try {
      const b = await pedir('/envios/enviar', { method: 'POST', body: JSON.stringify({ destinatarios: destinatarios(), nota }) });
      setOk(`Email enviado para ${b.destinatarios?.length || destinatarios().length} destinatário(s).`);
      setConfirmar(false);
      carregar();
    } catch (e) { setErro(e.message); }
    finally { setATrabalhar(false); }
  };
  const guardar = async () => {
    setATrabalhar(true); setErro(''); setOk('');
    try {
      const b = await pedir('/envios', { method: 'POST', body: JSON.stringify({ cadencia, dia_semana: diaSemana, dia_mes: diaMes, hora, nota, destinatarios: destinatarios() }) });
      setOk(`Agenda guardada. Próximo envio ${mkrQuando(b.proximo_em)}.`);
      carregar();
    } catch (e) { setErro(e.message); }
    finally { setATrabalhar(false); }
  };
  const pausar = async (row) => {
    setErro('');
    try {
      await pedir(`/envios/${row.id}`, { method: 'PATCH', body: JSON.stringify({ activo: !row.activo }) });
      carregar();
    } catch (e) { setErro(e.message); }
  };
  const apagar = async (id) => {
    setErro('');
    try {
      await pedir(`/envios/${id}`, { method: 'DELETE' });
      setAApagar(null);
      carregar();
    } catch (e) { setErro(e.message); }
  };

  const btn = (label, onClick, primario) => (
    <button type="button" onClick={onClick} disabled={aTrabalhar} style={{ border: primario ? 0 : '1px solid var(--border)', background: primario ? '#3859D0' : 'transparent', color: primario ? '#fff' : 'var(--text)', borderRadius: 8, padding: '8px 14px', fontSize: 13, fontWeight: 650, cursor: aTrabalhar ? 'wait' : 'pointer', fontFamily: 'inherit' }}>{label}</button>
  );
  const campo = { display: 'block', width: '100%', marginTop: 4, boxSizing: 'border-box', border: '1px solid var(--border)', borderRadius: 8, padding: '8px 10px', font: 'inherit', fontSize: 13, background: 'var(--bg)', color: 'var(--text)' };
  const q = busca.trim().toLowerCase();
  const visiveis = pessoas.filter(p => !q || `${p.nome} ${p.email} ${p.empresa} ${p.departamento}`.toLowerCase().includes(q));
  const seleccionadosPortal = pessoas.filter(p => escolhidos[p.email]);
  const chip = (key, label, sub, onRemove) => (
    <span key={key} style={{ display: 'inline-flex', alignItems: 'center', gap: 6, padding: '4px 8px', borderRadius: 99, background: 'var(--bg-sunken, #f1f5f9)', border: '1px solid var(--border)', fontSize: 12 }}>
      <span><b>{label}</b>{sub ? <span style={{ color: 'var(--text-muted)' }}> {sub}</span> : null}</span>
      <button type="button" aria-label={`Retirar ${label}`} onClick={onRemove} style={{ border: 0, background: 'transparent', cursor: 'pointer', color: 'var(--text-muted)', font: 'inherit', lineHeight: 1, padding: 0 }}>×</button>
    </span>
  );
  return (
    <div role="dialog" aria-modal="true" aria-label="Enviar o Report Mimaki" onClick={() => { if (!aTrabalhar) onClose(); }} style={{ position: 'fixed', inset: 0, zIndex: 400, background: 'rgba(15, 23, 42, .45)', display: 'flex', alignItems: 'flex-start', justifyContent: 'center', padding: '48px 16px', overflow: 'auto' }}>
      <div onClick={e => e.stopPropagation()} style={{ width: 'min(680px, 100%)', background: 'var(--bg-elev, #fff)', color: 'var(--text)', borderRadius: 12, border: '1px solid var(--border)', boxShadow: '0 16px 50px rgba(15,23,42,.2)', padding: '22px 22px 18px' }}>
        <div style={{ display: 'flex', alignItems: 'flex-start', gap: 12 }}>
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 11, fontWeight: 650, letterSpacing: '.06em', color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>REPORT MIMAKI</div>
            <h2 style={{ margin: '4px 0 0', fontSize: 18, fontWeight: 700 }}>Enviar por email</h2>
          </div>
          <button type="button" onClick={onClose} disabled={aTrabalhar} aria-label="Fechar" style={{ border: 0, background: 'transparent', fontSize: 20, lineHeight: 1, cursor: 'pointer', color: 'var(--text-muted)' }}>×</button>
        </div>
        <p style={{ margin: '10px 0 0', fontSize: 13, color: 'var(--text-muted)', lineHeight: 1.5 }}>
          O email sai com o período YTD no momento do envio, todas as equipas e todas as origens. Os filtros deste ecrã ficam de fora. O PDF é o Overview do portal.
        </p>
        <div style={{ marginTop: 16, display: 'grid', gap: 12 }}>
          <div>
            <div style={{ fontSize: 11, fontWeight: 650, color: 'var(--text-dim)', marginBottom: 6 }}>Quem recebe</div>
            {(seleccionadosPortal.length > 0 || externos.length > 0) ? (
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: 8 }}>
                {seleccionadosPortal.map(p => chip(p.email, p.nome, p.email, () => setEscolhidos(prev => ({ ...prev, [p.email]: false }))))}
                {externos.map(e => chip(`ext-${e}`, e, 'externo', () => setExternos(prev => prev.filter(x => x !== e))))}
              </div>
            ) : (
              <div style={{ fontSize: 12.5, color: 'var(--text-muted)', marginBottom: 8 }}>Ninguém seleccionado. Escolhe utilizadores do portal ou acrescenta um email externo.</div>
            )}
            <input value={busca} onChange={e => setBusca(e.target.value)} placeholder="Procurar utilizador do portal…" style={campo} />
            <div className="scrollbar" style={{ maxHeight: 220, overflowY: 'auto', marginTop: 8, border: '1px solid var(--border)', borderRadius: 8 }}>
              {!lista && <div style={{ padding: 10, fontSize: 12.5, color: 'var(--text-muted)' }}>A carregar utilizadores…</div>}
              {lista && visiveis.length === 0 && <div style={{ padding: 10, fontSize: 12.5, color: 'var(--text-muted)' }}>Nenhum utilizador com esse nome ou email.</div>}
              {visiveis.map((p, i) => (
                <label key={p.email} style={{ display: 'flex', gap: 8, alignItems: 'flex-start', padding: '7px 10px', borderTop: i ? '1px solid var(--border)' : 0, fontSize: 13, cursor: 'pointer' }}>
                  <input type="checkbox" checked={!!escolhidos[p.email]} onChange={() => { setEscolhidos(prev => ({ ...prev, [p.email]: !prev[p.email] })); setConfirmar(false); }} />
                  <span>
                    <b>{p.nome}</b>
                    <span style={{ display: 'block', color: 'var(--text-muted)', fontSize: 12 }}>{p.email}{[p.departamento, p.empresa].filter(Boolean).length ? ` · ${[p.departamento, p.empresa].filter(Boolean).join(' · ')}` : ''}</span>
                  </span>
                </label>
              ))}
            </div>
          </div>
          <div>
            <div style={{ fontSize: 12, color: 'var(--text-muted)' }}>Email externo</div>
            <div style={{ display: 'flex', gap: 8, marginTop: 4 }}>
              <input value={externoInput} onChange={e => setExternoInput(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); adicionarExterno(); } }} placeholder="nome@empresa.com" style={{ ...campo, marginTop: 0, flex: 1 }} />
              <button type="button" onClick={adicionarExterno} style={{ border: '1px solid var(--border)', background: 'transparent', color: 'var(--text)', borderRadius: 8, padding: '8px 12px', fontSize: 13, fontWeight: 650, cursor: 'pointer', fontFamily: 'inherit' }}>Adicionar</button>
            </div>
          </div>
          <label style={{ fontSize: 12, color: 'var(--text-muted)' }}>
            Nota no email, em inglês
            <textarea value={nota} onChange={e => setNota(e.target.value)} rows={3} style={{ display: 'block', width: '100%', marginTop: 4, boxSizing: 'border-box', border: '1px solid var(--border)', borderRadius: 8, padding: '8px 10px', font: 'inherit', fontSize: 13, background: 'var(--bg)', color: 'var(--text)', resize: 'vertical' }} />
          </label>
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
            {[['agora', 'Agora'], ['semana', 'Todas as semanas'], ['mes', 'Todos os meses']].map(([id, label]) => (
              <button key={id} type="button" onClick={() => { setCadencia(id); setConfirmar(false); }} style={{ border: `1px solid ${cadencia === id ? '#3859D0' : 'var(--border)'}`, background: cadencia === id ? '#3859D0' : 'transparent', color: cadencia === id ? '#fff' : 'var(--text)', borderRadius: 99, padding: '6px 12px', fontSize: 12.5, fontWeight: 650, cursor: 'pointer', fontFamily: 'inherit' }}>{label}</button>
            ))}
          </div>
          {cadencia !== 'agora' && (
            <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' }}>
              {cadencia === 'semana' ? (
                <select value={diaSemana} onChange={e => setDiaSemana(Number(e.target.value))} style={{ border: '1px solid var(--border)', borderRadius: 8, padding: '8px 10px', font: 'inherit', fontSize: 13, background: 'var(--bg)', color: 'var(--text)' }}>
                  {MKR_DIAS.map((d, i) => <option key={d} value={i + 1}>{d}</option>)}
                </select>
              ) : (
                <label style={{ fontSize: 13 }}>Dia
                  <input type="number" min={1} max={28} value={diaMes} onChange={e => setDiaMes(Number(e.target.value))} style={{ width: 64, marginLeft: 6, border: '1px solid var(--border)', borderRadius: 8, padding: '8px 10px', font: 'inherit', fontSize: 13, background: 'var(--bg)', color: 'var(--text)' }} />
                </label>
              )}
              <input type="time" value={hora} onChange={e => setHora(e.target.value || '09:00')} style={{ border: '1px solid var(--border)', borderRadius: 8, padding: '8px 10px', font: 'inherit', fontSize: 13, background: 'var(--bg)', color: 'var(--text)' }} />
              <span style={{ fontSize: 12, color: 'var(--text-muted)' }}>Hora de Lisboa</span>
            </div>
          )}
        </div>
        {confirmar && (
          <div style={{ marginTop: 14, padding: 12, borderRadius: 8, background: 'var(--bg-sunken, #f8fafc)', fontSize: 13, lineHeight: 1.5 }}>
            Confirmar envio para {destinatarios().join(', ') || 'ninguém'}. O PDF do Overview segue em anexo.
            {destinatarios().some(foraDigidelta) && <> Os endereços fora de @digidelta.pt recebem o mesmo email sem o botão Open the report.</>}
          </div>
        )}
        {erro && <div style={{ marginTop: 12, fontSize: 13, color: 'var(--danger, #dc2626)' }}>{erro}</div>}
        {ok && <div style={{ marginTop: 12, fontSize: 13, color: 'var(--success, #16a34a)' }}>{ok}</div>}
        <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginTop: 16 }}>
          {btn('Pré-visualizar', previsualizar, false)}
          {cadencia === 'agora' && !confirmar && btn('Enviar agora', () => { setErro(''); setConfirmar(true); }, true)}
          {cadencia === 'agora' && confirmar && btn(aTrabalhar ? 'A enviar…' : 'Confirmar envio', enviar, true)}
          {cadencia !== 'agora' && btn(aTrabalhar ? 'A guardar…' : 'Guardar agenda', guardar, true)}
        </div>
        {html && (
          <iframe title="Pré-visualização do email" srcDoc={html} style={{ width: '100%', height: 480, marginTop: 16, border: '1px solid var(--border)', borderRadius: 8, background: '#fff' }} />
        )}
        <div style={{ marginTop: 18, borderTop: '1px solid var(--border)', paddingTop: 12 }}>
          <div style={{ fontSize: 12, fontWeight: 700, marginBottom: 8 }}>Agendas</div>
          {!lista?.envios?.length && <div style={{ fontSize: 12.5, color: 'var(--text-muted)' }}>Ainda não há agendas. O envio regular só acontece depois de guardares uma.</div>}
          {(lista?.envios || []).map(row => (
            <div key={row.id} style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap', padding: '8px 0', borderTop: '1px solid var(--border)', fontSize: 12.5 }}>
              <span style={{ fontWeight: 650 }}>{mkrAgendaTexto(row)}</span>
              <span style={{ color: 'var(--text-muted)' }}>{(row.destinatarios || []).length === 1 ? '1 destinatário' : `${(row.destinatarios || []).length} destinatários`} · {row.activo ? `próximo ${mkrQuando(row.proximo_em)}` : `em pausa${row.falhas ? ` · ${row.falhas} falhas` : ''}`}</span>
              <span style={{ marginLeft: 'auto', display: 'inline-flex', gap: 8 }}>
                <button type="button" onClick={() => pausar(row)} style={{ border: 0, background: 'transparent', color: '#3859D0', cursor: 'pointer', font: 'inherit', fontWeight: 650 }}>{row.activo ? 'Pausar' : 'Retomar'}</button>
                <button type="button" onClick={() => aApagar === row.id ? apagar(row.id) : setAApagar(row.id)} style={{ border: 0, background: 'transparent', color: 'var(--danger, #dc2626)', cursor: 'pointer', font: 'inherit', fontWeight: 650 }}>{aApagar === row.id ? 'Confirmar' : 'Apagar'}</button>
              </span>
            </div>
          ))}
          {!!lista?.log?.length && <div style={{ fontSize: 12, fontWeight: 700, margin: '14px 0 6px' }}>Últimos envios</div>}
          {(lista?.log || []).map(row => (
            <div key={row.id} style={{ fontSize: 12, color: 'var(--text-muted)', padding: '4px 0' }}>
              {mkrQuando(row.criado_em)} · {row.estado === 'enviado' ? 'enviado' : 'erro'} · {(row.destinatarios || []).join(', ')}
              {row.erro ? ` · ${row.erro}` : ''}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
};

// ── Ecrã ────────────────────────────────────────────────────────────────────
function mkrFiltrosIniciais() {
  if (mkrEmImpressao()) return { periodo: 'ytd', year: new Date().getFullYear(), inicio: '', fim: '', equipa: '', origem: '' };
  try {
    const f = JSON.parse(sessionStorage.getItem(MKR_FILTROS_KEY) || 'null');
    if (f && f.periodo) return f;
  } catch {}
  return { periodo: 'ytd', year: new Date().getFullYear(), inicio: '', fim: '', equipa: '', origem: '' };
}

const MktResultadosMimakiScreen = ({ setSub, userEmail }) => {
  const print = mkrEmImpressao();
  const bp = window.useBreakpoint ? window.useBreakpoint() : { isMobile: false };
  const isMobile = bp.isMobile;
  const [f, setF] = React.useState(mkrFiltrosIniciais);
  const [envioAberto, setEnvioAberto] = React.useState(false);
  const [tab, setTabState] = React.useState(() => {
    if (mkrEmImpressao()) return 'overview';
    const h = new URLSearchParams(String(window.location.hash || '').replace(/^#/, '')).get('tab');
    if (h && MKR_TABS.some(t => t.id === h)) return h;
    try { return sessionStorage.getItem(MKR_TAB_KEY) || 'overview'; } catch { return 'overview'; }
  });
  const [data, setData] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [erro, setErro] = React.useState(null);
  const [overTab, setOverTab] = React.useState(null);
  const scrollRef = React.useRef(null);
  window.useMktZoneLoading && window.useMktZoneLoading(loading && !data);

  React.useLayoutEffect(() => {
    if (!print) return undefined;
    document.documentElement.classList.add('mkr-print');
    document.documentElement.dataset.theme = 'light';
    return () => document.documentElement.classList.remove('mkr-print');
  }, [print]);
  const setTab = (t) => {
    if (print) return;
    setTabState(t);
    try { sessionStorage.setItem(MKR_TAB_KEY, t); } catch {}
    if (scrollRef.current) scrollRef.current.scrollTop = 0;
  };
  const mudarF = (patch) => {
    if (print) return;
    setF(prev => {
      const n = { ...prev, ...patch };
      try { sessionStorage.setItem(MKR_FILTROS_KEY, JSON.stringify(n)); } catch {}
      return n;
    });
  };
  const params = print
    ? { periodo: 'ytd', year: new Date().getFullYear(), aviso: '1' }
    : { periodo: f.periodo, year: f.year, inicio: f.periodo === 'custom' ? f.inicio : '', fim: f.periodo === 'custom' ? f.fim : '', equipa: f.equipa, origem: f.origem || '' };

  React.useEffect(() => {
    let vivo = true;
    setLoading(true); setErro(null);
    fetch(`${MKR_API}?${mkrQs(params, userEmail)}`, { headers: { 'Content-Type': 'application/json' } })
      .then(async r => { const b = await r.json().catch(() => ({})); if (!r.ok) throw new Error(b.error || `HTTP ${r.status}`); return b; })
      .then(d => { if (vivo) setData(d); })
      .catch(e => { if (vivo) setErro(e.message); })
      .finally(() => { if (vivo) setLoading(false); });
    return () => { vivo = false; };
  }, [f.periodo, f.year, f.inicio, f.fim, f.equipa, f.origem, print, userEmail]);

  const Periodo = window.BriefingDateRangePicker;
  const Filtro = window.ResFiltro;
  const MenuAccoes = window.ResMenuAccoes;
  const anoCorrente = new Date().getFullYear();
  const periodoValor = (() => {
    if (f.periodo === 'custom' && f.inicio && f.fim) {
      const h = resHomologo();
      const homologo = f.inicio === resDataIso(h.start) && f.fim === resDataIso(h.end);
      return { preset: homologo ? 'homologo' : 'custom', start: new Date(`${f.inicio}T00:00:00`), end: new Date(`${f.fim}T00:00:00`) };
    }
    const r = resPeriodoDatas(f.periodo, f.year) || resPeriodoDatas('ytd', f.year);
    return { preset: `${f.periodo}:${f.year}`, start: r.start, end: r.end };
  })();
  const mudarPeriodo = (v) => {
    if (v.preset === 'custom' || v.preset === 'homologo') {
      mudarF({ periodo: 'custom', inicio: resDataIso(v.start), fim: resDataIso(v.end), year: v.end.getFullYear() });
      return;
    }
    const [p, a] = String(v.preset).split(':');
    mudarF({ periodo: p || 'ytd', year: Number(a) || anoCorrente, inicio: '', fim: '' });
  };

  const csvUrl = `${MKR_API}.csv?${mkrQs(params, userEmail)}`;
  const pdfUrl = `${MKR_API}.pdf?${mkrQs({}, userEmail)}`;
  const tabVista = print ? 'overview' : tab;
  const snap = data?.fontes?.snapshot_primavera;
  const nEquipa = Object.fromEntries((data?.equipas || []).map(e => [e.key, e.n]));
  const nOrigem = Object.fromEntries((data?.origens || []).map(o => [o.key, o.n]));
  const equipasOpts = [{ value: '', label: 'Todas' }, ...((data?.equipas_def) || []).map(e => ({ value: e.key, label: e.label, grupo: e.grupo, cor: e.cor, n: nEquipa[e.key] ?? 0 }))];
  const origensOpts = [{ value: '', label: 'Todas' }, ...((data?.origens_def) || []).map(o => ({ value: o.key, label: o.label, cor: o.cor, n: nOrigem[o.key] ?? 0 }))];
  const origemActiva = data?.origem ? (data.origens_def || []).find(o => o.key === data.origem) : null;
  const equipaActiva = data?.equipa ? (data.equipas_def || []).find(e => e.key === data.equipa) : null;
  const filtrosOn = !!(f.equipa || f.origem);
  const menu = [
    { id: 'resultados', label: 'Resultados', sub: 'Funil, objectivos e campanhas', onClick: () => setSub && setSub('resultados') },
    { id: 'analise', label: 'Análise da Digi AI', sub: 'Leitura, BP e campanhas a fazer', onClick: () => setSub && setSub('resultados-analise') },
    { id: 'mimaki', label: 'Report Mimaki', sub: 'Pipeline das equipas Mimaki em ranks Mimaki', checked: true, onClick: () => setTab('overview') },
    { separador: true },
    { id: 'email', label: 'Enviar por email', sub: 'Uma vez, ou todas as semanas ou meses', onClick: () => setEnvioAberto(true) },
    { id: 'pdf', label: 'Descarregar PDF', sub: 'Overview YTD, todas as equipas e origens', onClick: () => window.open(pdfUrl, '_blank', 'noopener') },
    ...(filtrosOn ? [{ id: 'limpar', label: 'Limpar filtros', sub: [equipaActiva?.label, origemActiva?.label].filter(Boolean).join(' · '), onClick: () => mudarF({ equipa: '', origem: '' }) }] : []),
    { id: 'csv', label: 'Exportar CSV para a Mimaki', sub: 'Uma linha por oportunidade, colunas em inglês', disabled: !data?.linhas?.length, onClick: () => window.open(csvUrl, '_blank', 'noopener') },
  ];

  return (
    <div id="mkr-root" style={{ display: 'flex', flexDirection: 'column', height: '100%', minHeight: 0, background: 'var(--bg)' }}>
      <style>{`.mkr-linha:hover td{background:var(--bg-hover,#f1f5f9)}.mkr-linha-eq:hover{background:var(--bg-hover,#f1f5f9)!important}${MKR_PRINT_CSS}`}</style>
      <div id="mkr-head" style={{ background: 'var(--bg-elev)', borderBottom: '1px solid var(--border)', padding: isMobile ? '16px 16px 0' : '20px 40px 0', flexShrink: 0 }}>
        <div style={{ fontSize: 11, fontWeight: 600, letterSpacing: '0.07em', color: 'var(--text-muted)', textTransform: 'uppercase', marginBottom: 6, fontFamily: 'var(--font-mono)' }}>
          MARKETING · RESULTADOS
        </div>
        <h1 style={{ margin: 0, fontSize: 21, fontWeight: 700, color: 'var(--text)', fontFamily: 'var(--font-display)', letterSpacing: '-0.01em', lineHeight: 1.2 }}>
          Report Mimaki
        </h1>
        <div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 4 }}>Digidelta Iberia · Mimaki pipeline report for Mimaki EMEA</div>
        {print && data && (
          <div style={{ fontSize: 13, color: 'var(--text)', marginTop: 8 }}>{mkrIntervaloEn(data.periodo.inicio, data.periodo.fim)} · All teams · All origins</div>
        )}
        <div className="mkr-no-print" style={{ height: 1, background: 'var(--border)', margin: isMobile ? '14px -16px 0' : '16px -40px 0' }} />
        <div className="mkr-no-print" style={{ display: 'flex', gap: 0, overflowX: 'auto' }}>
          {MKR_TABS.map(t => {
            const active = tab === t.id;
            const over = overTab === t.id;
            return (
              <button key={t.id} type="button" onClick={() => setTab(t.id)} onMouseEnter={() => setOverTab(t.id)} onMouseLeave={() => setOverTab(null)}
                style={{
                  background: 'none', border: 'none', outline: 'none',
                  borderBottom: `2px solid ${active ? 'var(--ai-500)' : 'transparent'}`,
                  color: active || over ? 'var(--text)' : 'var(--text-muted)',
                  fontSize: 13, fontWeight: active ? 600 : 500,
                  fontFamily: 'var(--font-display)', letterSpacing: '.01em',
                  padding: '10px 18px 12px', cursor: 'pointer', whiteSpace: 'nowrap',
                  transition: 'color .15s, border-color .15s',
                }}>
                {t.label}
                {t.id === 'contact' && data?.mimaki_contact?.sem_avanco_14d > 0 && <span style={{ marginLeft: 6, fontSize: 10, fontWeight: 700, color: '#fff', background: 'var(--danger, #dc2626)', borderRadius: 99, padding: '1px 6px' }}>{data.mimaki_contact.sem_avanco_14d}</span>}
              </button>
            );
          })}
        </div>
      </div>

      <div id="mkr-scroll" ref={scrollRef} className="scrollbar" style={{ overflowY: 'auto', flex: 1, minHeight: 0, padding: isMobile ? '18px 16px 64px' : '28px 40px 64px', background: 'var(--bg)' }}>
        <div className="mkr-no-print" style={{ 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)', marginBottom: 18 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
            <button type="button" onClick={() => setSub && setSub('resultados')} style={{ background: 'transparent', border: 0, cursor: 'pointer', padding: 0, fontSize: 12, color: 'var(--ai-500, #3859D0)', fontFamily: 'inherit' }}>Resultados</button>
            <span style={{ color: 'var(--text-dim)', fontSize: 12 }}>›</span>
            <span style={{ fontSize: 12, color: 'var(--text)', fontWeight: 700 }}>Report Mimaki</span>
            {data && (
              <span style={{ marginLeft: 'auto', display: 'inline-flex', alignItems: 'center', gap: 8, flexWrap: 'wrap', fontSize: 12, color: 'var(--text-muted)' }}>
                <span title="Leitura ao vivo dos tenants PTDIG, DIGES, NSC e TAS. A cópia do portal só entra se uma base não responder.">{mkrN(data.resumo.n)} oportunidades</span>
                <span style={{ fontFamily: 'var(--font-mono)', fontSize: 11 }}>{mkrIntervalo(data.periodo.inicio, data.periodo.fim)}</span>
                {snap?.mes_max && <span title="Vendas do snapshot Primavera (comercial_ytd_snap). As facturas não trazem a origem da lead.">Primavera até {String(snap.mes_max).padStart(2, '0')}/{String(data.periodo.fim).slice(0, 4)}</span>}
              </span>
            )}
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap', marginTop: 10 }}>
            {Periodo ? (
              <Periodo compact align="left" value={periodoValor} onChange={mudarPeriodo} presets={resPeriodoPresets()} resolver={resPeriodoResolver} />
            ) : Filtro && (
              <Filtro label="Período" value={periodoValor.preset} onChange={id => mudarPeriodo({ preset: id, ...(resPeriodoResolver(id) || {}) })}
                options={resPeriodoPresets().filter(p => p.id !== 'custom').map(p => ({ value: p.id, label: p.label }))} />
            )}
            {Filtro && <Filtro label="Equipa" value={f.equipa} marcado={!!f.equipa} onChange={(v) => mudarF({ equipa: v })} options={equipasOpts} menuMinWidth={230} menuMaxHeight={380} />}
            {Filtro && <Filtro label="Origem" value={f.origem || ''} marcado={!!f.origem} onChange={(v) => mudarF({ origem: v })} options={origensOpts} menuMinWidth={280} menuMaxHeight={380} />}
            {filtrosOn && (
              <button type="button" onClick={() => mudarF({ equipa: '', origem: '' })} style={{ background: 'transparent', border: 0, padding: '6px 4px', cursor: 'pointer', fontFamily: 'inherit', fontSize: 12, fontWeight: 600, color: 'var(--ai-500, #3859D0)' }}>Limpar</button>
            )}
            {MenuAccoes && <div style={{ marginLeft: 'auto' }}><MenuAccoes icone items={menu} /></div>}
          </div>
        </div>

        {loading && !data && <AnVazio altura={200} texto="A carregar o report Mimaki…" />}
        {erro && <div id={print ? 'mkr-erro' : undefined} style={{ padding: 16, color: 'var(--danger)', fontSize: 12.5 }}>{erro}</div>}
        {data && (
          <div style={{ opacity: loading ? 0.6 : 1, transition: 'opacity .15s' }}>
            {origemActiva && (
              <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap', padding: '9px 12px', marginBottom: 14, borderRadius: 8, border: `1px solid color-mix(in oklch, ${origemActiva.cor} 35%, transparent)`, background: `color-mix(in oklch, ${origemActiva.cor} 7%, transparent)`, fontSize: 12 }}>
                <span style={{ width: 9, height: 9, borderRadius: 2, background: origemActiva.cor }} />
                <span>Showing opportunities generated by <b>{origemActiva.label}</b>. Primavera sales and BP stay at team level, because invoices carry no lead source.</span>
                <button type="button" className="btn btn-sm" style={{ marginLeft: 'auto' }} onClick={() => mudarF({ origem: '' })}>Show all origins</button>
              </div>
            )}
            {print && data.aviso_espanha?.texto && (
              <div style={{ marginBottom: 14, padding: '12px 14px', borderRadius: 8, background: '#fffbeb', border: '1px solid #fcd34d', color: '#78350f', fontSize: 12.5, lineHeight: 1.5 }}>{data.aviso_espanha.texto}</div>
            )}
            {tabVista === 'overview' && <MkrOverview data={data} isMobile={print ? false : isMobile} mudarF={mudarF} />}
            {tabVista === 'teams' && <MkrEquipas data={data} mudarF={mudarF} isMobile={isMobile} />}
            {tabVista === 'segments' && <MkrSegmentos data={data} />}
            {tabVista === 'contact' && <MkrContacto data={data} isMobile={isMobile} />}
            {tabVista === 'ops' && <MkrOps data={data} />}
            {tabVista === 'map' && <MkrMapa data={data} isMobile={isMobile} />}
            <div style={{ fontSize: 10.5, color: 'var(--text-dim)', marginTop: 18, lineHeight: 1.5 }}>{data.fontes.regra}</div>
          </div>
        )}
        {print && data && !loading && <div id="mkr-ready" data-ok="1" style={{ height: 0, overflow: 'hidden' }} />}
      </div>
      {envioAberto && !print && <MkrEnvioModal email={userEmail} onClose={() => setEnvioAberto(false)} />}
    </div>
  );
};

window.MktResultadosMimakiScreen = MktResultadosMimakiScreen;
