import { createLogger } from "../utils/logger/index.js";
import { init_logger } from "../utils/logger.js";
import { loadPlaywrightCoreModule } from "./providers/playwright-doctor.js";
import { pickFreePort } from "./free-port.js";
import { existsSync } from "node:fs";
import { mkdir, rm } from "node:fs/promises";
import { join } from "node:path";
import { homedir, platform } from "node:os";
import { spawn } from "node:child_process";
//#region src/browser/cdp-local-launcher.ts
/**
* Launches a Chromium / Chrome instance locally with `--remote-debugging-port`
* so the user can wire CDP backend without typing the command themselves.
*
* Instances live for the lifetime of the gateway process (no on-disk state).
* Launching binds the debug port to 127.0.0.1; the user-data-dir is rooted
* under `~/.xopc/cdp-launch/` and removed on stop.
*/
init_logger();
const log = createLogger("CdpLocalLauncher");
const READY_TIMEOUT_MS = 2e4;
const READY_POLL_INTERVAL_MS = 250;
const instances = /* @__PURE__ */ new Map();
function defaultUserDataRoot() {
	return join(homedir(), ".xopc", "cdp-launch");
}
/** Resolve a Chrome / Chromium executable for the current platform. */
async function resolveChromeBinary(override) {
	if (override && override.trim()) {
		const candidate = override.trim();
		if (!existsSync(candidate)) throw new Error(`Chrome executable not found at ${candidate}`);
		return candidate;
	}
	try {
		const pw = await loadPlaywrightCoreModule();
		const exec = (pw.chromium ?? pw.default?.chromium)?.executablePath?.();
		if (exec && existsSync(exec)) return exec;
	} catch {}
	const os = platform();
	const probes = os === "darwin" ? [
		"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
		"/Applications/Chromium.app/Contents/MacOS/Chromium",
		"/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary"
	] : os === "win32" ? ["C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe", "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe"] : [
		"/usr/bin/google-chrome",
		"/usr/bin/chromium",
		"/usr/bin/chromium-browser",
		"/snap/bin/chromium"
	];
	for (const path of probes) if (existsSync(path)) return path;
	throw new Error("No Chrome / Chromium binary found. Install Chrome or run `npx playwright install chromium` first.");
}
async function waitForCdpEndpoint(port) {
	const deadline = Date.now() + READY_TIMEOUT_MS;
	while (Date.now() < deadline) {
		try {
			const res = await fetch(`http://127.0.0.1:${port}/json/version`, { signal: AbortSignal.timeout(1500) });
			if (res.ok) {
				const data = await res.json();
				if (data.webSocketDebuggerUrl) return data.webSocketDebuggerUrl;
			}
		} catch {}
		await new Promise((r) => setTimeout(r, READY_POLL_INTERVAL_MS));
	}
	throw new Error(`Chrome did not expose CDP within ${READY_TIMEOUT_MS / 1e3}s on port ${port}`);
}
async function launchLocalCdpChrome(opts = {}) {
	const executablePath = await resolveChromeBinary(opts.executablePath);
	const port = await pickFreePort();
	const root = defaultUserDataRoot();
	await mkdir(root, { recursive: true });
	const userDataDir = join(root, `${process.pid}-${port}`);
	await mkdir(userDataDir, { recursive: true });
	const args = [
		`--remote-debugging-address=127.0.0.1`,
		`--remote-debugging-port=${port}`,
		`--user-data-dir=${userDataDir}`,
		"--no-first-run",
		"--no-default-browser-check"
	];
	if (opts.headless) args.push("--headless=new");
	if (platform() === "darwin") args.push("--use-mock-keychain");
	log.info({
		executablePath,
		port,
		userDataDir
	}, "Spawning local debuggable Chrome");
	const child = spawn(executablePath, args, {
		stdio: [
			"ignore",
			"ignore",
			"ignore"
		],
		detached: false
	});
	const launchFailure = new Promise((_resolve, reject) => {
		const onErr = (err) => reject(/* @__PURE__ */ new Error(`spawn failed: ${err.message}`));
		const onExit = (code, signal) => {
			const reason = signal ? `signal ${signal}` : `exit ${code ?? "unknown"}`;
			reject(/* @__PURE__ */ new Error(`Chrome exited before CDP came up (${reason})`));
		};
		child.once("error", onErr);
		child.once("exit", onExit);
	});
	let wsEndpoint;
	try {
		wsEndpoint = await Promise.race([waitForCdpEndpoint(port), launchFailure]);
	} catch (e) {
		child.kill();
		await rm(userDataDir, {
			recursive: true,
			force: true
		}).catch(() => {});
		throw e;
	}
	const entry = {
		port,
		wsEndpoint,
		pid: child.pid ?? -1,
		executablePath,
		userDataDir,
		startedAt: Date.now(),
		child
	};
	instances.set(port, entry);
	child.once("exit", () => {
		instances.delete(port);
		rm(userDataDir, {
			recursive: true,
			force: true
		}).catch(() => {});
		log.info({ port }, "Local CDP Chrome exited");
	});
	return toPublic(entry);
}
async function stopLocalCdpChrome(port) {
	const entry = instances.get(port);
	if (!entry) return false;
	entry.child.kill();
	return true;
}
function listLocalCdpInstances() {
	return [...instances.values()].map(toPublic);
}
function toPublic(entry) {
	const { child: _child, ...pub } = entry;
	return pub;
}
/** Graceful shutdown — call from gateway exit hooks. */
async function stopAllLocalCdpChromes() {
	for (const [port] of instances) await stopLocalCdpChrome(port);
}
//#endregion
export { launchLocalCdpChrome, listLocalCdpInstances, resolveChromeBinary, stopAllLocalCdpChromes, stopLocalCdpChrome };

//# sourceMappingURL=cdp-local-launcher.js.map