import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("../../utils/logger.js", () => ({
    logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },
}));
const mockExecFileAsync = vi.fn();
vi.mock("node:util", () => ({
    promisify: () => mockExecFileAsync,
}));
const mockAccessSync = vi.fn();
vi.mock("node:fs", () => ({
    accessSync: (...args) => mockAccessSync(...args),
    constants: { X_OK: 1 },
}));
// Use dynamic import so mocks are applied
const { detectOpenCode } = await import("../detect.js");
describe("detectOpenCode", () => {
    const originalEnv = process.env;
    beforeEach(() => {
        vi.clearAllMocks();
        process.env = { ...originalEnv };
        delete process.env.OPENCODE_PATH;
    });
    afterEach(() => {
        process.env = originalEnv;
    });
    it("should find via OPENCODE_PATH when set and executable", async () => {
        process.env.OPENCODE_PATH = "/custom/path/opencode";
        mockAccessSync.mockReturnValue(undefined);
        mockExecFileAsync.mockResolvedValue({ stdout: "opencode v1.5.0\n" });
        const result = await detectOpenCode();
        expect(result).toEqual({
            available: true,
            path: "/custom/path/opencode",
            version: "opencode v1.5.0",
            source: "env",
        });
    });
    it("should skip OPENCODE_PATH if not executable", async () => {
        process.env.OPENCODE_PATH = "/bad/path/opencode";
        mockAccessSync.mockImplementation(() => {
            throw new Error("EACCES");
        });
        // which finds it in PATH
        mockExecFileAsync.mockImplementation((cmd) => {
            if (cmd === "which")
                return Promise.resolve({ stdout: "/usr/bin/opencode\n" });
            // version check for PATH binary
            return Promise.resolve({ stdout: "opencode v2.0.0\n" });
        });
        const result = await detectOpenCode();
        expect(result.source).toBe("path");
        expect(result.path).toBe("/usr/bin/opencode");
    });
    it("should find in PATH via which", async () => {
        mockExecFileAsync.mockImplementation((cmd) => {
            if (cmd === "which")
                return Promise.resolve({ stdout: "/usr/local/bin/opencode\n" });
            return Promise.resolve({ stdout: "opencode v1.2.3\n" });
        });
        const result = await detectOpenCode();
        expect(result).toEqual({
            available: true,
            path: "/usr/local/bin/opencode",
            version: "opencode v1.2.3",
            source: "path",
        });
    });
    it("should check common locations when not in PATH", async () => {
        // which fails
        mockExecFileAsync.mockImplementation((cmd, args) => {
            if (cmd === "which")
                return Promise.reject(new Error("not found"));
            // version check for common location
            if (args?.[0] === "--version")
                return Promise.resolve({ stdout: "opencode v0.9.0\n" });
            return Promise.reject(new Error("unknown"));
        });
        // First common location is executable
        mockAccessSync.mockReturnValue(undefined);
        const result = await detectOpenCode();
        expect(result.available).toBe(true);
        expect(result.source).toBe("common");
        expect(result.version).toBe("opencode v0.9.0");
    });
    it("should return not available when not found anywhere", async () => {
        // which fails
        mockExecFileAsync.mockRejectedValue(new Error("not found"));
        // all common locations not executable
        mockAccessSync.mockImplementation(() => {
            throw new Error("ENOENT");
        });
        const result = await detectOpenCode();
        expect(result).toEqual({ available: false });
    });
    it("should skip OPENCODE_PATH when version check fails", async () => {
        process.env.OPENCODE_PATH = "/custom/opencode";
        // executable check passes
        mockAccessSync.mockReturnValue(undefined);
        // all execFile calls fail
        mockExecFileAsync.mockRejectedValue(new Error("command failed"));
        const result = await detectOpenCode();
        expect(result).toEqual({ available: false });
    });
    it("should skip PATH binary when version check fails", async () => {
        mockExecFileAsync.mockImplementation((cmd) => {
            if (cmd === "which")
                return Promise.resolve({ stdout: "/usr/bin/opencode\n" });
            // version check fails
            return Promise.reject(new Error("segfault"));
        });
        // common locations not executable
        mockAccessSync.mockImplementation(() => {
            throw new Error("ENOENT");
        });
        const result = await detectOpenCode();
        expect(result).toEqual({ available: false });
    });
    it("should handle empty OPENCODE_PATH", async () => {
        process.env.OPENCODE_PATH = "";
        mockExecFileAsync.mockRejectedValue(new Error("not found"));
        mockAccessSync.mockImplementation(() => {
            throw new Error("ENOENT");
        });
        const result = await detectOpenCode();
        // Empty string is falsy, so it skips env check
        expect(result).toEqual({ available: false });
    });
    it("should prefer OPENCODE_PATH over PATH", async () => {
        process.env.OPENCODE_PATH = "/custom/opencode";
        mockAccessSync.mockReturnValue(undefined);
        mockExecFileAsync.mockResolvedValue({ stdout: "opencode v3.0.0\n" });
        const result = await detectOpenCode();
        expect(result.source).toBe("env");
        expect(result.path).toBe("/custom/opencode");
    });
    it("should handle which returning multiple paths (takes first)", async () => {
        mockExecFileAsync.mockImplementation((cmd) => {
            if (cmd === "which") {
                return Promise.resolve({ stdout: "/first/opencode\n/second/opencode\n" });
            }
            return Promise.resolve({ stdout: "opencode v1.0.0\n" });
        });
        const result = await detectOpenCode();
        expect(result.path).toBe("/first/opencode");
    });
});
//# sourceMappingURL=detect.test.js.map