"use strict";
/**
 * Shutdown supervisor process.
 *
 * This process watches a stdin lifeline. When the parent dies, stdin closes
 * and the supervisor performs best-effort cleanup:
 * - LOCAL: kill Chrome + remove temp profile
 * - STAGEHAND_API: request session release
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.maybeRunShutdownSupervisorFromArgv = exports.runShutdownSupervisor = void 0;
const sdk_1 = __importDefault(require("@browserbasehq/sdk"));
const cleanupLocal_js_1 = require("./cleanupLocal.js");
const SIGKILL_POLL_MS = 250;
const SIGKILL_TIMEOUT_MS = 7_000;
const PID_POLL_INTERVAL_MS = 500;
// `cleanupPromise` guarantees we execute cleanup at most once.
let config = null;
let cleanupPromise = null;
let started = false;
let localPidKnownGone = false;
const exit = (code = 0) => {
    try {
        process.exit(code);
    }
    catch {
        // ignore
    }
};
// Best-effort two-phase kill: SIGTERM first, then SIGKILL after timeout.
// Treat only ESRCH as "already gone"; other errors should not imply dead.
const politeKill = async (pid) => {
    const isAlive = () => {
        try {
            process.kill(pid, 0);
            return true;
        }
        catch (error) {
            const err = error;
            // ESRCH = "No such process" (PID is already gone).
            return err.code !== "ESRCH";
        }
    };
    if (!isAlive())
        return;
    try {
        process.kill(pid, "SIGTERM");
    }
    catch (error) {
        const err = error;
        // ESRCH = process already exited; no further action needed.
        if (err.code === "ESRCH")
            return;
    }
    const deadline = Date.now() + SIGKILL_TIMEOUT_MS;
    while (Date.now() < deadline) {
        await new Promise((resolve) => setTimeout(resolve, SIGKILL_POLL_MS));
        if (!isAlive())
            return;
    }
    try {
        process.kill(pid, "SIGKILL");
    }
    catch {
        // best-effort
    }
};
let pidPollTimer = null;
// Local-only fallback: if Chrome dies while parent still lives, run cleanup and exit.
const startPidPolling = (pid) => {
    if (pidPollTimer)
        return;
    pidPollTimer = setInterval(() => {
        try {
            process.kill(pid, 0);
            return;
        }
        catch (error) {
            const err = error;
            // Only ESRCH means the process is definitely gone.
            if (err.code !== "ESRCH")
                return;
        }
        localPidKnownGone = true;
        if (pidPollTimer) {
            clearInterval(pidPollTimer);
            pidPollTimer = null;
        }
        void runCleanup("Browser process exited").finally(() => exit(0));
    }, PID_POLL_INTERVAL_MS);
};
const cleanupLocal = async (cfg, reason) => {
    const deletingUserDataDir = Boolean(cfg.createdTempProfile && !cfg.preserveUserDataDir && cfg.userDataDir);
    await (0, cleanupLocal_js_1.cleanupLocalBrowser)({
        // If polling already observed ESRCH, avoid a follow-up PID kill.
        // The PID could be reused by a different process before cleanup runs.
        killChrome: cfg.pid && !localPidKnownGone
            ? () => {
                console.error(`[shutdown-supervisor] Shutting down Chrome pid=${cfg.pid} ` +
                    `(reason=${reason}, deletingUserDataDir=${deletingUserDataDir})`);
                return politeKill(cfg.pid);
            }
            : undefined,
        userDataDir: cfg.userDataDir,
        createdTempProfile: cfg.createdTempProfile,
        preserveUserDataDir: cfg.preserveUserDataDir,
    });
};
const cleanupBrowserbase = async (cfg, reason) => {
    if (!cfg.apiKey || !cfg.projectId || !cfg.sessionId)
        return;
    try {
        console.error(`[shutdown-supervisor] Ending Browserbase session ${cfg.sessionId} ` +
            `(reason=${reason})`);
        const bb = new sdk_1.default({ apiKey: cfg.apiKey });
        await bb.sessions.update(cfg.sessionId, {
            status: "REQUEST_RELEASE",
            projectId: cfg.projectId,
        });
    }
    catch {
        // best-effort cleanup
    }
};
// Idempotent cleanup entrypoint used by all supervisor shutdown paths.
const runCleanup = (reason) => {
    if (!cleanupPromise) {
        cleanupPromise = (async () => {
            const cfg = config;
            if (!cfg)
                return;
            if (cfg.kind === "LOCAL") {
                await cleanupLocal(cfg, reason);
                return;
            }
            if (cfg.kind === "STAGEHAND_API") {
                await cleanupBrowserbase(cfg, reason);
            }
        })();
    }
    return cleanupPromise;
};
const applyConfig = (nextConfig) => {
    config = nextConfig;
    localPidKnownGone = false;
    if (config.kind === "LOCAL" && config.pid) {
        startPidPolling(config.pid);
    }
};
const onLifelineClosed = (reason) => {
    void runCleanup(reason).finally(() => exit(0));
};
const parseConfigFromArgv = (argv = process.argv.slice(2)) => {
    const prefix = "--supervisor-config=";
    const raw = argv.find((arg) => arg.startsWith(prefix))?.slice(prefix.length);
    if (!argv.includes("--supervisor") || !raw)
        return null;
    try {
        return JSON.parse(raw);
    }
    catch {
        return null;
    }
};
const runShutdownSupervisor = (initialConfig) => {
    if (started)
        return;
    started = true;
    applyConfig(initialConfig);
    // Stdin is the lifeline; losing it means parent is gone.
    try {
        process.stdin.resume();
        process.stdin.on("end", () => onLifelineClosed("Stagehand process completed"));
        process.stdin.on("close", () => onLifelineClosed("Stagehand process completed"));
        process.stdin.on("error", () => onLifelineClosed("Stagehand process crashed or was killed"));
    }
    catch {
        // ignore
    }
};
exports.runShutdownSupervisor = runShutdownSupervisor;
const maybeRunShutdownSupervisorFromArgv = (argv = process.argv.slice(2)) => {
    const parsed = parseConfigFromArgv(argv);
    if (!parsed)
        return false;
    (0, exports.runShutdownSupervisor)(parsed);
    return true;
};
exports.maybeRunShutdownSupervisorFromArgv = maybeRunShutdownSupervisorFromArgv;
//# sourceMappingURL=supervisor.js.map