import { accessSync, constants, realpathSync } from "node:fs";
import path from "node:path";
/**
 * Prefer the host Codex CLI so normal `codex` updates are picked up by the
 * connector. Falls back to the SDK-bundled CLI when no external binary exists.
 */
export function resolveCodexCli(env = process.env) {
    const explicitPath = optionalString(env.CODEX_CLI_PATH);
    if (explicitPath) {
        return { path: explicitPath, source: "env" };
    }
    if (isEnabled(env.CODEX_USE_BUNDLED_CLI)) {
        return { source: "bundled" };
    }
    const pathMatch = findExecutableOnPath("codex", codexSearchPath(env), { pathext: env.PATHEXT });
    return pathMatch ? { path: pathMatch, source: "path" } : { source: "bundled" };
}
export function describeCodexCli(resolution) {
    if (resolution.path) {
        return `${resolution.source} (${resolution.path})`;
    }
    return "bundled @openai/codex";
}
export function findExecutableOnPath(command, pathValue, options = {}) {
    if (!pathValue) {
        return undefined;
    }
    const extensions = executableExtensions(options.platform ?? process.platform, options.pathext ?? process.env.PATHEXT);
    for (const rawDirectory of pathValue.split(path.delimiter)) {
        const directory = rawDirectory.trim();
        if (!directory) {
            continue;
        }
        for (const extension of extensions) {
            const candidate = path.join(directory, `${command}${extension}`);
            if (!isExecutable(candidate) || isProjectLocalBin(candidate)) {
                continue;
            }
            return candidate;
        }
    }
    return undefined;
}
function executableExtensions(platform, pathextValue) {
    if (platform !== "win32") {
        return [""];
    }
    const pathext = (pathextValue || ".COM;.EXE;.BAT;.CMD")
        .split(";")
        .map((extension) => extension.trim())
        .filter(Boolean)
        .map((extension) => extension.startsWith(".") ? extension : `.${extension}`);
    return [...new Set([...pathext, ""])];
}
function isExecutable(filePath) {
    try {
        accessSync(filePath, constants.X_OK);
        return true;
    }
    catch {
        return false;
    }
}
function isProjectLocalBin(filePath) {
    const absolute = path.resolve(filePath);
    const resolved = resolveRealPath(filePath);
    const localBin = path.resolve(process.cwd(), "node_modules", ".bin");
    return (absolute === localBin ||
        absolute.startsWith(`${localBin}${path.sep}`) ||
        resolved === localBin ||
        resolved.startsWith(`${localBin}${path.sep}`));
}
function resolveRealPath(filePath) {
    try {
        return realpathSync(filePath);
    }
    catch {
        return path.resolve(filePath);
    }
}
function optionalString(value) {
    const trimmed = value?.trim();
    return trimmed ? trimmed : undefined;
}
function isEnabled(value) {
    const normalized = value?.trim().toLowerCase();
    return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
}
function codexSearchPath(env) {
    return prependUniquePathDirs(env.PATH, [
        path.dirname(process.execPath),
        env.APPDATA ? path.join(env.APPDATA, "npm") : undefined,
    ]);
}
function prependUniquePathDirs(pathValue, directories) {
    const existing = (pathValue || "")
        .split(path.delimiter)
        .map((item) => item.trim())
        .filter(Boolean);
    const normalized = new Set(existing.map((item) => normalizePathKey(item)));
    const prefix = [];
    for (const directory of directories) {
        if (!directory) {
            continue;
        }
        const resolved = path.resolve(directory);
        const key = normalizePathKey(resolved);
        if (normalized.has(key)) {
            continue;
        }
        normalized.add(key);
        prefix.push(resolved);
    }
    return [...prefix, ...existing].join(path.delimiter) || undefined;
}
function normalizePathKey(value) {
    const resolved = path.resolve(value);
    return process.platform === "win32" ? resolved.toLowerCase() : resolved;
}
