import { readFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import tailwindcss from "@tailwindcss/vite";
import { tanstackRouter } from "@tanstack/router-plugin/vite";
import react from "@vitejs/plugin-react";
import { defineConfig, type Plugin } from "vite";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const host = process.env.TAURI_DEV_HOST;
const { version } = JSON.parse(
	readFileSync(path.resolve(__dirname, "../../package.json"), "utf-8"),
) as { version: string };

// Dev-mode mirror of the nginx /api reverse proxy: read X-Honcho-Upstream and
// forward /api/* there, so `make dev-web` behaves identically to the docker image
// (same-origin requests, no browser CORS). Connect strips the /api mount prefix,
// so req.url is already the upstream path (e.g. /v3/workspaces/list).
function honchoApiProxy(): Plugin {
	const HEADER = "x-honcho-upstream";
	// Mirror nginx's allowlist (spec §D): unset/empty => open; otherwise only
	// matching upstream hosts forward. Glob `*` -> any non-slash run, like nginx.
	const raw = process.env.OPENCONCHO_UPSTREAM_ALLOWLIST?.trim();
	const allowlist: RegExp[] | null = raw
		? raw
				.split(",")
				.map((h) => h.trim())
				.filter(Boolean)
				.map((host) => {
					const esc = host.replace(/[.]/g, "\\.").replace(/[*]/g, "[^/]*");
					return new RegExp(`^https?://${esc}(:[0-9]+)?(/.*)?$`);
				})
		: null;
	return {
		name: "honcho-api-proxy",
		configureServer(server) {
			server.middlewares.use("/api", async (req, res) => {
				const upstream = req.headers[HEADER];
				if (typeof upstream !== "string" || upstream.trim() === "") {
					res.statusCode = 421;
					res.setHeader("X-Honcho-Proxy-Reject", "no-upstream");
					res.end();
					return;
				}
				if (allowlist && !allowlist.some((re) => re.test(upstream))) {
					res.statusCode = 403;
					res.setHeader("X-Honcho-Proxy-Reject", "allowlist");
					res.end();
					return;
				}
				const target = upstream.replace(/\/+$/, "") + (req.url ?? "");
				const chunks: Buffer[] = [];
				for await (const c of req) chunks.push(c as Buffer);
				try {
					const upstreamRes = await fetch(target, {
						method: req.method,
						headers: {
							"content-type": req.headers["content-type"] ?? "application/json",
							...(req.headers.authorization ? { authorization: req.headers.authorization } : {}),
						},
						body: ["GET", "HEAD"].includes(req.method ?? "") ? undefined : Buffer.concat(chunks),
					});
					res.statusCode = upstreamRes.status;
					// undici's fetch auto-decompresses the body, so the original
					// content-encoding/length no longer describe what we re-send —
					// drop those (and hop-by-hop headers) to avoid ERR_CONTENT_DECODING_FAILED.
					const SKIP = new Set([
						"content-encoding",
						"content-length",
						"transfer-encoding",
						"connection",
					]);
					upstreamRes.headers.forEach((v, k) => {
						if (!SKIP.has(k.toLowerCase())) res.setHeader(k, v);
					});
					res.end(Buffer.from(await upstreamRes.arrayBuffer()));
				} catch (e) {
					res.statusCode = 502;
					res.end(`proxy error: ${e instanceof Error ? e.message : String(e)}`);
				}
			});
		},
	};
}

export default defineConfig({
	clearScreen: false,
	plugins: [tanstackRouter({ autoCodeSplitting: true }), react(), honchoApiProxy(), tailwindcss()],
	define: {
		__APP_VERSION__: JSON.stringify(version),
	},
	resolve: {
		alias: {
			"@": path.resolve(__dirname, "./src"),
		},
	},
	server: {
		port: 5173,
		strictPort: true,
		host: host || false,
		hmr: host ? { protocol: "ws", host, port: 1421 } : undefined,
		watch: { ignored: ["**/src-tauri/**"] },
	},
	envPrefix: ["VITE_", "TAURI_ENV_*"],
	build: {
		target: process.env.TAURI_ENV_PLATFORM === "windows" ? "chrome120" : "esnext",
		minify: !process.env.TAURI_ENV_DEBUG ? "esbuild" : false,
		sourcemap: !!process.env.TAURI_ENV_DEBUG,
	},
});
