import { spawn } from "node:child_process";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { z } from "zod";
import { log } from "../core/logger.js";
const SOURCE_PATH_TIMEOUT_MS = 120_000;
const MAX_STDERR_CHARS = 10_000;
const OPENSRC_BIN_ENV = "EZ_CODER_OPENSRC_BIN";
const LEGACY_OPENSRC_BIN_ENV = "EZ_CODER_OPENSRC_BIN";
const SourcePathParams = z.object({
    package: z
        .string()
        .min(1)
        .describe("Package, repo, or registry spec to fetch (e.g. zod, zod@3.22.0, pypi:requests, crates:serde, vercel/next.js)"),
    verbose: z.boolean().optional().describe("Show opensrc fetch progress in the tool result"),
});
export function createSourcePathTool(cwd) {
    return {
        name: "source_path",
        description: "Resolve a package or repository to its cached source-code path using opensrc. " +
            "For npm packages, lockfiles in the current project determine the installed version unless a version is specified. " +
            "Use this before assuming dependency APIs or framework internals; then inspect the returned path with read, grep, find, or ls.",
        parameters: SourcePathParams,
        async execute(args, context) {
            const startTime = Date.now();
            const opensrcBin = getBundledOpenSrcBinPath();
            const cliArgs = ["path", args.package, "--cwd", cwd];
            if (args.verbose === true)
                cliArgs.push("--verbose");
            log("INFO", "source_path", "opensrc path start", { package: args.package, cwd });
            return new Promise((resolve) => {
                const child = spawn(process.execPath, [opensrcBin, ...cliArgs], {
                    cwd,
                    stdio: ["ignore", "pipe", "pipe"],
                    env: getOpenSrcEnv(),
                });
                let stdout = "";
                let stderr = "";
                let settled = false;
                const finish = (message) => {
                    if (settled)
                        return;
                    settled = true;
                    clearTimeout(timer);
                    context.signal.removeEventListener("abort", onAbort);
                    resolve(message);
                };
                const timer = setTimeout(() => {
                    child.kill("SIGTERM");
                    finish(`Error: opensrc timed out after ${SOURCE_PATH_TIMEOUT_MS}ms while resolving ${args.package}.`);
                }, SOURCE_PATH_TIMEOUT_MS);
                const onAbort = () => {
                    child.kill("SIGTERM");
                    finish(`Error: source_path was aborted while resolving ${args.package}.`);
                };
                context.signal.addEventListener("abort", onAbort, { once: true });
                if (context.signal.aborted)
                    onAbort();
                child.stdout?.on("data", (chunk) => {
                    stdout += chunk.toString("utf-8");
                });
                child.stderr?.on("data", (chunk) => {
                    if (stderr.length >= MAX_STDERR_CHARS)
                        return;
                    stderr += chunk.toString("utf-8");
                    if (stderr.length > MAX_STDERR_CHARS)
                        stderr = stderr.slice(0, MAX_STDERR_CHARS);
                });
                child.on("error", (error) => {
                    log("ERROR", "source_path", "opensrc spawn failed", {
                        package: args.package,
                        error: error.message,
                    });
                    finish(`Error: could not run bundled opensrc for ${args.package}: ${error.message}. ` +
                        "Try installing ezcoder again or run `npm install -g opensrc`.");
                });
                child.on("close", (code) => {
                    const durationMs = Date.now() - startTime;
                    log(code === 0 ? "INFO" : "WARN", "source_path", "opensrc path done", {
                        package: args.package,
                        exitCode: String(code ?? 1),
                        durationMs: String(durationMs),
                    });
                    if (code !== 0) {
                        finish(`Error: opensrc failed for ${args.package} (exit ${code ?? 1}).\n` +
                            `${stderr.trim() || stdout.trim() || "No output."}`);
                        return;
                    }
                    const lines = stdout
                        .split("\n")
                        .map((line) => line.trim())
                        .filter((line) => line.length > 0);
                    const sourcePath = lines.at(-1);
                    if (!sourcePath) {
                        finish(`Error: opensrc returned no source path for ${args.package}.`);
                        return;
                    }
                    const progress = args.verbose === true && lines.length > 1 ? lines.slice(0, -1).join("\n") : "";
                    const pathLine = `Source path: ${sourcePath}`;
                    const nextSteps = "Use read, grep, find, or ls with this absolute path to inspect the dependency source.";
                    finish(progress ? `${progress}\n${pathLine}\n${nextSteps}` : `${pathLine}\n${nextSteps}`);
                });
            });
        },
    };
}
function getBundledOpenSrcBinPath() {
    const override = process.env[OPENSRC_BIN_ENV]?.trim() ?? process.env[LEGACY_OPENSRC_BIN_ENV]?.trim();
    if (override)
        return override;
    const currentDir = path.dirname(fileURLToPath(import.meta.url));
    return path.resolve(currentDir, "../../node_modules/opensrc/bin/opensrc.js");
}
function getOpenSrcEnv() {
    return {
        ...process.env,
        TERM: process.env.TERM ?? "dumb",
    };
}
//# sourceMappingURL=source-path.js.map