// Setor de Afiliados → Painel dos Três Gatilhos (spec 2026-09-10-sop-tres-gatilhos-design,
// Tasks 8-9). Espelha a Bíblia do Setor de Afiliados v1.2: AOV (7d) / Refund coorte (30d) /
// Take Rate 6 potes (90d acumulado) por afiliado, numa oferta. SEM PeriodSelector de
// propósito — as janelas são a régua, não um filtro (spec D7); ler um número fora da
// janela dele é erro, não outra visão. Self-contained, molde Leads.jsx. SEM mock — dado
// operacional de gestão (número inventado aqui vira conversa errada com afiliado).
//
// Ajuste de 11/set (review do usuário): o backend só devolve quem está vendendo AGORA
// (venda de front nos últimos 7 dias) — quem não vendeu na janela do AOV simplesmente
// não vem mais na lista. Por isso a tabela por padrão só mostra quem tem gatilho aceso
// (`acendeu.length > 0`); o checkbox "mostrar também quem está dentro da faixa" revela
// o resto, apagado. A legenda das colunas (GtpLegenda) fica sempre visível/recolhível na
// tela — as meninas de afiliados são novatas e não dá para depender só de tooltip de hover.
//
// Ajuste de 14/set: massa mínima virou POR GATILHO (AOV 5 / Refund 10 / Take 50), não
// mais um corte da linha inteira — aplicar o número do Take Rate à linha toda derrubava
// a fila de 135 afiliados ativos para 3 (identidade de afiliado partida no banco desde
// 31/ago). Cada célula sem a massa do seu próprio gatilho mostra o valor sem cor e não
// dispara (`legivel: false` no payload) — o `title` da célula explica o "N de M".

// Fallback usado só enquanto a lista do backend não chega — nunca como fonte da
// verdade. A lista real vem de GET /gatilhos/ofertas (spec 2026-09-19).
const GTP_OFERTAS_FALLBACK = ['Horsewood'];

// Réguas de cor de AOV/Take Rate/Delta/Multiplicador (cap. 2.2/2.3 da Bíblia): maior é
// melhor nos três primeiros grupos. `null` = sem referência — NUNCA pintar, é o pior erro
// possível aqui (oferta nova, janela sem massa).
const GTP_COR = { verde: 'var(--positive)', amarelo: 'var(--warning)', vermelho: 'var(--negative)' };
// Refund é invertido (maior é pior) e tem 4 estados: verde/alerta/pausa/desligar. Só há
// dois tokens de "problema" no CSS (warning/negative) — pausa e desligar dividem o
// --negative, mas a severidade não pode ficar idêntica: pausa fica bold, desligar vira
// pílula sólida (a régua manda suspender/desligar no mesmo dia, cap. 5.2).
const GTP_COR_REFUND = { verde: 'var(--positive)', alerta: 'var(--warning)', pausa: 'var(--negative)', desligar: 'var(--negative)' };

const GTP_PRAZO_LABEL = { mesmo_dia: 'Hoje', '24h': '24h', '48h': '48h' };
const GTP_PRAZO_COR = { mesmo_dia: 'var(--negative)', '24h': 'var(--warning)', '48h': 'var(--warning)' };

const GTP_COLS = [
  { key: 'afiliado', label: 'Afiliado', align: 'left'  },
  { key: 'massa',    label: 'Massa',    align: 'right' },
  { key: 'aov',      label: 'AOV',      align: 'right' },
  { key: 'delta',    label: 'Delta',    align: 'right' },
  { key: 'refund',   label: 'Refund',   align: 'right' },
  { key: 'take',     label: 'Take 6',   align: 'right' },
  { key: 'leitura',  label: 'Leitura',  align: 'left'  },
];

const gtpMoneySigned = (n) => n == null ? '—' : (n < 0 ? `−${USD(-n)}` : USD(n));

// Massa é POR GATILHO (spec 14/set): sem ela, a célula mostra o valor sem cor —
// nunca pintar de verde por falta de amostra — e o title explica o "N de M".
const gtpMassaTitle = (m) => (m && m.legivel === false && m.massaMin != null)
  ? `sem massa: ${NUM(m.massa || 0)} de ${NUM(m.massaMin)} pedidos`
  : null;

// Célula de refund: 4 estados de severidade crescente, sem faixa (cor null) fica cinza
// neutro — a UI nunca inventa cor onde o backend manda `cor: null` (oferta sem teto).
// NOTA: estas células devolvem só um <span> de cor/peso — o <td> pai já carrega
// padding/borda/tabular-nums via gtp.tdNum. Repetir tdNum aqui duplicaria o padding
// dentro da célula (bug pego na auto-revisão, antes do commit).
const GtpRefundCell = ({ refund }) => {
  if (refund.valor == null) return <span style={gtp.dash}>—</span>;
  if (!refund.cor) return <span style={{ color: 'var(--fg-3)' }} className="fenix-num">{PCT(refund.valor)}</span>;
  if (refund.cor === 'desligar') {
    return <span style={gtp.pillDesligar} className="fenix-num">{PCT(refund.valor)}</span>;
  }
  const bold = refund.cor === 'pausa';
  return (
    <span style={{ color: GTP_COR_REFUND[refund.cor], fontWeight: bold ? 800 : 700 }} className="fenix-num">
      {PCT(refund.valor)}
    </span>
  );
};

// Célula genérica de valor com régua verde/amarelo/vermelho (AOV, Delta, Take).
// `null` nunca recebe cor — mostra travessão em tom apagado (fg-4), nem erro nem êxito.
const GtpValCell = ({ valor, cor, fmt, muted }) => {
  if (valor == null) return <span style={gtp.dash}>—</span>;
  if (muted) return <span style={{ color: 'var(--fg-4)' }} className="fenix-num">{fmt(valor)}</span>;
  if (!cor) return <span className="fenix-num">{fmt(valor)}</span>;
  return <span style={{ color: GTP_COR[cor], fontWeight: 700 }} className="fenix-num">{fmt(valor)}</span>;
};

const GtpPrazoTag = ({ prazo }) => !prazo ? null : (
  <span style={{ ...gtp.prazoTag, color: GTP_PRAZO_COR[prazo] || 'var(--fg-3)',
    borderColor: GTP_PRAZO_COR[prazo] || 'var(--border-2)' }}>
    {GTP_PRAZO_LABEL[prazo] || prazo}
  </span>
);

// Legenda das colunas (pedido do usuário 11/set): "às vezes as meninas de afiliados são
// novatas e elas não sabem muito porque estão contatando algum afiliado" — texto exato
// da régua da Bíblia, não paráfrase. Fica em painel recolhível, NUNCA só em tooltip.
const GTP_LEGENDA_ITENS = [
  { col: 'Afiliado', texto: 'Nome do afiliado na BuyGoods. É por ele que o dashboard identifica a pessoa.' },
  { col: 'Massa', texto: 'Pedidos de front (a compra de entrada, sem upsell) nesta oferta nos últimos 90 dias. Cada gatilho exige a amostra que ele precisa para significar algo: AOV a partir de 5 vendas na semana, Refund a partir de 10 pedidos na coorte, e Take Rate a partir de 50 acumulados — abaixo disso o número aparece sem cor e não gera caso. Quanto maior a massa, mais firme o Take Rate: 50 erram ±13,6 pontos e servem para conversa consultiva; 100 é a massa da Bíblia; 200 sustenta ajuste de CPA; 400 sustenta desativação.' },
  { col: 'AOV', texto: 'Receita média por comprador nos últimos 7 dias, valor cheio, antes de reembolso. Comparado com o AOV da própria oferta no mesmo período: verde é igual ou acima, amarelo é entre 85% e 100%, vermelho é abaixo de 85%. Sozinho não diz nada — só significa alguma coisa contra o CPA daquele afiliado.' },
  { col: 'Delta', texto: 'AOV menos CPA: o que sobra por comprador. Verde a partir de $100, amarelo entre $60 e $99, vermelho abaixo de $60. Atenção: o CPA aqui é estimado pela comissão paga dividida pelos pedidos, não é o CPA configurado na BuyGoods.' },
  { col: 'Refund', texto: 'Taxa de devolução da coorte de 30 dias: conta os pedidos feitos no período, mesmo que o estorno tenha caído semanas depois. Cada oferta tem o teto dela, definido pelo Financeiro — alerta, pausa e desligamento. Oferta sem teto definido aparece sem cor.' },
  { col: 'Take 6', texto: 'De cada 100 pedidos de entrada dele, quantos fecham no pack de 6 potes. É o principal motor do AOV e responde a fechamento de oferta, não a criativo de topo. Comparado ao baseline da mesma oferta — comparar entre ofertas diferentes não diz nada.' },
  { col: 'Leitura', texto: 'O que os três gatilhos dizem quando cruzados, e a alavanca certa para esse caso. O selo de prazo (HOJE, 24H, 48H) é o tempo máximo para o primeiro contato.' },
];
const GTP_LEGENDA_KEY = 'gtp_legenda_aberta';

// Painel recolhível, começa expandido na 1ª visita; preferência salva no localStorage.
// try/catch nas duas pontas: em janela anônima o acesso a localStorage pode estourar.
const GtpLegenda = () => {
  const [aberto, setAberto] = React.useState(() => {
    try {
      const v = localStorage.getItem(GTP_LEGENDA_KEY);
      return v === null ? true : v === '1';
    } catch { return true; }
  });
  const toggle = () => setAberto(prev => {
    const next = !prev;
    try { localStorage.setItem(GTP_LEGENDA_KEY, next ? '1' : '0'); } catch { /* privado/estourou */ }
    return next;
  });
  return (
    <div style={gtp.legendaWrap}>
      <button type="button" style={gtp.legendaHead} onClick={toggle}>
        {aberto ? '▾' : '▸'} O que cada coluna significa
      </button>
      {aberto && (
        <div style={gtp.legendaBody}>
          <div style={gtp.legendaGrid}>
            {GTP_LEGENDA_ITENS.map(l => (
              <div key={l.col} style={gtp.legendaItem}>
                <div style={gtp.legendaCol}>{l.col}</div>
                <div style={gtp.legendaTexto}>{l.texto}</div>
              </div>
            ))}
          </div>
          <div style={gtp.legendaRodape}>
            As janelas de cada coluna são fixas porque fazem parte da régua — ler um
            número fora da janela dele dá conclusão errada.
          </div>
        </div>
      )}
    </div>
  );
};

const GatilhosPainel = ({ onKpiSelect } = {}) => {
  const [oferta, setOferta] = React.useState(GTP_OFERTAS_FALLBACK[0]);
  const [ofertas, setOfertas] = React.useState(GTP_OFERTAS_FALLBACK);
  const [afiliadoSel, setAfiliadoSel] = React.useState('todos');
  const [roster, setRoster] = React.useState([]);
  // Filtro local (não passa por withLoad/fetch — os dados já estão na mão): por padrão
  // só quem tem gatilho aceso; marcado, mostra todo mundo com opacidade nos apagados.
  const [mostrarTodos, setMostrarTodos] = React.useState(false);
  const [sortKey, setSortKey] = React.useState('massa');
  const [dir, setDir] = React.useState(-1);
  const [data, setData] = React.useState(null);
  const [err, setErr] = React.useState(false);
  const [loading, setLoading] = React.useState(true);
  const fullLoading = useFullLoading();
  const withLoad = (fn) => (...a) => { fullLoading.mark(); fn(...a); };

  const setOfertaF = withLoad((v) => { setRoster([]); setAfiliadoSel('todos'); setOferta(v); });
  const setAfiliadoSelF = withLoad(setAfiliadoSel);

  const retry = useAutoRetry(err);   // falhou → tenta de novo a cada 15s, sozinho

  const [ofertasErro, setOfertasErro] = React.useState(false);

  // Lista de ofertas do dropdown, carregada uma vez. Falhou (ou veio vazia, o que é
  // tão anômalo quanto falha de rede): segue com o fallback de 1 oferta, mas com
  // aviso visível — dado ausente vira aviso, nunca fallback mudo (foi o silêncio
  // que deixou US$ 212 mil invisíveis nesta mesma entrega). Não acoplado ao
  // useAutoRetry do painel abaixo: aquele retry é do payload do painel, não desta lista.
  React.useEffect(() => {
    let cancelled = false;
    fetchGatilhosOfertas()
      .then(d => {
        if (cancelled) return;
        if (!d.ofertas || !d.ofertas.length) { setOfertasErro(true); return; }
        setOfertas(d.ofertas.map(o => o.nome));
        setOfertasErro(false);
      })
      // Falha aqui NÃO pode ser muda: sem aviso, o seletor mostra 1 oferta de 26
      // e parece que só existe uma. Dado ausente vira aviso, nunca fallback mudo.
      .catch(() => { if (!cancelled) setOfertasErro(true); });
    return () => { cancelled = true; };
  }, []);

  React.useEffect(() => {
    const began = fullLoading.begin();
    let cancelled = false; setLoading(true);
    const q = afiliadoSel === 'todos' ? '' : afiliadoSel;
    fetchGatilhosPainel({ oferta, q })
      .then(d => {
        if (cancelled) return;
        setData(d); setErr(false);
        if (afiliadoSel === 'todos') setRoster(d.linhas.map(r => r.afiliado));
      })
      .catch(() => { if (!cancelled) setErr(true); })
      .finally(() => { if (!cancelled) setLoading(false); fullLoading.end(began); });
    return () => { cancelled = true; };
  }, [oferta, afiliadoSel, retry]);

  const sortBy = (k) => k === sortKey ? setDir(-dir) : (setSortKey(k), setDir(-1));
  const ind = (k) => sortKey === k ? (dir < 0 ? ' ▾' : ' ▴') : '';

  const linhas = (data && data.linhas) || [];
  // Quantos o checkbox revelaria: ativos que NÃO têm gatilho aceso.
  const dentroDaFaixa = linhas.filter(r => r.acendeu.length === 0).length;
  const list = React.useMemo(() => {
    const val = (r) => {
      switch (sortKey) {
        case 'afiliado': return r.afiliado;
        case 'massa':    return r.pedidos_front;
        case 'aov':      return r.aov.valor;
        case 'delta':    return r.aov.delta;
        case 'refund':   return r.refund.valor;
        case 'take':     return r.take.valor;
        case 'leitura':  return r.leitura;
        default:         return r.pedidos_front;
      }
    };
    const l = linhas.slice();
    l.sort((a, b) => {
      const av = val(a), bv = val(b);
      // null sempre por último, nas duas direções — valor sem referência nunca "vence".
      if (av == null && bv == null) return 0;
      if (av == null) return 1;
      if (bv == null) return -1;
      if (typeof av === 'string') return av.localeCompare(bv) * dir;
      return (av - bv) * dir;
    });
    return mostrarTodos ? l : l.filter(r => r.acendeu.length > 0);
  }, [linhas, sortKey, dir, mostrarTodos]);

  // 1º load falhou: LoadingScreen offline + retry automático — nunca card estático sem retry.
  if (err && !data) return (
    <>
      <LoadingScreen conn="offline" />
      <div style={gtp.reconNota}>
        O Painel dos Três Gatilhos lê direto do banco — se a API caiu, esta tela
        reconecta sozinha a cada 15s.
      </div>
    </>
  );
  if (!data) return <LoadingScreen conn="connecting"/>;

  const kpis = data.kpis || {};
  const baselines = data.baselines || {};
  const rosterOptions = [{ key: 'todos', label: 'Todos os afiliados' },
    ...roster.map(a => ({ key: a, label: a }))];

  return (
    <div style={{ position: 'relative' }}>
      <div style={gtp.header}>
        <div>
          <div style={gtp.eye}>Setor de Afiliados</div>
          <div style={gtp.title}>Painel dos Três Gatilhos</div>
          <div style={gtp.regua}>
            AOV {data.janelas.aov}d · Refund coorte {data.janelas.refund}d · Take Rate 6
            potes acumulado {data.janelas.baseline}d
            {baselines.elegiveis > 0 &&
              ` · baseline sobre ${baselines.elegiveis} afiliado${baselines.elegiveis === 1 ? '' : 's'} com ${data.massaMinima}+ pedidos`}
          </div>
        </div>
        <div style={gtp.filterRow}>
          <Dropdown icon="cart" label="Oferta" value={oferta} onChange={setOfertaF}
            options={ofertas.map(o => ({ key: o, label: o }))} />
          <SearchDropdown icon="users" label="Afiliado" value={afiliadoSel} onChange={setAfiliadoSelF}
            options={rosterOptions} />
          {/* A contagem no rótulo não é enfeite: na maioria das ofertas TODO afiliado
              ativo já está aceso, então marcar a caixa não revelaria ninguém — e um
              controle que promete revelar algo e não revela parece quebrado. Com o
              número à vista, a caixa diz a verdade antes de ser clicada. */}
          <label style={{ ...gtp.checkboxLabel,
                          opacity: dentroDaFaixa === 0 ? 0.45 : 1,
                          cursor: dentroDaFaixa === 0 ? 'default' : 'pointer' }}>
            <input type="checkbox" checked={mostrarTodos} disabled={dentroDaFaixa === 0}
              onChange={e => setMostrarTodos(e.target.checked)} />
            <span>
              {dentroDaFaixa === 0
                ? 'todos os ativos já estão na lista'
                : `mostrar também quem está dentro da faixa (${NUM(dentroDaFaixa)})`}
            </span>
          </label>
        </div>
      </div>

      {ofertasErro && (
        <div style={gtp.ofertasErroNote}>
          Não foi possível carregar a lista de ofertas. Mostrando apenas
          {' '}<b>{GTP_OFERTAS_FALLBACK[0]}</b> — recarregue a página para tentar de novo.
        </div>
      )}

      <GtpLegenda />

      {err && data && (
        <div style={gtp.notice}><Icon name="alert" size={14} color="currentColor" />
          Falha ao atualizar — mostrando a última leitura carregada.</div>
      )}

      {data.semFaixaRefund && (
        <div style={gtp.notice}><Icon name="alert" size={14} color="currentColor" />
          {oferta} não tem faixa de refund definida: {data.semFaixaRefund} A coluna Refund
          fica sem cor nesta oferta.
        </div>
      )}

      {data.tetoProvisorio && (
        <div style={gtp.tetoNote}>
          Teto provisório — não veio do Financeiro. Régua DOCX 5.1
          (13,7 / 17 / 20). Trocar quando a Elaine calcular o teto desta oferta.
        </div>
      )}

      <div style={{ ...gtp.kpiRow, gridTemplateColumns: 'repeat(5,minmax(0,1fr))' }}>
        <DotKpi eye="A contatar" color="var(--fenix-blue)" value={NUM(kpis.aContatar || 0)} />
        <DotKpi eye="Vermelho" color="var(--negative)" value={NUM(kpis.vermelho || 0)}
          valColor={kpis.vermelho > 0 ? 'var(--negative)' : undefined} />
        <DotKpi eye="Amarelo" color="var(--warning)" value={NUM(kpis.amarelo || 0)}
          valColor={kpis.amarelo > 0 ? 'var(--warning)' : undefined} />
        <DotKpi eye="Ativos" color="var(--gray-400)" value={NUM(kpis.ativos || 0)} />
        <DotKpi eye="AOV da oferta" color="var(--fenix-orange)"
          value={baselines.aov != null ? USD(baselines.aov) : '—'} />
      </div>

      <div style={gtp.tableWrap}>
        <div style={gtp.scroll}>
          <table style={gtp.table}>
            <thead><tr>
              {GTP_COLS.map(c => (
                <th key={c.key} onClick={() => sortBy(c.key)}
                  style={{ ...gtp.th, textAlign: c.align }}>{c.label}{ind(c.key)}</th>
              ))}
            </tr></thead>
            <tbody>
              {/* key = NOME, nunca afiliadoId: um mesmo affiliate_id cobre vários
                  afiliados no banco (o id 102 cobre ghostrider56 e melocompany),
                  e usá-lo aqui duplica chave de React, que duplica ou some linha
                  na fila. O SQL agrupa por nome — ele é único por construção. */}
              {list.map(r => (
                <tr key={r.afiliado}
                  style={{ opacity: r.acendeu.length > 0 ? 1 : 0.5, cursor: onKpiSelect ? 'pointer' : 'default' }}
                  onClick={() => onKpiSelect && onKpiSelect({
                    kind: 'gt-afiliado',
                    eye: r.afiliado,
                    val: USD(r.aov.valor),
                    sub: `${oferta} · ${NUM(r.pedidos_front)} pedidos de front em 90d`,
                    icon: 'users', tint: 'var(--fenix-blue-100)', fg: 'var(--fenix-blue)',
                    // Esta tela lê refund SEMPRE por coorte (cap. 3.2), nunca pelo
                    // filtro global visão-caixa — sem isto o drawer exibiria o aviso
                    // "reembolsos pela data do cancelamento", que contradiz o número
                    // que a própria tela acabou de calcular.
                    refundBy: 'pedido',
                    row: r, baselines, baselineAnterior: data.baselineAnterior,
                  })}
                  onMouseEnter={e => { e.currentTarget.style.background = 'var(--gray-50)'; }}
                  onMouseLeave={e => { e.currentTarget.style.background = 'transparent'; }}>
                  <td style={{ ...gtp.td, textAlign: 'left' }}>
                    {r.afiliado}
                  </td>
                  <td style={gtp.tdNum} className="fenix-num">
                    {NUM(r.pedidos_front)}
                  </td>
                  <td style={gtp.tdNum} title={gtpMassaTitle(r.aov) || undefined}>
                    <GtpValCell valor={r.aov.valor} cor={r.aov.cor} fmt={USD} />
                  </td>
                  <td style={gtp.tdNum} title={gtpMassaTitle(r.aov) || undefined}>
                    <GtpValCell valor={r.aov.delta} cor={r.aov.corDelta} fmt={gtpMoneySigned} />
                  </td>
                  <td style={gtp.tdNum} title={gtpMassaTitle(r.refund) || undefined}>
                    <GtpRefundCell refund={r.refund} />
                  </td>
                  <td style={gtp.tdNum}
                    title={gtpMassaTitle(r.take)
                      || (r.take.packs ? `Pacotes de front: 6un ${NUM(r.take.packs.p6 || 0)} · 3un ${NUM(r.take.packs.p3 || 0)} · 2un ${NUM(r.take.packs.p2 || 0)} · outros ${NUM(r.take.packs.outros || 0)}` : undefined)}>
                    <GtpValCell valor={r.take.valor} cor={r.take.cor} fmt={PCT} />
                  </td>
                  <td style={{ ...gtp.td, textAlign: 'left' }} title={r.alavanca || undefined}>
                    <GtpPrazoTag prazo={r.prazo} />
                    <span style={r.semClassificacao ? { color: 'var(--warning)' } : undefined}>{r.leitura}</span>
                  </td>
                </tr>
              ))}
              {!loading && list.length === 0 && (
                <tr><td colSpan={GTP_COLS.length} style={gtp.empty}>Nenhum afiliado encontrado.</td></tr>
              )}
            </tbody>
          </table>
        </div>
      </div>
      <FullLoadingOverlay show={fullLoading.show && !!data} conn="connecting"/>
    </div>
  );
};

const gtp = {
  header: { display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16, marginBottom: 16, flexWrap: 'wrap' },
  eye: { font: 'var(--text-label)', letterSpacing: '.05em', textTransform: 'uppercase', color: 'var(--fg-3)' },
  title: { font: '800 22px/1.1 var(--font-sans)', color: 'var(--fg-1)', letterSpacing: '-.01em', marginTop: 4 },
  regua: { font: 'var(--text-caption)', color: 'var(--fg-3)', marginTop: 6 },
  filterRow: { display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' },
  checkboxLabel: { display: 'flex', alignItems: 'center', gap: 7, font: '600 12.5px/1.2 var(--font-sans)', color: 'var(--fg-2)', cursor: 'pointer', userSelect: 'none' },
  notice: { display: 'flex', alignItems: 'center', gap: 6, font: '600 12px/1.4 var(--font-sans)', color: 'var(--warning)', background: 'var(--warning-bg)', padding: '10px 16px', borderRadius: 'var(--r-md)', marginBottom: 12 },
  tetoNote:{ padding:'6px 10px', marginBottom:8, borderRadius:6, fontSize:11,
    background:'var(--warning-bg, #FEF3C7)', color:'var(--warning, #E0922B)' },
  ofertasErroNote:{ padding:'6px 10px', marginBottom:8, borderRadius:6, fontSize:11,
    background:'var(--negative-bg, #FDE7E5)', color:'var(--negative, #D8453B)' },
  kpiRow: { display: 'grid', gap: 14, marginBottom: 16 },
  legendaWrap: { border: '1px solid var(--border-1)', borderRadius: 'var(--r-lg)', background: '#fff', marginBottom: 16, overflow: 'hidden' },
  legendaHead: { width: '100%', textAlign: 'left', border: 0, background: 'var(--bg-sunken)', cursor: 'pointer', padding: '11px 16px', font: '700 12.5px/1.2 var(--font-sans)', color: 'var(--fg-2)' },
  legendaBody: { padding: '16px 18px' },
  legendaGrid: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill,minmax(230px,1fr))', gap: '14px 20px' },
  legendaItem: { display: 'flex', flexDirection: 'column', gap: 4 },
  legendaCol: { font: '800 10.5px/1 var(--font-sans)', letterSpacing: '.04em', textTransform: 'uppercase', color: 'var(--fenix-blue)' },
  legendaTexto: { font: '500 12px/1.45 var(--font-sans)', color: 'var(--fg-3)' },
  legendaRodape: { marginTop: 16, paddingTop: 12, borderTop: '1px solid var(--border-1)', font: 'var(--text-caption)', color: 'var(--fg-4)', lineHeight: 1.4 },
  tableWrap: { background: '#fff', border: '1px solid var(--border-1)', borderRadius: 'var(--r-lg)', boxShadow: 'var(--shadow-sm)', overflow: 'hidden' },
  scroll: { maxHeight: 600, overflowY: 'auto', overflowX: 'auto' },
  table: { width: '100%', borderCollapse: 'collapse', minWidth: 860 },
  th: { font: '700 10.5px/1 var(--font-sans)', letterSpacing: '.04em', textTransform: 'uppercase', color: 'var(--fg-3)', padding: '12px 14px', background: 'var(--bg-sunken)', borderBottom: '1px solid var(--border-2)', whiteSpace: 'nowrap', userSelect: 'none', cursor: 'pointer', position: 'sticky', top: 0, zIndex: 2 },
  td: { padding: '11px 14px', borderBottom: '1px solid var(--border-1)', font: 'var(--text-body-sm)', color: 'var(--fg-1)', whiteSpace: 'nowrap' },
  tdNum: { padding: '11px 14px', borderBottom: '1px solid var(--border-1)', font: 'var(--text-body-sm)', color: 'var(--fg-1)', whiteSpace: 'nowrap', textAlign: 'right', fontFamily: 'var(--font-num)', fontVariantNumeric: 'tabular-nums' },
  dash: { color: 'var(--fg-4)', fontFamily: 'var(--font-num)', fontVariantNumeric: 'tabular-nums' },
  pillDesligar: { display: 'inline-block', background: 'var(--negative)', color: '#fff', fontWeight: 800, padding: '2px 9px', borderRadius: 'var(--r-pill)' },
  prazoTag: { display: 'inline-block', marginRight: 8, font: '700 9.5px/1 var(--font-sans)', letterSpacing: '.03em', textTransform: 'uppercase', border: '1px solid', borderRadius: 'var(--r-pill)', padding: '2px 6px' },
  empty: { padding: '34px 22px', textAlign: 'center', font: 'var(--text-body-sm)', color: 'var(--fg-4)' },
  reconNota: { textAlign: 'center', font: 'var(--text-body-sm)', color: 'var(--fg-4)', maxWidth: 520, margin: '-28px auto 0' },
};

window.GatilhosPainel = GatilhosPainel;
