import { bundler } from "@suseejs/bundler";
import { suseeCompiler } from "@suseejs/compiler";
import { files } from "@suseejs/files";
import { getCompilerOptions } from "@suseejs/tsoptions";
import { utils } from "@suseejs/utilities";
import type { BuildEntryPoint, BuildOptions } from "./suseeConfig.js";

/**
 * Compiler for the JavaScript API.
 * It bundles each configured entry point, emits CommonJS and ESM outputs,
 * and optionally updates package export metadata.
 */
class Compiler {
	private _files: files.OutFiles;
	private _object: BuildOptions;
	/**
	 * Creates a compiler instance with normalized build options.
	 * @param {BuildOptions} object - build options generated from the susee config.
	 */
	constructor(object: BuildOptions) {
		this._object = object;
		this._files = {
			commonjs: undefined,
			commonjsTypes: undefined,
			esm: undefined,
			esmTypes: undefined,
			main: undefined,
			module: undefined,
			types: undefined,
		};
	}
	private _update() {
		return this._object.updatePackage;
	}
	private async _commonjs(point: BuildEntryPoint) {
		const isMain = point.exportPath === ".";
		const opts = getCompilerOptions(point.tsconfigFilePath);
		const compilerOptions = opts.commonjs(point.outputDirectoryPath);
		const bundledCode = await bundler(
			point.entry,
			point.plugins,
			point.warning,
			point.rename,
		);
		const is_jsx = utils.checks.isJsxContent(bundledCode);
		const compiled = suseeCompiler({
			sourceCode: bundledCode, 
			fileName: point.entry, 
			compilerOptions, 
			isJsx: is_jsx, 
		}); 
		let compiledCode = compiled.code; 
		const mainFilePath = files.joinPath( 
			compiled.out_dir, 
			`${compiled.file_name}.cjs`, 
		); 
		const dtsFilePath = files.joinPath(
			compiled.out_dir,
			`${compiled.file_name}.d.cts`,
		); 
		const mapFilePath = files.joinPath(
			compiled.out_dir,
			`${compiled.file_name}.cjs.map`,
		);
		// replace source mapping url
		compiledCode = compiledCode.replace(
			new RegExp(`${compiled.file_name}.js.map`, "gm"),
			`${compiled.file_name}.cjs.map`,
		);
		// call post-process plugin
		if (point.plugins.length > 0) {
			for (const plugin of point.plugins) {
				const _plugin = typeof plugin === "function" ? plugin() : plugin;
				if (_plugin.type === "post-process") {
					if (_plugin.async) {
						compiledCode = await _plugin.func(compiledCode, point.entry);
					} else {
						compiledCode = _plugin.func(compiledCode, point.entry);
					}
				}
			}
		}
		// if allow update create file object
		if (this._update()) {
			this._files.commonjs = mainFilePath;
			if (compiled.dts) {
				this._files.commonjsTypes = dtsFilePath;
			}
			if (isMain && point.format.includes("commonjs")) {
				if (this._files.commonjs) this._files.main = this._files.commonjs;
				if (this._files.commonjsTypes)
					this._files.types = this._files.commonjsTypes;
			}
		} //update
		await files.writeFile(mainFilePath, compiledCode);
		if (compiled.dts) await files.writeFile(dtsFilePath, compiled.dts);
		if (compiled.map) await files.writeFile(mapFilePath, compiled.map);
	}
	private async _esm(point: BuildEntryPoint) {
		const isMain = point.exportPath === ".";
		const opts = getCompilerOptions(point.tsconfigFilePath);
		const compilerOptions = opts.esm(point.outputDirectoryPath);
		const bundledCode = await bundler(
			point.entry, 
			point.plugins,
			point.warning,
			point.rename,
		);
		const is_jsx = utils.checks.isJsxContent(bundledCode);
		const compiled = suseeCompiler({
			sourceCode: bundledCode,
			fileName: point.entry,
			compilerOptions,
			isJsx: is_jsx,
		});
		let compiledCode = compiled.code;
		const mainFilePath = files.joinPath(
			compiled.out_dir,
			`${compiled.file_name}.mjs`,
		);
		const dtsFilePath = files.joinPath(
			compiled.out_dir,
			`${compiled.file_name}.d.mts`,
		);
		const mapFilePath = files.joinPath(
			compiled.out_dir,
			`${compiled.file_name}.mjs.map`,
		);
		// replace source mapping url
		compiledCode = compiledCode.replace(
			new RegExp(`${compiled.file_name}.js.map`, "gm"),
			`${compiled.file_name}.mjs.map`,
		);
		// call post-process plugin
		if (point.plugins.length > 0) {
			for (const plugin of point.plugins) {
				const _plugin = typeof plugin === "function" ? plugin() : plugin;
				if (_plugin.type === "post-process") {
					if (_plugin.async) {
						compiledCode = await _plugin.func(compiledCode, point.entry);
					} else {
						compiledCode = _plugin.func(compiledCode, point.entry);
					}
				}
			}
		}
		if (this._update()) {
			this._files.esm = mainFilePath;
			if (compiled.dts) {
				this._files.esmTypes = dtsFilePath;
			}
			if (isMain && this._files.esm) {
				this._files.module = this._files.esm;
			}
		} //update
		await files.writeFile(mainFilePath, compiledCode);
		if (compiled.dts) await files.writeFile(dtsFilePath, compiled.dts);
		if (compiled.map) await files.writeFile(mapFilePath, compiled.map);
	}
	/**
	 * Clears the output directory and compiles all configured entry points.
	 * It also updates package.json export fields when package updates are enabled.
	 * @returns {Promise<void>}
	 */
	async compile(): Promise<void> {
		await files.clearFolder(this._object.outDir);
		for (const point of this._object.buildEntryPoints) {
			for (const format of point.format) {
				switch (format) {
					case "commonjs":
						await this._commonjs(point);
						if (this._update()) {
							files.writePackageJson(this._files, point.exportPath);
						}
						break;
					case "esm":
						await this._esm(point);
						if (this._update()) {
							files.writePackageJson(this._files, point.exportPath);
						}
						break;
				}
			}
		}
	}
}

export { Compiler };