"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MCP_SEARCH_SCHEMA_VERSION = exports.MCP_SEARCH_KIND = void 0;
exports.searchMcps = searchMcps;
const requirement_exclusions_1 = require("../catalog/requirement-exclusions");
const metadata_1 = require("../catalog/metadata");
const search_text_1 = require("../core/search-text");
const catalog_1 = require("./catalog");
const inventory_1 = require("./inventory");
exports.MCP_SEARCH_KIND = "alpaca.mcpSearch";
exports.MCP_SEARCH_SCHEMA_VERSION = 5;
const SEARCH_KIND_ORDER = {
    installed: 0,
    installable: 1,
    discovered: 2,
};
async function searchMcps(input) {
    const query = input.query.trim();
    const topics = await (0, metadata_1.loadEffectiveCatalogTopics)({ repoRoot: input.repoRoot });
    const topicDescriptions = (0, metadata_1.toCatalogTopicMap)(topics);
    const items = [];
    const diagnostics = [];
    const errors = [];
    if (input.mode !== "installable") {
        const installed = await (0, inventory_1.listAvailableMcpServers)({
            repoRoot: input.repoRoot,
            workers: input.targets,
            configuredWorkers: input.configuredWorkers,
            ...(input.authProfiles === undefined
                ? {}
                : { authProfiles: input.authProfiles }),
            ...(input.env === undefined ? {} : { env: input.env }),
            ...(input.homeDirectory === undefined
                ? {}
                : { homeDirectory: input.homeDirectory }),
            includeExcluded: true,
        });
        diagnostics.push(...installed.diagnostics);
        items.push(...installed.items
            .filter((item) => matchesQuery(query, [item.serverName, ...item.tools]) ||
            (input.forcedIds ?? []).includes(`${item.worker}:${item.serverName}`) ||
            (input.forcedIds ?? []).includes(item.serverName))
            .map((item) => ({
            id: `${item.worker}:${item.serverName}`,
            title: item.serverName,
            description: item.tools.length === 0
                ? null
                : `Configured tools: ${item.tools.join(", ")}`,
            kind: "installed",
            sourceKind: "installed",
            target: item.worker,
            ref: item.ref,
            catalogId: null,
            source: item.source,
            topics: [],
            installed: true,
            installable: false,
            ...(item.excludedFromRequires === undefined
                ? {}
                : { excludedFromRequires: item.excludedFromRequires }),
        })));
    }
    if (input.mode !== "installed") {
        const catalog = await (0, catalog_1.loadEffectiveMcpCatalog)({ repoRoot: input.repoRoot });
        items.push(...catalog.entries
            .filter((entry) => catalogEntrySupportsTargets(entry, input.targets))
            .filter((entry) => (0, search_text_1.searchTextMatches)({
            id: entry.id,
            title: entry.id,
            description: entry.description,
            topics: entry.topics ?? [],
            topicDescriptions,
        }, query) || (input.forcedIds ?? []).includes(entry.id))
            .map((entry) => catalogEntryToSearchItem(entry, input.targets))
            .filter((item) => input.mode !== "installable" || item.installable));
    }
    const exclusions = await (0, requirement_exclusions_1.loadEffectiveRequirementExclusions)({
        repoRoot: input.repoRoot,
        ...(input.homeDirectory === undefined
            ? {}
            : { homeDirectory: input.homeDirectory }),
    });
    const allItems = applyMcpSearchRequirementExclusions({
        items,
        exclusions,
    });
    const sortedItems = sortSearchItems(allItems, query, topicDescriptions);
    const visibleItems = input.includeExcluded === true
        ? sortedItems
        : sortedItems.filter((item) => item.excludedFromRequires === undefined);
    const hiddenItems = sortedItems.filter((item) => item.excludedFromRequires !== undefined);
    const exclusionHint = input.includeExcluded === true
        ? undefined
        : (0, requirement_exclusions_1.buildRequirementExclusionHintForVisibleResult)({
            visibleCount: visibleItems.length,
            hiddenMatches: hiddenItems.flatMap((item) => item.excludedFromRequires === undefined
                ? []
                : [item.excludedFromRequires]),
        });
    return {
        kind: exports.MCP_SEARCH_KIND,
        schemaVersion: exports.MCP_SEARCH_SCHEMA_VERSION,
        repositoryRoot: input.repoRoot,
        query,
        targets: [...input.targets],
        mode: input.mode,
        items: visibleItems,
        diagnostics,
        errors,
        ...(exclusionHint === undefined ? {} : { exclusionHint }),
    };
}
function catalogEntryToSearchItem(entry, targets) {
    const installable = catalogEntryIsInstallable(entry, targets);
    return {
        id: entry.id,
        title: entry.id,
        description: entry.description,
        kind: installable ? "installable" : "discovered",
        sourceKind: entry.origin === "user" ? "user_catalog" : "system_catalog",
        target: resolveCatalogEntryTarget(entry, targets),
        ref: null,
        catalogId: entry.id,
        source: (0, catalog_1.formatMcpCatalogEntrySource)(entry),
        topics: [...(entry.topics ?? [])],
        installed: false,
        installable,
        ...(entry.quality?.stars === undefined
            ? {}
            : { stars: entry.quality.stars }),
        ...(entry.quality?.updatedAt === undefined
            ? {}
            : { updatedAt: entry.quality.updatedAt }),
        ...(entry.quality?.createdAt === undefined
            ? {}
            : { createdAt: entry.quality.createdAt }),
        ...(entry.quality?.sourceUrl === undefined
            ? {}
            : { sourceUrl: entry.quality.sourceUrl }),
    };
}
function matchesQuery(query, values) {
    const normalizedQuery = query.toLowerCase();
    return values.some((value) => value.toLowerCase().includes(normalizedQuery));
}
function catalogEntrySupportsTargets(entry, targets) {
    if (entry.servers === undefined) {
        return true;
    }
    return targets.some((target) => entry.servers?.[target] !== undefined ||
        entry.servers?.default !== undefined);
}
function catalogEntryIsInstallable(entry, targets) {
    return (entry.setup?.safeAutoInstall === true &&
        targets.some((target) => (0, catalog_1.resolveMcpCatalogServerForTarget)(entry, target) !== null));
}
function resolveCatalogEntryTarget(entry, targets) {
    if (entry.servers === undefined) {
        return "all";
    }
    const supportedTargets = targets.filter((target) => entry.servers?.[target] !== undefined ||
        entry.servers?.default !== undefined);
    return supportedTargets.length === 1 ? (supportedTargets[0] ?? "all") : "all";
}
function sortSearchItems(items, query, topicDescriptions) {
    const normalizedQuery = (0, search_text_1.normalizeSearchText)(query);
    return [...items].sort((left, right) => {
        const leftExact = (0, search_text_1.normalizeSearchText)(left.id) === normalizedQuery ? 0 : 1;
        const rightExact = (0, search_text_1.normalizeSearchText)(right.id) === normalizedQuery ? 0 : 1;
        return (leftExact - rightExact ||
            (0, search_text_1.scoreSearchText)({ ...right, topicDescriptions }, query) -
                (0, search_text_1.scoreSearchText)({ ...left, topicDescriptions }, query) ||
            sourcePriority(left.sourceKind) - sourcePriority(right.sourceKind) ||
            SEARCH_KIND_ORDER[left.kind] - SEARCH_KIND_ORDER[right.kind] ||
            compareQualityMetadata(left, right) ||
            left.id.localeCompare(right.id) ||
            left.source.localeCompare(right.source) ||
            left.title.localeCompare(right.title));
    });
}
function applyMcpSearchRequirementExclusions(input) {
    return input.items.flatMap((item) => {
        const exclusion = item.excludedFromRequires ??
            (0, requirement_exclusions_1.findRequirementExclusionMatch)(input.exclusions, "mcp", {
                ref: item.ref === null
                    ? null
                    : item.target === "all"
                        ? item.ref
                        : `${item.target}:${item.ref}`,
                id: item.catalogId ?? item.id,
                source: item.source,
            }) ??
            (0, requirement_exclusions_1.findRequirementExclusionMatch)(input.exclusions, "mcp", {
                ref: item.ref,
                id: item.catalogId ?? item.id,
                source: item.source,
            });
        return [
            {
                ...item,
                ...(exclusion === null ? {} : { excludedFromRequires: exclusion }),
            },
        ];
    });
}
function compareQualityMetadata(left, right) {
    return ((right.stars ?? -1) - (left.stars ?? -1) ||
        compareOptionalDateDesc(left.updatedAt, right.updatedAt) ||
        compareOptionalDateAsc(left.createdAt, right.createdAt));
}
function compareOptionalDateDesc(left, right) {
    const leftTimestamp = parseOptionalTimestamp(left);
    const rightTimestamp = parseOptionalTimestamp(right);
    if (leftTimestamp === rightTimestamp) {
        return 0;
    }
    if (leftTimestamp === null) {
        return 1;
    }
    if (rightTimestamp === null) {
        return -1;
    }
    return rightTimestamp - leftTimestamp;
}
function compareOptionalDateAsc(left, right) {
    const leftTimestamp = parseOptionalTimestamp(left);
    const rightTimestamp = parseOptionalTimestamp(right);
    if (leftTimestamp === rightTimestamp) {
        return 0;
    }
    if (leftTimestamp === null) {
        return 1;
    }
    if (rightTimestamp === null) {
        return -1;
    }
    return leftTimestamp - rightTimestamp;
}
function parseOptionalTimestamp(value) {
    if (value === undefined) {
        return null;
    }
    const timestamp = Date.parse(value);
    return Number.isNaN(timestamp) ? null : timestamp;
}
function sourcePriority(sourceKind) {
    switch (sourceKind) {
        case "installed":
            return 0;
        case "user_catalog":
            return 1;
        case "system_catalog":
            return 2;
    }
}
