"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isValidRequiredMcpRef = exports.dedupeRequiredMcpRefs = void 0;
exports.checkRequiredMcpAvailability = checkRequiredMcpAvailability;
exports.formatRequiredMcpAvailabilityReport = formatRequiredMcpAvailabilityReport;
exports.resolveRunnableWorkersForRequiredMcp = resolveRunnableWorkersForRequiredMcp;
exports.collectRequiredMcpForWorkflowTree = collectRequiredMcpForWorkflowTree;
const store_1 = require("../auth-profiles/store");
const requirement_exclusions_1 = require("../catalog/requirement-exclusions");
const catalog_1 = require("../i18n/catalog");
const inventory_1 = require("../mcp/inventory");
const ref_1 = require("../mcp/ref");
Object.defineProperty(exports, "dedupeRequiredMcpRefs", { enumerable: true, get: function () { return ref_1.dedupeRequiredMcpRefs; } });
Object.defineProperty(exports, "isValidRequiredMcpRef", { enumerable: true, get: function () { return ref_1.isValidRequiredMcpRef; } });
const validation_1 = require("./validation");
async function checkRequiredMcpAvailability(input) {
    const mcpRefs = (0, ref_1.dedupeRequiredMcpRefs)(input.mcpRefs).sort();
    const configuredWorkers = [...input.workers].sort();
    if (mcpRefs.length === 0) {
        return {
            ok: true,
            mcpRefs,
            missing: [],
            runnableWorkers: configuredWorkers,
        };
    }
    const authProfiles = input.authProfiles ??
        (await (0, store_1.loadAuthProfiles)(input.homeDirectory).then((file) => file.profiles));
    const exclusions = await (0, requirement_exclusions_1.loadEffectiveRequirementExclusions)({
        repoRoot: input.repoRoot,
        ...(input.homeDirectory === undefined
            ? {}
            : { homeDirectory: input.homeDirectory }),
    });
    const inventory = input.inventory ??
        (await (0, inventory_1.listAvailableMcpServers)({
            repoRoot: input.repoRoot,
            workers: configuredWorkers,
            configuredWorkers,
            authProfiles,
            ...(input.env === undefined ? {} : { env: input.env }),
            ...(input.homeDirectory === undefined
                ? {}
                : { homeDirectory: input.homeDirectory }),
            ...(input.run === undefined ? {} : { run: input.run }),
            includeExcluded: true,
        }));
    const missing = findMissingRequiredMcp({
        workers: configuredWorkers,
        items: inventory.items,
        mcpRefs,
        exclusions,
    });
    const runnableWorkers = resolveRunnableWorkersForRequiredMcpRefs({
        workers: configuredWorkers,
        items: inventory.items,
        mcpRefs,
        exclusions,
    });
    return {
        ok: missing.length === 0 && runnableWorkers.length > 0,
        mcpRefs,
        missing,
        runnableWorkers,
    };
}
function formatRequiredMcpAvailabilityReport(input) {
    const { report, locale } = input;
    if (report.ok) {
        return report.mcpRefs.length === 0
            ? (0, catalog_1.translate)(locale, "workflow.requiredMcp.report.none")
            : (0, catalog_1.translate)(locale, "workflow.requiredMcp.report.available", {
                mcpRefs: report.mcpRefs.join(", "),
            });
    }
    return [
        (0, catalog_1.translate)(locale, "workflow.requiredMcp.report.missing"),
        ...(report.runnableWorkers.length === 0
            ? [
                (0, catalog_1.translate)(locale, "workflow.requiredMcp.report.noWorker", {
                    mcpRefs: report.mcpRefs.join(", "),
                }),
            ]
            : []),
        ...report.missing.map((missing) => missing.excludedFromRequires === undefined
            ? (0, catalog_1.translate)(locale, "workflow.requiredMcp.report.item", {
                mcpRef: missing.mcpRef,
                worker: missing.worker,
                expected: formatExpectedMcp(locale, missing.expected),
            })
            : (0, catalog_1.translate)(locale, "workflow.requiredMcp.report.itemExcluded", {
                mcpRef: missing.mcpRef,
                worker: missing.worker,
                reason: missing.excludedFromRequires.reason,
                path: missing.excludedFromRequires.path,
            })),
    ].join("\n");
}
async function resolveRunnableWorkersForRequiredMcp(input) {
    const mcpRefs = (0, ref_1.dedupeRequiredMcpRefs)(input.mcpRefs);
    const configuredWorkers = [...input.workers].sort();
    if (mcpRefs.length === 0) {
        return configuredWorkers;
    }
    const { items } = await (0, inventory_1.listAvailableMcpServers)({
        repoRoot: input.repoRoot,
        workers: configuredWorkers,
        configuredWorkers,
        ...(input.authProfiles === undefined
            ? {}
            : { authProfiles: input.authProfiles }),
        ...(input.env === undefined ? {} : { env: input.env }),
        ...(input.homeDirectory === undefined
            ? {}
            : { homeDirectory: input.homeDirectory }),
        ...(input.run === undefined ? {} : { run: input.run }),
        includeExcluded: true,
    });
    const exclusions = await (0, requirement_exclusions_1.loadEffectiveRequirementExclusions)({
        repoRoot: input.repoRoot,
        ...(input.homeDirectory === undefined
            ? {}
            : { homeDirectory: input.homeDirectory }),
    });
    return resolveRunnableWorkersForRequiredMcpRefs({
        workers: configuredWorkers,
        items,
        mcpRefs,
        exclusions,
    });
}
function collectRequiredMcpForWorkflowTree(input) {
    const mcpRefs = new Set();
    const visitedTemplatePaths = new Set();
    const visit = (template, activeStack, depth) => {
        if (visitedTemplatePaths.has(template.templatePath)) {
            return;
        }
        visitedTemplatePaths.add(template.templatePath);
        for (const mcpRef of template.metadata?.requiredMcp ?? []) {
            mcpRefs.add(mcpRef);
        }
        if (depth >= input.maxNestedWorkflowDepth) {
            return;
        }
        const nextActiveStack = [...activeStack, template.templatePath];
        for (const { templateName: templatePath } of (0, validation_1.scanWorkflowTemplateDirectives)(template).directives) {
            if (nextActiveStack.includes(templatePath)) {
                continue;
            }
            const childTemplate = input.catalog.templateByPath.get(templatePath);
            if (childTemplate === undefined) {
                continue;
            }
            visit(childTemplate, nextActiveStack, depth + 1);
        }
    };
    visit(input.rootTemplate, [], 0);
    return [...mcpRefs].sort();
}
function findMissingRequiredMcp(input) {
    const configuredWorkers = [...input.workers].sort();
    const missing = [];
    for (const mcpRef of (0, ref_1.dedupeRequiredMcpRefs)(input.mcpRefs).sort()) {
        const targetWorkers = resolveTargetWorkersForRequiredMcpRef({
            mcpRef,
            workers: configuredWorkers,
        });
        const satisfyingWorkers = targetWorkers.filter((worker) => requiredMcpRefIsAvailable({
            items: input.items,
            exclusions: input.exclusions,
            worker,
            mcpRef,
        }));
        if (satisfyingWorkers.length > 0) {
            continue;
        }
        for (const worker of targetWorkers) {
            const excludedFromRequires = findRequiredMcpInventoryExclusion({
                items: input.items,
                mcpRef,
                worker,
            }) ??
                findRequiredMcpExclusion({
                    exclusions: input.exclusions,
                    mcpRef,
                    worker,
                });
            missing.push({
                mcpRef,
                worker,
                expected: resolveExpectedMcp(worker, mcpRef),
                ...(excludedFromRequires === null ? {} : { excludedFromRequires }),
            });
        }
    }
    return missing;
}
function resolveRunnableWorkersForRequiredMcpRefs(input) {
    const mcpRefs = (0, ref_1.dedupeRequiredMcpRefs)(input.mcpRefs);
    if (mcpRefs.length === 0) {
        return [...input.workers].sort();
    }
    return [...input.workers].sort().filter((worker) => mcpRefs.every((mcpRef) => (0, ref_1.requiredMcpRefCanRunOnWorker)(mcpRef, worker) &&
        requiredMcpRefIsAvailable({
            items: input.items,
            exclusions: input.exclusions,
            worker,
            mcpRef,
        })));
}
function findRequiredMcpInventoryExclusion(input) {
    const parsed = (0, ref_1.parseRequiredMcpRef)(input.mcpRef);
    if (parsed === null) {
        return null;
    }
    if (parsed.worker !== null && parsed.worker !== input.worker) {
        return null;
    }
    const item = input.items.find((candidate) => candidate.worker === input.worker &&
        candidate.serverName === parsed.serverName);
    return item?.excludedFromRequires ?? null;
}
function requiredMcpRefIsAvailable(input) {
    return (findRequiredMcpExclusion({
        exclusions: input.exclusions,
        mcpRef: input.mcpRef,
        worker: input.worker,
    }) === null &&
        mcpItemSatisfiesRef({
            items: input.items,
            worker: input.worker,
            mcpRef: input.mcpRef,
        }));
}
function findRequiredMcpExclusion(input) {
    const parsed = (0, ref_1.parseRequiredMcpRef)(input.mcpRef);
    if (parsed === null) {
        return null;
    }
    return ((0, requirement_exclusions_1.findRequirementExclusionMatch)(input.exclusions, "mcp", {
        ref: input.mcpRef,
        id: parsed.serverName,
    }) ??
        (0, requirement_exclusions_1.findRequirementExclusionMatch)(input.exclusions, "mcp", {
            ref: `${input.worker}:${parsed.serverName}${parsed.toolName === null ? "" : `/${parsed.toolName}`}`,
            id: parsed.serverName,
        }));
}
function mcpItemSatisfiesRef(input) {
    const parsed = (0, ref_1.parseRequiredMcpRef)(input.mcpRef);
    if (parsed === null) {
        return false;
    }
    const item = input.items.find((candidate) => candidate.worker === input.worker &&
        candidate.serverName === parsed.serverName);
    if (item === undefined) {
        return false;
    }
    if (parsed.toolName === null) {
        return item.usable;
    }
    return item.usable && item.toolsKnown && item.tools.includes(parsed.toolName);
}
function resolveTargetWorkersForRequiredMcpRef(input) {
    const parsed = (0, ref_1.parseRequiredMcpRef)(input.mcpRef, input.workers);
    if (parsed === null) {
        return [];
    }
    if (parsed.worker === null) {
        return [...input.workers];
    }
    return [parsed.worker];
}
function resolveExpectedMcp(worker, mcpRef) {
    const parsed = (0, ref_1.parseRequiredMcpRef)(mcpRef);
    if (parsed === null) {
        return { kind: "invalid-ref" };
    }
    return {
        kind: "server",
        worker,
        serverName: parsed.serverName,
        toolName: parsed.toolName,
    };
}
function formatExpectedMcp(locale, expected) {
    if (expected.kind === "invalid-ref") {
        return (0, catalog_1.translate)(locale, "workflow.requiredMcp.report.expected.invalidRef");
    }
    const server = expected.toolName
        ? (0, catalog_1.translate)(locale, "workflow.requiredMcp.report.expected.serverWithTool", {
            serverName: expected.serverName,
            toolName: expected.toolName,
        })
        : expected.serverName;
    if (expected.worker === "codex") {
        return (0, catalog_1.translate)(locale, "workflow.requiredMcp.report.expected.codexServer", {
            server,
        });
    }
    if (expected.worker === "claude") {
        return (0, catalog_1.translate)(locale, "workflow.requiredMcp.report.expected.claudeServer", {
            server,
        });
    }
    return `${expected.worker} MCP server "${server}"`;
}
