// cSpell:ignore yesno

/**
 * @file Makes sure that node dependencies are installed
 */

// @ts-ignore
import jetpack from "fs-jetpack";
import {ask} from "./yesno.js";
import {execSync} from "child_process";

// We need to load package.json to detect kinds of dependencies
// Don't want to install a dev dependency as a normal dependency and vise versa
/** @type {object} */
const packageContents = jetpack.read("package.json", "json");
/** @type {object} */
const devDependencies = packageContents.devDependencies;
/** @type {object} */
const dependencies = packageContents.dependencies;

// make sure settings.json exists and has all the required properties
execSync("node devTools/scripts/setup.js --settings");

// load settings.json
/** @type {import("./setup.js").Settings} */
const settings = jetpack.read("settings.json", "json");

function installGitHooks() {
	execSync("node devTools/scripts/gitHooks/addHooks.js");
}

/**
 * Adds any packages with the wrong version to problems
 * @param {string} npmList results of `npm ls` command
 * @param {string[]} problems
 */
async function parseWrongVersion(npmList, problems) {
	npmList.split("\n").forEach(line => {
		// npm ERR! invalid: eslint@7.0.0 C:\Users\dev\Documents\Projects\fc pregmod\node_modules\eslint
		if (line.trim().startsWith("npm ERR! invalid: ")) {
			line = line.replace("npm ERR! invalid: ", "");
			let nodePackage = line.match(/^[^ ]*/)[0];
			problems.push(nodePackage.split("@")[0]);
		}
	});
	return problems;
}

/**
 * Adds any missing packages to problems
 * @param {string} npmList results of `npm ls` command
 * @param {string[]} problems
 */
async function parseMissing(npmList, problems) {
	npmList.split("\n").forEach(line => {
		// npm ERR! missing: eslint@^8.0.0, required by free-cities@1.0.0
		if (line.trim().startsWith("npm ERR! missing: ")) {
			line = line.replace("npm ERR! missing: ", "");
			let nodePackage = line.match(/^[^,]*/)[0];
			problems.push(nodePackage.split("@")[0]);
		}
	});
	return problems;
}

/**
 * Checks for missing and outdated node packages.
 */
async function main() {
	let npmList = "";
	try {
		npmList = execSync('npm ls', {maxBuffer: 1024 * 1024 * 1024, stdio: ['pipe']}, ).toString();
	} catch (e) {
		e.output.forEach(out => {
			if (out === null) { return; }
			npmList += "\n" + out.toString();
		});
	}

	/** @type {string[]} */
	let problems = [];

	if (npmList.includes("npm ERR! invalid: ")) {
		problems = await parseWrongVersion(npmList, problems);
	}

	if (npmList.includes("npm ERR! missing: ")) {
		problems = await parseMissing(npmList, problems);
	}

	// remove empty strings from problems
	problems = problems.filter(problem => problem.trim() !== "");

	let devDependencyCommand = "npm install --save-dev";
	let dependencyCommand = "npm install --save";

	problems.forEach(problem => {
		if (problem.trim().length === 0) { return; }
		let matched = false;
		for (let [key, value] of Object.entries(devDependencies)) {
			if (matched === true) { continue; }
			if (key === problem) {
				matched = true;
				devDependencyCommand += ` ${key}@${value}`;
			}
		}
		if (matched === true) { return; }
		for (let [key, value] of Object.entries(dependencies)) {
			if (matched === true) { continue; }
			if (key === problem) {
				matched = true;
				dependencyCommand += ` ${key}@${value}`;
			}
		}
		if (matched === false) {
			console.log(`Unknown dependency type for ${problem}`);
		}
	});

	if (problems.length === 0) {
		installGitHooks();
		return;
	}

	console.log("The Node packages below are missing or are the wrong version.");
	console.log("");
	problems.forEach(problem => console.log(problem));
	console.log("");
	console.log("The command(s) that need to be run to fix this problem are:");
	console.log("");
	if (devDependencyCommand !== "npm install --save-dev") {
		console.log(devDependencyCommand);
	}
	if (dependencyCommand !== "npm install --save") {
		console.log(dependencyCommand);
	}
	console.log("");
	if (settings.manageNodePackages === 0) {
		// @ts-ignore
		const answer = await ask({
			question: "Would you like us to run the above command(s) for you? [Y/N]",
		});
		if (answer === true) {
			if (devDependencyCommand !== "npm install --save-dev") {
				execSync(devDependencyCommand, {stdio:[0, 1, 2]});
			}
			if (dependencyCommand !== "npm install --save") {
				execSync(dependencyCommand, {stdio:[0, 1, 2]});
			}
			const settingsScript = (process.platform === "win32") ? "setup.bat" : "setup.sh";
			console.log(`If you wish not to be asked about Node packages again, run '${settingsScript}' and change option #1 in "Edit Miscellaneous Settings"`);
			installGitHooks();
		} else {
			console.log("If you wish not to be asked about Node packages again, change 'manageNodePackages' in settings.json to -1 (no) or 1 (yes).");
		}
	} else if (settings.manageNodePackages === 1) {
		if (devDependencyCommand !== "npm install --save-dev") {
			execSync(devDependencyCommand, {stdio:[0, 1, 2]});
		}
		if (dependencyCommand !== "npm install --save") {
			execSync(dependencyCommand, {stdio:[0, 1, 2]});
		}
		installGitHooks();
	}
}

// @ts-ignore
await main();