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

const sourceResolverAsync: SourceResolverAsync = (filePath: string) => {
	return readFile(filePath, "utf-8")
};

const sourceResolverSync: SourceResolverSync = (filePath: string): string => {
	return readFileSync(filePath, "utf-8")
};

export async function toAst(inputPath: string, outputPath?: string) {
	console.log(`[BattleScript ${version}] toAst: ${inputPath}`);
	try {
		const sourceCode = await sourceResolverAsync(inputPath);
		const ast = fromSourceCodeToFullAst(sourceCode, {
			filePath: inputPath,
			includeMetaData: true,
			sourceResolver: sourceResolverSync
		});

		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) {
	console.log(`[BattleScript ${version}] toAstSync: ${inputPath}`);
	try {
		const sourceCode = sourceResolverSync(inputPath);
		const ast = fromSourceCodeToFullAst(sourceCode, {
			filePath: inputPath,
			includeMetaData: true,
			sourceResolver: sourceResolverSync
		});

		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) {
	console.log(`[BattleScript ${version}] fromAst: ${inputPath}`);
	try {
		const astString = await sourceResolverAsync(inputPath);
		const ast = JSON.parse(astString);
		const code = fromFullAstToSourceCode(ast, {
			filePath: inputPath,
			sourceResolver: sourceResolverSync
		});
		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) {
	console.log(`[BattleScript ${version}] toMiniAst: ${inputPath}`);
	try {
		const sourceCode = await sourceResolverAsync(inputPath);
		const ast = fromSourceCodeToMiniAst(sourceCode, {
			filePath: inputPath,
			includeMetaData: true,
			sourceResolver: sourceResolverSync
		});

		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 fromMiniAst(inputPath: string, outputPath: string) {
	console.log(`[BattleScript ${version}] fromMiniAst: ${inputPath}`);
	try {
		const miniAstString = await sourceResolverAsync(inputPath);
		const miniAst = JSON.parse(miniAstString);
		const sourceCode = fromMiniAstToSourceCode(miniAst);
		await writeFile(outputPath, sourceCode);
		console.log(`[BattleScript ${version}] Decompiled to ${outputPath}`);
	} catch (error) {
		console.error(`[BattleScript ${version}] Error:`, (error as Error).stack);
		process.exit(1);
	}
}

export async function toBinary(inputPath: string, outputPath: string) {
	console.log(`[BattleScript ${version}] toBinary: ${inputPath}`);
	try {
		console.log(`[BattleScript ${version}] Reading file contents: ${inputPath}`);
		const sourceCode = await sourceResolverAsync(inputPath);

		console.log(`[BattleScript ${version}] fromSourceCodeToBinary: ${inputPath}`);
		const binary = fromSourceCodeToBinary(sourceCode, {
			includeMetaData: true,
			flatten: true,
			filePath: inputPath,
			sourceResolver: sourceResolverSync
		});

		console.log(`[BattleScript ${version}] Writing file contents: ${outputPath}`);
		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 sourceResolverAsync(inputPath);
		const binary = fromMiniAstToBinary(JSON.parse(sourceCode), {
			includeMetaData: true,
			flatten: true,
			filePath: inputPath,
			sourceResolver: sourceResolverSync
		});
		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);
	}
}

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