// Setor de Afiliados → SOP dos Três Gatilhos → Alertas (spec 2026-09-10-sop-tres-gatilhos-design,
// Tasks 15-17). Fila de CASOS: cada ticket é um par afiliado×oferta que acendeu pelo menos um
// dos três gatilhos. Uma pessoa pega, fala com o afiliado, resolve com a alavanca aplicada.
// Molde Leads.jsx / irmã GatilhosPainel.jsx (prefixo gtp) — aqui o prefixo é gta. Self-contained,
// SEM mock (fila operacional: número inventado aqui vira conversa errada com afiliado).
//
// Decisões de leitura do domínio (não pedir, decidir e documentar — pedido já é detalhado):
// - `snapshot` é a LINHA DO PAINEL congelada (payload_insert em gatilhos_tickets.py faz
//   `json.dumps(linha, default=str)`), ou seja `snapshot.aov/.refund/.take` já chegam como
//   objetos {valor, cor, ...} — não como número solto. Normalizamos defensivamente mesmo assim
//   (gtaMetric) para o card nunca quebrar se o shape mudar.
// - O baseline NUMÉRICO da oferta mora só no payload do Painel (`data.baselines`), nunca foi
//   persistido dentro do ticket — não existe campo para "puxar" um número aqui sem inventar.
//   O jeito honesto de mostrar "o baseline ao lado" é a legenda por cor (o `cor` de cada campo
//   JÁ é o resultado da comparação contra o baseline no momento da abertura — cap. 2.2 da Bíblia).
// - `dado_fechou_em` é o instante em que a varredura rodou (main.py: `dado_fechou_em=agora`) —
//   a base do prazo é ELE, não `criado_em` (que no banco tende a ser o mesmo valor, mas o
//   contrato semântico é "quando o dado fechou", não "quando a linha foi gravada").

// ---------------------------------------------------------------------------
// Réguas de cor — CÓPIAS com prefixo próprio (não depender da ordem de carga de
// GatilhosPainel.jsx, mesmo os dois lendo a mesma Bíblia).
const GTA_COR = { verde: 'var(--positive)', amarelo: 'var(--warning)', vermelho: 'var(--negative)' };
const GTA_COR_REFUND = { verde: 'var(--positive)', alerta: 'var(--warning)', pausa: 'var(--negative)', desligar: 'var(--negative)' };

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

const GTA_ESTADOS = [
  { key: 'aberto',         label: 'Abertos' },
  { key: 'em_atendimento', label: 'Em atendimento' },
  { key: 'observacao',     label: 'Em observação' },
  { key: 'resolvido',      label: 'Resolvidos' },
];

const GTA_VAZIO = {
  aberto: 'Nenhum caso aberto. A fila é recalculada ao abrir a tela e a cada 10 minutos.',
  em_atendimento: 'Ninguém com caso em atendimento agora.',
  observacao: 'Nenhum caso em observação no momento.',
  resolvido: 'Nenhum caso resolvido nesta janela.',
};

// Legenda por cor — o que cada uma significa vs. o baseline da oferta (cap. 2.2/2.3),
// já que o número do baseline em si não sobrevive no ticket (só o veredito colorido).
const GTA_AOV_CAPTION = {
  verde: 'no nível do AOV da oferta', amarelo: 'entre 85% e 100% do AOV da oferta',
  vermelho: 'abaixo de 85% do AOV da oferta',
};
const GTA_TAKE_CAPTION = {
  verde: 'no nível do Take Rate da oferta', amarelo: 'entre 85% e 100% do baseline',
  vermelho: 'abaixo de 85% do baseline',
};
const GTA_REFUND_CAPTION = {
  verde: 'dentro do teto da oferta', alerta: 'acima do teto — alerta',
  pausa: 'acima do teto — pausar CPA', desligar: 'acima do teto — desligar oferta',
};

const GTA_ESTADO_LABEL = { aberto: 'Aberto', em_atendimento: 'Em atendimento', observacao: 'Em observação', resolvido: 'Resolvido' };
const GTA_ESTADO_COR = { aberto: 'var(--fenix-blue)', em_atendimento: 'var(--warning)', observacao: 'var(--info)', resolvido: 'var(--positive)' };
const GTA_EVENTO_VERBO = {
  criado: 'abriu o caso', checkpoint: 'abriu o checkpoint D+8', pegar: 'pegou o caso',
  devolver: 'devolveu para a fila', resolver: 'resolveu', reabrir: 'reabriu o caso',
  encerrado: 'encerrou o caso', nota: 'escreveu',
};
const GTA_NOTA_MAX = 2000;

// Complemento curto da linha de ação: o QUE mudou, lido de `dados`.
const gtaDetalheEvento = (ev) => {
  const d = ev.dados || {};
  if (ev.tipo === 'resolver' && d.alavanca) return `Alavanca: ${d.alavanca}`;
  if (ev.tipo === 'reabrir' && d.de) return `${GTA_ESTADO_LABEL[d.de] || d.de} → Aberto`;
  if (ev.tipo === 'encerrado' && d.motivo) return d.motivo;
  if (ev.tipo === 'checkpoint' && d.checkpoint_de != null) return `retomada do caso #${d.checkpoint_de}`;
  return null;
};

// ---------------------------------------------------------------------------
// Datas/horas. `observacao_ate` é DATE puro (sem hora) — precisa do sufixo T12:00:00
// (gotcha do projeto: sem ele, o parse UTC-midnight some um dia em fuso negativo).
const gtaFmtDiaSolo = (iso) => iso
  ? new Date(iso + 'T12:00:00').toLocaleDateString('pt-BR', { day: '2-digit', month: 'short', year: 'numeric' })
  : '—';
const gtaFmtDataHora = (iso) => iso
  ? new Date(iso).toLocaleDateString('pt-BR', { day: '2-digit', month: 'short', year: 'numeric' })
  : '—';
// Linha do tempo do caso: dia E hora ("quem fez o quê, quando" — spec 2026-09-23).
const gtaFmtCarimbo = (iso) => iso
  ? new Date(iso).toLocaleString('pt-BR', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' })
  : '—';
const gtaFmtHora = (iso) => iso
  ? new Date(iso).toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' })
  : '—';

// Prazo = tempo máximo para o primeiro contato, contado a partir de quando o dado
// fechou (cap. 5.2). mesmo_dia = até o fim do dia de `dado_fechou_em`; 24h/48h somam
// horas corridas.
function gtaDeadline(dadoFechouEm, prazo) {
  if (!dadoFechouEm || !prazo) return null;
  const base = new Date(dadoFechouEm);
  if (Number.isNaN(base.getTime())) return null;
  if (prazo === 'mesmo_dia') {
    const fim = new Date(base);
    fim.setHours(23, 59, 59, 999);
    return fim;
  }
  const horas = prazo === '24h' ? 24 : prazo === '48h' ? 48 : null;
  return horas == null ? null : new Date(base.getTime() + horas * 3600000);
}

const gtaDurTxt = (ms) => {
  const abs = Math.abs(ms);
  const dias = Math.floor(abs / 86400000);
  const h = Math.floor((abs % 86400000) / 3600000);
  const m = Math.floor((abs % 3600000) / 60000);
  if (dias > 0) return `${dias}d${h > 0 ? ` ${h}h` : ''}`;
  if (h > 0) return `${h}h${m > 0 ? ` ${m}min` : ''}`;
  return `${Math.max(m, 1)}min`;
};

function gtaPrazoTexto(dadoFechouEm, prazo) {
  const dl = gtaDeadline(dadoFechouEm, prazo);
  if (!dl) return '';
  const diff = dl.getTime() - Date.now();
  return diff > 0 ? `vence em ${gtaDurTxt(diff)}` : `vencido há ${gtaDurTxt(diff)}`;
}

// Normalização defensiva do snapshot: aceita objeto {valor,cor,...} (shape real, é a
// linha do painel congelada) OU número solto, sem quebrar o card se o backend mudar.
const gtaMetric = (v) => {
  if (v == null) return { valor: null, cor: null };
  if (typeof v === 'object') return v;
  return { valor: v, cor: null };
};

// ---------------------------------------------------------------------------
// `legivel === false` (spec 14/set): a métrica não tem a massa que aquele gatilho
// exige. O valor aparece igual, mas SEM cor (nem pílula de desligar) — nunca
// pintar de verde/vermelho por falta de amostra — e a legenda declara o "N de M".
const GtaMetricCard = ({ label, valor, fmt, cor, corMap, caption, aceso, legivel, massa, massaMin, badge }) => {
  const semMassa = legivel === false;
  const tom = semMassa ? null : corMap[cor];
  const pill = !semMassa && cor === 'desligar';
  const legenda = semMassa && massaMin != null
    ? `sem massa: ${NUM(massa || 0)} de ${NUM(massaMin)} pedidos`
    : caption;
  return (
    <div style={{ ...gta.metric, ...(aceso ? gta.metricAceso : {}) }}>
      <div style={gta.metricLabel}>
        {label}
        <span style={{ display: 'flex', alignItems: 'center' }}>
          {aceso && <span style={gta.metricFlag}>● gatilho</span>}{badge}
        </span>
      </div>
      {pill ? (
        <span style={gta.pillDesligar} className="fenix-num">{valor == null ? '—' : fmt(valor)}</span>
      ) : (
        <div style={{ ...gta.metricVal, ...(tom ? { color: tom } : {}) }} className="fenix-num">
          {valor == null ? '—' : fmt(valor)}
        </div>
      )}
      <div style={gta.metricCap}>{legenda}</div>
    </div>
  );
};

// As três métricas congeladas no snapshot — usadas pelo cartão e pelo pop-up do caso.
const GtaMetricsRow = ({ t }) => {
  const snap = t.snapshot || {};
  const aov = gtaMetric(snap.aov);
  const refund = gtaMetric(snap.refund);
  const take = gtaMetric(snap.take);
  const acesos = t.gatilhos_acesos || [];
  // Baselines congelados na abertura do ticket (ausentes nos criados antes de 11/set).
  const base = snap.baselines || {};
  return (
    <div style={gta.metricsRow}>
      <GtaMetricCard label="AOV (7d)" valor={aov.valor} fmt={USD} cor={aov.cor} corMap={GTA_COR}
        caption={base.aov != null ? `baseline da oferta ${USD(base.aov)}`
                 : (aov.cor ? GTA_AOV_CAPTION[aov.cor] : 'sem baseline da oferta')}
        aceso={acesos.includes('aov')} legivel={aov.legivel} massa={aov.massa} massaMin={aov.massaMin} />
      <GtaMetricCard label="Refund (30d)" valor={refund.valor} fmt={PCT} cor={refund.cor} corMap={GTA_COR_REFUND}
        caption={refund.cor ? GTA_REFUND_CAPTION[refund.cor] : (refund.semFaixa || 'sem teto definido')}
        aceso={acesos.includes('refund')} legivel={refund.legivel} massa={refund.massa} massaMin={refund.massaMin}
        badge={snap.tetoProvisorio && (
          <span style={gta.selo} title="Régua DOCX 5.1 (13,7 / 17 / 20). O Financeiro ainda não calculou o teto desta oferta.">
            teto provisório
          </span>
        )} />
      <GtaMetricCard label="Take Rate 6" valor={take.valor} fmt={PCT} cor={take.cor} corMap={GTA_COR}
        caption={base.take6 != null ? `baseline da oferta ${PCT(base.take6)}`
                 : (take.cor ? GTA_TAKE_CAPTION[take.cor] : 'sem baseline da oferta')}
        aceso={acesos.includes('take')} legivel={take.legivel} massa={take.massa} massaMin={take.massaMin} />
    </div>
  );
};

// Cartão do ticket — mostra os três gatilhos E a alavanca juntos, de propósito (cap. 4
// da Bíblia): olhar só o número acesso e recomendar a alavanca errada é o erro central
// que esta tela existe para evitar.
const GtaCard = ({ t, alavancas, busy, onPegar, onDevolver, onResolverClick, onOpen, onReabrirClick }) => {
  const snap = t.snapshot || {};
  const prazoCor = GTA_PRAZO_COR[t.prazo] || 'var(--fg-3)';

  return (
    <div style={{ ...gta.card, cursor: 'pointer' }} onClick={onOpen}>
      <div style={{ ...gta.prazoBar, background: prazoCor }}>
        <span>{GTA_PRAZO_LABEL[t.prazo] || t.prazo || '—'}</span>
        <span style={gta.prazoTempo}>{gtaPrazoTexto(t.dado_fechou_em, t.prazo)}</span>
      </div>

      <div style={gta.cardBody}>
        <div style={gta.idRow}>
          <span style={gta.idTxt}>#{t.id}</span>
          {t.checkpoint_de != null && <span style={gta.badgeCheckpoint}>checkpoint D+7</span>}
          {t.n_notas > 0 && <span style={gta.notasBadge}>{t.n_notas} nota{t.n_notas === 1 ? '' : 's'}</span>}
        </div>

        <div style={gta.afiliadoNome}>{t.afiliado_nome}</div>
        <div style={gta.ofertaTxt}>{t.oferta}</div>
        <div style={gta.volumeTxt}>{NUM(t.volume_recente || 0)} pedidos de front em 7d</div>

        {/* Desde 11/set o snapshot congela os baselines da oferta no momento da
            abertura: o cartão mostra o NÚMERO contra o qual a decisão foi tomada.
            Tickets anteriores caem na legenda por cor. */}
        <GtaMetricsRow t={t} />

        <div style={gta.divider} />

        <div style={gta.leituraBlock}>
          <div style={gta.leituraLabel}>LEITURA</div>
          <div style={gta.leituraTxt}>{snap.leitura || '—'}</div>
        </div>
        <div style={gta.leituraBlock}>
          <div style={gta.leituraLabel}>ALAVANCA</div>
          <div style={gta.leituraTxt}>{snap.alavanca || '—'}</div>
        </div>

        <div style={gta.footer} onClick={e => e.stopPropagation()}>
          {t.estado === 'aberto' && (
            <button style={{ ...gta.btnPrimary, ...(busy ? gta.btnDisabled : {}) }}
              disabled={busy} onClick={onPegar}>
              {busy ? 'Pegando…' : 'Pegar caso'}
            </button>
          )}

          {t.estado === 'em_atendimento' && (
            <>
              <div style={gta.footerTxt}>
                <span style={gta.dotVivo} /> {t.pego_por_nome || '—'} pegou às {gtaFmtHora(t.pego_em)}
              </div>
              <div style={gta.footerBtns}>
                <button style={{ ...gta.btnGhost, ...(busy ? gta.btnDisabled : {}) }}
                  disabled={busy} onClick={onDevolver}>
                  {busy ? '…' : 'Devolver'}
                </button>
                <button style={{ ...gta.btnResolve, ...(busy ? gta.btnDisabled : {}) }}
                  disabled={busy} onClick={onResolverClick}>
                  Resolver
                </button>
              </div>
            </>
          )}

          {t.estado === 'observacao' && (
            <div style={{ ...gta.footerTxt, flexDirection: 'column', alignItems: 'stretch' }}>
              Resolvido por {t.resolvido_por_nome || '—'} · em observação até {gtaFmtDiaSolo(t.observacao_ate)}
              {t.alavanca && <div style={gta.footerAlavanca}>Alavanca: {t.alavanca}</div>}
              {t.observacao && <div style={gta.footerObs}>“{t.observacao}”</div>}
              {onReabrirClick && (
                <div style={gta.footerBtns}>
                  <button style={gta.btnGhost} onClick={onReabrirClick}>Reabrir</button>
                </div>
              )}
            </div>
          )}

          {t.estado === 'resolvido' && (
            <div style={{ ...gta.footerTxt, flexDirection: 'column', alignItems: 'stretch' }}>
              Resolvido por {t.resolvido_por_nome || '—'} · em {gtaFmtDataHora(t.resolvido_em)}
              {t.alavanca && <div style={gta.footerAlavanca}>Alavanca: {t.alavanca}</div>}
              {t.observacao && <div style={gta.footerObs}>“{t.observacao}”</div>}
              {onReabrirClick && (
                <div style={gta.footerBtns}>
                  <button style={gta.btnGhost} onClick={onReabrirClick}>Reabrir</button>
                </div>
              )}
            </div>
          )}
        </div>
      </div>
    </div>
  );
};

// Resolver exige alavanca (seleção fechada); observação é texto livre OPCIONAL.
const GtaResolverModal = ({ ticket, alavancas, busy, onClose, onConfirm }) => {
  const [alavanca, setAlavanca] = React.useState(null);
  const [observacao, setObservacao] = React.useState('');
  return (
    <div style={gta.overlay} onClick={e => { if (e.target === e.currentTarget && !busy) onClose(); }}>
      <div style={gta.modal}>
        <div style={gta.modalTitle}>Resolver caso #{ticket.id}</div>
        <div style={gta.modalSub}>{ticket.afiliado_nome} · {ticket.oferta}</div>

        <label style={gta.label}>
          Alavanca aplicada
          <Dropdown icon="bolt" label="Alavanca" value={alavanca}
            onChange={setAlavanca}
            options={alavancas.map(a => ({ key: a, label: a }))} />
        </label>

        <label style={gta.label}>
          Observação (opcional)
          <textarea style={gta.textarea} value={observacao}
            onChange={e => setObservacao(e.target.value)}
            placeholder="Opcional — o que foi combinado com o afiliado" />
        </label>

        <div style={gta.modalActions}>
          <button style={{ ...gta.btnGhost, flex: 'none' }} disabled={busy} onClick={onClose}>Cancelar</button>
          <button style={{ ...gta.btnResolve, flex: 'none', ...((!alavanca || busy) ? gta.btnDisabled : {}) }}
            disabled={!alavanca || busy}
            onClick={() => onConfirm(alavanca, observacao)}>
            {busy ? 'Resolvendo…' : 'Confirmar resolução'}
          </button>
        </div>
      </div>
    </div>
  );
};

// Nota em balão; ação em linha compacta; Sistema em cinza (spec 2026-09-23, D5/D9).
const GtaTimelineItem = ({ ev }) => {
  const sistema = ev.autor_id == null && ev.autor_nome === 'Sistema';
  const quando = gtaFmtCarimbo(ev.criado_em);
  if (ev.tipo === 'nota') {
    return (
      <div style={gta.tlNota}>
        <div style={gta.tlNotaHead}><b>{ev.autor_nome}</b><span>{quando}</span></div>
        <div style={gta.tlNotaTxt}>{ev.texto}</div>
      </div>
    );
  }
  const detalhe = gtaDetalheEvento(ev);
  return (
    <div style={{ ...gta.tlAcao, ...(sistema ? { color: 'var(--fg-4)' } : {}) }}>
      <span style={{ ...gta.tlDot, background: sistema ? 'var(--fg-4)' : 'var(--fenix-blue)' }} />
      <span><b>{ev.autor_nome}</b> {GTA_EVENTO_VERBO[ev.tipo] || ev.tipo}
        {detalhe && <> · {detalhe}</>} · {quando}
        {ev.texto && <div style={gta.tlAcaoTxt}>“{ev.texto}”</div>}
      </span>
    </div>
  );
};

// Pop-up do caso (spec 2026-09-23). Reusado pela Fila e pela Auditoria.
// Busca o ticket ao abrir e depois de cada ação; o polling da fila NÃO o recarrega,
// para não apagar a nota que a pessoa está digitando.
const GtaTicketModal = ({ ticketId, iniciarReabrindo = false, onClose, onChanged, onOpenTicket }) => {
  const [det, setDet] = React.useState(null);
  const [erro, setErro] = React.useState(null);
  const [aviso, setAviso] = React.useState(null);      // { texto, vivo }
  const [busy, setBusy] = React.useState(false);
  const [nota, setNota] = React.useState('');
  const [reabrindo, setReabrindo] = React.useState(iniciarReabrindo);
  const [motivo, setMotivo] = React.useState('');
  const [resolvendo, setResolvendo] = React.useState(false);
  const podeAtender = !window.FenixAuth || window.FenixAuth.can('gatilhos.atender');

  const carregar = () => fetchGatilhosTicket(ticketId)
    .then(d => { setDet(d); setErro(null); })
    .catch(e => setErro(e.status === 404 ? 'Ticket não encontrado.' : 'Falha ao carregar o caso.'));

  React.useEffect(() => {
    setDet(null); setAviso(null); setNota(''); setMotivo('');
    setReabrindo(iniciarReabrindo); setResolvendo(false);
    carregar();
    // eslint-disable-next-line
  }, [ticketId]);

  const acao = (nome, body, depois) => {
    setBusy(true);
    gatilhosAcao(ticketId, nome, body)
      .then(() => { setAviso(null); if (depois) depois(); if (onChanged) onChanged(); })
      .catch(e => setAviso({ texto: e.message || 'Falha na ação', vivo: e.data && e.data.ticketVivo }))
      .finally(() => { setBusy(false); carregar(); });
  };

  const fechar = () => { if (!busy) onClose(); };
  const t = det && det.ticket;
  const notaLimpa = nota.trim();

  return (
    <div style={gta.overlay} onClick={e => { if (e.target === e.currentTarget) fechar(); }}>
      <div style={gta.modalGrande}>
        {!det && !erro && <div style={gta.empty}>Carregando caso #{ticketId}…</div>}
        {erro && !det && <div style={gta.empty}>{erro}</div>}
        {t && (
          <>
            <div style={gta.mgHead}>
              <div>
                <div style={gta.idTxt}>#{t.id}{t.checkpoint_de != null && ' · checkpoint'}</div>
                <div style={gta.afiliadoNome}>{t.afiliado_nome}</div>
                <div style={gta.ofertaTxt}>{t.oferta}</div>
              </div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                <span style={{ ...gta.estadoChip, color: GTA_ESTADO_COR[t.estado], borderColor: GTA_ESTADO_COR[t.estado] }}>
                  {GTA_ESTADO_LABEL[t.estado] || t.estado}
                </span>
                <span style={{ ...gta.estadoChip, color: GTA_PRAZO_COR[t.prazo] || 'var(--fg-3)' }}>
                  prazo {GTA_PRAZO_LABEL[t.prazo] || t.prazo}
                </span>
                <button style={gta.noticeClose} onClick={fechar} aria-label="Fechar">×</button>
              </div>
            </div>

            {aviso && (
              <div style={gta.notice}>
                <Icon name="alert" size={14} color="currentColor" /> {aviso.texto}
                {aviso.vivo != null && onOpenTicket && (
                  <button style={gta.linkBtn} onClick={() => onOpenTicket(aviso.vivo)}>abrir #{aviso.vivo}</button>
                )}
              </div>
            )}

            <GtaMetricsRow t={t} />

            {podeAtender && (
              <div style={gta.mgAcoes}>
                {t.estado === 'aberto' && (
                  <button style={{ ...gta.btnPrimary, flex: 'none' }} disabled={busy}
                    onClick={() => acao('pegar')}>Pegar caso</button>
                )}
                {t.estado === 'em_atendimento' && (
                  <>
                    <button style={{ ...gta.btnGhost, flex: 'none' }} disabled={busy}
                      onClick={() => acao('devolver')}>Devolver</button>
                    <button style={{ ...gta.btnResolve, flex: 'none' }} disabled={busy}
                      onClick={() => setResolvendo(true)}>Resolver</button>
                  </>
                )}
                {(t.estado === 'observacao' || t.estado === 'resolvido') && !reabrindo && (
                  <button style={{ ...gta.btnGhost, flex: 'none' }} disabled={busy}
                    onClick={() => setReabrindo(true)}>Reabrir caso</button>
                )}
              </div>
            )}

            {podeAtender && reabrindo && (t.estado === 'observacao' || t.estado === 'resolvido') && (
              <div style={gta.mgReabrir}>
                <label style={gta.label}>
                  Motivo (opcional)
                  <textarea style={gta.textarea} value={motivo} maxLength={GTA_NOTA_MAX}
                    onChange={e => setMotivo(e.target.value)}
                    placeholder="Por que o caso volta para a fila" />
                </label>
                <div style={gta.modalActions}>
                  <button style={{ ...gta.btnGhost, flex: 'none' }} disabled={busy}
                    onClick={() => { setReabrindo(false); setMotivo(''); }}>Cancelar</button>
                  <button style={{ ...gta.btnPrimary, flex: 'none' }} disabled={busy}
                    onClick={() => acao('reabrir', { motivo: motivo.trim() || null },
                                        () => { setReabrindo(false); setMotivo(''); })}>
                    {busy ? 'Reabrindo…' : 'Reabrir'}
                  </button>
                </div>
              </div>
            )}

            <div style={gta.leituraLabel}>LINHA DO TEMPO</div>
            <div style={gta.timeline}>
              {det.eventos.length === 0
                ? <div style={gta.volumeTxt}>Sem eventos registrados.</div>
                : det.eventos.map(ev => <GtaTimelineItem key={ev.id} ev={ev} />)}
            </div>

            {podeAtender && (
              <div style={gta.mgNota}>
                <textarea style={gta.textarea} value={nota} maxLength={GTA_NOTA_MAX}
                  onChange={e => setNota(e.target.value)} placeholder="Adicionar nota ao caso" />
                <div style={gta.mgNotaRodape}>
                  <span style={gta.volumeTxt}>{nota.length}/{GTA_NOTA_MAX}</span>
                  <button style={{ ...gta.btnResolve, flex: 'none', ...((!notaLimpa || busy) ? gta.btnDisabled : {}) }}
                    disabled={!notaLimpa || busy}
                    onClick={() => acao('notas', { texto: notaLimpa }, () => setNota(''))}>
                    {busy ? 'Enviando…' : 'Adicionar nota'}
                  </button>
                </div>
              </div>
            )}
          </>
        )}
      </div>

      {resolvendo && t && (
        <GtaResolverModal ticket={t} alavancas={det.alavancas || []} busy={busy}
          onClose={() => setResolvendo(false)}
          onConfirm={(alavanca, observacao) => acao('resolver',
            { alavanca, observacao: observacao || undefined }, () => setResolvendo(false))} />
      )}
    </div>
  );
};

// ---------------------------------------------------------------------------
const GatilhosAlertas = () => {
  const [estado, setEstado] = React.useState('aberto');
  const [meus, setMeus] = React.useState(false);
  const [data, setData] = React.useState(null);
  const [err, setErr] = React.useState(false);
  const [loading, setLoading] = React.useState(true);
  const [gerando, setGerando] = React.useState(true);
  const [tick, setTick] = React.useState(0);
  const [actionMsg, setActionMsg] = React.useState(null);
  const [busyId, setBusyId] = React.useState(null);
  const [resolvendo, setResolvendo] = React.useState(null);
  const [resolvBusy, setResolvBusy] = React.useState(false);
  const [detalhe, setDetalhe] = React.useState(null);   // { id, reabrir }
  const podeAtender = !window.FenixAuth || window.FenixAuth.can('gatilhos.atender');

  const fullLoading = useFullLoading();
  const withLoad = (fn) => (...a) => { fullLoading.mark(); fn(...a); };
  const setMeusF = withLoad(setMeus);

  const retry = useAutoRetry(err);   // 1º load falhou → tenta de novo a cada 15s, sozinho
  const refetch = () => setTick(n => n + 1);

  // Ao montar: dispara a varredura SEM esperar (o 1º load do dia leva ~37s — a tela
  // não trava por causa disso) e, quando ela terminar, refaz o fetch da fila.
  React.useEffect(() => {
    let cancelled = false;
    gatilhosGerar().catch(() => {}).finally(() => {
      if (cancelled) return;
      setGerando(false);
      refetch();
    });
    return () => { cancelled = true; };
    // eslint-disable-next-line
  }, []);

  // Polling de 30s — única tela do dashboard cujo estado muda por ação de pessoa, não
  // por ingestão. Não passa por mark(): fica em silêncio, sem piscar overlay.
  React.useEffect(() => {
    const it = setInterval(refetch, 30000);
    return () => clearInterval(it);
  }, []);

  // Busca SEM filtro de estado: a fila inteira é pequena (fila de trabalho, não
  // histórico de vendas) e os contadores do topo (Abertos/Em atendimento/SLA
  // estourado) precisam enxergar todos os estados ao mesmo tempo — o Segmented
  // abaixo é um filtro só de EXIBIÇÃO sobre o mesmo payload.
  React.useEffect(() => {
    const began = fullLoading.begin();
    let cancelled = false; setLoading(true);
    fetchGatilhosTickets({ meus })
      .then(d => { if (cancelled) return; setData(d); setErr(false); })
      .catch(() => { if (!cancelled) setErr(true); })
      .finally(() => { if (!cancelled) setLoading(false); fullLoading.end(began); });
    return () => { cancelled = true; };
  }, [meus, retry, tick]);

  const doAcao = (id, acao, body) => {
    setBusyId(id);
    gatilhosAcao(id, acao, body)
      .then(() => {
        setActionMsg(null);
        // Pegar move o caso de "Abertos" para "Em atendimento". Sem levar a pessoa
        // junto, o cartão que ela acabou de pegar simplesmente SOME da tela dela —
        // parece erro, e ela não acha o botão de resolver. Devolver faz o caminho
        // inverso pelo mesmo motivo.
        if (acao === 'pegar') setEstado('em_atendimento');
        if (acao === 'devolver') setEstado('aberto');
      })
      .catch(e => setActionMsg(e.message || 'Falha na ação'))
      .finally(() => { setBusyId(null); refetch(); });
  };

  const doResolver = (alavanca, observacao) => {
    setResolvBusy(true);
    gatilhosAcao(resolvendo.id, 'resolver', { alavanca, observacao: observacao || undefined })
      .then(() => { setActionMsg(null); setResolvendo(null); })
      .catch(e => setActionMsg(e.message || 'Falha ao resolver'))
      .finally(() => { setResolvBusy(false); refetch(); });
  };

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

  const tickets = data.tickets || [];
  const alavancas = data.alavancas || [];
  const now = Date.now();
  const UMA_SEMANA_MS = 7 * 86400000;

  const vencido = (t) => t.estado === 'aberto'
    && (() => { const dl = gtaDeadline(t.dado_fechou_em, t.prazo); return dl != null && dl.getTime() < now; })();

  const counts = {
    aberto: tickets.filter(t => t.estado === 'aberto').length,
    em_atendimento: tickets.filter(t => t.estado === 'em_atendimento').length,
    resolvidosSemana: tickets.filter(t => t.estado === 'resolvido' && t.resolvido_em
      && (now - new Date(t.resolvido_em).getTime()) <= UMA_SEMANA_MS).length,
    slaEstourado: tickets.filter(vencido).length,
  };

  const visiveis = tickets.filter(t => t.estado === estado);

  // Estado "Em atendimento": agrupado por pessoa — é como a supervisão enxerga
  // distribuição de carga (quem pegou o quê é visível para todos).
  const grupos = (estado === 'em_atendimento' && visiveis.length > 0)
    ? Object.values(visiveis.reduce((acc, t) => {
        const nome = t.pego_por_nome || '—';
        (acc[nome] = acc[nome] || { nome, itens: [] }).itens.push(t);
        return acc;
      }, {})).sort((a, b) => a.nome.localeCompare(b.nome))
    : null;

  const cardProps = (t) => ({
    key: t.id, t, alavancas, busy: busyId === t.id,
    onPegar: () => doAcao(t.id, 'pegar'),
    onDevolver: () => doAcao(t.id, 'devolver'),
    onResolverClick: () => setResolvendo(t),
    onOpen: () => setDetalhe({ id: t.id, reabrir: false }),
    onReabrirClick: podeAtender ? () => setDetalhe({ id: t.id, reabrir: true }) : null,
  });

  return (
    <div style={{ position: 'relative' }}>
      <div style={gta.header}>
        <div>
          <div style={gta.eye}>Setor de Afiliados</div>
          <div style={gta.title}>Alertas</div>
          <div style={gta.regua}>Prazo primeiro, volume dentro da faixa · atualiza a cada 30 segundos</div>
        </div>
        <label style={gta.checkboxLabel}>
          <input type="checkbox" checked={meus} onChange={e => setMeusF(e.target.checked)} />
          <span>só os meus</span>
        </label>
      </div>

      {gerando && (
        <div style={gta.noticeInfo}>
          <Icon name="refresh" size={14} color="currentColor" /> procurando casos novos…
        </div>
      )}

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

      {actionMsg && (
        <div style={gta.notice}>
          <Icon name="alert" size={14} color="currentColor" /> {actionMsg}
          <button style={gta.noticeClose} onClick={() => setActionMsg(null)} aria-label="Fechar">×</button>
        </div>
      )}

      <div style={{ ...gta.kpiRow, gridTemplateColumns: 'repeat(4,minmax(0,1fr))' }}>
        <DotKpi eye="Abertos" color="var(--fenix-blue)" value={NUM(counts.aberto)} />
        <DotKpi eye="Em atendimento" color="var(--warning)" value={NUM(counts.em_atendimento)} />
        <DotKpi eye="Resolvidos na semana" color="var(--positive)" value={NUM(counts.resolvidosSemana)} />
        <DotKpi eye="SLA estourado" color="var(--negative)" value={NUM(counts.slaEstourado)}
          valColor={counts.slaEstourado > 0 ? 'var(--negative)' : undefined} />
      </div>

      <div style={gta.filterRow}>
        <Segmented options={GTA_ESTADOS} value={estado} onChange={setEstado} />
      </div>

      {visiveis.length === 0 ? (
        <div style={gta.empty}>{GTA_VAZIO[estado] || 'Nenhum caso encontrado.'}</div>
      ) : grupos ? (
        grupos.map(g => (
          <div key={g.nome} style={gta.grupoBloco}>
            <div style={gta.grupoHead}>{g.nome} · {g.itens.length} caso{g.itens.length === 1 ? '' : 's'}</div>
            <div style={gta.grid}>
              {g.itens.map(t => <GtaCard {...cardProps(t)} />)}
            </div>
          </div>
        ))
      ) : (
        <div style={gta.grid}>
          {visiveis.map(t => <GtaCard {...cardProps(t)} />)}
        </div>
      )}

      {resolvendo && (
        <GtaResolverModal ticket={resolvendo} alavancas={alavancas} busy={resolvBusy}
          onClose={() => setResolvendo(null)} onConfirm={doResolver} />
      )}

      {detalhe && (
        <GtaTicketModal ticketId={detalhe.id} iniciarReabrindo={detalhe.reabrir}
          onClose={() => setDetalhe(null)} onChanged={refetch}
          onOpenTicket={(id) => setDetalhe({ id, reabrir: false })} />
      )}

      <FullLoadingOverlay show={fullLoading.show && !!data} conn="connecting"/>
    </div>
  );
};

const gta = {
  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 },
  checkboxLabel: { display: 'flex', alignItems: 'center', gap: 7, font: '600 12.5px/1.2 var(--font-sans)', color: 'var(--fg-2)', cursor: 'pointer', userSelect: 'none', marginTop: 4 },
  noticeInfo: { display: 'flex', alignItems: 'center', gap: 6, font: '600 12px/1.4 var(--font-sans)', color: 'var(--info)', background: 'var(--info-bg)', padding: '10px 16px', borderRadius: 'var(--r-md)', marginBottom: 12 },
  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, position: 'relative' },
  noticeClose: { marginLeft: 'auto', border: 0, background: 'transparent', font: '700 16px/1 var(--font-sans)', color: 'inherit', cursor: 'pointer', padding: '0 2px' },
  kpiRow: { display: 'grid', gap: 14, marginBottom: 16 },
  filterRow: { display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap', marginBottom: 16 },
  grid: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill,minmax(320px,1fr))', gap: 14, marginBottom: 16 },
  grupoBloco: { marginBottom: 6 },
  grupoHead: { font: '700 11px/1 var(--font-sans)', letterSpacing: '.05em', textTransform: 'uppercase', color: 'var(--fg-3)', margin: '4px 0 10px' },
  empty: { padding: '48px 22px', textAlign: 'center', font: 'var(--text-body-sm)', color: 'var(--fg-4)', background: 'var(--bg-surface)', border: '1px solid var(--border-1)', borderRadius: 'var(--r-lg)', marginBottom: 16 },
  reconNota: { textAlign: 'center', font: 'var(--text-body-sm)', color: 'var(--fg-4)', maxWidth: 520, margin: '-28px auto 0' },

  card: { background: 'var(--bg-surface)', border: '1px solid var(--border-1)', borderRadius: 'var(--r-lg)', boxShadow: 'var(--shadow-sm)', overflow: 'hidden', display: 'flex', flexDirection: 'column' },
  prazoBar: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '7px 14px', font: '800 10.5px/1 var(--font-sans)', letterSpacing: '.04em', textTransform: 'uppercase', color: '#fff' },
  prazoTempo: { fontWeight: 700, textTransform: 'none', letterSpacing: 0, fontSize: 10.5 },
  cardBody: { padding: '14px 16px 16px', display: 'flex', flexDirection: 'column', gap: 4 },
  idRow: { display: 'flex', alignItems: 'center', gap: 8, marginBottom: 2 },
  idTxt: { font: '600 11px/1 var(--font-mono)', color: 'var(--fg-4)' },
  badgeCheckpoint: { font: '700 9.5px/1 var(--font-sans)', letterSpacing: '.03em', textTransform: 'uppercase', color: 'var(--fenix-blue)', background: 'var(--fenix-blue-100)', borderRadius: 'var(--r-pill)', padding: '2px 8px' },
  afiliadoNome: { font: '800 16px/1.2 var(--font-sans)', color: 'var(--fg-1)' },
  ofertaTxt: { font: '600 12px/1.3 var(--font-sans)', color: 'var(--fg-3)' },
  volumeTxt: { font: 'var(--text-caption)', color: 'var(--fg-4)', marginBottom: 6 },

  metricsRow: { display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: 8, marginBottom: 10 },
  metric: { background: 'var(--bg-sunken)', borderRadius: 'var(--r-md)', padding: '8px 9px', display: 'flex', flexDirection: 'column', gap: 2 },
  metricAceso: { boxShadow: 'inset 0 0 0 1.5px var(--negative)' },
  metricLabel: { font: '700 9px/1 var(--font-sans)', letterSpacing: '.03em', textTransform: 'uppercase', color: 'var(--fg-3)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' },
  metricFlag: { font: '800 8px/1 var(--font-sans)', color: 'var(--negative)', letterSpacing: 0, textTransform: 'none' },
  selo:{ marginLeft:6, padding:'1px 6px', borderRadius:999, fontSize:10,
    background:'var(--warning-bg, #FEF3C7)', color:'var(--warning, #E0922B)' },
  metricVal: { font: '800 16px/1.15 var(--font-mono)', color: 'var(--fg-1)' },
  metricCap: { font: '500 9.5px/1.3 var(--font-sans)', color: 'var(--fg-4)' },
  pillDesligar: { display: 'inline-block', width: 'fit-content', background: 'var(--negative)', color: '#fff', fontWeight: 800, padding: '2px 8px', borderRadius: 'var(--r-pill)', font: '800 13px/1.3 var(--font-mono)' },

  divider: { borderTop: '1px solid var(--border-1)', margin: '2px 0 8px' },
  leituraBlock: { marginBottom: 6 },
  leituraLabel: { font: '800 9px/1 var(--font-sans)', letterSpacing: '.05em', textTransform: 'uppercase', color: 'var(--fenix-blue)', marginBottom: 3 },
  leituraTxt: { font: '500 12px/1.45 var(--font-sans)', color: 'var(--fg-2)' },

  footer: { marginTop: 8, paddingTop: 10, borderTop: '1px solid var(--border-1)' },
  footerTxt: { display: 'flex', alignItems: 'center', gap: 6, font: '600 12px/1.4 var(--font-sans)', color: 'var(--fg-2)' },
  footerAlavanca: { marginTop: 4, font: '600 11.5px/1.4 var(--font-sans)', color: 'var(--fg-1)' },
  footerObs: { marginTop: 3, font: '500 11.5px/1.4 var(--font-sans)', color: 'var(--fg-3)', fontStyle: 'italic' },
  footerBtns: { display: 'flex', gap: 8, marginTop: 8 },
  dotVivo: { width: 7, height: 7, borderRadius: '50%', background: 'var(--positive)', flex: 'none' },

  btnPrimary: { flex: 1, font: 'var(--text-label)', color: '#fff', background: 'var(--fenix-orange)', border: 'none', borderRadius: 'var(--r-sm)', padding: '9px 14px', cursor: 'pointer' },
  btnResolve: { flex: 1, font: 'var(--text-label)', color: '#fff', background: 'var(--fenix-blue)', border: 'none', borderRadius: 'var(--r-sm)', padding: '9px 14px', cursor: 'pointer' },
  btnGhost: { flex: 1, font: 'var(--text-label)', color: 'var(--fg-2)', background: 'transparent', border: '1px solid var(--border-2)', borderRadius: 'var(--r-sm)', padding: '9px 14px', cursor: 'pointer' },
  btnDisabled: { opacity: .5, cursor: 'not-allowed' },

  overlay: { position: 'fixed', inset: 0, background: 'rgba(1,27,54,.45)', display: 'grid', placeItems: 'center', zIndex: 90 },
  modal: { width: 'min(480px, calc(100vw - 40px))', maxHeight: 'calc(100vh - 60px)', overflowY: 'auto', background: 'var(--bg-surface)', borderRadius: 'var(--r-lg)', boxShadow: 'var(--shadow-lg)', padding: '22px 22px 18px', display: 'flex', flexDirection: 'column', gap: 14 },
  modalTitle: { font: 'var(--text-h3)', color: 'var(--fg-1)' },
  modalSub: { font: 'var(--text-body-sm)', color: 'var(--fg-3)', marginTop: -10 },
  label: { font: 'var(--text-label)', color: 'var(--fg-2)', display: 'flex', flexDirection: 'column', gap: 6 },
  textarea: { font: 'var(--text-body-sm)', color: 'var(--fg-1)', background: 'var(--bg-surface)', border: '1px solid var(--border-2)', borderRadius: 'var(--r-sm)', padding: '9px 11px', outline: 'none', minHeight: 72, resize: 'vertical', fontFamily: 'var(--font-sans)' },
  modalActions: { display: 'flex', justifyContent: 'flex-end', gap: 10, marginTop: 4 },

  modalGrande: { width: 'min(880px, calc(100vw - 40px))', maxHeight: 'calc(100vh - 60px)', overflowY: 'auto', background: 'var(--bg-surface)', borderRadius: 'var(--r-lg)', boxShadow: 'var(--shadow-lg)', padding: '20px 22px 18px', display: 'flex', flexDirection: 'column', gap: 12 },
  mgHead: { display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 },
  estadoChip: { font: '700 10.5px/1 var(--font-sans)', textTransform: 'uppercase', letterSpacing: '.04em', border: '1px solid var(--border-2)', borderRadius: 'var(--r-pill)', padding: '4px 9px' },
  mgAcoes: { display: 'flex', gap: 8, flexWrap: 'wrap' },
  mgReabrir: { background: 'var(--bg-sunken)', borderRadius: 'var(--r-md)', padding: 12, display: 'flex', flexDirection: 'column', gap: 10 },
  timeline: { display: 'flex', flexDirection: 'column', gap: 8, borderLeft: '2px solid var(--border-1)', paddingLeft: 12 },
  tlNota: { background: 'var(--fenix-blue-100)', borderRadius: 'var(--r-md)', padding: '8px 11px' },
  tlNotaHead: { display: 'flex', justifyContent: 'space-between', gap: 10, font: '600 11px/1.3 var(--font-sans)', color: 'var(--fg-2)', marginBottom: 3 },
  tlNotaTxt: { font: '500 12.5px/1.45 var(--font-sans)', color: 'var(--fg-1)', whiteSpace: 'pre-wrap', wordBreak: 'break-word' },
  tlAcao: { display: 'flex', gap: 8, alignItems: 'flex-start', font: '500 12px/1.45 var(--font-sans)', color: 'var(--fg-2)' },
  tlDot: { width: 7, height: 7, borderRadius: '50%', flex: 'none', marginTop: 5 },
  tlAcaoTxt: { fontStyle: 'italic', color: 'var(--fg-3)', whiteSpace: 'pre-wrap', wordBreak: 'break-word' },
  mgNota: { display: 'flex', flexDirection: 'column', gap: 6, borderTop: '1px solid var(--border-1)', paddingTop: 12 },
  mgNotaRodape: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' },
  linkBtn: { marginLeft: 8, border: 0, background: 'transparent', color: 'inherit', textDecoration: 'underline', font: 'inherit', cursor: 'pointer', padding: 0 },
  notasBadge: { font: '700 9.5px/1 var(--font-sans)', color: 'var(--fg-3)', background: 'var(--bg-sunken)', borderRadius: 'var(--r-pill)', padding: '2px 8px' },
};

window.GatilhosAlertas = GatilhosAlertas;
window.GtaTicketModal = GtaTicketModal;   // reusado pela Auditoria
