import { execFileSync } from "node:child_process";
import { existsSync, readFileSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { getPlaywrightProfileDir } from "../config/index.js";
import { logger } from "../logger.js";
const PM2_PROCESS_NAME = "playwriter-relay";
const RELAY_PROTOCOL_VERSION = 1;
const HEALTH_CHECK_TTL_MS = 30_000;
const EXTENSION_ID = "jfeammnjpkecdekppnclgkkffahnhfhe";
let cachedStatus = null;
let cachedStatusAt = 0;
function isBundled() {
    const v = process.env.VISIONCLAW_BUNDLED_VERSION;
    return typeof v === "string" && v !== "undefined";
}
function resolveRelayEntryPath() {
    const __dir = path.dirname(fileURLToPath(import.meta.url));
    const packageRoot = path.resolve(__dir, isBundled() ? ".." : "../..");
    return path.join(packageRoot, "dist-agent", "playwriter-relay.cjs");
}
function getRelayBaseUrl(host, port) {
    return `http://${host}:${port}`;
}
export function getRelayCdpEndpoint(host, port) {
    return `ws://${host}:${port}/cdp`;
}
export function assertPm2Available() {
    try {
        execFileSync("pm2", ["--version"], { stdio: "pipe", timeout: 5_000, encoding: "utf-8" });
    }
    catch {
        throw new Error("pm2 is required for the Playwriter relay server. Install with: npm install -g pm2");
    }
}
function getPm2RelayProcess() {
    try {
        const output = execFileSync("pm2", ["jlist"], {
            encoding: "utf-8",
            stdio: ["pipe", "pipe", "pipe"],
            timeout: 10_000,
        });
        const processes = JSON.parse(output);
        return processes.find((p) => p.name === PM2_PROCESS_NAME) ?? null;
    }
    catch {
        return null;
    }
}
function pm2Start(entryPath, host, port) {
    const versionStr = process.env.VISIONCLAW_BUNDLED_VERSION ?? "dev";
    execFileSync("pm2", ["start", entryPath, "--name", PM2_PROCESS_NAME, "--update-env"], {
        encoding: "utf-8",
        stdio: "pipe",
        timeout: 15_000,
        env: {
            ...process.env,
            PLAYWRITER_RELAY_HOST: host,
            PLAYWRITER_RELAY_PORT: String(port),
            VISIONCLAW_VERSION: versionStr,
        },
    });
    execFileSync("pm2", ["save", "--force"], { encoding: "utf-8", stdio: "pipe", timeout: 10_000 });
}
function pm2Delete() {
    try {
        execFileSync("pm2", ["delete", PM2_PROCESS_NAME], {
            encoding: "utf-8",
            stdio: "pipe",
            timeout: 10_000,
        });
    }
    catch (err) {
        logger.system(`Playwriter relay: pm2 delete failed (may not exist): ${err instanceof Error ? err.message : String(err)}`);
    }
}
function pm2Restart(host, port) {
    const versionStr = process.env.VISIONCLAW_BUNDLED_VERSION ?? "dev";
    execFileSync("pm2", ["restart", PM2_PROCESS_NAME, "--update-env"], {
        encoding: "utf-8",
        stdio: "pipe",
        timeout: 15_000,
        env: {
            ...process.env,
            PLAYWRITER_RELAY_HOST: host,
            PLAYWRITER_RELAY_PORT: String(port),
            VISIONCLAW_VERSION: versionStr,
        },
    });
    execFileSync("pm2", ["save", "--force"], { encoding: "utf-8", stdio: "pipe", timeout: 10_000 });
}
function hasPlaywriterExtensionJson() {
    if (process.platform !== "darwin")
        return false;
    const extJsonPath = path.join(os.homedir(), "Library", "Application Support", "Google", "Chrome", "External Extensions", `${EXTENSION_ID}.json`);
    return existsSync(extJsonPath);
}
export async function getPlaywriterRelayStatus(host = "127.0.0.1", port = 19988) {
    try {
        const response = await fetch(`${getRelayBaseUrl(host, port)}/extension/status`, {
            signal: AbortSignal.timeout(2_000),
        });
        if (!response.ok) {
            return { reachable: true, extensionConnected: false, activeTargets: 0 };
        }
        const status = (await response.json());
        return {
            reachable: true,
            extensionConnected: status.connected === true,
            activeTargets: typeof status.activeTargets === "number" ? status.activeTargets : 0,
        };
    }
    catch {
        return { reachable: false, extensionConnected: false, activeTargets: 0 };
    }
}
export function getRelayInfoFromFile() {
    const infoPath = path.join(path.dirname(getPlaywrightProfileDir()), ".playwriter-relay-info.json");
    try {
        if (!existsSync(infoPath))
            return null;
        return JSON.parse(readFileSync(infoPath, "utf-8"));
    }
    catch {
        return null;
    }
}
export async function getCachedRelayStatus(host = "127.0.0.1", port = 19988) {
    if (cachedStatus && Date.now() - cachedStatusAt < HEALTH_CHECK_TTL_MS) {
        return cachedStatus;
    }
    cachedStatus = await getPlaywriterRelayStatus(host, port);
    cachedStatusAt = Date.now();
    return cachedStatus;
}
export function invalidateRelayStatusCache() {
    cachedStatus = null;
    cachedStatusAt = 0;
}
export function reconcilePlaywriterRelay(host = "127.0.0.1", port = 19988) {
    const expectedEntry = resolveRelayEntryPath();
    const current = getPm2RelayProcess();
    if (!current) {
        logger.system("Playwriter relay: starting new pm2 process");
        pm2Start(expectedEntry, host, port);
        return;
    }
    const currentPath = current.pm2_env?.pm_exec_path ?? "";
    const currentStatus = current.pm2_env?.status ?? "";
    if (currentPath !== expectedEntry) {
        logger.system(`Playwriter relay: entry path changed (${currentPath} → ${expectedEntry}), recreating`);
        pm2Delete();
        pm2Start(expectedEntry, host, port);
        return;
    }
    const currentEnv = current.pm2_env?.env ?? {};
    if (currentEnv.PLAYWRITER_RELAY_PORT !== String(port) ||
        currentEnv.PLAYWRITER_RELAY_HOST !== host) {
        logger.system("Playwriter relay: env mismatch, restarting with updated env");
        pm2Restart(host, port);
        return;
    }
    const currentVersion = process.env.VISIONCLAW_BUNDLED_VERSION ?? "dev";
    if (currentEnv.VISIONCLAW_VERSION && currentEnv.VISIONCLAW_VERSION !== currentVersion) {
        logger.system(`Playwriter relay: VisionClaw version changed (${currentEnv.VISIONCLAW_VERSION} → ${currentVersion}), recreating`);
        pm2Delete();
        pm2Start(expectedEntry, host, port);
        return;
    }
    if (currentStatus !== "online") {
        logger.system(`Playwriter relay: status is "${currentStatus}", restarting`);
        pm2Restart(host, port);
        return;
    }
    const relayInfo = getRelayInfoFromFile();
    if (relayInfo?.relayProtocolVersion !== undefined &&
        relayInfo.relayProtocolVersion < RELAY_PROTOCOL_VERSION) {
        logger.system(`Playwriter relay: protocol version stale (${relayInfo.relayProtocolVersion} < ${RELAY_PROTOCOL_VERSION}), recreating`);
        pm2Delete();
        pm2Start(expectedEntry, host, port);
        return;
    }
    logger.system("Playwriter relay: already running and up-to-date");
}
export async function ensurePlaywriterRelay(host = "127.0.0.1", port = 19988) {
    assertPm2Available();
    if (!hasPlaywriterExtensionJson()) {
        throw new Error("Playwriter External Extensions JSON not found. Run extension install first.");
    }
    reconcilePlaywriterRelay(host, port);
    const deadline = Date.now() + 15_000;
    while (Date.now() < deadline) {
        const status = await getPlaywriterRelayStatus(host, port);
        if (status.reachable && status.extensionConnected) {
            invalidateRelayStatusCache();
            return status;
        }
        await new Promise((r) => setTimeout(r, 500));
    }
    const finalStatus = await getPlaywriterRelayStatus(host, port);
    invalidateRelayStatusCache();
    logger.system(`Playwriter relay: startup poll timed out (reachable=${finalStatus.reachable}, extensionConnected=${finalStatus.extensionConnected})`);
    return finalStatus;
}
export function cleanupOrphanRelay() {
    const current = getPm2RelayProcess();
    if (current) {
        logger.system("Playwriter relay: cleaning up orphan pm2 process (backend disabled)");
        pm2Delete();
        try {
            execFileSync("pm2", ["save", "--force"], { encoding: "utf-8", stdio: "pipe", timeout: 10_000 });
        }
        catch (err) {
            logger.system(`Playwriter relay: pm2 save failed: ${err instanceof Error ? err.message : String(err)}`);
        }
    }
}
export { RELAY_PROTOCOL_VERSION };
//# sourceMappingURL=playwriter-relay.js.map