"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MAX_COMMAND_SEARCH_LIMIT = exports.DEFAULT_COMMAND_SEARCH_LIMIT = void 0;
exports.searchCommandDescriptors = searchCommandDescriptors;
const command_descriptor_projections_1 = require("./command-descriptor-projections");
exports.DEFAULT_COMMAND_SEARCH_LIMIT = 10;
exports.MAX_COMMAND_SEARCH_LIMIT = 50;
function searchCommandDescriptors(registry, query, limit = exports.DEFAULT_COMMAND_SEARCH_LIMIT, options = {}) {
    const normalizedQuery = query.trim().toLowerCase();
    if (normalizedQuery.length === 0)
        throw new Error("Command search query must not be empty.");
    if (!Number.isInteger(limit) || limit < 1 || limit > exports.MAX_COMMAND_SEARCH_LIMIT) {
        throw new Error(`Command search limit must be an integer from 1 to ${exports.MAX_COMMAND_SEARCH_LIMIT}.`);
    }
    const queryTokens = [...new Set(normalizedQuery.split(/\s+/).filter(Boolean))];
    const runtimeCommands = options.runtimeCommands === undefined
        ? null
        : new Set(options.runtimeCommands);
    const includeNonCallable = options.includeNonCallable ?? false;
    const ranked = registry
        .filter((descriptor) => includeNonCallable
        || (descriptor.availability === "callable"
            && (runtimeCommands === null || runtimeCommands.has(descriptor.command))))
        .map((descriptor) => ({ descriptor, score: scoreDescriptor(descriptor, normalizedQuery, queryTokens) }))
        .filter((entry) => entry.score > 0)
        .sort((left, right) => right.score - left.score || left.descriptor.command.localeCompare(right.descriptor.command));
    return Object.freeze({
        query: normalizedQuery,
        limit,
        includeNonCallable,
        totalMatches: ranked.length,
        matches: Object.freeze(ranked.slice(0, limit).map(({ descriptor, score }) => (0, command_descriptor_projections_1.projectCommandDescriptorSearchMatch)(descriptor, score))),
        readOnly: true
    });
}
function scoreDescriptor(descriptor, query, queryTokens) {
    const command = descriptor.command.toLowerCase();
    if (command === query)
        return 1000;
    if (command.startsWith(`${query} `) || command.startsWith(query))
        return 800;
    if (command.split(/\s+/).includes(query))
        return 650;
    if (command.includes(query))
        return 550;
    const fields = [
        [descriptor.summary, 400],
        [descriptor.capability, 350],
        [descriptor.artifactFamily, 325],
        [descriptor.authorization, 300],
        [descriptor.mutationPosture, 275],
        [descriptor.effects.filesystem, 250],
        [descriptor.effects.network, 225]
    ];
    const phraseScore = fields.reduce((score, [value, weight]) => Math.max(score, value.toLowerCase().includes(query) ? weight : 0), 0);
    if (queryTokens.length <= 1)
        return phraseScore;
    const tokenScores = queryTokens.map((token) => scoreDescriptorToken(descriptor, token));
    const matched = tokenScores.filter((score) => score > 0);
    if (matched.length === 0)
        return phraseScore;
    return Math.max(phraseScore, matched.reduce((total, score) => total + score, 0)
        + matched.length * 40);
}
function scoreDescriptorToken(descriptor, token) {
    const command = descriptor.command.toLowerCase();
    if (command === token)
        return 500;
    if (command.split(/\s+/).includes(token))
        return 360;
    if (command.includes(token))
        return 300;
    const fields = [
        [descriptor.summary, 220],
        [descriptor.capability, 190],
        [descriptor.artifactFamily, 175],
        [descriptor.authorization, 160],
        [descriptor.mutationPosture, 145],
        [descriptor.effects.filesystem, 130],
        [descriptor.effects.network, 115],
        [descriptor.validationNotes.join(" "), 100]
    ];
    return fields.reduce((score, [value, weight]) => Math.max(score, value.toLowerCase().includes(token) ? weight : 0), 0);
}
