import { spawn as nodeSpawn, spawnSync as nodeSpawnSync, } from "node:child_process";
import crossSpawn from "cross-spawn";
const EXIT_STDIO_GRACE_MS = 100;
export function spawnProcess(command, args, options) {
    return process.platform === "win32" ? crossSpawn(command, args, options) : nodeSpawn(command, args, options);
}
export function spawnProcessSync(command, args, options) {
    return process.platform === "win32"
        ? crossSpawn.sync(command, args, options)
        : nodeSpawnSync(command, args, options);
}
/**
 * Wait for a child process to terminate without hanging on inherited stdio handles.
 *
 * A short-lived child can `exit` while a detached descendant keeps its stdout/stderr
 * pipe open. We must not resolve and destroy the streams on a fixed deadline measured
 * from `exit`, or output still being written past that deadline is silently lost
 * (earendil-works/pi#5303). Instead, after `exit` we wait for the pipes to fall idle:
 * the grace timer is re-armed on every chunk, so an actively writing descendant keeps
 * us reading, while a quiet inherited handle (e.g. a Windows daemonized descendant
 * that never lets `close` fire) still releases us after the grace elapses.
 */
export function waitForChildProcess(child) {
    return new Promise((resolve, reject) => {
        let settled = false;
        let exited = false;
        let exitCode = null;
        let postExitTimer;
        let stdoutEnded = child.stdout === null;
        let stderrEnded = child.stderr === null;
        const cleanup = () => {
            if (postExitTimer) {
                clearTimeout(postExitTimer);
                postExitTimer = undefined;
            }
            child.removeListener("error", onError);
            child.removeListener("exit", onExit);
            child.removeListener("close", onClose);
            child.stdout?.removeListener("end", onStdoutEnd);
            child.stderr?.removeListener("end", onStderrEnd);
            child.stdout?.removeListener("data", onData);
            child.stderr?.removeListener("data", onData);
        };
        const finalize = (code) => {
            if (settled)
                return;
            settled = true;
            cleanup();
            child.stdout?.destroy();
            child.stderr?.destroy();
            resolve(code);
        };
        const maybeFinalizeAfterExit = () => {
            if (!exited || settled)
                return;
            if (stdoutEnded && stderrEnded) {
                finalize(exitCode);
            }
        };
        const armIdleTimer = () => {
            if (postExitTimer)
                clearTimeout(postExitTimer);
            postExitTimer = setTimeout(() => finalize(exitCode), EXIT_STDIO_GRACE_MS);
        };
        const onData = () => {
            // Output is still arriving after exit; defer finalizing so we don't
            // destroy the stream mid-write and truncate the tail.
            if (exited && !settled)
                armIdleTimer();
        };
        const onStdoutEnd = () => {
            stdoutEnded = true;
            maybeFinalizeAfterExit();
        };
        const onStderrEnd = () => {
            stderrEnded = true;
            maybeFinalizeAfterExit();
        };
        const onError = (err) => {
            if (settled)
                return;
            settled = true;
            cleanup();
            reject(err);
        };
        const onExit = (code) => {
            exited = true;
            exitCode = code;
            maybeFinalizeAfterExit();
            if (!settled) {
                armIdleTimer();
            }
        };
        const onClose = (code) => {
            finalize(code);
        };
        child.stdout?.once("end", onStdoutEnd);
        child.stderr?.once("end", onStderrEnd);
        child.stdout?.on("data", onData);
        child.stderr?.on("data", onData);
        child.once("error", onError);
        child.once("exit", onExit);
        child.once("close", onClose);
    });
}
//# sourceMappingURL=child-process.js.map