import {readFile, writeFile} from "fs/promises";
import {basename, dirname, extname, join} from "path";
import {
	disassembleBytecode,
	fromFullAstToSourceCode,
	fromMiniAstToBinary,
	fromSourceCodeToBinary,
	fromSourceCodeToFullAst,
	fromSourceCodeToMiniAst,
	miniAstFromBinaryBuffer
} from "@/services";
import type {ByteCodeJson} from "@/types/byteCode/ByteCodeJson";
import {version} from "../package.json";
import {readFileSync} from "fs";
import {writeFileSync} from "node:fs";

export async function toAst(inputPath: string, outputPath?: string) {
	try {
		const sourceCode = await readFile(inputPath, "utf-8");
		const ast = fromSourceCodeToFullAst(sourceCode, {filePath: inputPath, includeMetaData: true});

		if (outputPath) {
			await writeFile(outputPath, JSON.stringify(ast, null, 2));
			console.log(`[BattleScript ${version}] Compiled to ${outputPath}`);
		} else {
			return ast;
		}
	} catch (error) {
		console.error(`[BattleScript ${version}] Error:`, (error as Error).stack);
		process.exit(1);
	}
}

export function toAstSync(inputPath: string, outputPath?: string) {
	try {
		const sourceCode = readFileSync(inputPath, "utf-8");
		const ast = fromSourceCodeToFullAst(sourceCode, {filePath: inputPath, includeMetaData: true});

		if (outputPath) {
			writeFileSync(outputPath, JSON.stringify(ast, null, 2));
			console.log(`[BattleScript ${version}] Compiled to ${outputPath}`);
		} else {
			return ast;
		}
	} catch (error) {
		console.error(`[BattleScript ${version}] Error:`, (error as Error).stack);
		process.exit(1);
	}
}

export async function fromAst(inputPath: string, outputPath: string) {
	try {
		const astString = await readFile(inputPath, "utf-8");
		const ast = JSON.parse(astString);
		const code = fromFullAstToSourceCode(ast);
		await writeFile(outputPath, code);
		console.log(`[BattleScript ${version}] Decompiled to ${outputPath}`);
	} catch (error) {
		console.error(`[BattleScript ${version}] Error:`, (error as Error).stack);
		process.exit(1);
	}
}

export async function toMiniAst(inputPath: string, outputPath: string) {
	try {
		const sourceCode = await readFile(inputPath, "utf-8");
		const ast = fromSourceCodeToMiniAst(sourceCode, {filePath: inputPath, includeMetaData: true});

		await writeFile(outputPath, JSON.stringify(ast));
		console.log(`[BattleScript ${version}] Compiled to ${outputPath}`);
	} catch (error) {
		console.error(`[BattleScript ${version}] Error:`, (error as Error).stack);
		process.exit(1);
	}
}

export async function toBinary(inputPath: string, outputPath: string) {
	try {
		const sourceCode = await readFile(inputPath, "utf-8");
		const binary = fromSourceCodeToBinary(sourceCode, {includeMetaData: true, flatten: true, filePath: inputPath});
		await writeFile(outputPath, Buffer.concat(binary))

		console.log(`[BattleScript ${version}] Compiled to ${outputPath}`);
	} catch (error) {
		console.error(`[BattleScript ${version}] Error:`, (error as Error).stack);
		process.exit(1);
	}
}

export async function miniAstToBinary(inputPath: string, outputPath: string) {
	try {
		const sourceCode = await readFile(inputPath, "utf-8");
		const binary = fromMiniAstToBinary(JSON.parse(sourceCode), {
			includeMetaData: true,
			flatten: true,
			filePath: inputPath
		});
		await writeFile(outputPath, Buffer.concat(binary))

		console.log(`[BattleScript ${version}] Compiled to ${outputPath}`);
	} catch (error) {
		console.error(`[BattleScript ${version}] Error:`, (error as Error).stack);
		process.exit(1);
	}
}

export async function fromBinary(inputPath: string, outputPath: string) {
	try {
		const binaryBuffer = await readFile(inputPath);
		const miniAstJson = miniAstFromBinaryBuffer(binaryBuffer);
		await writeFile(outputPath, JSON.stringify(miniAstJson));
		console.log(`[BattleScript ${version}] Disassembled to ${outputPath}`);
	} catch (error) {
		console.error(`[BattleScript ${version}] Error:`, (error as Error).stack);
		process.exit(1);
	}
}

// async function toByteCode(inputPath: string, outputPath: string) {
// 	try {
// 		const sourceCode = await readFile(inputPath, "utf-8");
// 		const bcodeJson = fromSourceCodeToByteCode(sourceCode, {includeMetaData: true, flatten: true});
// 		await writeFile(outputPath, JSON.stringify(bcodeJson).replace(/\[\[/g, "[\n[").replace(/]]/g, "]\n]").replace(/],/g, "],\n"));
// 		console.log(`Compiled to ${outputPath}`);
// 	} catch (error) {
// 		console.error("Error:", (error as Error).stack);
// 		process.exit(1);
// 	}
// }

export async function fromByteCode(inputPath: string, outputPath: string) {
	try {
		const byteCodeJsonString = await readFile(inputPath, "utf-8");
		const byteCodeJson: ByteCodeJson = JSON.parse(byteCodeJsonString);
		const code = disassembleBytecode(byteCodeJson);
		await writeFile(outputPath, code.join("\n"));
		console.log(`[BattleScript ${version}] Disassembled to ${outputPath}`);
	} catch (error) {
		console.error(`[BattleScript ${version}] Error:`, (error as Error).stack);
		process.exit(1);
	}
}

export function changeExtension(filePath: string, newExt: string): string {
	const dir = dirname(filePath);
	const base = basename(filePath, extname(filePath));
	return join(dir, `${base}.${newExt}`);
}