import { AsyncLocalStorage } from "node:async_hooks";
import { execFile, spawn, spawnSync } from "node:child_process";
const processContext = new AsyncLocalStorage();
let processObserver = null;
const activeTrackedProcesses = new Map();
export function setProcessObserver(next) {
    processObserver = next;
}
export function getProcessContext() {
    return processContext.getStore() ?? {};
}
export function runWithProcessContext(ctx, fn) {
    return processContext.run(ctx, fn);
}
function registerProcess(info) {
    if (!processObserver)
        return null;
    const ctx = getProcessContext();
    return processObserver.register({
        ...info,
        runId: info.runId ?? ctx.runId ?? null,
        source: info.source ?? ctx.source ?? null,
    });
}
function registerActiveProcess(proc, processGroup) {
    activeTrackedProcesses.set(proc, { processGroup });
    const cleanup = () => {
        activeTrackedProcesses.delete(proc);
    };
    proc.on("error", cleanup);
    proc.on("close", cleanup);
}
export function terminateTrackedProcesses(signal) {
    for (const [proc, metadata] of activeTrackedProcesses) {
        if (proc.exitCode !== null || proc.signalCode !== null) {
            activeTrackedProcesses.delete(proc);
            continue;
        }
        if (process.platform === "win32" && proc.pid) {
            const result = spawnSync("taskkill", ["/PID", String(proc.pid), "/T", "/F"], {
                stdio: "ignore",
                windowsHide: true,
            });
            if (result.status === 0)
                continue;
        }
        if (metadata.processGroup && proc.pid) {
            try {
                process.kill(-proc.pid, signal);
                continue;
            }
            catch {
                // The group may have exited between the state check and signal.
            }
        }
        try {
            proc.kill(signal);
        }
        catch {
            // Process already exited.
        }
    }
}
function attachLineReader(stream, onLine) {
    if (!stream)
        return;
    let buffer = "";
    stream.setEncoding("utf8");
    stream.on("data", (chunk) => {
        buffer += chunk;
        const lines = buffer.split(/\r?\n/);
        buffer = lines.pop() ?? "";
        for (const line of lines) {
            if (line === "")
                continue;
            onLine(line);
        }
    });
    stream.on("end", () => {
        const line = buffer.trim();
        if (line)
            onLine(line);
        buffer = "";
    });
}
export function trackChildProcess(proc, info, options) {
    registerActiveProcess(proc, options?.processGroup === true);
    const handle = registerProcess(info);
    if (!handle)
        return null;
    handle.setPid(proc.pid ?? null);
    const captureOutput = options?.captureOutput !== false;
    if (captureOutput) {
        attachLineReader(proc.stdout, (line) => handle.appendOutput("stdout", line));
        attachLineReader(proc.stderr, (line) => handle.appendOutput("stderr", line));
    }
    let finished = false;
    const finishOnce = (result) => {
        if (finished)
            return;
        finished = true;
        handle.finish(result);
    };
    proc.on("error", (error) => {
        const message = error instanceof Error ? error.message : String(error);
        finishOnce({ exitCode: null, signal: null, error: message });
    });
    proc.on("close", (code, signal) => {
        finishOnce({ exitCode: code ?? null, signal: signal ?? null });
    });
    return handle;
}
export function spawnTracked(command, args, options = {}) {
    const { label, kind, runId, source, captureOutput, ...spawnOptions } = options;
    const processGroup = process.platform !== "win32" && spawnOptions.detached !== false;
    const effectiveSpawnOptions = processGroup && spawnOptions.detached === undefined
        ? { ...spawnOptions, detached: true }
        : spawnOptions;
    const proc = spawn(command, args, effectiveSpawnOptions);
    const handle = trackChildProcess(proc, {
        command,
        args,
        label,
        kind,
        runId,
        source,
        cwd: effectiveSpawnOptions.cwd ? String(effectiveSpawnOptions.cwd) : null,
        env: effectiveSpawnOptions.env ?? null,
    }, { captureOutput, processGroup });
    return { proc, handle };
}
export function execFileTracked(file, args, options, callback) {
    let resolvedArgs = [];
    let resolvedOptions = {};
    let resolvedCallback;
    if (Array.isArray(args)) {
        resolvedArgs = args;
        if (typeof options === "function") {
            resolvedCallback = options;
        }
        else {
            resolvedOptions = options ?? {};
            resolvedCallback = callback;
        }
    }
    else if (typeof args === "function") {
        resolvedCallback = args;
    }
    else {
        resolvedOptions = (args ?? {});
        if (typeof options === "function") {
            resolvedCallback = options;
        }
    }
    const proc = execFile(file, resolvedArgs, resolvedOptions, resolvedCallback);
    trackChildProcess(proc, {
        command: file,
        args: Array.from(resolvedArgs),
        label: file,
        kind: file,
        cwd: resolvedOptions.cwd ? String(resolvedOptions.cwd) : null,
        env: resolvedOptions.env ?? null,
    }, { captureOutput: true });
    return proc;
}
//# sourceMappingURL=processes.js.map