const esbuild = require("esbuild");
const fs = require("node:fs");
const _path = require("node:path");
const { visualizer } = require("esbuild-visualizer");

// NOTE: IP Protection via client-side obfuscation removed.
// Architectural IP protection is server-side in api.snapback.dev.
// Bundle size invariant (under 2MB) is enforced by check-bundle-size.js

const production = process.argv.includes("--production");
const watch = process.argv.includes("--watch");

async function main() {
	// Build extension (thin client)
	const extensionCtx = await esbuild.context({
		entryPoints: ["./src/extension.ts"],
		bundle: true,
		format: "cjs",
		platform: "node",
		target: "node20",
		outfile: "dist/extension.js",

		// External dependencies - ONLY vscode API and native modules
		// Per VS Code best practices: bundle everything except vscode and native .node modules
		// PLUS: IP-sensitive proprietary packages (loaded from node_modules, not bundled)
		external: [
			"vscode",
			// IP Protection: Proprietary packages - NOT bundled to protect intellectual property
			"@vreko/engine", // Core engine algorithms and runtime
			"@vreko/engine/*", // Subpath imports
			"@vreko/intelligence", // Proprietary AI/ML logic
			"@vreko/intelligence/*", // Subpath imports like /vitals, /domain
			// @vreko/local-service-client is BUNDLED  -  client library for daemon communication
			// Must be bundled because VSIX packaging uses --no-dependencies
			"@vreko-oss/infrastructure", // Infrastructure utilities - loaded from node_modules
			"@vreko-oss/infrastructure/*", // Subpath imports
			// Additional vreko packages  -  must not be bundled into extension
			"@vreko/auth", // Auth package  -  heavy, not needed in extension process
			"@vreko/auth/*",
			"@vreko/infrastructure", // Internal infrastructure
			"@vreko/infrastructure/*",
			"@vreko/platform", // Platform layer
			"@vreko/platform/*",
			// @snapback/core is BUNDLED  -  actively imported by state/user-state.ts, handlers/AnalysisCoordinator.ts
			// Provides state-machine, probe-store, diagnostics used by extension
			// Heavy third-party packages that must not be bundled
			"better-auth", // Auth framework  -  not needed in extension
			"@opentelemetry/*", // Telemetry SDK  -  large, loaded from server process
			"protobufjs", // Protocol Buffers  -  used only in server/daemon
			"protobufjs/*",
			// Native modules that contain .node binaries - CANNOT be bundled
			"onnxruntime-node",
			"onnxruntime-common",
			"onnxruntime-web",
			"@huggingface/transformers", // Uses onnxruntime-node for local inference
			"@xenova/transformers", // Alternative name for Hugging Face Transformers.js
			"sharp", // Native .node binaries
			"sql.js", // Uses WASM
			"@sentry/node", // Error tracking - external for shared environment safety
			"@sentry/core",
			"@sentry/types",
			"@sentry/profiling-node", // Contains native .node binaries - not usable in VSCode extension
			"@sentry-internal/node-cpu-profiler",
			// Heavy third-party packages NOT needed for extension runtime
			"posthog-node", // Using local telemetry client
			"@typescript-eslint/*", // From SDK/contracts if pulled in
			"drizzle-orm", // Database ORM from platform - not needed locally
			"drizzle-orm/*",
			"@aws-sdk/*", // AWS S3 client - not needed for local extension
			// Static analysis tools - not needed at runtime
			"madge",
			"typescript",
			"dependency-tree",
			"precinct",
			"filing-cabinet",
			"requirejs",
			"gonzales-pe",
			"detective-*",
			"esprima",
			// Template engines (optional deps from consolidate.js)
			"velocityjs",
			"dustjs-linkedin",
			"atpl",
			"liquor",
			"twig",
			"ejs",
			"eco",
			"jazz",
			"jqtpl",
			"hamljs",
			"hamlet",
			"whiskers",
			"haml-coffee",
			"hogan.js",
			"templayed",
			"handlebars",
			"underscore",
			"lodash",
			"walrus",
			"mustache",
			"just",
			"ect",
			"mote",
			"toffee",
			"dot",
			"bracket-template",
			"ractive",
			"nunjucks",
			"htmling",
			"babel-core",
			"plates",
			"react-dom/server",
			"react",
			"arc-templates",
			"vash",
			"slm",
			"marko",
			"teacup/lib/express",
			"teacup",
			"coffee-script",
			"squirrelly",
			"twing",
			"@vue/compiler-sfc",
		],

		// Minification (production only)
		minify: production,
		minifyWhitespace: production,
		minifyIdentifiers: production, // Enable in production for IP protection
		minifySyntax: production,

		// Tree-shaking
		treeShaking: true,

		// CRITICAL: Preserve class constructor names during bundling
		// Without this, ESM→CJS conversion mangles class names causing:
		// "TypeError: yb.AutoDecisionEngine is not a constructor"
		// See: SB-190 release blocker fix
		keepNames: true,

		// Source maps (dev only)
		sourcemap: !production, // No sourcemaps in production to reduce bundle size

		// Drop debugger statements in production (console preserved for logging)
		drop: production ? ["debugger"] : [],

		// Legal comments
		legalComments: "none",

		// Logging
		logLevel: "info",

		// Main fields for resolution
		mainFields: ["module", "main"],

		// CRITICAL: Inject env guards at the very top of the bundle (char 0),
		// BEFORE any require() calls execute. process.env.SKIP_ENV_VALIDATION
		// set inside extension.ts body runs too late because external packages
		// like @snapback/auth/workspace → auth.js → @snapback/config/env call
		// createEnv() synchronously during require(), before the module body runs.
		banner: {
			js: 'process.env.SKIP_ENV_VALIDATION="1";process.env.MCP_QUIET="1";process.env.VREKO_CLI="1";',
		},

		// Environment
		define: {
			"process.env.NODE_ENV": production ? '"production"' : '"development"',
			"process.env.VSCODE_EXTENSION": '"true"',
			// Daemon config values  -  baked in at build time so the extension is self-sufficient.
			// The daemon (local-service) only needs these two; DATABASE_URL / REDIS_URL are
			// API server secrets and are NOT needed by the daemon at all.
			//
			// POSTHOG_API_KEY: write-only analytics key, safe to bundle (industry standard).
			// Doppler injects it during `doppler run --project vreko-shared --config prd`
			// at deploy time, falling back to empty string for local dev builds.
			"process.env.DAEMON_POSTHOG_API_KEY": JSON.stringify(process.env.POSTHOG_API_KEY || ""),
			// SNAPBACK_API_URL: public endpoint  -  hardcoded default is always correct.
			// Can be overridden for staging by setting the env var at build time.
			"process.env.DAEMON_SNAPBACK_API_URL": JSON.stringify(
				process.env.SNAPBACK_API_URL || "https://api.snapback.dev",
			),
		},

		// Enable metafile for bundle analysis
		metafile: production,

		// Add plugins to handle native modules and problematic dependencies
		plugins: [
			// Resolve monorepo packages from their dist/index.js files
			{
				name: "monorepo-resolver",
				setup(build) {
					const path = require("node:path");

					// Map package names to their actual built files
					// ONLY non-proprietary packages (contracts, types, utilities)
					// Proprietary packages (@vreko/engine, @vreko/intelligence) are external
					const packageMappings = {
						"@vreko/contracts": path.resolve(__dirname, "../../packages/contracts/dist/index.js"),
						"@vreko/core": path.resolve(__dirname, "../../packages/core/dist/index.js"),
					};

					// Resolve exact package imports
					for (const [pkg, resolvedPath] of Object.entries(packageMappings)) {
						build.onResolve(
							{
								filter: new RegExp(`^${pkg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`),
							},
							() => {
								return { path: resolvedPath };
							},
						);
					}

					// Resolve subpath imports like @vreko/engine/signals and @vreko-oss/sdk
					// Handles both directory-based (signals/index.js) and file-based (id-generator.js)
					build.onResolve({ filter: /^@vreko(-oss)?\// }, (args) => {
						const fs = require("node:fs");
						const parts = args.path.split("/");
						// parts[0] = @vreko or @vreko-oss, parts[1] = package name, parts[2+] = subpath
						if (parts.length < 2) {
							return undefined;
						}

						const pkgName = `${parts[0]}/${parts[1]}`; // e.g., @vreko/engine
						const basePkg = packageMappings[pkgName];

						if (!basePkg) {
							return undefined;
						}

						// If no subpath, return the base package
						if (parts.length === 2) {
							return { path: basePkg };
						}

						// Handle subpath like @vreko/engine/signals or @vreko/contracts/id-generator
						const subpath = parts.slice(2).join("/");
						const baseDir = path.dirname(basePkg);

						// Try as directory with index.js first
						const indexPath = path.resolve(baseDir, `${subpath}/index.js`);
						if (fs.existsSync(indexPath)) {
							return { path: indexPath };
						}

						// Try as direct .js file
						const directPath = path.resolve(baseDir, `${subpath}.js`);
						if (fs.existsSync(directPath)) {
							return { path: directPath };
						}

						// Fallback: let esbuild handle it
						return undefined;
					});
				},
			},
			// Externalize native modules that contain .node binaries
			{
				name: "native-module-externals",
				setup(build) {
					// Intercept @sentry/* packages (external for shared environment safety)
					build.onResolve({ filter: /^@sentry\// }, (args) => {
						return { path: args.path, external: true };
					});

					// Intercept @xenova/transformers (has sharp as dependency with native .node files)
					build.onResolve({ filter: /^@xenova\/transformers/ }, (args) => {
						return { path: args.path, external: true };
					});

					// Intercept sharp directly (native .node binaries)
					build.onResolve({ filter: /^sharp/ }, (args) => {
						return { path: args.path, external: true };
					});

					// Intercept better-sqlite3 (native module)
					build.onResolve({ filter: /^better-sqlite3/ }, (args) => {
						return { path: args.path, external: true };
					});

					// Intercept all .node files as a fallback safety net
					build.onResolve({ filter: /\.node$/ }, (args) => {
						return { path: args.path, external: true };
					});
				},
			},
			// esbuild-visualizer for bundle analysis
			{
				name: "visualizer",
				setup(build) {
					build.onEnd(async (result) => {
						if (production && result.metafile) {
							try {
								const html = await visualizer(result.metafile, {
									title: "SnapBack VSCode Bundle Analysis",
									template: "treemap",
								});
								fs.writeFileSync("dist/bundle-analysis.html", html);
							} catch {
								/* intentionally empty */
							}
						}
					});
				},
			},
			// Handle problematic dependencies
			{
				name: "native-module-handler",
				setup(build) {
					// NOTE: better-sqlite3 and bindings are no longer used
					// (Extension now uses file-based storage instead of SQLite)

					// Handle piscina (worker thread pool, not used in extension)
					build.onResolve({ filter: /^piscina$/ }, (args) => {
						return {
							path: args.path,
							namespace: "worker-stub",
						};
					});

					// Provide stub for worker thread dependencies
					build.onLoad({ filter: /.*/, namespace: "worker-stub" }, () => {
						return {
							contents: "module.exports = {}",
							loader: "js",
						};
					});

					// Handle import.meta.url polyfill for packages that use ESM patterns
					// These packages use import.meta.url which becomes undefined in CommonJS bundles
					// Affects: @snapback/engine, @snapback/infrastructure, and any ESM-first packages
					build.onLoad(
						{
							filter: /(?:packages|\/packages)\/(engine|infrastructure)\/dist\/.*\.js$/,
						},
						async (args) => {
							const contents = await fs.promises.readFile(args.path, "utf8");
							// Replace all variants of fileURLToPath(import.meta.url) with safe fallback
							// This prevents "Received undefined" errors during extension loading
							// Handles: __filename, __filename$1, __filename$2, etc. (minified variants)
							const transformed = contents
								.replace(
									/var (__filename\$\d+|__filename) = fileURLToPath\(import\.meta\.url\);/g,
									"var $1 = process.cwd();",
								)
								.replace(
									/const (__filename\$\d+|__filename) = fileURLToPath\(import\.meta\.url\);/g,
									"const $1 = process.cwd();",
								)
								.replace(
									/const __dirname = fileURLToPath\(new URL\("\.", import\.meta\.url\)\);/g,
									"const __dirname = process.cwd();",
								)
								// Handle direct import.meta.url comparisons (used in CLI entry point detection)
								.replace(/import\.meta\.url\s*===\s*`file:/g, "false && `file:")
								.replace(/import\.meta\.url\s*===\s*new URL/g, "false && new URL");
							return { contents: transformed, loader: "js" };
						},
					);
				},
			},
			// IP Protection: Obfuscation removed - architectural protection is server-side
		],
	});

	// Build language server (heavy packages)
	const serverCtx = await esbuild.context({
		entryPoints: ["./server/index.ts"],
		bundle: true,
		format: "cjs",
		platform: "node",
		target: "node20",
		outfile: "dist/server/index.js",

		// External - keep native modules external
		external: [
			"@vreko/intelligence", // Proprietary AI/ML logic  -  not bundled
			"@vreko/intelligence/*", // Subpath imports like /vitals
			"onnxruntime-node",
			"onnxruntime-common",
			"onnxruntime-web",
			"@huggingface/transformers",
			"@xenova/transformers", // Has sharp dependency with native .node files
			"sharp", // Native .node binaries
			"better-sqlite3", // Native module
			"sql.js",
			"@sentry/node", // Error tracking - external for shared environment safety
			"@sentry/core",
			"@sentry/types",
			"@sentry/profiling-node", // Contains native .node binaries - not usable in VSCode extension
			"@sentry-internal/node-cpu-profiler",
			// Optional template engines from @vue/compiler-sfc's consolidate.js
			"velocityjs",
			"dustjs-linkedin",
			"atpl",
			"liquor",
			"twig",
			"ejs",
			"eco",
			"jazz",
			"jqtpl",
			"hamljs",
			"hamlet",
			"whiskers",
			"haml-coffee",
			"hogan.js",
			"templayed",
			"handlebars",
			"underscore",
			"lodash",
			"walrus",
			"mustache",
			"just",
			"ect",
			"mote",
			"toffee",
			"dot",
			"bracket-template",
			"ractive",
			"nunjucks",
			"htmling",
			"babel-core",
			"plates",
			"react-dom/server",
			"react",
			"arc-templates",
			"vash",
			"slm",
			"marko",
			"teacup/lib/express",
			"teacup",
			"coffee-script",
			"squirrelly",
			"twing",
		],

		// Plugins - same native module handling as extension
		plugins: [
			{
				name: "server-workspace-externals",
				setup(build) {
					// Intercept @xenova/transformers (has sharp as dependency with native .node files)
					build.onResolve({ filter: /^@xenova\/transformers/ }, (args) => {
						return {
							path: args.path,
							external: true,
						};
					});

					// Intercept sharp directly (native .node binaries)
					build.onResolve({ filter: /^sharp/ }, (args) => {
						return {
							path: args.path,
							external: true,
						};
					});

					// Intercept better-sqlite3 (native module)
					build.onResolve({ filter: /^better-sqlite3/ }, (args) => {
						return {
							path: args.path,
							external: true,
						};
					});

					// Intercept all .node files as a fallback safety net
					build.onResolve({ filter: /\.node$/ }, (args) => {
						return {
							path: args.path,
							external: true,
						};
					});
				},
			},
		],

		// Minification (production only)
		minify: production,
		minifyWhitespace: production,
		minifyIdentifiers: false,
		minifySyntax: production,

		// Tree-shaking
		treeShaking: true,

		// Source maps (dev only)
		sourcemap: !production,

		// Logging
		logLevel: "info",

		// Environment
		define: {
			"process.env.NODE_ENV": production ? '"production"' : '"development"',
		},
	});

	if (watch) {
		await extensionCtx.watch();
		await serverCtx.watch();
	} else {
		await extensionCtx.rebuild();
		await serverCtx.rebuild();
		await extensionCtx.dispose();
		await serverCtx.dispose();

		// Log bundle sizes
		const _extensionStats = fs.statSync("./dist/extension.js");
		const _serverStats = fs.statSync("./dist/server/index.js");
	}
}

main().catch((_e) => {
	process.exit(1);
});
