import { fileURLToPath } from 'node:url';
import { defineConfig } from 'vite';
import { VitePWA } from 'vite-plugin-pwa';
import { buildSourceZip } from './scripts/make-source-release.mjs';
import { pageHtml } from './src/landing/markup';

const rootDir = fileURLToPath(new URL('.', import.meta.url));

// Prerender the landing page into index.html so crawlers (and the very first
// paint) get real, styled HTML instead of an empty "needs JavaScript" shell —
// the whole point of the exercise, since search bots deprioritise JS-only SPAs.
// pageHtml() is a pure string builder with no DOM dependency, so it runs fine
// here at build time. On load, main.ts's mountLanding() clears #app and rebuilds
// the identical markup, so there's no duplication and the SPA takes over cleanly.
// The editor/legal views still render client-side; only the marketing page is
// baked in, which is all that needs to be crawlable.
const prerenderLanding = {
  name: 'prerender-landing',
  transformIndexHtml(html: string) {
    return html.replace(
      '<div id="app" aria-live="polite"></div>',
      `<div id="app" aria-live="polite"><div class="landing-folio">${pageHtml()}</div></div>`,
    );
  },
};

// COOP/COEP make multi-threaded WASM (SharedArrayBuffer) available where supported.
// They are applied here for the dev server; production headers live in public/_headers
// and netlify.toml so hosts like Cloudflare Pages / Netlify / Vercel set them statically.
const crossOriginIsolation = {
  name: 'cross-origin-isolation',
  configureServer(server: any) {
    server.middlewares.use((_req: any, res: any, next: any) => {
      res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
      res.setHeader('Cross-Origin-Embedder-Policy', 'credentialless');
      next();
    });
  },
  configurePreviewServer(server: any) {
    server.middlewares.use((_req: any, res: any, next: any) => {
      res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
      res.setHeader('Cross-Origin-Embedder-Policy', 'credentialless');
      next();
    });
  },
};

// The production build zips the full source into dist/yeetpdf-source.zip (see
// scripts/make-source-release.mjs). Dev/preview don't run that build step, so serve
// it live here too — otherwise the site's "Source" download link 404s locally.
const sourceRelease = {
  name: 'source-release',
  configureServer(server: any) {
    server.middlewares.use('/yeetpdf-source.zip', (_req: any, res: any) => {
      res.setHeader('Content-Type', 'application/zip');
      res.end(buildSourceZip(rootDir));
    });
  },
  configurePreviewServer(server: any) {
    server.middlewares.use('/yeetpdf-source.zip', (_req: any, res: any) => {
      res.setHeader('Content-Type', 'application/zip');
      res.end(buildSourceZip(rootDir));
    });
  },
};

export default defineConfig({
  base: '/',
  plugins: [
    prerenderLanding,
    crossOriginIsolation,
    sourceRelease,
    VitePWA({
      registerType: 'autoUpdate',
      includeAssets: ['favicon.svg', 'icons/*.png'],
      workbox: {
        // WASM engines + fonts are large; precache generously so the whole app works offline.
        maximumFileSizeToCacheInBytes: 30 * 1024 * 1024,
        globPatterns: ['**/*.{js,css,html,svg,png,woff,woff2,wasm}'],
        navigateFallback: '/index.html',
        // The SPA fallback above catches *every* navigation request and serves
        // index.html — including <a href> clicks to real static downloads like
        // /yeetpdf-source.zip, /LICENSE.txt and /THIRD-PARTY-NOTICES.md, which
        // would otherwise get the app shell instead of the file. (It works on
        // the dev server, which runs no service worker, but breaks in prod.)
        // Routing is hash-based, so no real app route ever has a file extension
        // in its path — exempt any path whose last segment looks like a file so
        // those navigations hit the network and actually download.
        navigateFallbackDenylist: [/\/[^/]+\.[^/]+$/],
        cleanupOutdatedCaches: true,
        // The bundled TTFs are deliberately kept OUT of globPatterns above so
        // they aren't precached — they load on demand only when a matching font
        // is first used. This runtime rule then persists each one in Cache
        // Storage so the editor's fonts keep working offline after first use.
        runtimeCaching: [
          {
            urlPattern: /\/fonts\/.*\.ttf$/,
            handler: 'CacheFirst',
            options: {
              cacheName: 'yeet-fonts',
              expiration: { maxEntries: 64, maxAgeSeconds: 60 * 60 * 24 * 365 },
              cacheableResponse: { statuses: [0, 200] },
            },
          },
        ],
      },
      manifest: {
        name: 'YeetPDF — Free client-side PDF editor',
        short_name: 'YeetPDF',
        description:
          'Edit, sign, merge, convert and redact PDFs entirely in your browser. Your files never leave your device.',
        theme_color: '#ffd400',
        background_color: '#fcfcfb',
        display: 'standalone',
        start_url: '/',
        icons: [
          { src: '/icons/icon-192.png', sizes: '192x192', type: 'image/png' },
          { src: '/icons/icon-512.png', sizes: '512x512', type: 'image/png' },
          {
            src: '/icons/icon-512-maskable.png',
            sizes: '512x512',
            type: 'image/png',
            purpose: 'maskable',
          },
        ],
      },
    }),
  ],
  worker: {
    format: 'es',
  },
  optimizeDeps: {
    // qpdf's Emscripten glue is CommonJS and is imported only from a Web Worker, which
    // Vite doesn't scan for pre-bundling — force it so the CJS→ESM interop is correct.
    include: ['@neslinesli93/qpdf-wasm'],
    // mupdf is a native ESM module that instantiates its ~10MB wasm at import time via
    // `new URL('mupdf-wasm.wasm', import.meta.url)` — pre-bundling would mangle that
    // asset resolution, so exclude it and let Vite emit the wasm as an asset directly.
    exclude: ['mupdf'],
  },
  build: {
    // mupdf's entry uses top-level await; esnext guarantees esbuild/rollup keep it.
    target: 'esnext',
    sourcemap: false,
    minify: true,
    rollupOptions: {
      output: {
        manualChunks(id: string) {
          if (id.includes('pdfjs-dist')) return 'pdfjs';
          if (id.includes('@cantoo/pdf-lib')) return 'pdflib';
          return undefined;
        },
      },
    },
  },
});
