import { execSync, spawn } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import { getPlaywrightProfileDir } from "../config/index.js";
import { logger } from "../logger.js";
const CDP_PORT = 9222;
const CDP_STARTUP_TIMEOUT_MS = 8_000;
const CDP_POLL_INTERVAL_MS = 300;
const NEW_BROWSER_SETTLE_MS = 1_000;
const CDP_BASE = `http://127.0.0.1:${CDP_PORT}`;
/**
 * Detect whether we're running inside a virtual machine (e.g. Lume VM).
 * On macOS, VMs report a model like "VirtualMac2,1" via sysctl.
 */
function isVirtualMachine() {
    if (process.platform !== "darwin")
        return false;
    try {
        const model = execSync("sysctl -n hw.model", { encoding: "utf-8" }).trim();
        return model.startsWith("VirtualMac");
    }
    catch {
        return false;
    }
}
function findChromePath() {
    if (process.platform === "darwin") {
        return "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
    }
    // Windows: check common installation paths
    const candidates = [
        path.join(process.env.PROGRAMFILES ?? "C:\\Program Files", "Google", "Chrome", "Application", "chrome.exe"),
        path.join(process.env["PROGRAMFILES(X86)"] ?? "C:\\Program Files (x86)", "Google", "Chrome", "Application", "chrome.exe"),
        path.join(process.env.LOCALAPPDATA ?? "", "Google", "Chrome", "Application", "chrome.exe"),
    ];
    for (const p of candidates) {
        if (fs.existsSync(p))
            return p;
    }
    return candidates[0]; // fallback — will fail with a clear "not found" message
}
const CHROME_PATH = findChromePath();
/**
 * Check whether a CDP-enabled browser is responding on the configured port.
 */
async function isCdpAlive() {
    try {
        const resp = await fetch(`${CDP_BASE}/json/version`);
        return resp.ok;
    }
    catch {
        return false;
    }
}
/**
 * Probe `/json/list` to get the current set of CDP targets.
 * Returns null if CDP is unreachable.
 */
async function listCdpTargets() {
    try {
        const resp = await fetch(`${CDP_BASE}/json/list`, { signal: AbortSignal.timeout(3_000) });
        if (!resp.ok)
            return null;
        return (await resp.json());
    }
    catch {
        return null;
    }
}
/**
 * Heuristic: check whether Chrome's page targets look healthy enough for
 * Playwright's `connectOverCDP` to succeed.  Playwright hangs in
 * `_waitForAllPagesToBeInitialized()` when a page target exists but its
 * webSocketDebuggerUrl is missing (already attached) or the page URL
 * indicates a crash.
 */
async function arePagesHealthy() {
    const targets = await listCdpTargets();
    if (!targets)
        return false;
    const pages = targets.filter((t) => t.type === "page");
    if (pages.length === 0)
        return true;
    for (const page of pages) {
        if (page.url.startsWith("chrome-error://") || page.url === "chrome://crash/") {
            logger.warn(`CDP target ${page.id} has crash URL: ${page.url}`);
            return false;
        }
    }
    return true;
}
/**
 * Kill any Chrome processes that were launched with our CDP port and
 * profile dir.  Best-effort — failures are silently ignored.
 */
function killChrome() {
    try {
        if (process.platform === "darwin" || process.platform === "linux") {
            execSync(`pkill -f -- '--remote-debugging-port=${CDP_PORT}'`, { stdio: "ignore" });
        }
        else {
            execSync(`taskkill /F /IM chrome.exe`, { stdio: "ignore" });
        }
    }
    catch {
        // pkill exits non-zero when no matching process is found — expected
    }
}
/**
 * Force-kill Chrome and wait for the CDP port to become free.
 */
async function killAndWait() {
    killChrome();
    const deadline = Date.now() + 5_000;
    while (Date.now() < deadline) {
        if (!(await isCdpAlive()))
            return;
        await new Promise((r) => setTimeout(r, 300));
    }
    logger.warn("Chrome process did not exit within 5 s after kill signal");
}
/**
 * Launch the system Chrome as a persistent detached process with remote
 * debugging enabled and a dedicated user-data-dir.  The process is unref'd
 * so the agent can exit without waiting for it, and the browser stays
 * visible on the desktop between wake cycles.
 *
 * Returns `true` if a new process was spawned, `false` if the browser
 * was already running.
 */
async function launchBrowser() {
    if (await isCdpAlive()) {
        return false;
    }
    if (!fs.existsSync(CHROME_PATH)) {
        logger.warn(`Chrome not found at ${CHROME_PATH}. Please install Google Chrome.`);
        return false;
    }
    const profileDir = getPlaywrightProfileDir();
    const flags = [
        `--remote-debugging-port=${CDP_PORT}`,
        `--user-data-dir=${profileDir}`,
        "--no-first-run",
        "--no-default-browser-check",
        "--profile-directory=Default",
    ];
    if (isVirtualMachine()) {
        flags.push("--disable-gpu", "--disable-software-rasterizer");
        logger.system("VM detected — Chrome launched with GPU disabled");
    }
    const child = spawn(CHROME_PATH, flags, {
        detached: true,
        stdio: "ignore",
    });
    child.on("error", (err) => {
        logger.warn(`Browser process error: ${err.message}`);
    });
    child.unref();
    const deadline = Date.now() + CDP_STARTUP_TIMEOUT_MS;
    while (Date.now() < deadline) {
        await new Promise((r) => setTimeout(r, CDP_POLL_INTERVAL_MS));
        if (await isCdpAlive()) {
            logger.system(`Chrome launched on CDP port ${CDP_PORT}`);
            await new Promise((r) => setTimeout(r, NEW_BROWSER_SETTLE_MS));
            return true;
        }
    }
    logger.warn(`Chrome process spawned but CDP not responding after ${CDP_STARTUP_TIMEOUT_MS}ms`);
    return true;
}
/**
 * Ensure a CDP-enabled Chrome is running with healthy page targets.
 * If page targets look unhealthy (crashed tabs, etc.) the browser is
 * killed and relaunched so Playwright's `connectOverCDP` doesn't hang.
 */
export async function ensureBrowser() {
    try {
        if (await isCdpAlive()) {
            if (await arePagesHealthy()) {
                logger.debug("Browser already running on CDP port");
                return;
            }
            logger.warn("CDP page targets unhealthy — restarting Chrome");
            await killAndWait();
        }
        await launchBrowser();
    }
    catch (err) {
        logger.warn(`Failed to ensure browser: ${err instanceof Error ? err.message : String(err)}`);
    }
}
/**
 * Kill the current Chrome and launch a fresh instance.  Called as a
 * recovery step when a Playwright MCP tool call times out.
 */
export async function restartBrowser() {
    logger.system("Restarting Chrome for Playwright recovery");
    await killAndWait();
    await launchBrowser();
}
export function getCdpEndpoint() {
    return `http://127.0.0.1:${CDP_PORT}`;
}
//# sourceMappingURL=browser-launcher.js.map