import { readFile } from "fs/promises";
import { isAbsolute, relative, resolve, sep } from "path";
import { collectMcpConfigs, getProjectRootAsync, getUserHome, writeBuiltinMcpConfig, writeRuntimeMcpConfig } from "../util/fs.js";
import { stableValueHash } from "./stable-json.js";
import { sortToolPrefixSpecs } from "./tool-registry-contract.js";
export async function buildOmkToolPlaneManifest(input) {
    const requiresRuntimeMcp = input.requiresRuntimeMcp === true;
    const resolved = await resolveRuntimeMcpConfigFile(input.mcpScope, input.mcpAllowlist);
    const runtimeRead = resolved.mcpConfigFile ? await readMcpServerNames(resolved.mcpConfigFile) : { servers: [], diagnostics: [] };
    const diagnostics = [...resolved.diagnostics, ...runtimeRead.diagnostics];
    if (requiresRuntimeMcp && (diagnostics.some((diagnostic) => diagnostic.level === "error") || !resolved.mcpConfigFile || runtimeRead.servers.length === 0)) {
        throw new Error(formatRequiredRuntimeMcpError(diagnostics, resolved.mcpConfigFile, runtimeRead.servers));
    }
    const toolContracts = sortToolPrefixSpecs(input.toolContracts ?? []);
    const manifest = {
        owner: "omk",
        mcpServers: runtimeRead.servers,
        mcpConfigFile: resolved.mcpConfigFile ?? undefined,
        skills: unique(input.skills ?? []),
        hooks: unique(input.hooks ?? []),
        tools: unique(input.tools ?? []),
        toolContracts,
        toolSpecsHash: stableValueHash(toolContracts),
        runtimeOwnsMcp: false,
        requiresRuntimeMcp,
        diagnostics,
    };
    return manifest;
}
async function resolveRuntimeMcpConfigFile(scope, allowlist) {
    if (scope === "none")
        return { mcpConfigFile: null, diagnostics: [] };
    const configPaths = await collectMcpConfigs(scope);
    const diagnostics = await readMcpConfigDiagnostics(configPaths);
    const builtinMcp = await writeBuiltinMcpConfig();
    const runtimeAllowlist = allowlist !== undefined
        ? unique([...allowlist, "omk-project"])
        : undefined;
    const mcpConfigFile = await writeRuntimeMcpConfig(builtinMcp ? [...configPaths, builtinMcp] : configPaths, runtimeAllowlist);
    return { mcpConfigFile, diagnostics };
}
async function readMcpServerNames(configFile) {
    try {
        const parsed = JSON.parse(await readFile(configFile, "utf-8"));
        const servers = parsed.mcpServers;
        if (!servers || typeof servers !== "object" || Array.isArray(servers))
            return { servers: [], diagnostics: [] };
        return { servers: unique(Object.keys(servers)), diagnostics: [] };
    }
    catch (err) {
        return {
            servers: [],
            diagnostics: [await createMcpDiagnostic("runtime_mcp_config_parse_failed", configFile, err)],
        };
    }
}
async function readMcpConfigDiagnostics(configPaths) {
    const diagnostics = [];
    for (const configPath of configPaths) {
        try {
            JSON.parse(await readFile(configPath, "utf-8"));
        }
        catch (err) {
            diagnostics.push(await createMcpDiagnostic(err instanceof SyntaxError ? "mcp_config_parse_failed" : "mcp_config_read_failed", configPath, err));
        }
    }
    return diagnostics;
}
async function createMcpDiagnostic(code, path, err) {
    return {
        level: "error",
        code,
        path: await formatDiagnosticPath(path),
        message: mcpDiagnosticMessage(err),
    };
}
async function formatDiagnosticPath(path) {
    const root = resolve(await getProjectRootAsync());
    const resolvedPath = resolve(path);
    const rootRelative = relative(root, resolvedPath);
    if (isRelativeChildPath(rootRelative))
        return rootRelative.split(sep).join("/");
    const home = resolve(getUserHome());
    const homeRelative = relative(home, resolvedPath);
    if (isRelativeChildPath(homeRelative))
        return `~/${homeRelative.split(sep).join("/")}`;
    return resolvedPath;
}
function isRelativeChildPath(path) {
    return path !== "" && path !== ".." && !path.startsWith(`..${sep}`) && !path.startsWith("../") && !isAbsolute(path);
}
function mcpDiagnosticMessage(err) {
    if (err instanceof SyntaxError)
        return "invalid JSON";
    if (err && typeof err === "object" && "code" in err && typeof err.code === "string") {
        return `unreadable MCP config (${err.code})`;
    }
    return "invalid MCP config";
}
function formatRequiredRuntimeMcpError(diagnostics, mcpConfigFile, servers) {
    const errors = diagnostics
        .filter((diagnostic) => diagnostic.level === "error")
        .map((diagnostic) => `${diagnostic.path ?? "runtime MCP"}: ${diagnostic.message}`);
    if (!mcpConfigFile)
        errors.push("runtime MCP config was not generated");
    if (mcpConfigFile && servers.length === 0)
        errors.push("runtime MCP config contains no servers");
    return `[omk] Runtime MCP is required but unavailable: ${unique(errors).join("; ")}`;
}
function unique(values) {
    return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
}
