/**
 * ============================================================
 *  src/tools/rgPath.ts —— 解析 ripgrep 可执行文件路径
 * ------------------------------------------------------------
 *  策略（按优先级）：
 *    ① MINIMAL_AGENT_RIPGREP_PATH 环境变量（手动指定，调试时最高优先）
 *    ② 项目自带的 vendored rg —— vendor/ripgrep/<arch>-<platform>/rg(.exe)
 *       这是默认路径！开箱即用，不依赖系统装没装。
 *    ③ 系统 PATH 上的 rg (用户自己装的 brew/scoop/apt 版本)
 *    ④ Claude Code 全局 npm 包自带的 rg（兜底）
 *
 *  借鉴：kakadeai/utils/ripgrep.ts 的 getRipgrepConfig，但砍掉了：
 *    - 嵌入式 Bun 模式（我们不用 bun build 打包）
 *    - 单线程 EAGAIN 重试（最小化版用不到）
 *
 *  vendor/ripgrep 6 个平台二进制（arm64/x64 × win32/darwin/linux）从
 *  Claude Code 自带的同一份原料复制而来，许可证 MIT/Unlicense，可自由分发。
 *  macOS 上首次跑可能需要 codesign（见 codesignIfNeeded 注释）。
 *
 *  用法：
 *    const rg = await resolveRgPath();
 *    if (!rg) → 返回友好错误
 *    spawn(rg, args)
 * ============================================================
 */
import { spawn } from 'node:child_process';
import { chmodSync, existsSync } from 'node:fs';
import { resolve } from 'node:path';
import { execSiblingDir } from '../../utils/greenRoot.js';
import { findPackageRoot } from '../../utils/packageRoot.js';
let cached;
/**
 * 解析一次 rg 路径，结果缓存（同一进程内复用）。
 * 返回 null 表示找不到。
 */
export async function resolveRgPath() {
    if (cached !== undefined)
        return cached;
    cached = await detect();
    return cached;
}
/** 仅供测试重置缓存 */
export function _resetRgCache() {
    cached = undefined;
}
async function detect() {
    // ① 显式 env 覆盖
    const fromEnv = process.env.MINIMAL_AGENT_RIPGREP_PATH;
    if (fromEnv && existsSync(fromEnv))
        return fromEnv;
    // ② 项目自带的 vendored rg（默认路径！开箱即用）
    const vendored = vendoredRgPath();
    if (vendored && existsSync(vendored)) {
        ensureExecutable(vendored);
        return vendored;
    }
    // ②.5 绿色版（bun --compile 单文件）：exe 同级 vendor/ripgrep/<arch>-<platform>/rg
    //   compile 后 import.meta.url 是虚拟路径，findPackageRoot 失效（② 命不中），
    //   绿色目录把 vendor/ 摆在可执行文件旁边，从这里捞回来。dev/npm 下 exe 同级
    //   没有 vendor/ → existsSync 过滤掉 → 不影响 ②/③/④ 原有优先级。
    const sibling = resolve(execSiblingDir(), 'vendor', 'ripgrep', subdir(), exeName());
    if (existsSync(sibling)) {
        ensureExecutable(sibling);
        return sibling;
    }
    // ③ PATH 上的 rg —— 直接试 spawn rg --version
    if (await trySpawn('rg'))
        return 'rg';
    // ④ Claude Code 全局 npm 包自带的 rg
    for (const candidate of claudeCodeCandidates()) {
        if (existsSync(candidate)) {
            ensureExecutable(candidate);
            return candidate;
        }
    }
    return null;
}
/**
 * 项目内 vendor 路径：vendor/ripgrep/<arch>-<platform>/rg(.exe)
 * 路径以本文件为锚点向上找，不依赖 cwd（用户在任何目录跑 minimal-agent 都行）。
 */
function vendoredRgPath() {
    try {
        const projectRoot = findPackageRoot(import.meta.url);
        return resolve(projectRoot, 'vendor', 'ripgrep', subdir(), exeName());
    }
    catch {
        return null;
    }
}
function subdir() {
    return `${process.arch}-${process.platform}`;
}
function exeName() {
    return process.platform === 'win32' ? 'rg.exe' : 'rg';
}
/**
 * Unix 上 git/tarball/cp 偶尔会丢可执行位。Windows 不需要。
 * 失败也忽略（最坏情况下后面 spawn 会报权限错，给出明确信号）。
 */
function ensureExecutable(path) {
    if (process.platform === 'win32')
        return;
    try {
        chmodSync(path, 0o755);
    }
    catch {
        /* ignore */
    }
}
/**
 * 试着 spawn 一下，看 --version 能不能跑通。
 * 注意：直接传 'rg'（不是绝对路径）让 OS 解析 PATH，
 * 在 Windows 上配合 NoDefaultCurrentDirectoryInExePath 防 PATH 劫持。
 */
function trySpawn(cmd) {
    return new Promise((resolveP) => {
        let settled = false;
        const done = (ok) => {
            if (settled)
                return;
            settled = true;
            resolveP(ok);
        };
        try {
            const child = spawn(cmd, ['--version'], {
                stdio: ['ignore', 'pipe', 'ignore'],
                windowsHide: true,
            });
            let stdout = '';
            child.stdout.setEncoding('utf8');
            child.stdout.on('data', (c) => (stdout += c));
            child.on('error', () => done(false));
            child.on('close', (code) => done(code === 0 && stdout.startsWith('ripgrep ')));
            // 兜底超时：5 秒
            setTimeout(() => {
                try {
                    child.kill();
                }
                catch { }
                done(false);
            }, 5_000);
        }
        catch {
            done(false);
        }
    });
}
/**
 * 枚举 Claude Code 自带 rg 在常见安装路径下的位置。
 * 不存在的路径不会被实际访问 —— 调用方用 existsSync 过滤。
 */
function claudeCodeCandidates() {
    const arch = process.arch; // 'x64' | 'arm64' | ...
    const platform = process.platform; // 'win32' | 'darwin' | 'linux' | ...
    const subdir = `${arch}-${platform}`;
    const exe = platform === 'win32' ? 'rg.exe' : 'rg';
    // 各种 npm global 安装路径
    const npmRoots = [];
    if (platform === 'win32') {
        // npm 默认 prefix: %APPDATA%/npm
        if (process.env.APPDATA) {
            npmRoots.push(resolve(process.env.APPDATA, 'npm', 'node_modules'));
        }
        // 也试试 user profile/AppData/Roaming（Bash on Windows 偶尔 APPDATA 没设）
        if (process.env.USERPROFILE) {
            npmRoots.push(resolve(process.env.USERPROFILE, 'AppData', 'Roaming', 'npm', 'node_modules'));
        }
    }
    else {
        // Unix：常见的 npm global prefix
        const home = process.env.HOME ?? '';
        if (home) {
            npmRoots.push(resolve(home, '.npm-global', 'lib', 'node_modules'));
            npmRoots.push(resolve(home, '.npm', 'lib', 'node_modules'));
            npmRoots.push(resolve(home, 'node_modules'));
        }
        npmRoots.push('/usr/local/lib/node_modules');
        npmRoots.push('/usr/lib/node_modules');
        npmRoots.push('/opt/homebrew/lib/node_modules');
    }
    return npmRoots.map((root) => resolve(root, '@anthropic-ai', 'claude-code', 'vendor', 'ripgrep', subdir, exe));
}
