import * as fs from "node:fs";
import { createRequire } from "node:module";
import * as path from "node:path";

const require = createRequire(import.meta.url);

export function resolvePiPackageRoot(): string | undefined {
	try {
		const entry = process.argv[1];
		if (!entry) return undefined;
		let dir = path.dirname(fs.realpathSync(entry));
		while (dir !== path.dirname(dir)) {
			try {
				const pkg = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf-8"));
				if (pkg.name === "@mariozechner/pi-coding-agent") return dir;
			} catch {}
			dir = path.dirname(dir);
		}
	} catch {}
	return undefined;
}

export interface PiSpawnDeps {
	platform?: NodeJS.Platform;
	execPath?: string;
	argv1?: string;
	existsSync?: (filePath: string) => boolean;
	readFileSync?: (filePath: string, encoding: "utf-8") => string;
	resolvePackageJson?: () => string;
	piPackageRoot?: string;
	piExecutable?: string;
}

interface PiSpawnCommand {
	command: string;
	args: string[];
}

function isRunnableNodeScript(filePath: string, existsSync: (filePath: string) => boolean): boolean {
	if (!existsSync(filePath)) return false;
	return /\.(?:mjs|cjs|js)$/i.test(filePath);
}

function normalizePath(filePath: string): string {
	return path.isAbsolute(filePath) ? filePath : path.resolve(filePath);
}

export function resolvePiCliScript(deps: PiSpawnDeps = {}): string | undefined {
	const existsSync = deps.existsSync ?? fs.existsSync;
	const readFileSync = deps.readFileSync ?? ((filePath, encoding) => fs.readFileSync(filePath, encoding));
	const piExecutable = deps.piExecutable ?? process.env.ORCHESTRA_PI_EXECUTABLE;
	const argv1 = deps.argv1 ?? process.argv[1];

	if (piExecutable) {
		const executablePath = normalizePath(piExecutable);
		if (isRunnableNodeScript(executablePath, existsSync)) {
			return executablePath;
		}
	}

	if (argv1) {
		const argvPath = normalizePath(argv1);
		if (isRunnableNodeScript(argvPath, existsSync)) {
			return argvPath;
		}
	}

	try {
		const resolvePackageJson = deps.resolvePackageJson ?? (() => {
			const root = deps.piPackageRoot ?? resolvePiPackageRoot();
			if (root) return path.join(root, "package.json");
			return require.resolve("@mariozechner/pi-coding-agent/package.json");
		});
		const packageJsonPath = resolvePackageJson();
		const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8")) as {
			bin?: string | Record<string, string>;
		};
		const binField = packageJson.bin;
		const binPath = typeof binField === "string"
			? binField
			: binField?.pi ?? Object.values(binField ?? {})[0];
		if (!binPath) return undefined;
		const candidate = normalizePath(path.resolve(path.dirname(packageJsonPath), binPath));
		if (isRunnableNodeScript(candidate, existsSync)) {
			return candidate;
		}
	} catch {
		return undefined;
	}

	return undefined;
}

export function resolveWindowsPiCliScript(deps: PiSpawnDeps = {}): string | undefined {
	return resolvePiCliScript(deps);
}

export function getPiSpawnCommand(args: string[], deps: PiSpawnDeps = {}): PiSpawnCommand {
	const piExecutable = deps.piExecutable ?? process.env.ORCHESTRA_PI_EXECUTABLE;
	const existsSync = deps.existsSync ?? fs.existsSync;
	if (piExecutable) {
		const executablePath = normalizePath(piExecutable);
		if (isRunnableNodeScript(executablePath, existsSync)) {
			return {
				command: deps.execPath ?? process.execPath,
				args: [executablePath, ...args],
			};
		}
		if (existsSync(executablePath)) {
			return { command: executablePath, args };
		}
	}

	const piCliPath = resolvePiCliScript(deps);
	if (piCliPath) {
		return {
			command: deps.execPath ?? process.execPath,
			args: [piCliPath, ...args],
		};
	}

	return { command: "pi", args };
}
