import { existsSync, readFileSync } from "node:fs";
import { spawnSync } from "node:child_process";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { OPENCODE_MIN_VERSION, OPENCODE_TESTED_VERSION, SEED_DEFAULT_AGENT, SEED_PACKAGE_NAME } from "./constants.js";
import { isMissingExecutable, runOpenCodeSync } from "./opencode-command.js";
import { readOpenCodePluginSpecs } from "./opencode-config.js";
import { projectOpenCodeConfigPath, projectSeedConfigPath, resolveOpenCodeConfigPath, resolveUserSeedConfigPath } from "./paths.js";
import { isSeedSupportedPlatform, unsupportedPlatformMessage } from "./platform.js";
import { stripJsonComments } from "../config/loader.js";
export async function runDoctor(options = {}) {
    const cwd = options.cwd ?? process.cwd();
    const spawn = options.spawn ?? spawnSync;
    const opencodeConfigPath = options.config ?? (options.project ? projectOpenCodeConfigPath(cwd) : resolveOpenCodeConfigPath());
    const seedConfigPath = options.seedConfig ?? (options.project ? projectSeedConfigPath(cwd) : resolveUserSeedConfigPath());
    const platform = options.platform ?? process.platform;
    const checks = [
        isSeedSupportedPlatform(platform)
            ? { name: "platform", status: "pass", message: "Linux runtime supported." }
            : { name: "platform", status: "fail", message: unsupportedPlatformMessage(platform) },
    ];
    if (!isSeedSupportedPlatform(platform))
        return { checks, exitCode: 1 };
    const commandOptions = { env: options.env, platform };
    const version = run(spawn, ["--version"], cwd, commandOptions);
    checks.push(opencodeVersionCheck(version));
    if (!existsSync(opencodeConfigPath)) {
        checks.push({ name: "plugin config", status: "fail", message: `OpenCode config not found: ${opencodeConfigPath}` });
    }
    else {
        const plugins = await readOpenCodePluginSpecs(opencodeConfigPath);
        const hasSeed = plugins.some((spec) => isSeedPluginSpec(spec, opencodeConfigPath, cwd));
        checks.push(hasSeed
            ? { name: "plugin config", status: "pass", message: `SEED plugin registered in ${opencodeConfigPath}` }
            : { name: "plugin config", status: "fail", message: `SEED plugin missing from ${opencodeConfigPath}` });
    }
    const seedConfigExists = existsSync(seedConfigPath);
    checks.push(seedConfigExists
        ? { name: "seed config", status: "pass", message: `SEED config found: ${seedConfigPath}` }
        : { name: "seed config", status: "warn", message: `SEED config not found; defaults will be used: ${seedConfigPath}` });
    const providers = run(spawn, ["providers", "list"], cwd, commandOptions);
    const providerOutput = `${providers.stdout}\n${providers.stderr}`;
    const configuredProviders = seedConfigExists ? configuredModelProviders(seedConfigPath) : [];
    checks.push(providerVisibilityCheck(providers, providerOutput, configuredProviders));
    if (options.includeAgents) {
        const agents = run(spawn, ["agent", "list"], cwd, commandOptions);
        const output = `${agents.stdout}\n${agents.stderr}`;
        checks.push(agentListCheck(agents.status ?? 1, output));
    }
    const exitCode = checks.some((check) => check.status === "fail") ? 1 : 0;
    return { checks, exitCode };
}
function providerVisibilityCheck(providers, output, configuredProviders) {
    if (isMissingExecutable(providers)) {
        return {
            name: "provider auth",
            status: "warn",
            message: "Provider visibility could not be checked because the OpenCode CLI did not run.",
        };
    }
    if (providers.status !== 0) {
        return {
            name: "provider auth",
            status: "warn",
            message: "Provider visibility could not be checked because `opencode providers list` failed.",
        };
    }
    if (configuredProviders.length === 0) {
        return {
            name: "provider auth",
            status: "pass",
            message: "SEED config is model-neutral; the OpenCode selected model/provider will be used at runtime.",
        };
    }
    const missing = configuredProviders.filter((provider) => !new RegExp(escapeRegExp(provider), "i").test(output));
    if (missing.length === 0) {
        return {
            name: "provider auth",
            status: "pass",
            message: `OpenCode reports configured provider availability: ${configuredProviders.join(", ")}. API key values were not read or printed.`,
        };
    }
    return {
        name: "provider auth",
        status: "warn",
        message: `Configured provider(s) were not visible in \`opencode providers list\`: ${missing.join(", ")}. Run \`opencode providers login -p <provider>\` or export the provider API key before starting OpenCode.`,
    };
}
function configuredModelProviders(seedConfigPath) {
    try {
        const parsed = JSON.parse(stripJsonComments(readFileSync(seedConfigPath, "utf8")));
        const models = [
            ...modelsFromRecord(recordValue(parsed.model_profiles)),
            ...modelsFromRecord(recordValue(parsed.agents)),
        ];
        return uniqueStrings(models.map(providerFromModel).filter((provider) => Boolean(provider)));
    }
    catch {
        return [];
    }
}
function modelsFromRecord(record) {
    const result = [];
    for (const value of Object.values(record)) {
        if (typeof value === "object" && value !== null && !Array.isArray(value)) {
            const model = value.model;
            if (typeof model === "string" && model.trim())
                result.push(model.trim());
            const fallbacks = value.fallbacks;
            if (Array.isArray(fallbacks)) {
                for (const fallback of fallbacks) {
                    if (typeof fallback === "string" && fallback.trim()) {
                        result.push(fallback.trim());
                    }
                    else if (typeof fallback === "object" && fallback !== null && !Array.isArray(fallback)) {
                        const fallbackModel = fallback.model;
                        if (typeof fallbackModel === "string" && fallbackModel.trim())
                            result.push(fallbackModel.trim());
                    }
                }
            }
        }
    }
    return result;
}
function providerFromModel(model) {
    const slash = model.indexOf("/");
    return slash > 0 ? model.slice(0, slash).trim().toLowerCase() : undefined;
}
function uniqueStrings(values) {
    return [...new Set(values)];
}
function recordValue(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
}
function escapeRegExp(value) {
    return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function agentListCheck(status, output) {
    if (status !== 0) {
        return { name: "seed agents", status: "warn", message: "SEED lab agents could not be checked because `opencode agent list` did not complete." };
    }
    const agentEntries = parseAgentListEntries(output);
    if (!agentEntries.has(SEED_DEFAULT_AGENT)) {
        return {
            name: "seed agents",
            status: "fail",
            message: "Lab-Representative was not visible to OpenCode. Rerun the latest SEED installer in this project, then restart OpenCode.",
        };
    }
    const leaked = [
        ...["build", "plan"].filter((name) => agentEntries.has(name)),
    ];
    if (leaked.length) {
        return {
            name: "seed agents",
            status: "fail",
            message: `OpenCode still exposes non-SEED primary agents (${leaked.join(", ")}). Rerun the latest SEED installer in this project.`,
        };
    }
    return { name: "seed agents", status: "pass", message: "Lab-Representative is visible and native OpenCode agents are hidden." };
}
function parseAgentListEntries(output) {
    const entries = new Map();
    for (const line of output.split(/\r?\n/)) {
        const trimmed = line.trim();
        if (!trimmed)
            continue;
        const cleaned = trimmed
            .replace(/\x1b\[[0-9;]*m/g, "")
            .replace(/^[●○◇◆▪\-\*\s]+/, "")
            .trim();
        const match = cleaned.match(/^([A-Za-z0-9][A-Za-z0-9_-]*)\b(?:\s+\(([^)]+)\))?/);
        if (match)
            entries.set(match[1].toLowerCase(), match[2]?.toLowerCase());
    }
    return entries;
}
export function formatDoctorResult(result) {
    const lines = ["SEED-agent doctor", ""];
    for (const check of result.checks) {
        lines.push(`${label(check.status)} ${check.name}: ${check.message}`);
    }
    lines.push("");
    lines.push(result.exitCode === 0 ? "OK: no blocking installation failures found." : "FAIL: fix the failed checks before pushing this as ready.");
    return lines.join("\n");
}
function isSeedPluginSpec(spec, configPath, cwd) {
    return spec === SEED_PACKAGE_NAME
        || spec.startsWith(`${SEED_PACKAGE_NAME}@`)
        || spec.includes("/seed-harness")
        || spec.includes("opencode-seed-agent")
        || pathPluginPackageName(spec, configPath, cwd) === SEED_PACKAGE_NAME;
}
function pathPluginPackageName(spec, configPath, cwd) {
    if (!spec.startsWith("file://") && !spec.startsWith(".") && !spec.startsWith("/"))
        return;
    const resolved = spec.startsWith("file://") ? fileURLToPath(spec) : path.resolve(path.dirname(configPath), spec);
    const candidates = [path.join(resolved, "package.json"), path.join(path.dirname(resolved), "package.json")];
    if (path.resolve(resolved) === path.resolve(cwd))
        candidates.push(path.join(cwd, "package.json"));
    for (const file of candidates) {
        if (!existsSync(file))
            continue;
        try {
            const pkg = JSON.parse(readFileSync(file, "utf8"));
            if (pkg.name)
                return pkg.name;
        }
        catch { }
    }
}
function label(status) {
    if (status === "pass")
        return "[pass]";
    if (status === "warn")
        return "[warn]";
    return "[fail]";
}
function run(spawn, args, cwd, commandOptions) {
    return runOpenCodeSync(spawn, args, {
        cwd,
        encoding: "utf8",
        timeout: 120_000,
        maxBuffer: 16 * 1024 * 1024,
    }, commandOptions);
}
function oneLine(value) {
    return value.trim().split(/\r?\n/)[0] ?? "";
}
function opencodeVersionCheck(version) {
    if (version.status !== 0) {
        const command = version.command ? ` via ${version.command}` : "";
        return { name: "opencode", status: "fail", message: `OpenCode CLI was not found or did not run${command}` };
    }
    const raw = oneLine(`${version.stdout}\n${version.stderr}`);
    const parsed = parseVersion(raw);
    if (!parsed) {
        return {
            name: "opencode",
            status: "warn",
            message: `OpenCode version output was '${raw || "(empty)"}'; SEED-agent requires >=${OPENCODE_MIN_VERSION} and was tested with ${OPENCODE_TESTED_VERSION}.`,
        };
    }
    const installed = parsed.join(".");
    if (compareVersions(parsed, parseVersion(OPENCODE_MIN_VERSION) ?? [0, 0, 0]) < 0) {
        return {
            name: "opencode",
            status: "fail",
            message: `OpenCode ${installed} is below the required >=${OPENCODE_MIN_VERSION}. Install/update OpenCode before using SEED-agent.`,
        };
    }
    return {
        name: "opencode",
        status: "pass",
        message: `OpenCode ${installed} (required >=${OPENCODE_MIN_VERSION}; tested ${OPENCODE_TESTED_VERSION})`,
    };
}
export function parseVersion(value) {
    const match = value.match(/(\d+)\.(\d+)\.(\d+)/);
    if (!match)
        return;
    return [Number(match[1]), Number(match[2]), Number(match[3])];
}
export function compareVersions(left, right) {
    for (let index = 0; index < 3; index += 1) {
        const delta = left[index] - right[index];
        if (delta !== 0)
            return delta;
    }
    return 0;
}
//# sourceMappingURL=doctor.js.map