import { join, extname } from "path";
import * as fs from "fs";
import util from "node:util";
import * as child_process from "node:child_process";
const _exec = util.promisify(child_process.exec);

const dark_colors = {
	"#fc7f7f": "#fc9c9c",
	"#8da5f3": "#a5b7f3",
	"#e0e0e0": "#e0e0e0",
	"#c38ef1": "#cea4f1",
	"#8eef97": "#a5efac",
};
const light_colors = {
	"#fc7f7f": "#ff5f5f",
	"#8da5f3": "#6d90ff",
	"#e0e0e0": "#4f4f4f",
	"#c38ef1": "#bb6dff",
	"#8eef97": "#29d739",
};

function replace_colors(colors: object, data: string) {
	for (const [from, to] of Object.entries(colors)) {
		data = data.replace(from, to);
	}
	return data;
}

const iconsPath = "editor/icons";
const modulesPath = "modules";
const outputPath = "resources/godot_icons";
const godotPath = process.argv[2];

async function exec(command) {
	const { stdout, stderr } = await _exec(command);
	return stdout;
}

const git = {
	diff: "git diff HEAD",
	check_branch: "git rev-parse --abbrev-ref HEAD",
	reset: "git reset --hard",
	stash_push: "git stash push",
	stash_pop: "git stash pop",
	checkout: "git checkout ",
	checkout_4: "git checkout master",
	checkout_3: "git checkout 3.x",
};

function to_title_case(str) {
	return str.replace(
		/\w\S*/g,
		function (txt) {
			return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
		}
	);
}

function get_class_list(modules) {
	const classes: string[] = [
		"ArrowDown.svg",
		"ArrowLeft.svg",
		"ArrowRight.svg",
		"ArrowUp.svg",
		"GuiVisibilityHidden.svg",
		"GuiVisibilityVisible.svg",
		"GuiVisibilityXray.svg",
		"Edit.svg",
		"Help.svg",
		"HelpSearch.svg",
		"ImportCheck.svg",
		"ImportFail.svg",
		"Info.svg",
		"Play.svg",
		"PlayBackwards.svg",
		"PlayCustom.svg",
		"PlayRemote.svg",
		"PlayScene.svg",
		"PlayStart.svg",
		"Progress1.svg",
		"Progress2.svg",
		"Progress3.svg",
		"Progress4.svg",
		"Progress5.svg",
		"Progress6.svg",
		"Progress7.svg",
		"Progress8.svg",
		"Progress9.svg",
		"Reload.svg",
		"ReloadSmall.svg",
		"Script.svg",
		"ScriptCreate.svg",
		"ScriptRemove.svg",
		"Search.svg",
		"Signals.svg",
		"SignalsAndGroups.svg",
		"Slot.svg",
		"Stop.svg",
		"Lock.svg",
		"Unlock.svg",
		"Zoom.svg",
		"ZoomLess.svg",
		"ZoomMore.svg",
		"ZoomReset.svg",
	];

	const files = ["scene/register_scene_types.cpp"];
	modules.forEach(mod => {
		files.push(join(mod, "register_types.cpp"));
	});

	const patterns = [
		/GDREGISTER_CLASS\((\w*)\)/,
		/register_class<(\w*)>/,
	];

	files.forEach(fileName => {
		const file = fs.readFileSync(fileName, "utf8");
		file.split("\n").forEach(line => {
			patterns.forEach(pattern => {
				const match = line.match(pattern);
				if (match) {
					classes.push(match[1] + ".svg");
				}
			});
		});
	});


	return classes;
}

function discover_modules() {
	const modules: string[] = [];

	// a valid module is a subdir of modulesPath, and contains a subdir 'icons'
	fs.readdirSync(modulesPath, { withFileTypes: true }).forEach(mod => {
		if (mod.isDirectory()) {
			fs.readdirSync(join(modulesPath, mod.name), { withFileTypes: true }).forEach(child => {
				if (child.isDirectory() && child.name == "icons") {
					modules.push(join(modulesPath, mod.name));
				}
			});
		}
	});
	return modules;
}

interface IconData {
	name: string;
	contents: string;
}


function get_icons(): IconData[] {
	const modules = discover_modules();
	const classes = get_class_list(modules);

	const searchPaths = [iconsPath];
	modules.forEach(mod => {
		searchPaths.push(join(mod, "icons"));
	});

	const icons: IconData[] = [];
	searchPaths.forEach(searchPath => {
		fs.readdirSync(searchPath).forEach(file => {
			if (extname(file) === ".svg") {
				let name = file;
				if (name.startsWith("icon_")) {
					name = name.replace("icon_", "");
					let parts = name.split("_");
					parts = parts.map(to_title_case);
					name = parts.join("");
				}
				if (!classes.includes(name)) {
					return;
				}
				const f = {
					name: name,
					contents: fs.readFileSync(join(searchPath, file), "utf8")
				};
				icons.push(f);
			}
		});
	});

	return icons;
}

function ensure_paths() {
	const paths = [
		outputPath,
		join(outputPath, "light"),
		join(outputPath, "dark"),
	];

	paths.forEach(path => {
		if (!fs.existsSync(path)) {
			fs.mkdirSync(path);
		}
	});
}

async function run() {
	if (godotPath == undefined) {
		console.log("Please provide the absolute path to your godot repo");
		return;
	}

	const original_cwd = process.cwd();

	process.chdir(godotPath);

	const diff = (await exec(git.diff)).trim();
	if (diff) {
		console.log("There appear to be uncommitted changes in your godot repo");
		console.log("Revert or stash these changes and try again");
		return;
	}

	const branch = (await exec(git.check_branch)).trim();

	console.log("Gathering Godot 3 icons...");
	await exec(git.checkout_3);
	const g3 = get_icons();

	console.log("Gathering Godot 4 icons...");
	await exec(git.checkout_4);
	const g4 = get_icons();

	await exec(git.checkout + branch);

	process.chdir(original_cwd);

	console.log(`Found ${g3.length + g4.length} icons...`);

	const light_icons: Map<string, string> = new Map();
	const dark_icons: Map<string, string> = new Map();

	console.log("Generating themed icons...");
	g3.forEach(file => {
		light_icons[file.name] = replace_colors(light_colors, file.contents);
	});
	g4.forEach(file => {
		light_icons[file.name] = replace_colors(light_colors, file.contents);
	});
	g3.forEach(file => {
		dark_icons[file.name] = replace_colors(dark_colors, file.contents);
	});
	g4.forEach(file => {
		dark_icons[file.name] = replace_colors(dark_colors, file.contents);
	});

	console.log("Ensuring output directory...");
	ensure_paths();

	console.log("Writing icons to output directory...");
	for (const [file, contents] of Object.entries(light_icons)) {
		fs.writeFileSync(join(outputPath, "light", file), contents);
	}
	for (const [file, contents] of Object.entries(dark_icons)) {
		fs.writeFileSync(join(outputPath, "dark", file), contents);
	}
}

run();
