// engine/skill-registry/unified-search.ts — SPEC-150: Unified skill search across registries
// Queries all enabled registries in parallel, merges and ranks results by relevance.
import { searchAnthropicSkills } from './anthropic-adapter.js';
import { searchSkillsSh } from './skillssh-adapter.js';
import { searchAgentSkills } from './agentskill-adapter.js';
import { searchBuiltInSkills } from './builtin-catalog-adapter.js';
const DEFAULT_LIMIT = 10;
/** Dispatch a search to the correct adapter based on registry ID. */
async function queryRegistry(registry, query, stack) {
    try {
        switch (registry.id) {
            case 'anthropic':
                return await searchAnthropicSkills(query);
            case 'skillssh':
                return await searchSkillsSh(query, stack);
            case 'agentskill':
                return await searchAgentSkills(query, stack);
            case 'builtin':
                return searchBuiltInSkills(query);
            default:
                return [];
        }
    }
    catch {
        return [];
    }
}
/**
 * Compute a relevance score 0-1 for a skill result.
 *
 * Factors (weighted):
 *  - Exact name match: +0.40
 *  - Name contains query: +0.25
 *  - Description contains query: +0.15
 *  - Tag match: +0.10
 *  - Registry priority (inverted, capped at 10 registries): up to 0.05
 *  - Install popularity (log scale): up to 0.05
 */
function computeRelevanceScore(result, query, registryPriority) {
    const q = query.toLowerCase();
    const nameLower = result.name.toLowerCase();
    const descLower = result.description.toLowerCase();
    let score = 0;
    if (nameLower === q) {
        score += 0.4;
    }
    else if (nameLower.includes(q)) {
        score += 0.25;
    }
    if (descLower.includes(q)) {
        score += 0.15;
    }
    if (result.tags?.some((t) => t.toLowerCase().includes(q))) {
        score += 0.1;
    }
    // Priority: priority=1 (highest) yields 0.05, priority=10+ yields 0
    const priorityBonus = Math.max(0, (11 - registryPriority) / 10) * 0.05;
    score += priorityBonus;
    // Popularity: log10(installs+1) capped at 6 (1M installs) → 0-0.05
    if (result.installs && result.installs > 0) {
        const popularityBonus = Math.min(Math.log10(result.installs + 1) / 6, 1) * 0.05;
        score += popularityBonus;
    }
    return Math.min(score, 1);
}
/** Deduplicate results by name, keeping the one with the highest relevance score. */
function deduplicate(results) {
    const best = new Map();
    for (const r of results) {
        const existing = best.get(r.name);
        if (!existing || r.relevanceScore > existing.relevanceScore) {
            best.set(r.name, r);
        }
    }
    return Array.from(best.values());
}
/**
 * Search across all enabled registries in parallel.
 *
 * Steps:
 *  1. Filter enabled registries (and by params.registry if specified).
 *  2. Query each adapter in parallel with Promise.allSettled (failures silenced).
 *  3. Assign relevance scores, merge, deduplicate by name.
 *  4. Sort by relevance desc, cap at params.limit.
 */
export async function searchAllRegistries(params, registries) {
    const enabled = registries.filter((r) => {
        if (!r.enabled) {
            return false;
        }
        if (params.registry && params.registry !== 'all' && r.id !== params.registry) {
            return false;
        }
        return true;
    });
    if (enabled.length === 0) {
        return [];
    }
    const settled = await Promise.allSettled(enabled.map((reg) => queryRegistry(reg, params.query, params.stack)));
    const priorityMap = new Map(enabled.map((r) => [r.id, r.priority]));
    const allResults = [];
    for (const outcome of settled) {
        if (outcome.status !== 'fulfilled') {
            continue;
        }
        for (const result of outcome.value) {
            const priority = priorityMap.get(result.source) ?? 99;
            allResults.push({
                ...result,
                relevanceScore: computeRelevanceScore(result, params.query, priority),
            });
        }
    }
    const deduped = deduplicate(allResults);
    deduped.sort((a, b) => b.relevanceScore - a.relevanceScore);
    const cap = params.limit ?? DEFAULT_LIMIT;
    return deduped.slice(0, cap);
}
//# sourceMappingURL=unified-search.js.map