import vue from "@vitejs/plugin-vue";
import { defineConfig, loadEnv, type Plugin } from "vite";

const monorepoRoot = new URL("../..", import.meta.url).pathname;

type AppVariant = "dev" | "pro";

function appIdentityPlugin(mode: string): Plugin {
  const env = loadEnv(mode, monorepoRoot, "");
  const envValue = (name: string) => process.env[name] || env[name];
  const defaultVariant: AppVariant = mode === "development" ? "dev" : "pro";
  const configuredVariant = envValue("APP_VARIANT");
  const variant: AppVariant = configuredVariant === "dev" || configuredVariant === "pro" ? configuredVariant : defaultVariant;
  const appName = envValue("VITE_APP_NAME") || (variant === "dev" ? "有数记账 Dev" : "有数记账");
  const shortName = envValue("VITE_APP_SHORT_NAME") || appName;
  const appId = envValue("VITE_APP_ID") || (variant === "dev" ? "/?app=cents-dev" : "/?app=cents");
  const iconPrefix = variant === "dev" ? "icon-dev" : "icon";
  const appleTouchIcon = variant === "dev" ? "/apple-touch-icon-dev.png" : "/apple-touch-icon.png";

  const manifest = JSON.stringify(
    {
      name: appName,
      short_name: shortName,
      id: appId,
      description: "家庭共享账本",
      start_url: "/",
      display: "standalone",
      background_color: "#ffffff",
      theme_color: "#43c9bd",
      icons: [
        {
          src: `/${iconPrefix}-192.png`,
          sizes: "192x192",
          type: "image/png",
          purpose: "any",
        },
        {
          src: `/${iconPrefix}-512.png`,
          sizes: "512x512",
          type: "image/png",
          purpose: "any",
        },
        {
          src: `/${iconPrefix}-maskable-512.png`,
          sizes: "512x512",
          type: "image/png",
          purpose: "maskable",
        },
      ],
    },
    null,
    2,
  );

  return {
    name: "cents-app-identity",
    configureServer(server) {
      server.middlewares.use((req, res, next) => {
        if (req.url?.split("?")[0] !== "/manifest.webmanifest") {
          next();
          return;
        }

        res.setHeader("Content-Type", "application/manifest+json; charset=utf-8");
        res.end(manifest);
      });
    },
    transformIndexHtml(html) {
      return html
        .replace(/<title>.*<\/title>/, `<title>${appName}</title>`)
        .replace('href="/icon-192.png"', `href="/${iconPrefix}-192.png"`)
        .replace('href="/apple-touch-icon.png"', `href="${appleTouchIcon}"`);
    },
    generateBundle() {
      this.emitFile({
        type: "asset",
        fileName: "manifest.webmanifest",
        source: manifest,
      });
    },
  };
}

function offlineServiceWorkerPlugin(): Plugin {
  const publicAssets = [
    "/apple-touch-icon.png",
    "/apple-touch-icon-dev.png",
    "/icon-192.png",
    "/icon-512.png",
    "/icon-maskable-512.png",
    "/icon-dev-192.png",
    "/icon-dev-512.png",
    "/icon-dev-maskable-512.png",
  ];

  return {
    name: "cents-offline-service-worker",
    generateBundle(_options, bundle) {
      const bundledAssets = Object.keys(bundle)
        .filter((fileName) => fileName !== "sw.js")
        .map((fileName) => `/${fileName}`);
      const precache = [...new Set(["/", ...bundledAssets, ...publicAssets])];
      const source = `const CACHE_NAME = "cents-shell-v4";
const PRECACHE = ${JSON.stringify(precache, null, 2)};

function fetchWithTimeout(request, timeoutMs) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);
  return fetch(request, { signal: controller.signal }).finally(() => clearTimeout(timer));
}

self.addEventListener("install", (event) => {
  event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE)).then(() => self.skipWaiting()));
});

self.addEventListener("activate", (event) => {
  event.waitUntil(
    caches.keys().then((keys) => Promise.all(
      keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key)),
    )).then(() => self.clients.claim()),
  );
});

self.addEventListener("fetch", (event) => {
  if (event.request.method !== "GET") return;
  const url = new URL(event.request.url);
  if (url.origin !== self.location.origin || url.pathname.startsWith("/api/")) return;

  if (event.request.mode === "navigate") {
    event.respondWith(
      fetchWithTimeout(event.request, 1500).then((response) => {
        if (!response.ok) throw new Error("navigation unavailable");
        const copy = response.clone();
        void caches.open(CACHE_NAME).then((cache) => cache.put("/", copy));
        return response;
      }).catch(async () => (await caches.match("/")) ?? caches.match("/index.html")),
    );
    return;
  }

  event.respondWith(
    caches.match(event.request).then((cached) => cached ?? fetch(event.request).then((response) => {
      if (response.ok) {
        const copy = response.clone();
        void caches.open(CACHE_NAME).then((cache) => cache.put(event.request, copy));
      }
      return response;
    })),
  );
});
`;
      this.emitFile({ type: "asset", fileName: "sw.js", source });
    },
  };
}

export default defineConfig(({ mode }) => ({
  envDir: monorepoRoot,
  plugins: [vue(), appIdentityPlugin(mode), offlineServiceWorkerPlugin()],
  server: {
    allowedHosts: ["dev.cents.homemade.net.cn"],
    proxy: {
      "/api": {
        target: "http://127.0.0.1:3000",
        changeOrigin: false,
      },
    },
  },
}));
