import { execFileSync as nodeExecFileSync } from "node:child_process";
import { accessSync, constants, existsSync, realpathSync } from "node:fs";
import { homedir } from "node:os";
import { basename, dirname, join, resolve } from "node:path";
function runText(execFileSync, bin, args) {
    return String(execFileSync(bin, args, {
        encoding: "utf8",
        timeout: 10_000,
        stdio: ["ignore", "pipe", "pipe"],
    })).trim();
}
function writableDirectory(path) {
    try {
        if (!existsSync(path)) {
            return false;
        }
        accessSync(path, constants.W_OK);
        return true;
    }
    catch {
        return false;
    }
}
function isPathInside(path, parent) {
    if (!path) {
        return false;
    }
    try {
        const candidate = realpathSync(path);
        const root = realpathSync(parent);
        return candidate === root || candidate.startsWith(`${root}/`);
    }
    catch {
        const candidate = resolve(path);
        const root = resolve(parent);
        return candidate === root || candidate.startsWith(`${root}/`);
    }
}
function probeInstaller(kind, bin, entryScript, execFileSync) {
    const base = {
        kind,
        bin,
        working: false,
        installable: false,
        matchesCurrentInstall: false,
    };
    try {
        base.version = runText(execFileSync, bin, ["--version"]);
        base.working = base.version.length > 0;
        base.globalRoot = runText(execFileSync, bin, ["root", "-g"]);
        base.globalBinDir =
            kind === "pnpm"
                ? runText(execFileSync, bin, ["bin", "-g"])
                : join(runText(execFileSync, bin, ["prefix", "-g"]), "bin");
        if (!base.globalRoot || !writableDirectory(base.globalRoot)) {
            base.reason = "global package root is missing or not writable";
            return base;
        }
        if (!base.globalBinDir || !writableDirectory(base.globalBinDir)) {
            base.reason = "global executable directory is missing or not writable";
            return base;
        }
        base.installable = true;
        base.matchesCurrentInstall = isPathInside(entryScript, base.globalRoot);
        return base;
    }
    catch (error) {
        base.reason = error instanceof Error ? error.message : String(error);
        return base;
    }
}
function resolveFromPath(command, execFileSync) {
    try {
        const result = runText(execFileSync, "which", [command]);
        return result || undefined;
    }
    catch {
        return undefined;
    }
}
/** Resolve a package manager that can update the installation currently running. */
export function resolveGlobalInstaller(options = {}) {
    const env = options.env ?? process.env;
    const homeDir = options.homeDir ?? homedir();
    const entryScript = options.entryScript ?? process.argv[1];
    const execFileSync = options.execFileSync ?? nodeExecFileSync;
    const candidates = [];
    if (env.NEUROLINK_PACKAGE_MANAGER_PATH) {
        const configuredKind = env.NEUROLINK_PACKAGE_MANAGER?.toLowerCase();
        const inferredName = basename(env.NEUROLINK_PACKAGE_MANAGER_PATH);
        const kind = configuredKind === "npm" || configuredKind === "pnpm"
            ? configuredKind
            : inferredName.startsWith("npm")
                ? "npm"
                : "pnpm";
        candidates.push({ kind, bin: env.NEUROLINK_PACKAGE_MANAGER_PATH });
    }
    if (env.NEUROLINK_PNPM_PATH) {
        candidates.push({ kind: "pnpm", bin: env.NEUROLINK_PNPM_PATH });
    }
    if (env.PNPM_HOME) {
        candidates.push({ kind: "pnpm", bin: join(env.PNPM_HOME, "pnpm") });
    }
    const nodeBinDir = dirname(process.execPath);
    candidates.push({ kind: "npm", bin: join(nodeBinDir, "npm") });
    const pathPnpm = resolveFromPath("pnpm", execFileSync);
    const pathNpm = resolveFromPath("npm", execFileSync);
    if (pathPnpm) {
        candidates.push({ kind: "pnpm", bin: pathPnpm });
    }
    if (pathNpm) {
        candidates.push({ kind: "npm", bin: pathNpm });
    }
    candidates.push({ kind: "pnpm", bin: join(homeDir, ".local", "share", "pnpm", "pnpm") }, { kind: "pnpm", bin: join(homeDir, "Library", "pnpm", "pnpm") }, { kind: "npm", bin: "/opt/homebrew/bin/npm" }, { kind: "npm", bin: "/usr/local/bin/npm" });
    const seen = new Set();
    const tried = candidates
        .filter(({ kind, bin }) => {
        const key = `${kind}:${bin}`;
        if (!bin || seen.has(key)) {
            return false;
        }
        seen.add(key);
        return true;
    })
        .map(({ kind, bin }) => probeInstaller(kind, bin, entryScript, execFileSync));
    const matchingInstaller = tried.find((probe) => probe.installable && probe.matchesCurrentInstall);
    // When the running entry script is known, installing into a different
    // global root cannot update that process and may shadow another install.
    const installer = entryScript
        ? matchingInstaller
        : (matchingInstaller ?? tried.find((probe) => probe.installable));
    return { installer, tried };
}
export function getGlobalInstallArgs(kind, packageSpec) {
    return kind === "pnpm"
        ? ["add", "-g", packageSpec]
        : ["install", "--global", "--no-audit", "--no-fund", packageSpec];
}
function capturedOutput(error, key) {
    if (!error || typeof error !== "object" || !(key in error)) {
        return "";
    }
    const raw = error[key];
    return String(raw ?? "")
        .trim()
        .slice(0, 1_000);
}
export function describeInstallFailure(error) {
    const message = error instanceof Error ? error.message : String(error);
    const stdout = capturedOutput(error, "stdout");
    const stderr = capturedOutput(error, "stderr");
    return [message, stdout && `stdout: ${stdout}`, stderr && `stderr: ${stderr}`]
        .filter(Boolean)
        .join("\n");
}
//# sourceMappingURL=globalInstaller.js.map