import { createRoot } from 'react-dom/client';
import { QueryClientProvider } from '@tanstack/react-query';
/* ⚠️ front#680 pedia este import dinâmico também (`import('@sentry/react')`
 * dentro de `requestIdleCallback`). Investigado e NÃO feito — deixaria o
 * import estático aqui, mas sem reduzir bytes:
 *
 *     `src/utils/logger.ts`        → `import * as Sentry from '@sentry/react'`
 *                                     (925 arquivos chamam `logger.*`)
 *     `src/components/ErrorBoundary.tsx` → idem, e é o root boundary,
 *                                     montado incondicionalmente aqui embaixo
 *
 * Os dois já ancoram `@sentry/react`/`@sentry/core` no grafo estático da
 * entrada — tornar dinâmico SÓ este import não tira nada do bundle, porque o
 * Rollup segue outro caminho estático até o mesmo módulo. Fazer valer de
 * verdade exigiria tornar `logger.ts` e `ErrorBoundary.tsx` lazy também, o
 * que é reescrever como 925 chamadas capturam erro — risco desproporcional
 * ao ganho, e além disso contradiz a decisão já tomada duas linhas abaixo
 * (`browserTracingIntegration` fica eager de propósito, para medir o próprio
 * carregamento inicial). O que DEU pra fazer sem esse risco: isolar
 * `@sentry/react` num chunk próprio via `manualChunks` (`vite.config.ts`) —
 * não adia a execução, mas tira o peso de dentro do arquivo de entrada e
 * melhora cache entre deploys. */
import * as Sentry from '@sentry/react';
import App from './App';
import { aplicaTemaSalvo } from './hooks/temaSalvo';
import { ErrorBoundary } from './components/ErrorBoundary';
import { MercadoLivreIntegrationsProvider } from './contexts/MercadoLivreIntegrationsContext';
// Contexts SOLID
import { UserProvider } from './contexts/UserContext';
import { AuthGate } from './components/auth/AuthGate';
import { ToolsProvider } from './contexts/ToolsContext';
import { FeatureProvider } from './contexts/FeatureContext';
import { setupFetchInterceptor } from './utils/fetchInterceptor';
import { queryClient } from './lib/queryClient';
import { reportWebVitals } from './reportWebVitals';
/* ⚠️ front#680 — AQUI HAVIA `import '@fontsource/iosevka/700.css'`.
 *
 * A fonte pesava 2,9 MB no `dist` (988 KB de `.woff2` + 1,92 MB de `.woff`) e
 * **nada a usava**:
 *
 *     grep -rn "iosevka" src tailwind.config.js  →  só este import
 *
 * O `tailwind.config.js` mapeia `geist-mono` para `Consolas, Courier New,
 * monospace` (front#217, Inter é a fonte oficial), e `font-mono` usa a stack
 * padrão do Tailwind. Iosevka não está em nenhuma das duas.
 *
 * O custo não era só o download: o `@font-face` ficava no CSS crítico de TODA
 * página, incluindo a landing paga, onde 96% do tráfego é celular. */
import './index.css';
import { removeCredenciais } from './utils/erros/sanitizaErro';
import { logger } from './utils/logger';

// Inicializar GlitchTip (compatível com Sentry SDK)
if (import.meta.env.VITE_SENTRY_DSN) {
  Sentry.init({
    dsn: import.meta.env.VITE_SENTRY_DSN,
    environment: import.meta.env.MODE,
    // Rastreamento de performance
    /* ⚠️ front#1186 — o REPLAY SAIU DAQUI, MAS **NÃO** SAIU DO PAYLOAD.
     *
     * 🔴 Medido no build da `dev` em 12/08/2026: as assinaturas do replay
     * continuam dentro dos arquivos que o navegador baixa antes de qualquer
     * interação (`e_naR7z7.js`, 280,8 kB raw · `spXO3kBM.js`, 131,6 kB).
     *
     * Tornar ESTE import dinâmico não bastou, e o motivo está escrito no bloco
     * do front#680 lá em cima: `utils/logger.ts` e `components/ErrorBoundary.tsx`
     * importam `@sentry/react` estaticamente, e 925 arquivos chamam `logger.*`.
     * **Um caminho estático sobrevivente puxa o módulo de volta para a entrada** —
     * import dinâmico só remove do grafo quando TODOS os caminhos são dinâmicos.
     *
     * A mudança abaixo continua certa (ela adia a EXECUÇÃO, que é real), mas a
     * afirmação de que o payload encolheu era falsa e ficou aqui por dias.
     *
     * > Régua, e ela é sobre medição, não sobre bundle: **mudança de bundle só
     * > está feita quando o artefato construído confirma.** O import virar
     * > dinâmico é o meio, não o resultado.
     *
     * `replayIntegration()` aqui seria síncrono: o código de gravação de sessão
     * é baixado, parseado e executado ANTES da primeira tela aparecer.
     *
     * Ele é carregado logo abaixo, depois do app montar. O custo dessa troca é
     * conhecido e pequeno: um erro nos primeiros instantes pode não ter replay.
     * Mas `replaysSessionSampleRate` é 0 — só sessão COM erro é gravada —, e
     * quem cai em erro nos primeiros milissegundos tem stack trace e
     * breadcrumbs de qualquer forma. O replay é o extra, não a evidência.
     *
     * O `browserTracingIntegration` fica: ele precisa estar presente desde o
     * início para medir justamente o carregamento inicial. Adiá-lo apagaria a
     * métrica que esta issue existe para melhorar. */
    integrations: [
      Sentry.browserTracingIntegration(),
    ],
    // Captura 100% dos erros
    tracesSampleRate: 1.0,
    // Captura apenas replays de sessões com erro
    replaysSessionSampleRate: 0.0,
    replaysOnErrorSampleRate: 1.0,

    /* front#701 — REDE DE SEGURANÇA CONTRA DADO SENSÍVEL NO BREADCRUMB.
       O SDK captura `console.*` como breadcrumb por padrão, e com o replay
       ligado toda sessão com erro é gravada. Um `console.log` distraído em
       qualquer ponto do app manda o que estiver nele para o GlitchTip.

       Foi o que aconteceu com a chave PIX do saque: o log de origem já foi
       corrigido, mas corrigir a origem não impede o PRÓXIMO. Esta peneira é o
       que torna o descuido inofensivo em vez de incidente.

       Só descarta o breadcrumb: o erro em si continua chegando inteiro. */
    beforeBreadcrumb(breadcrumb) {
      if (breadcrumb.category !== 'console') return breadcrumb;

      const texto = JSON.stringify(breadcrumb.data ?? '') + (breadcrumb.message ?? '');
      const SENSIVEL = /pix[_-]?key|chavepix|\bcpf\b|\bcnpj\b|senha|password|secret|authorization|cart[aã]o|cvv/i;

      return SENSIVEL.test(texto) ? null : breadcrumb;
    },

    /* front#728 — A SEGUNDA PENEIRA, no evento inteiro.
     *
     * O `beforeBreadcrumb` acima protege o `console.*`, e o próprio comentário
     * dele admite o limite: "o erro em si continua chegando inteiro".
     *
     * O `logger.error` passou a sanitizar o que ANEXA ao evento, mas ainda
     * chama `captureException(erro)` com a exceção original — é dela que vem o
     * stack trace, e sem stack o erro não serve.
     *
     * Um `AxiosError` carrega `config` inteiro: `headers.Authorization` e
     * `data`, o corpo ENVIADO. O SDK não serializa campos customizados de
     * `Error` por padrão — mas isso é garantia de configuração, não de
     * desenho: basta alguém acrescentar `extraErrorDataIntegration`, que existe
     * exatamente para isso e é tentadora, e a credencial volta a viajar.
     *
     * Esta peneira olha o evento JÁ MONTADO, seja qual for o caminho que o
     * produziu. É a mesma lição do breadcrumb: corrigir a origem não impede a
     * próxima.
     *
     * ⚠️ Ela preserva stack, mensagem, status e caminho de propósito. Peneira
     * que apaga demais é peneira que o time desliga — e aí não protege nada. */
    beforeSend(evento) {
      return removeCredenciais(evento);
    },
  });

  /* O replay entra depois — front#1186.
   *
   * `requestIdleCallback` espera o navegador ficar ocioso, ou seja: só carrega
   * quando não há mais nada disputando com a renderização. O `setTimeout` é o
   * fallback para Safari, que não implementa a API.
   *
   * Falha aqui é silenciosa de propósito: gravação de sessão é diagnóstico, e
   * quebrar o app porque um recurso de observabilidade não carregou seria
   * inverter a prioridade. */
  const carregaReplay = () => {
    Sentry.lazyLoadIntegration('replayIntegration')
      .then((replay) => {
        Sentry.getClient()?.addIntegration(replay());
      })
      .catch(() => {
        /* Sem replay. O erro continua chegando com stack e breadcrumbs. */
      });
  };

  /* `typeof`, não `'x' in window`: o operador `in` estreita o tipo de `window`
     e, no `else`, o TS o reduz a `never` — o `setTimeout` deixa de existir para
     ele. Foi o que o typecheck acusou. */
  if (typeof window.requestIdleCallback === 'function') {
    window.requestIdleCallback(carregaReplay, { timeout: 5000 });
  } else {
    window.setTimeout(carregaReplay, 3000);
  }
}

/* ⚠️ OS DOIS HANDLERS GLOBAIS MANDAVAM O ERRO CRU AO SENTRY.
 *
 * `Sentry.captureException(event.error)` e `(event.reason)`, sem passar pelo
 * `logger` — que é onde mora a sanitização da front#728.
 *
 * Aquela issue achou que um `AxiosError` carrega o request inteiro, incluindo
 * `config.headers.Authorization`: o JWT do usuário ia para o Sentry a cada
 * falha de API. A correção foi posta DENTRO do `logger.error`, com o argumento
 * de que "corrigir 164 chamadas não impediria a 165ª".
 *
 * ⚠️ Estes dois eram a 165ª. E o de `unhandledrejection` é o pior lugar
 * possível para essa brecha: promise rejeitada e não capturada é EXATAMENTE
 * onde um `AxiosError` solto termina. O caminho mais provável de vazar o token
 * era justamente o que ficou de fora da correção.
 *
 * Passam pelo `logger.error`, que sanitiza e envia — mesmo canal, mesmo stack
 * trace, sem o cabeçalho de autorização junto.
 *
 * E os oito `console.error` saíram: `logger.error` já imprime em
 * desenvolvimento e cala em produção, onde o console é público. */
window.addEventListener('error', (event) => {
  logger.error(
    `erro global não tratado: ${event.message} (${event.filename}:${event.lineno}:${event.colno})`,
    event.error,
  );
});

window.addEventListener('unhandledrejection', (event) => {
  logger.error('promise rejeitada não tratada', event.reason);
});

// Configurar interceptor de fetch antes de qualquer requisição
setupFetchInterceptor();

/**
 * Remove o HTML estático do prerender assim que o React pintar — front#1145.
 *
 * As rotas públicas são gravadas no build com o conteúdo já renderizado, num
 * `<div id="prerender">` **irmão** do `#root`. O motivo de ser irmão: o
 * `createRoot` esvazia o container que recebe, então conteúdo colocado dentro do
 * `#root` seria descartado — medido aqui, isso dava conteúdo aos 10 ms, branco
 * aos 97 ms e o conteúdo de volta só aos ~2,1 s.
 *
 * Como irmão, ele fica visível durante toda a subida do React e sai daqui.
 *
 * A remoção espera o React **ter pintado**, e não um número de quadros. A
 * primeira versão contava dois `requestAnimationFrame` a partir do `render()` —
 * e medindo deu isto na `/calculadora`:
 *
 *     estático removido aos  167ms
 *     React pintou aos       230ms   ← 63ms de janela vazia
 *
 * `render()` agenda o trabalho, não o executa, e o caminho até a primeira pintura
 * passa por `Suspense` e `import()` de rota. Em conexão lenta essa janela cresce.
 * Observar o `#root` até ele ter filho amarra a remoção ao fato, não ao relógio.
 *
 * Em rota sem prerender (todo o app logado) não há o elemento e a função retorna
 * na primeira linha.
 */
function removePrerenderQuandoPintar() {
  const estatico = document.getElementById('prerender');
  if (!estatico) return;

  const raiz = document.getElementById('root');
  if (!raiz) {
    estatico.remove();
    return;
  }

  /* O critério é TEXTO, não estrutura.
   *
   * A versão anterior esperava `raiz.childElementCount > 0`, e isso dispara cedo
   * demais: o React monta o shell — `AuthGate`, o fallback de um `Suspense` —
   * que tem filho e nenhuma palavra. Medido em 3G lento, dava isto:
   *
   *     /calculadora  conteúdo aos 1352ms → tela vazia aos 5158ms
   *
   * Ou seja, o estático saía e o visitante ficava quase 4s no branco. Em rede de
   * escritório a janela é pequena e passa despercebida; no 3G do celular, que é
   * 96% do tráfego pago deste produto, ela é a experiência inteira.
   *
   * Comparar com o próprio estático (e não com um número fixo) faz o critério
   * acompanhar o tamanho de cada página: 30% do texto já é conteúdo de verdade,
   * e não exige render idêntico — o React reordena e reescreve à vontade.
   *
   * `textContent`, não `innerText`: o segundo força cálculo de layout a cada
   * mutação, e isto roda no caminho crítico da primeira pintura. */
  const alvo = Math.max(
    40,
    Math.floor(((estatico.textContent || '').trim().length || 0) * 0.3),
  );
  const jaTemConteudo = () => (raiz.textContent || '').trim().length >= alvo;

  /* Um quadro extra depois do texto chegar: ter nó no DOM ainda não é ter pixel
   * na tela, e é justamente na troca que a piscada apareceria. */
  const removeNoProximoQuadro = () => requestAnimationFrame(() => estatico.remove());

  if (jaTemConteudo()) {
    removeNoProximoQuadro();
    return;
  }

  const observador = new MutationObserver(() => {
    if (jaTemConteudo()) {
      observador.disconnect();
      clearTimeout(rede);
      removeNoProximoQuadro();
    }
  });
  observador.observe(raiz, { childList: true, subtree: true, characterData: true });

  /* Rede de segurança: se o React nunca montar (erro de chunk, rede caída), o
   * estático some assim mesmo. Deixá-lo para sempre daria uma página que parece
   * carregada e não responde a clique nenhum — pior que a tela de erro. */
  const rede = setTimeout(() => {
    observador.disconnect();
    estatico.remove();
  }, 10_000);
}

/* 🎨 O tema salvo vale já no primeiro pixel — front#1887.
 *
 * 📝 2026-08-18 16:15 · JarvisGS · substituiu o `ThemeProvider`, que ninguém consumia
 *
 * ⚠️ Antes disto quem carimbava a classe aqui em cima era um **provider sem
 * consumidor**, e ele só agia depois do primeiro efeito. O `App` tem dois
 * `return` antecipados fora do `ThemeV5Provider` — para quem usa o tema claro,
 * a tela ficava escura até o `App` montar. */
aplicaTemaSalvo();

createRoot(document.getElementById('root')!).render(
  // StrictMode desabilitado - causava dupla renderização e piscadas visuais
  // Re-habilitar apenas para debug de problemas específicos
  // <StrictMode>
    <ErrorBoundary>
      <QueryClientProvider client={queryClient}>
          {/* Contexts SOLID */}
          <UserProvider>
            {/* AuthGate: bloqueia providers autenticados até JWT estar pronto */}
            <AuthGate>
              <ToolsProvider>
                <FeatureProvider>
                  {/* 📝 2026-08-19 14:4x · JarvisGS · o `StoreProvider` SAIU daqui — front#1906
                    *
                    * 🔴 Um contexto inteiro montado na árvore, chamando
                    * `useAllIntegrations()`, e **`useStore()` tinha zero
                    * consumidores**. Mesma família do `hasTool` (front#1904) e do
                    * `canAccessApp` (front#1711), agora no tamanho de um provider.
                    *
                    * ⚠️ O comentário que estava aqui dizia *"MercadoLivre precisa
                    * vir ANTES de StoreProvider"* — e era falso: o `StoreContext`
                    * não citava MercadoLivre em linha nenhuma. **A ordem afirmada
                    * não existia no código**, e ninguém tinha por que duvidar dela. */}
                  <MercadoLivreIntegrationsProvider>
                    <App />
                  </MercadoLivreIntegrationsProvider>
                </FeatureProvider>
              </ToolsProvider>
            </AuthGate>
          </UserProvider>
      </QueryClientProvider>
    </ErrorBoundary>
  // </StrictMode>
);

removePrerenderQuandoPintar();

// Iniciar coleta de Web Vitals (apenas em produção para evitar poluir analytics em dev)
if (import.meta.env.PROD) {
  reportWebVitals();
}
