import { join, dirname, resolve } from "path";
import { dlopen, suffix, ptr } from "bun:ffi";
import { existsSync } from "fs";

// Set CWD to bin/ directory where the binary lives.
// Without this, pkexec sets CWD to $HOME and all ../Resources/ paths break.
const pathToMacOS = dirname(process.argv0);
process.chdir(pathToMacOS);
const coreLibFileName =
	process.platform === "win32"
		? "SparkBunCore.dll"
		: `libSparkBunCore.${suffix}`;
const coreLibPath = join(pathToMacOS, coreLibFileName);
const absoluteCoreLibPath = resolve(coreLibPath);

function isAdmin(): boolean {
	if (process.platform === "win32") {
		const shell32 = dlopen("shell32.dll", {
			IsUserAnAdmin: { args: [], returns: "bool" },
		});
		const admin = shell32.symbols.IsUserAnAdmin();
		shell32.close();
		return admin;
	}
	if (process.platform === "linux") {
		return process.getuid?.() === 0;
	}
	return true;
}

function elevateAndExit(): void {
	if (process.platform === "win32") {
		const shell32 = dlopen("shell32.dll", {
			ShellExecuteW: {
				args: ["ptr", "ptr", "ptr", "ptr", "ptr", "i32"],
				returns: "ptr",
			},
		});
		const encode = (s: string) => ptr(new Uint8Array(Buffer.from(s + "\0", "utf16le")));
		shell32.symbols.ShellExecuteW(
			null,
			encode("runas"),
			encode(process.argv0),
			null,
			null,
			1, // SW_SHOWNORMAL
		);
		shell32.close();
	} else if (process.platform === "linux") {
		Bun.spawnSync(["pkexec", process.argv0, ...process.argv.slice(1)], {
			stdout: "inherit",
			stderr: "inherit",
		});
	}
	process.exit(0);
}

// Wrap main logic in a function to avoid top-level return
function main() {
	// Read version.json early to get identifier, name, and channel for CEF initialization
	let channel = "";
	let identifier = "";
	let name = "";
	try {
		const pathToLauncherBin = process.argv0;
		const pathToBinDir = dirname(pathToLauncherBin);
		const versionJsonPath = join(
			pathToBinDir,
			"..",
			"Resources",
			"version.json",
		);

		if (existsSync(versionJsonPath)) {
			const versionInfo = require(versionJsonPath);
			if (versionInfo.identifier) {
				identifier = versionInfo.identifier;
			}
			if (versionInfo.name) {
				name = versionInfo.name;
			}
			if (versionInfo.channel) {
				channel = versionInfo.channel;
			}
			console.log(
				`[LAUNCHER] Loaded identifier: ${identifier}, name: ${name}, channel: ${channel}`,
			);
		}
	} catch (error) {
		console.error(`[LAUNCHER] Warning: Could not read version.json:`, error);
		// Continue anyway - this is not critical for dev builds
	}

	// Check if admin elevation is required
	if (process.platform !== "darwin") {
		try {
			const binDir = dirname(process.argv0);
			const buildJsonPath = join(binDir, "..", "Resources", "build.json");
			if (existsSync(buildJsonPath)) {
				const buildConfig = JSON.parse(
					require("fs").readFileSync(buildJsonPath, "utf-8"),
				);
				if (buildConfig.requireAdmin && !isAdmin()) {
					console.log("[LAUNCHER] Admin required, elevating...");
					elevateAndExit();
					return;
				} else if (buildConfig.requireAdmin) {
					console.log("[LAUNCHER] Already running as admin");
				}
			}
		} catch (error) {
			console.error(`[LAUNCHER] Warning: Could not check admin requirement:`, error);
		}
	}

	// Check for CEF libraries and warn if LD_PRELOAD not set (Linux only)
	if (process.platform === "linux") {
		const cefLibs = [
			join(pathToMacOS, "libcef.so"),
			join(pathToMacOS, "libvk_swiftshader.so"),
		];
		const existingCefLibs = cefLibs.filter((lib) => existsSync(lib));

		if (existingCefLibs.length > 0 && !process.env["LD_PRELOAD"]) {
			console.error(
				`[LAUNCHER] ERROR: CEF libraries found but LD_PRELOAD not set!`,
			);
			console.error(
				`[LAUNCHER] Please run through the wrapper script: ./run.sh`,
			);
			console.error(
				`[LAUNCHER] Or set: LD_PRELOAD="${existingCefLibs.join(":")}" before starting.`,
			);

			// Try to re-exec ourselves with LD_PRELOAD set
			const { spawn } = require("child_process");
			const env = { ...process.env, LD_PRELOAD: existingCefLibs.join(":") };
			const child = spawn(process.argv[0], process.argv.slice(1), {
				env,
				stdio: "inherit",
			});
			child.on("exit", (code: number | null) => process.exit(code ?? 1));
			return; // Don't continue in this process
		}
	}

	let lib;
	try {

		lib = dlopen(coreLibPath, {
			sparkbun_core_run_main_thread: {
				args: ["cstring", "cstring", "cstring", "i32"],
				returns: "i32",
			},
			sparkbun_core_last_error: {
				args: [],
				returns: "cstring",
			},
		});
	} catch (error) {
		console.error(
			`[LAUNCHER] Failed to load SparkBunCore: ${(error as Error).message}`,
		);

		// Try with absolute path as fallback
		try {
			lib = dlopen(absoluteCoreLibPath, {
				sparkbun_core_run_main_thread: {
					args: ["cstring", "cstring", "cstring", "i32"],
					returns: "i32",
				},
				sparkbun_core_last_error: {
					args: [],
					returns: "cstring",
				},
			});
		} catch (absError) {
			console.error(
				`[LAUNCHER] Core library loading failed. Try running: ldd ${coreLibPath}`,
			);
			throw error;
		}
	}

	// from different locations
	const pathToLauncherBin = process.argv0;
	const pathToBinDir = dirname(pathToLauncherBin);

	const resourcesDir = join(pathToBinDir, "..", "Resources");
	const appFolderPath = join(resourcesDir, "app");
	const appEntrypointPath = join(appFolderPath, "bun", "index.js");

	// Register signal handlers on the main thread to prevent default termination.
	// The worker thread's SIGINT handler will call quit() for graceful shutdown.
	// Without these, SIGINT kills the process before the worker can run beforeQuit.
	process.on("SIGINT", () => {});
	process.on("SIGTERM", () => {});

	new Worker(appEntrypointPath, {
		// consider adding a preload with error handling
		// preload: [''];
	});

	const runStatus = lib.symbols.sparkbun_core_run_main_thread(
		ptr(new Uint8Array(Buffer.from(identifier + "\0", "utf8"))),
		ptr(new Uint8Array(Buffer.from(name + "\0", "utf8"))),
		ptr(new Uint8Array(Buffer.from(channel + "\0", "utf8"))),
		0,
	);

	if (runStatus !== 0) {
		const coreError = lib.symbols.sparkbun_core_last_error();
		console.error(
			`[LAUNCHER] SparkBunCore failed: ${coreError ? coreError.toString() : "Unknown error"}`,
		);
		process.exit(runStatus);
	}
}

// Call the main function
main();
