import { createRequire } from "node:module";
import { existsSync } from "node:fs";
import { extname } from "node:path";
import { execCommand } from "./spawn.js";
import { isWindowsCommandScript } from "./windows-command.js";
export { quoteWindowsArgument, quoteWindowsCommand } from "./windows-command.js";
const require = createRequire(import.meta.url);
const which = require("which");
const PROBE_TIMEOUT_MS = 2000;
function hasPathSeparator(value) {
    return value.includes("/") || value.includes("\\");
}
async function enumerateCandidates(name) {
    if (process.platform !== "win32" && existsSync("/usr/bin/which")) {
        return enumerateCandidatesViaSystemWhich(name);
    }
    return enumerateCandidatesViaLibrary(name);
}
async function enumerateCandidatesViaSystemWhich(name) {
    try {
        const { stdout } = await execCommand("/usr/bin/which", ["-a", name], {
            timeout: 3000,
            killSignal: "SIGKILL",
        });
        return Array.from(new Set(stdout.trim().split("\n").filter(Boolean)));
    }
    catch {
        return [];
    }
}
async function enumerateCandidatesViaLibrary(name) {
    let candidates;
    try {
        candidates = await which(name, { all: true });
    }
    catch (error) {
        // `which` throws ENOENT when the command is absent from PATH.
        if (error.code === "ENOENT") {
            return [];
        }
        throw error;
    }
    const seen = new Set();
    return candidates.filter((candidate) => {
        if (seen.has(candidate)) {
            return false;
        }
        seen.add(candidate);
        return true;
    });
}
export async function probeExecutable(executablePath, timeoutMs = PROBE_TIMEOUT_MS) {
    try {
        await execCommand(executablePath, ["--version"], {
            timeout: timeoutMs,
            killSignal: "SIGKILL",
            maxBuffer: 64 * 1024,
            shell: isWindowsCommandScript(executablePath),
        });
        return true;
    }
    catch (error) {
        return classifyProbeError(error);
    }
}
function classifyProbeError(error) {
    const err = error;
    if (err.killed) {
        return true;
    }
    if (typeof err.code === "number") {
        return true;
    }
    if (err.code === "ENOENT" ||
        err.code === "EACCES" ||
        err.code === "ENOEXEC" ||
        err.code === "UNKNOWN") {
        return false;
    }
    return false;
}
/**
 * Check a literal executable path. PATH search is handled by findExecutable().
 */
export function executableExists(executablePath, exists = existsSync) {
    if (exists(executablePath))
        return executablePath;
    if (process.platform === "win32" && !extname(executablePath)) {
        for (const ext of [".exe", ".cmd"]) {
            const candidate = executablePath + ext;
            if (exists(candidate))
                return candidate;
        }
    }
    return null;
}
export async function findExecutable(name, probeTimeoutMs = PROBE_TIMEOUT_MS) {
    const trimmed = name.trim();
    if (!trimmed) {
        return null;
    }
    if (hasPathSeparator(trimmed)) {
        return (await probeExecutable(trimmed, probeTimeoutMs)) ? trimmed : null;
    }
    const candidates = await enumerateCandidates(trimmed);
    for (const candidate of candidates) {
        if (await probeExecutable(candidate, probeTimeoutMs)) {
            return candidate;
        }
    }
    return null;
}
export async function isCommandAvailable(command) {
    return (await findExecutable(command)) !== null;
}
//# sourceMappingURL=executable.js.map