import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { defineConfig, type Plugin } from 'vite';
import { svelte } from '@sveltejs/vite-plugin-svelte';
import { VitePWA } from 'vite-plugin-pwa';

/**
 * injectBootVersion stamps the app version into index.html's boot-capability guard, replacing its
 * __BOOT_VERSION__ placeholder with the same VITE_APP_VERSION build-arg that feeds __APP_VERSION__.
 * The guard runs before the bundle, so it cannot read the bundle's version; this lets its on-demand
 * diagnostic report name the client version anyway. Falls back to "dev" for a local build.
 */
function injectBootVersion(): Plugin {
  const version = process.env.VITE_APP_VERSION || 'dev';
  return {
    name: 'inject-boot-version',
    transformIndexHtml(html) {
      return html.replace(/__BOOT_VERSION__/g, version);
    },
  };
}

/**
 * emitPolyfills writes the prebuilt core-js bundle to `dist/polyfills.js`. The index.html gate
 * script loads it via document.write only on an old engine that lacks the es2020+ runtime APIs the
 * app uses (globalThis, structuredClone, Array.at, …) — e.g. the Chromium 66 Android System WebView
 * behind the Telegram/VK in-app browser — before the deferred module runs. A modern engine never
 * requests it, so it adds nothing to that payload and stays out of the module graph the bundle-size
 * gate measures.
 */
function emitPolyfills(): Plugin {
  return {
    name: 'emit-polyfills',
    generateBundle() {
      const src = readFileSync(resolve(import.meta.dirname, 'node_modules/core-js-bundle/minified.js'), 'utf8');
      this.emitFile({ type: 'asset', fileName: 'polyfills.js', source: src });
    },
  };
}

// The edge Connect service is scrabble.edge.v1.Gateway; the gateway serves it over
// h2c on :8081 by default. In dev we proxy the RPC path so the browser (which can
// not speak h2c directly) talks to the dev server on the same origin. In `mock`
// mode the app runs entirely against an in-memory fake transport — no gateway,
// no backend, no Postgres — which is what `pnpm start` launches.
const RPC_PREFIX = '/scrabble.edge.v1.Gateway';

export default defineConfig(({ mode }) => ({
  // Relative asset base so the one build serves under any path — the gateway maps the
  // Telegram Mini App to /telegram/ (the hash router is path-agnostic).
  base: './',
  define: {
    // App version shown on the About screen, injected at build time from `git describe`
    // via a Docker build-arg. Falls back to "dev" for a plain local/mock build,
    // so a missing build-arg never breaks the build.
    __APP_VERSION__: JSON.stringify(process.env.VITE_APP_VERSION || 'dev'),
    __DICT_VERSION__: JSON.stringify(process.env.VITE_DICT_VERSION || 'dev'),
  },
  // emitPolyfills ships dist/polyfills.js (loaded only by old engines; see its docstring + the
  // index.html boot guard). injectBootVersion stamps the app version into that guard's diagnostic.
  plugins: [
    svelte(),
    emitPolyfills(),
    injectBootVersion(),
    // App-shell precache for the offline mode: a custom (injectManifest) service worker precaches
    // index.html + the hashed assets so the installed web PWA cold-launches with no network. It
    // supersedes the former install-only public/sw.js and outputs to dist/sw.js (same path, so the
    // existing /app/ registration is unchanged). Registration is manual + web-only (injectRegister
    // off; see pwa.svelte.ts) and the whole plugin is disabled in the mock build so Playwright is
    // never perturbed. manifest:false — we already ship public/manifest.webmanifest.
    VitePWA({
      strategies: 'injectManifest',
      srcDir: 'src',
      filename: 'sw.ts',
      injectRegister: false,
      manifest: false,
      disable: mode === 'mock',
      injectManifest: {
        // The app shell only: exclude the separate landing page and the conditional polyfill bundle
        // (loaded document.write-only on an old engine; never part of the offline shell).
        globPatterns: ['**/*.{js,css,html,svg,png,ico,woff,woff2}'],
        globIgnores: ['**/landing*', '**/polyfills.js'],
      },
      devOptions: { enabled: false },
    }),
  ],
  server: {
    port: 5173,
    proxy:
      mode === 'mock'
        ? undefined
        : {
            [RPC_PREFIX]: {
              target: process.env.GATEWAY_URL || 'http://localhost:8081',
              changeOrigin: true,
            },
          },
  },
  build: {
    // Down-level to es2019 so an old Android System WebView (seen: Telegram/VK in-app WebView on
    // Chromium 66, Android 9) can parse the bundle — esbuild lowers es2020+ syntax (?., ??, private
    // fields, static blocks, …) that Chrome 66 rejects with "Unexpected token ?", which left the SPA
    // on a white screen. esbuild lowers syntax only, not runtime APIs — the es2020+ globals the
    // bundle/deps call at runtime (globalThis, structuredClone, Array.at, …) are covered separately
    // by the conditional core-js loader (emitPolyfills + the index.html gate), loaded only on an
    // engine that actually lacks them.
    target: 'es2019',
    // Emit sourcemaps everywhere except the production build. A shipped `.map`
    // carries full `sourcesContent` — the entire TypeScript/Svelte source — and the
    // gateway/landing images serve `dist/` verbatim, so production maps would expose
    // the whole client source at the edge. Dev and the `mock` e2e build
    // (`vite build --mode mock`) keep maps for debugging; the production `vite build`
    // (the Docker image build) drops them.
    sourcemap: mode !== 'production',
    // Two entries: the game SPA (index.html, served at /app/ + /telegram/) and the
    // public landing page (landing.html, served at /). Assets are shared in dist/assets/, and
    // the relative base lets one build serve under any path.
    rollupOptions: {
      input: {
        main: resolve(import.meta.dirname, 'index.html'),
        landing: resolve(import.meta.dirname, 'landing.html'),
      },
    },
  },
}));
