export class CliInterruptedError extends Error {
    exitCode;
    silent = true;
    signal;
    constructor(signal) {
        super(`Interrupted by ${signal}`);
        this.name = "CliInterruptedError";
        this.signal = signal;
        this.exitCode = signal === "SIGTERM" ? 143 : 130;
    }
}
function toUtf8String(value) {
    return typeof value === "string" ? value : value.toString("utf8");
}
function formatErrorMessageWithStderr(message, stderrText, separator = ": ") {
    const trimmedStderr = stderrText.trim();
    if (!trimmedStderr || message.includes(trimmedStderr))
        return message;
    return `${message}${separator}${trimmedStderr}`;
}
function formatTimeoutLabel(timeoutMs) {
    if (Number.isFinite(timeoutMs) && timeoutMs > 0) {
        if (timeoutMs % 60_000 === 0)
            return `${Math.floor(timeoutMs / 60_000)}m`;
        if (timeoutMs % 1000 === 0)
            return `${Math.floor(timeoutMs / 1000)}s`;
        return `${Math.floor(timeoutMs)}ms`;
    }
    return "unknown time";
}
function getExecErrorCodeText(error) {
    if (typeof error.code === "string")
        return error.code;
    if (Buffer.isBuffer(error.code))
        return toUtf8String(error.code);
    if (typeof error.code === "number")
        return String(error.code);
    return "";
}
function isExecTimeoutError(error) {
    if (getExecErrorCodeText(error).toUpperCase() === "ETIMEDOUT")
        return true;
    return error.killed === true && error.signal === "SIGTERM";
}
function getExecErrorMessage(error) {
    return typeof error.message === "string" && error.message.trim().length > 0
        ? error.message.trim()
        : "CLI command failed";
}
function getExecCommand(error, cmd, args) {
    return typeof error.cmd === "string" && error.cmd.trim().length > 0
        ? error.cmd.trim()
        : [cmd, ...args].join(" ");
}
function abortReason(signal) {
    return signal.reason instanceof Error
        ? signal.reason
        : new DOMException("This operation was aborted", "AbortError");
}
export async function execCliWithInput({ execFileImpl, cmd, args, input, timeoutMs, env, cwd, signal, }) {
    return await new Promise((resolve, reject) => {
        let interruptedSignal = null;
        let aborted = false;
        let child = null;
        const forwardSignal = (signal) => {
            if (interruptedSignal)
                return;
            interruptedSignal = signal;
            try {
                child?.kill(signal);
            }
            catch {
                // Process may have already exited between signal delivery and forwarding.
            }
        };
        const handleSigint = () => forwardSignal("SIGINT");
        const handleSigterm = () => forwardSignal("SIGTERM");
        const handleAbort = () => {
            if (aborted)
                return;
            aborted = true;
            try {
                child?.kill("SIGTERM");
            }
            catch {
                // Process may have already exited between cancellation and forwarding.
            }
        };
        const cleanupSignalHandlers = () => {
            process.removeListener("SIGINT", handleSigint);
            process.removeListener("SIGTERM", handleSigterm);
            signal?.removeEventListener("abort", handleAbort);
        };
        if (signal?.aborted) {
            reject(abortReason(signal));
            return;
        }
        // Run before progress UI exit handlers so active CLI backends don't survive Ctrl+C.
        process.prependOnceListener("SIGINT", handleSigint);
        process.prependOnceListener("SIGTERM", handleSigterm);
        signal?.addEventListener("abort", handleAbort, { once: true });
        child = execFileImpl(cmd, args, {
            timeout: timeoutMs,
            env: { ...process.env, ...env },
            cwd,
            maxBuffer: 50 * 1024 * 1024,
        }, (error, stdout, stderr) => {
            cleanupSignalHandlers();
            const stderrText = toUtf8String(stderr);
            if (aborted && signal) {
                reject(abortReason(signal));
                return;
            }
            if (interruptedSignal) {
                reject(new CliInterruptedError(interruptedSignal));
                return;
            }
            if (error) {
                if (isExecTimeoutError(error)) {
                    const timeoutMessage = `CLI command timed out after ${formatTimeoutLabel(timeoutMs)}: ${getExecCommand(error, cmd, args)}. ` +
                        "Increase --timeout (e.g. 5m).";
                    reject(new Error(formatErrorMessageWithStderr(timeoutMessage, stderrText, "\n"), {
                        cause: error,
                    }));
                    return;
                }
                reject(new Error(formatErrorMessageWithStderr(getExecErrorMessage(error), stderrText), {
                    cause: error,
                }));
                return;
            }
            resolve({ stdout: toUtf8String(stdout), stderr: stderrText });
        });
        if (aborted) {
            try {
                child.kill("SIGTERM");
            }
            catch {
                // Process may have exited synchronously in an injected exec implementation.
            }
        }
        if (child.stdin) {
            child.stdin.write(input);
            child.stdin.end();
        }
    });
}
//# sourceMappingURL=cli-exec.js.map