import tailwindcss from "@tailwindcss/vite";
import react from "@vitejs/plugin-react";
import dotenv from "dotenv";
import path from "node:path";
import { defineConfig } from "vite";
import { VitePWA } from "vite-plugin-pwa";

// Load .env explicitly so BASE_PATH (and other vars) are available while
// resolving this config, regardless of how the build/dev command is invoked.
dotenv.config({ path: path.resolve(import.meta.dirname, ".env") });

// Sub-path under which the app is served (e.g. "/app" on cPanel when the
// "Application URL" includes a sub-folder). Empty string = served at the
// domain root. Used to generate correct asset URLs in the production build;
// the same value is exposed to client code via `import.meta.env.BASE_URL`.
const BASE_PATH = (process.env.BASE_PATH ?? "").trim().replace(/\/+$/, "");
const SCOPE = BASE_PATH ? `${BASE_PATH}/` : "/";

export default defineConfig({
  plugins: [
    react(),
    tailwindcss(),
    VitePWA({
      // ADR-007: nunca autoUpdate — atualização só por prompt do usuário.
      // Auto-reload durante reprodução de áudio interromperia o ouvinte.
      registerType: "prompt",

      // Registro do SW é feito pelo próprio componente (UpdatePrompt.tsx,
      // via useRegisterSW). injectRegister:"auto" registraria de novo por
      // conta própria, gerando dois registros concorrentes — foi a causa
      // do botão "Atualizar" não funcionar de forma confiável.
      injectRegister: null,

      // CORREÇÃO (jul/2026): antes era `manifest: false`, o que desligava
      // completamente a geração/injeção do manifest — por isso instalação,
      // splash screen e theme_color de PWA nunca funcionaram de verdade.
      // Configurar aqui é a forma oficial recomendada pelo plugin: ele
      // gera o manifest.webmanifest e injeta o <link rel="manifest">
      // sozinho, sem precisar mexer no index.html.
      manifest: {
        name: "SpotOnFide",
        short_name: "SpotOnFide",
        description: "Podcasts, música e conteúdo cristão no SpotOnFide.",
        start_url: SCOPE,
        scope: SCOPE,
        id: SCOPE,
        display: "standalone",
        orientation: "portrait",
        background_color: "#2a0110",
        theme_color: "#2a0110",
        icons: [
          {
            src: "icons/icon-192.png",
            sizes: "192x192",
            type: "image/png",
            purpose: "any",
          },
          {
            src: "icons/icon-512.png",
            sizes: "512x512",
            type: "image/png",
            purpose: "any",
          },
          {
            src: "icons/icon-maskable-192.png",
            sizes: "192x192",
            type: "image/png",
            purpose: "maskable",
          },
          {
            src: "icons/icon-maskable-512.png",
            sizes: "512x512",
            type: "image/png",
            purpose: "maskable",
          },
        ],
      },

      workbox: {
        // Assets estáticos: pré-cacheados no build (JS/CSS/HTML/ícones)
        // — o hash no nome do arquivo já invalida sozinho a cada deploy,
        // então isso já se comporta como CacheFirst com invalidação automática.
        globPatterns: ["**/*.{js,css,html,ico,jpg,jpeg,png,svg,woff,woff2}"],

        // Fallback de navegação SPA — qualquer rota não pré-cacheada cai
        // aqui (necessário pro deep-link funcionar com o SW ativo).
        navigateFallback: `${SCOPE}index.html`,
        // /share/*: exclusão necessária a partir de jul/2026 (Share
        // Service). Sem isso, um Service Worker já instalado poderia
        // interceptar a navegação e devolver o app shell em cache em vez
        // de deixar a requisição chegar no servidor — quebrando o
        // redirecionamento client-side que /share/* depende.
        // CORREÇÃO (jul/2026): SEM "^" no início do regex de propósito.
        // Com "^", a regra só bate se o caminho começar exatamente com
        // "/share/" — mas o caminho real de uma navegação neste app é
        // "/app/share/faixa/11" (inclui o BASE_PATH). A âncora "^" fazia
        // essa exclusão nunca bater, nem uma vez — confirmado no log de
        // acesso do servidor: a requisição pro /share/* chegava e
        // respondia 200 certo, mas o Service Worker aplicava o fallback
        // da SPA por cima mesmo assim, fazendo o React abrir no lugar da
        // página de compartilhamento. Sem "^", a expressão bate em
        // qualquer lugar da URL que contenha "/share/", com ou sem prefixo.
        navigateFallbackDenylist: [/\/api\//, /\/share\//],

        runtimeCaching: [
          {
            // /share/*: NUNCA cacheado pelo Service Worker.
            //
            // CORREÇÃO (jul/2026): a exclusão em navigateFallbackDenylist
            // (abaixo) só evita o fallback offline — não impedia a regra de
            // navegação genérica logo em seguida de guardar essas respostas
            // em "html-cache" via NetworkFirst. Resultado: a primeira visita
            // funcionava (buscava da rede), mas ficava em cache; numa visita
            // seguinte, se a rede engasgasse por um instante (comum em
            // mobile), o NetworkFirst caía pro cache — que podia guardar uma
            // resposta velha (ex: 404 de antes de o servidor ter essa rota).
            // Como cada página de compartilhamento é gerada na hora a partir
            // do banco e serve só de ponte pro app, cache nunca ajuda aqui —
            // só arrisca mostrar conteúdo desatualizado. Regra precisa vir
            // ANTES da regra de navegação genérica: Workbox usa a primeira
            // que bater.
            urlPattern: /\/share\//,
            handler: "NetworkOnly",
          },
          {
            // Navegação (HTML): NetworkFirst — sempre tenta buscar a
            // versão mais nova primeiro, cai pro cache só se a rede falhar.
            urlPattern: ({ request }) => request.mode === "navigate",
            handler: "NetworkFirst",
            options: {
              cacheName: "html-cache",
              networkTimeoutSeconds: 5,
              expiration: {
                maxEntries: 30,
                maxAgeSeconds: 60 * 60 * 24, // 1 dia
              },
            },
          },
          {
            // API tRPC: NetworkFirst (dados sempre frescos, fallback em cache)
            // HUB-INTEGRATION-POINT (Fase 6): quando a busca global do HUB
            // existir, adicionar sua URL aqui como segunda entrada de runtimeCaching.
            urlPattern: /\/api\/trpc\//,
            handler: "NetworkFirst",
            options: {
              cacheName: "trpc-api-cache",
              expiration: {
                maxEntries: 100,
                maxAgeSeconds: 60 * 60, // 1 hora
              },
              networkTimeoutSeconds: 10,
            },
          },
          {
            // Imagens servidas fora do build (capas de episódio/faixa etc.)
            urlPattern: /\/uploads\/images\//,
            handler: "StaleWhileRevalidate",
            options: {
              cacheName: "uploads-images-cache",
              expiration: {
                maxEntries: 200,
                maxAgeSeconds: 60 * 60 * 24 * 30, // 30 dias
              },
            },
          },
          {
            // Áudio: NetworkOnly — de propósito, NÃO cacheado pelo Service
            // Worker.
            //
            // CORREÇÃO (jul/2026): a regra anterior usava CacheFirst pra
            // áudio. Isso é um problema conhecido do Workbox: interceptar
            // requisições de mídia sem o plugin de "range requests" quebra
            // a forma como o navegador busca pedaços do arquivo (essencial
            // pra streaming/retomada), e é uma causa plausível de a
            // reprodução parar em segundo plano no mobile. Deixando essas
            // requisições passarem direto pro navegador (sem o Service
            // Worker no meio), o range request nativo funciona normal.
            urlPattern: /\/uploads\/audio\//,
            handler: "NetworkOnly",
          },
        ],

        // Não tentar pré-cachear arquivos de áudio/vídeo (muito grandes)
        globIgnores: ["**/*.mp3", "**/*.mp4", "**/*.wav", "**/*.ogg"],
      },

      // Em desenvolvimento, não registrar o service worker para não
      // interferir com o hot reload do Vite
      devOptions: {
        enabled: false,
      },
    }),
  ],

  base: BASE_PATH ? `${BASE_PATH}/` : "/",

  resolve: {
    alias: {
      "@": path.resolve(import.meta.dirname, "client", "src"),
      "@shared": path.resolve(import.meta.dirname, "shared"),
      "@assets": path.resolve(import.meta.dirname, "attached_assets"),
    },
  },

  envDir: path.resolve(import.meta.dirname),
  root: path.resolve(import.meta.dirname, "client"),
  publicDir: path.resolve(import.meta.dirname, "client", "public"),

  build: {
    outDir: path.resolve(import.meta.dirname, "dist/public"),
    emptyOutDir: true,

    // Code splitting: separa vendor pesados em chunks independentes.
    // Reduz o bundle principal de > 500kB para < 200kB.
    rollupOptions: {
      output: {
        manualChunks: {
          "vendor-react": ["react", "react-dom"],
          "vendor-query": ["@tanstack/react-query"],
          "vendor-trpc": ["@trpc/client", "@trpc/react-query"],
          "vendor-ui": [
            "@radix-ui/react-dialog",
            "@radix-ui/react-select",
            "@radix-ui/react-slider",
            "@radix-ui/react-switch",
            "@radix-ui/react-tabs",
          ],
        },
      },
    },
  },
});
