// Copyright 2021-2026 Prosopo (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import { randomBytes } from "node:crypto";
import * as fs from "node:fs";
import * as path from "node:path";
import {
	ViteFrontendConfig,
	VitePluginRemoveUnusedTranslations,
} from "@prosopo/config";
import { loadEnv } from "@prosopo/dotenv";
import { at, flatten } from "@prosopo/util";
import fg from "fast-glob";
import { defineConfig } from "vite";

// Per-build opaque chunk name for the honeypot module. Re-rolled on every
// build so the emitted URL (e.g. `c8f2a1b0-<hash>.js`) doesn't leak the
// component's purpose and can't be fingerprinted by scrapers that maintain
// static URL blocklists.
const honeypotChunkName = `c${randomBytes(4).toString("hex")}`;

// Per-build opaque chunk name for the module that holds `@prosopo/fingerprint`
// + `@prosopo/fingerprintjs`. Same rationale as `honeypotChunkName` — but the
// primary reason for pulling these into a single shared chunk is dedup: prior
// to this the module was inlined into multiple downstream chunks (widget
// bundle + procaptcha-pow), each with its own module-local `componentsCache`,
// so the fingerprint sources (canvas / WebGL `getContext` / `getParameter` /
// `getImageData` — expensive on real GPUs) ran twice per widget session. One
// shared chunk = one module instance = one populated cache = one run.
const sharedBrowserChunkName = `b${randomBytes(4).toString("hex")}`;

// load env using our util because vite loadEnv is not working for .env.development
loadEnv();

// Vite doesn't find the tsconfig for some reason
process.env.TS_NODE_PROJECT = path.resolve("./tsconfig.json");

// Package specific config
const copyTo = ["../../demos/client-bundle-example/src/assets"];
const bundleName = "procaptcha";
const packageName = "@prosopo/procaptcha-bundle";
const entry = "./src/index.ts";
const copyOptions = copyTo
	? {
			srcDir: "./dist/bundle",
			destDir: copyTo,
		}
	: undefined;
const tsConfigPaths = [path.resolve("./tsconfig.json")];
const packagesDir = path.resolve("..");
const workspaceRoot = path.resolve("../../");
// Get all folders in packagesDir
const packages = fs
	.readdirSync(packagesDir)
	.filter((f) => fs.statSync(path.join(packagesDir, f)).isDirectory());
for (const packageName of packages) {
	// Add the tsconfig for each package to tsConfigPaths
	tsConfigPaths.push(path.resolve(`../${packageName}/tsconfig.json`));
}

const copyDir = {
	srcDir: `${workspaceRoot}/packages/locale/src/locales`,
	destDir: `${workspaceRoot}/packages/procaptcha-bundle/dist/bundle/locales`,
};

const localeFiles = await fg.glob(
	`${workspaceRoot}/packages/locale/src/locales/**/*.json`,
);

const translationKeys = Object.keys(
	flatten(JSON.parse(fs.readFileSync(at(localeFiles, 0), "utf-8"))),
);

// Merge with generic frontend config
export default defineConfig(async ({ command, mode }) => {
	// Test-only escape hatch for the firefox cypress leg: cypress can only
	// dispatch trusted input over the chrome devtools protocol (chromium
	// only), so on firefox the specs click synthetically and the widget's
	// isEventTrusted() gate would drop every one. Anything other than an
	// explicit "1" on a non-production build compiles the allowance out.
	const allowUntrustedEvents =
		mode !== "production" && process.env.PROSOPO_ALLOW_UNTRUSTED_EVENTS === "1";

	const frontendConfig = await ViteFrontendConfig(
		packageName,
		bundleName,
		path.resolve(),
		entry,
		command,
		mode,
		copyOptions,
		tsConfigPaths,
		workspaceRoot,
	);

	return {
		...frontendConfig,
		define: {
			...frontendConfig.define,
			__PROSOPO_ALLOW_UNTRUSTED_EVENTS__: JSON.stringify(allowUntrustedEvents),
		},
		worker: {
			format: "es",
		},
		build: {
			assetsDir: "",
			...frontendConfig.build,
			rollupOptions: {
				...frontendConfig.build?.rollupOptions,
				output: {
					...frontendConfig.build?.rollupOptions?.output,
					manualChunks(id: string) {
						if (
							id.includes(
								"packages/procaptcha-common/dist/reactComponents/Honeypot",
							)
						) {
							return honeypotChunkName;
						}
						// Force @prosopo/fingerprint + @prosopo/fingerprintjs into one
						// shared chunk. Without this, Vite inlines them into every
						// downstream chunk that imports them (widget bundle for the
						// account/frictionless flow, procaptcha-pow for the challenge
						// proof), giving each chunk its own module-local
						// `componentsCache` — so the canvas / WebGL fingerprint sources
						// run once per copy, costing ~220ms of main-thread work per
						// duplicate. One chunk = one instance = one cache hit.
						if (
							id.includes("packages/fingerprint/dist") ||
							id.includes("packages/fingerprintjs/dist")
						) {
							return sharedBrowserChunkName;
						}
						if (id.includes("wasm-crypto-wasm")) {
							return "web3Chunk";
						}
						// Split up the extension code into 2 chunks
						if (id.includes("packages/account/dist/extension")) {
							if (id.includes("ExtensionWeb3.js")) {
								return "web3Chunk";
							}
							return "web2Chunk";
						}
						if (
							id.includes("packages/common/dist") ||
							id.includes("packages/logger/dist")
						) {
							return "commonChunk";
						}
						if (id.includes("zod")) {
							return "zodChunk";
						}
						if (id.includes("@polkadot/extension-dapp")) {
							return "web3Chunk";
						}
						if (id.includes("@polkadot/keyring")) {
							return "web3Chunk";
						}
						// util-crypto rides in the shared browser chunk rather than
						// its own. Split apart, Rolldown emits a cycle between the two
						// (fingerprint <-> utilCryptoChunk) on this branch, where the
						// detector is no longer bundled into the widget and the module
						// graph is shaped differently. Whichever chunk evaluated first
						// then read the other's module-scope bindings before they were
						// assigned, so the widget died on load — first with
						// "init_dist is not a function", then with isHex reading
						// `.test` of undefined. Same chunk = one evaluation order = no
						// cycle, and it preserves the single-instance property the
						// shared chunk exists for.
						if (id.includes("packages/util-crypto/dist")) {
							return sharedBrowserChunkName;
						}
						if (id.includes("@noble/hash") || id.includes("@noble/curves")) {
							return "nobleChunk";
						}
						if (id.includes("@polkadot/util") && !id.includes("util-crypto")) {
							return "utilChunk";
						}
						if (id.includes("bn.js")) {
							return "bnChunk";
						}
						return undefined;
					},
				},
			},
		},
		plugins: [
			{
				name: "copy-dir",
				async writeBundle() {
					if (copyDir) {
						const containingFolder = path.dirname(copyDir.destDir);
						if (!fs.existsSync(containingFolder)) {
							fs.mkdirSync(containingFolder, { recursive: true });
						}
						console.log(`Copying ${copyDir.srcDir} to ${copyDir.destDir}`);
						fs.cpSync(copyDir.srcDir, copyDir.destDir, {
							recursive: true,
						});
					}
				},
			},
			VitePluginRemoveUnusedTranslations(
				translationKeys,
				`${copyDir.destDir}/**/*.json`,
			),

			...(frontendConfig.plugins ? frontendConfig.plugins : []),
		],
	};
});
