import { execFileSync } from "node:child_process";
import { existsSync, readdirSync, readFileSync } from "node:fs";
import { join, resolve } from "node:path";

const repoRoot = resolve(import.meta.dirname, "..");
const sfRoot = join(repoRoot, "src", "resources", "extensions", "sf");
const extensionsRoot = join(repoRoot, "src", "resources", "extensions");
const manifestPath = join(sfRoot, "extension-manifest.json");

const RESOURCE_SOURCE_RE = /\.(?:js|mjs|cjs|json|md|yaml|yml|d\.ts)$/;
const DYNAMIC_TOOL_NAMES = [
	"AstGrep",
	"Bash",
	"Edit",
	"Glob",
	"Grep",
	"InsertAroundSymbol",
	"LS",
	"Read",
	"ReplaceSymbol",
	"Write",
];
const BASE_DIRECT_COMMAND_NAMES = ["kill", "wt"];
const BASE_RUNTIME_COMMAND_NAMES = new Set([
	"settings",
	"model",
	"scoped-models",
	"export",
	"share",
	"copy",
	"name",
	"session",
	"changelog",
	"hotkeys",
	"fork",
	"tree",
	"provider",
	"login",
	"logout",
	"new",
	"compact",
	"resume",
	"reload",
	"thinking",
	"edit-mode",
	"terminal",
	"stop",
	"exit",
	"quit",
]);
const HIDDEN_OR_ALIAS_SUBCOMMANDS = new Set([
	"?",
	"agent", // internal persistent-agent diagnostics, not part of the product command catalog
	"auto",
	"footer-config", // alias for /statusline
	"h",
	"stop", // platform-intercepted via BASE_RUNTIME_COMMANDS — never reaches SF handler
	"undo-turn", // alias for /rewind
	"wt",
]);

function _rel(path) {
	return path.replace(`${repoRoot}/`, "");
}

function read(path) {
	return readFileSync(path, "utf8");
}

function readJsonOrNull(path) {
	try {
		return JSON.parse(read(path));
	} catch {
		return null;
	}
}

function uniqueSorted(values) {
	return [...new Set(values)].sort((a, b) => a.localeCompare(b));
}

function failSection(title, values) {
	return [`${title}:`, ...values.map((value) => `  - ${value}`)].join("\n");
}

function ignoredResourceSources() {
	const output = execFileSync(
		"git",
		[
			"ls-files",
			"-o",
			"-i",
			"--exclude-standard",
			"src/resources/extensions/**",
		],
		{ cwd: repoRoot, encoding: "utf8" },
	);
	return output
		.split(/\r?\n/)
		.filter(Boolean)
		.filter((path) => RESOURCE_SOURCE_RE.test(path));
}

function untrackedResourceSources() {
	const output = execFileSync(
		"git",
		["ls-files", "-o", "--exclude-standard", "src/resources/extensions/**"],
		{ cwd: repoRoot, encoding: "utf8" },
	);
	return output
		.split(/\r?\n/)
		.filter(Boolean)
		.filter((path) => RESOURCE_SOURCE_RE.test(path));
}

function isLoadableExtensionDir(dirPath) {
	const packageJsonPath = join(dirPath, "package.json");
	if (existsSync(packageJsonPath)) {
		const pkg = readJsonOrNull(packageJsonPath);
		if (pkg?.pi && typeof pkg.pi === "object") {
			return Array.isArray(pkg.pi.extensions) && pkg.pi.extensions.length > 0;
		}
	}
	return (
		existsSync(join(dirPath, "index.js")) ||
		existsSync(join(dirPath, "index.ts"))
	);
}

function manifestlessLoadableExtensions() {
	return readdirSync(extensionsRoot, { withFileTypes: true })
		.filter((entry) => entry.isDirectory())
		.map((entry) => entry.name)
		.filter((name) => {
			const dirPath = join(extensionsRoot, name);
			return (
				isLoadableExtensionDir(dirPath) &&
				!existsSync(join(dirPath, "extension-manifest.json"))
			);
		})
		.sort((a, b) => a.localeCompare(b));
}

function parseManifest() {
	const raw = JSON.parse(read(manifestPath));
	return {
		tools: uniqueSorted(raw?.provides?.tools ?? []),
		commands: uniqueSorted(raw?.provides?.commands ?? []),
	};
}

function parseRegisteredTools() {
	const files = [
		"bootstrap/db-tools.js",
		"bootstrap/dynamic-tools.js",
		"bootstrap/exec-tools.js",
		"bootstrap/journal-tools.js",
		"bootstrap/judgment-tools.js",
		"bootstrap/memory-tools.js",
		"bootstrap/product-audit-tool.js",
		"bootstrap/query-tools.js",
		"subagent/index.js",
		"tools/sift-search-tool.js",
	];
	const names = new Set(DYNAMIC_TOOL_NAMES);
	for (const file of files) {
		const source = read(join(sfRoot, file));
		for (const match of source.matchAll(/\bname:\s*["`]([^"`]+)["`]/g)) {
			names.add(match[1]);
		}
	}
	return uniqueSorted(names);
}

function parseTopLevelCatalogCommands() {
	const source = read(join(sfRoot, "commands", "catalog.js"));
	const start = source.indexOf("export const TOP_LEVEL_SUBCOMMANDS");
	const end = source.indexOf("const NESTED_COMPLETIONS");
	if (start === -1 || end === -1 || end <= start) {
		throw new Error(
			"Could not locate TOP_LEVEL_SUBCOMMANDS in commands/catalog.js",
		);
	}
	return uniqueSorted(
		[...source.slice(start, end).matchAll(/\bcmd:\s*"([^"]+)"/g)].map(
			(match) => match[1],
		),
	);
}

function parsePublicDirectCommands() {
	const source = read(join(sfRoot, "commands", "catalog.js"));
	const start = source.indexOf("export const PUBLIC_DIRECT_COMMANDS");
	const end = source.indexOf("export const PUBLIC_TOP_LEVEL_SUBCOMMANDS");
	if (start === -1 || end === -1 || end <= start) {
		throw new Error(
			"Could not locate PUBLIC_DIRECT_COMMANDS in commands/catalog.js",
		);
	}
	return new Set(
		[...source.slice(start, end).matchAll(/"([^"]+)"/g)].map(
			(match) => match[1],
		),
	);
}

function parseHandledTopLevelCommands() {
	const handlerFiles = [
		"core.js",
		"autonomous.js",
		"parallel.js",
		"workflow.js",
		"ops.js",
	];
	const commands = new Set();
	for (const file of handlerFiles) {
		const source = read(join(sfRoot, "commands", "handlers", file));
		for (const match of source.matchAll(/trimmed\s*(?:===|!==)\s*"([^"]+)"/g)) {
			commands.add(match[1].trim().split(/\s+/)[0]);
		}
		for (const match of source.matchAll(/trimmed\.startsWith\(\s*"([^"]+)"/g)) {
			commands.add(match[1].trim().split(/\s+/)[0]);
		}
	}
	return uniqueSorted(commands);
}

function parseDirectRegisteredCommands() {
	const files = [
		"commands/legacy/audit.js",
		"commands/legacy/create-extension.js",
		"commands/legacy/create-slash-command.js",
		"guards/inturn.js",
		"notifications/notify.js",
		"permissions/index.js",
		"subagent/index.js",
		"ui/color-band.js",
		"ui/emoji.js",
		"ui/usage-bar.js",
	];
	const commands = new Set();
	for (const file of files) {
		const source = read(join(sfRoot, file));
		for (const match of source.matchAll(/pi\.registerCommand\(\s*"([^"]+)"/g)) {
			commands.add(match[1]);
		}
	}
	return uniqueSorted(commands);
}

function main() {
	const failures = [];

	const ignoredSources = ignoredResourceSources();
	if (ignoredSources.length > 0) {
		failures.push(
			failSection(
				`Runtime extension source files are hidden by .gitignore (${ignoredSources.length})`,
				ignoredSources
					.slice(0, 40)
					.concat(
						ignoredSources.length > 40
							? [`... ${ignoredSources.length - 40} more`]
							: [],
					),
			),
		);
	}

	const untrackedSources = untrackedResourceSources();
	if (untrackedSources.length > 0) {
		failures.push(
			failSection(
				`Runtime extension source files are visible but untracked (${untrackedSources.length})`,
				untrackedSources
					.slice(0, 40)
					.concat(
						untrackedSources.length > 40
							? [`... ${untrackedSources.length - 40} more`]
							: [],
					),
			),
		);
	}

	const manifestlessExtensions = manifestlessLoadableExtensions();
	if (manifestlessExtensions.length > 0) {
		failures.push(
			failSection(
				`Loadable bundled extensions missing extension-manifest.json (${manifestlessExtensions.length})`,
				manifestlessExtensions,
			),
		);
	}

	const manifest = parseManifest();
	const registeredTools = parseRegisteredTools();
	const catalogCommands = parseTopLevelCatalogCommands();
	const publicDirectCommands = parsePublicDirectCommands();
	const directCommandNames = uniqueSorted(
		BASE_DIRECT_COMMAND_NAMES.concat(
			catalogCommands.filter(
				(command) =>
					publicDirectCommands.has(command) &&
					!BASE_RUNTIME_COMMAND_NAMES.has(command),
			),
			parseDirectRegisteredCommands().filter(
				(command) => !BASE_RUNTIME_COMMAND_NAMES.has(command),
			),
		),
	);
	const missingManifestTools = registeredTools.filter(
		(tool) => !manifest.tools.includes(tool),
	);
	const staleManifestTools = manifest.tools.filter(
		(tool) => !registeredTools.includes(tool),
	);
	if (missingManifestTools.length > 0) {
		failures.push(
			failSection(
				"Registered tools missing from extension-manifest.json",
				missingManifestTools,
			),
		);
	}
	if (staleManifestTools.length > 0) {
		failures.push(
			failSection(
				"Manifest tools not registered by SF bootstrap",
				staleManifestTools,
			),
		);
	}

	const missingManifestCommands = directCommandNames.filter(
		(command) => !manifest.commands.includes(command),
	);
	const staleManifestCommands = manifest.commands.filter(
		(command) => !directCommandNames.includes(command),
	);
	if (missingManifestCommands.length > 0) {
		failures.push(
			failSection(
				"Direct commands missing from extension-manifest.json",
				missingManifestCommands,
			),
		);
	}
	if (staleManifestCommands.length > 0) {
		failures.push(
			failSection(
				"Manifest direct commands not registered by SF bootstrap",
				staleManifestCommands,
			),
		);
	}

	const handledCommands = parseHandledTopLevelCommands().filter(
		(command) => !HIDDEN_OR_ALIAS_SUBCOMMANDS.has(command),
	);
	const missingCatalogCommands = handledCommands.filter(
		(command) => !catalogCommands.includes(command),
	);
	const unroutedCatalogCommands = catalogCommands.filter(
		(command) =>
			command !== "help" &&
			!HIDDEN_OR_ALIAS_SUBCOMMANDS.has(command) &&
			!handledCommands.includes(command),
	);
	if (missingCatalogCommands.length > 0) {
		failures.push(
			failSection(
				"Handled SF commands missing from TOP_LEVEL_SUBCOMMANDS",
				missingCatalogCommands,
			),
		);
	}
	if (unroutedCatalogCommands.length > 0) {
		failures.push(
			failSection(
				"Catalog SF commands with no routed handler",
				unroutedCatalogCommands,
			),
		);
	}

	if (failures.length > 0) {
		console.error(failures.join("\n\n"));
		process.exit(1);
	}

	console.log(
		`SF extension inventory OK: ${registeredTools.length} tools, ${directCommandNames.length} direct commands, ${catalogCommands.length} catalog commands.`,
	);
}

main();
