import type { Adapter } from "@sveltejs/kit";
import { sveltekit } from "@sveltejs/kit/vite";
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
import tailwindcss from "@tailwindcss/vite";
import Icons from "unplugin-icons/vite";
import { defineConfig } from "vitest/config";
import oauthMetadata, {
	callbackPath,
	loopbackClientId,
	loopbackScope
} from "./src/lib/oauth-client-metadata.ts";
import { testids } from "./scripts/testids.ts";
import { timeAgoTag } from "./src/lib/timeAgoScript.ts";
import { readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { storybookTest } from "@storybook/addon-vitest/vitest-plugin";
import { playwright } from "@vitest/browser-playwright";

const dirname = import.meta.dirname;

const NOSCRIPT = `<noscript><p style="margin: 2rem; color: var(--y-muted)">this page needs javascript, the knot hasn't rendered it</p></noscript>`;

const spa = (out = "build", server = "build-server"): Adapter => ({
	name: "spa",
	async adapt(builder) {
		builder.rimraf(out);
		builder.rimraf(server);
		builder.writeClient(out);
		builder.writePrerendered(out);
		const fallback = path.join(out, "index.html");
		await builder.generateFallback(fallback);
		// only urls the knot didn't prerender get this shell, which is blank without js
		const shell = await readFile(fallback, "utf8");
		await writeFile(fallback, shell.replace("</body>", `${NOSCRIPT}</body>`));
		await builder.compress(out);
		builder.writeServer(server);
		builder.generateServerInstance(path.join(server, "instance.js"), {
			serverDirectory: server
		});
	}
});

const chromiumBrowser = {
	enabled: true,
	headless: true,
	provider: playwright({
		launchOptions: process.env.E2E_CHROMIUM
			? { executablePath: process.env.E2E_CHROMIUM }
			: undefined
	}),
	instances: [{ browser: "chromium" } as const]
};
// More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon
const devHost = process.env.VITE_DEV_HOST ?? "127.0.0.1";
const devPort = Number(process.env.VITE_DEV_PORT ?? 5173);
// docker desktop's macos file sharing never forwards inotify to the container,
// so an edit on the host lands on disk but the watcher never fires and hmr goes
// stale. polling is the only way to see those writes, and only the container
// needs it
const watch =
	process.env.VITE_WATCH_POLL === "true" ? { usePolling: true, interval: 300 } : undefined;
const allowedHosts = [
	".ts.net",
	...(process.env.VITE_ALLOWED_HOSTS?.split(",")
		.map((h) => h.trim())
		.filter(Boolean) ?? [])
];
const devRedirectUri = `http://127.0.0.1:${devPort}${callbackPath}`;
const devScope =
	process.env.VITE_OAUTH_SCOPE ??
	(process.env.VITE_OAUTH_CLIENT_ID ? oauthMetadata.scope : loopbackScope);
const devClientId = loopbackClientId(devRedirectUri, devScope);
const buildClientId = process.env.VITE_OAUTH_CLIENT_ID ?? oauthMetadata.client_id;
const injectTestIds = process.env.E2E_TESTIDS === "true";

export default defineConfig({
	plugins: [
		{
			name: "oauth-env",
			config(_config, { command }) {
				const serve = command === "serve";
				return {
					define: {
						"import.meta.env.VITE_OAUTH_CLIENT_ID": JSON.stringify(
							process.env.VITE_OAUTH_CLIENT_ID ??
								(serve ? devClientId : buildClientId)
						),
						"import.meta.env.VITE_OAUTH_REDIRECT_URI": JSON.stringify(
							process.env.VITE_OAUTH_REDIRECT_URI ??
								(serve ? devRedirectUri : oauthMetadata.redirect_uris[0])
						),
						"import.meta.env.VITE_OAUTH_SCOPE": JSON.stringify(
							serve ? devScope : (process.env.VITE_OAUTH_SCOPE ?? oauthMetadata.scope)
						)
					}
				};
			}
		},
		tailwindcss(),
		Icons({
			compiler: "svelte",
			// every icon carries this, which svfe.css aligns to the text beside it
			iconCustomizer: (_collection, _icon, props) => {
				props["data-icon"] = "";
			}
		}),
		sveltekit({
			preprocess: [vitePreprocess(), ...(injectTestIds ? [testids()] : [])],
			compilerOptions: {
				runes: ({ filename }) =>
					filename.split(/[/\\]/).includes("node_modules") ? undefined : true,
				// `await` in templates, so a page can hand a streamed promise straight to
				// <svelte:boundary pending> instead of threading {#await} through every view
				experimental: { async: true }
			},
			adapter: spa(),
			// defaults to the build's clock, which makes every build of the same source
			// differ. the deploys pass the web tree's git hash, so it only changes with the app
			version: { name: process.env.APP_VERSION || undefined },
			paths: {
				// GFE is evil and rewrites // to /
				// so we do this to have /_app/ be at root always which means it doesnt break
				// bcz // -> / removes a depth level which makes browser try to fetch app
				// bundle that doesnt exist at that path
				relative: false
			}
		})
	],
	define: { __TIME_AGO_TAG__: JSON.stringify(timeAgoTag) },
	resolve: {
		alias: {
			"$icon/": "~icons/lucide/"
		}
	},
	worker: { format: "es" },
	server: {
		host: devHost,
		port: devPort,
		strictPort: true,
		allowedHosts,
		watch
	},
	// pierre's worker entry pulls all of shiki (300+ lang/theme chunks). left to
	// runtime discovery it triggers a re-optimize mid-page and in-flight dep
	// requests 504 silently. dev-only, the production build ignores it
	optimizeDeps: {
		include: [
			"@pierre/diffs",
			"@pierre/diffs/worker",
			"@pierre/diffs/ssr",
			"@pierre/diffs/edit"
		],
		exclude: ["@pierre/diffs/react"]
	},
	test: {
		expect: {
			requireAssertions: true
		},
		projects: [
			{
				extends: "./vite.config.ts",
				test: {
					name: "server",
					environment: "node",
					include: ["src/**/*.{test,spec}.{js,ts}", "scripts/**/*.test.ts"],
					exclude: ["src/**/*.svelte.{test,spec}.{js,ts}"]
				}
			},
			{
				extends: "./vite.config.ts",
				test: {
					name: "client",
					include: ["src/**/*.svelte.{test,spec}.{js,ts}"],
					browser: chromiumBrowser
				}
			},
			{
				extends: true,
				plugins: [
					// The plugin will run tests for the stories defined in your Storybook config
					// See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest
					storybookTest({
						configDir: path.join(dirname, ".storybook")
					})
				],
				// vite only crawls kit routes here, so undeclared story deps would
				// optimize mid-run and wedge chromium; sanitize-html is cjs
				optimizeDeps: {
					noDiscovery: true,
					include: ["storybook/test", "sanitize-html", "textarea-caret"]
				},
				test: {
					name: "storybook",
					browser: chromiumBrowser
				}
			}
		]
	}
});
