// validate-pack.js — Verify the npm tarball is installable before publishing.
//
// Usage: npm run validate-pack (or node scripts/validate-pack.js)
// Exit 0 = safe to publish, Exit 1 = broken package.

import { execFileSync } from "node:child_process";
import {
	existsSync,
	mkdirSync,
	mkdtempSync,
	readFileSync,
	rmSync,
	statSync,
	writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";

const __filename = import.meta.filename;
const __dirname = import.meta.dirname;
const ROOT = resolve(__dirname, "..");

let tarball = null;
let installDir = null;
let npmCacheDir = null;
const DEFAULT_MAX_BUFFER = 50 * 1024 * 1024;

function getNpmCommand() {
	return process.platform === "win32" ? "npm.cmd" : "npm";
}

function runNpm(args, options = {}) {
	return execFileSync(getNpmCommand(), args, {
		cwd: ROOT,
		encoding: "utf8",
		shell: process.platform === "win32",
		stdio: ["pipe", "pipe", "pipe"],
		maxBuffer: DEFAULT_MAX_BUFFER,
		env: {
			...process.env,
			npm_config_cache: npmCacheDir ?? process.env.npm_config_cache,
		},
		...options,
	});
}

function formatBytes(bytes) {
	if (bytes < 1024) return `${bytes} B`;
	if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
	return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}

try {
	npmCacheDir = mkdtempSync(join(tmpdir(), "validate-pack-npm-cache-"));
	mkdirSync(npmCacheDir, { recursive: true });

	// --- Guard: workspace packages must not have @singularity-forge/* cross-deps ---
	console.log(
		"==> Checking workspace packages for @singularity-forge/* cross-deps...",
	);
	const workspaces = ["native", "agent-core", "ai", "coding-agent", "tui"];
	let crossFailed = false;

	for (const ws of workspaces) {
		const pkgPath = join(ROOT, "packages", ws, "package.json");
		if (!existsSync(pkgPath)) continue;
		const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
		const deps = Object.keys(pkg.dependencies || {}).filter((d) =>
			d.startsWith("@singularity-forge/"),
		);
		if (deps.length) {
			console.log(`    LEAKED in ${ws}: ${deps.join(", ")}`);
			crossFailed = true;
		}
	}

	if (crossFailed) {
		console.log(
			"ERROR: Workspace packages have @singularity-forge/* cross-dependencies.",
		);
		console.log(
			"    These cause 404s when npm resolves them from the registry.",
		);
		process.exit(1);
	}
	console.log("    No @singularity-forge/* cross-dependencies.");

	// --- Pack tarball ---
	console.log("==> Packing tarball...");
	const packOutput = runNpm(["pack", "--json", "--ignore-scripts"]);
	const packEntries = JSON.parse(packOutput);
	const packEntry = Array.isArray(packEntries) ? packEntries[0] : null;
	const tarballName = packEntry?.filename;
	tarball = join(ROOT, tarballName);

	if (!existsSync(tarball)) {
		console.log("ERROR: npm pack produced no tarball");
		process.exit(1);
	}

	const stats = statSync(tarball);
	console.log(
		`==> Tarball: ${tarballName} (${formatBytes(stats.size)} compressed)`,
	);

	// --- Check critical files using npm pack metadata ---
	console.log("==> Checking critical files...");
	const packedFiles = new Set(
		Array.isArray(packEntry?.files)
			? packEntry.files.map((entry) => entry?.path).filter(Boolean)
			: [],
	);

	const requiredFiles = [
		"dist/loader.js",
		"packages/coding-agent/dist/index.js",
		"packages/rpc-client/dist/index.js",
		"packages/daemon/dist/cli.js",
		"scripts/link-workspace-packages.cjs",
		"dist/web/standalone/server.js",
	];

	let missing = false;
	for (const required of requiredFiles) {
		if (!packedFiles.has(required)) {
			console.log(`    MISSING: ${required}`);
			missing = true;
		}
	}

	if (missing) {
		console.log("ERROR: Critical files missing from tarball.");
		process.exit(1);
	}
	console.log("    Critical files present.");

	// --- Install test ---
	console.log("==> Testing install in isolated directory...");
	installDir = mkdtempSync(join(tmpdir(), "validate-pack-"));
	writeFileSync(
		join(installDir, "package.json"),
		JSON.stringify(
			{ name: "test-install", version: "1.0.0", private: true },
			null,
			2,
		),
	);

	try {
		const installOutput = execFileSync(getNpmCommand(), ["install", tarball], {
			cwd: installDir,
			encoding: "utf8",
			shell: process.platform === "win32",
			stdio: ["pipe", "pipe", "pipe"],
			maxBuffer: DEFAULT_MAX_BUFFER,
			env: {
				...process.env,
				npm_config_cache: npmCacheDir,
			},
		});
		console.log(installOutput);
		console.log("==> Install succeeded.");
	} catch (err) {
		console.log("");
		console.log("ERROR: npm install of tarball failed.");
		if (err.stdout) console.log(err.stdout);
		if (err.stderr) console.log(err.stderr);
		process.exit(1);
	}

	// --- Verify @singularity-forge/* packages resolved correctly post-install ---
	// This catches the Windows-style failure where symlinkSync fails silently and
	// node_modules/@singularity-forge/ is never populated, causing ERR_MODULE_NOT_FOUND at runtime.
	console.log(
		"==> Verifying @singularity-forge/* workspace package resolution...",
	);
	const installedRoot = join(installDir, "node_modules", "singularity-forge");
	const criticalPackages = [
		{ scope: "@singularity-forge", name: "coding-agent" },
		{ scope: "@singularity-forge", name: "rpc-client" },
		{ scope: "@singularity-forge", name: "daemon" },
	];
	let resolutionFailed = false;
	for (const pkg of criticalPackages) {
		const pkgPath = join(installedRoot, "node_modules", pkg.scope, pkg.name);
		const fallbackPath = join(installedRoot, "packages", pkg.name);
		if (!existsSync(pkgPath)) {
			if (existsSync(fallbackPath)) {
				console.log(
					`    MISSING symlink/copy: node_modules/${pkg.scope}/${pkg.name} (packages/${pkg.name} exists — postinstall may not have run)`,
				);
			} else {
				console.log(
					`    MISSING: node_modules/${pkg.scope}/${pkg.name} (packages/${pkg.name} also absent — package is broken)`,
				);
			}
			resolutionFailed = true;
		}
	}
	if (resolutionFailed) {
		console.log(
			"ERROR: @singularity-forge/* packages are not resolvable after install.",
		);
		console.log(
			"    This will cause ERR_MODULE_NOT_FOUND on first run (especially on Windows).",
		);
		process.exit(1);
	}
	console.log("    @singularity-forge/* packages are resolvable.");

	// --- Run the binary to confirm end-to-end resolution ---
	console.log("==> Running installed binary (sf -v)...");
	const loaderPath = join(installedRoot, "dist", "loader.js");
	const daemonCliPath = join(
		installedRoot,
		"packages",
		"daemon",
		"dist",
		"cli.js",
	);
	if (!existsSync(daemonCliPath)) {
		console.log("ERROR: Bundled daemon CLI missing after install.");
		console.log(`    Expected: ${daemonCliPath}`);
		process.exit(1);
	}
	try {
		const versionOutput = execFileSync(process.execPath, [loaderPath, "-v"], {
			cwd: installDir,
			encoding: "utf8",
			stdio: ["pipe", "pipe", "pipe"],
			timeout: 15000,
			maxBuffer: DEFAULT_MAX_BUFFER,
		}).trim();
		console.log(`    sf -v => ${versionOutput}`);
		if (!versionOutput.match(/^\d+\.\d+\.\d+/)) {
			console.log(
				"ERROR: sf -v returned unexpected output (expected a version string).",
			);
			process.exit(1);
		}
	} catch (err) {
		console.log("ERROR: Running sf -v failed after install.");
		if (err.stdout) console.log(err.stdout);
		if (err.stderr) console.log(err.stderr);
		process.exit(1);
	}

	console.log("==> Running installed daemon binary (sf-server --help)...");
	try {
		const helpOutput = execFileSync(
			process.execPath,
			[daemonCliPath, "--help"],
			{
				cwd: installDir,
				encoding: "utf8",
				stdio: ["pipe", "pipe", "pipe"],
				timeout: 15000,
				maxBuffer: DEFAULT_MAX_BUFFER,
			},
		);
		if (!helpOutput.includes("Usage: sf-server")) {
			console.log("ERROR: sf-server --help returned unexpected output.");
			process.exit(1);
		}
	} catch (err) {
		console.log("ERROR: Running sf-server --help failed after install.");
		if (err.stdout) console.log(err.stdout);
		if (err.stderr) console.log(err.stderr);
		process.exit(1);
	}

	console.log("");
	console.log("Package is installable. Safe to publish.");
	process.exit(0);
} finally {
	if (installDir && existsSync(installDir)) {
		rmSync(installDir, { recursive: true, force: true });
	}
	if (tarball && existsSync(tarball)) {
		rmSync(tarball, { force: true });
	}
	if (npmCacheDir && existsSync(npmCacheDir)) {
		rmSync(npmCacheDir, { recursive: true, force: true });
	}
}
