"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.searchDocFiles = searchDocFiles;
exports.searchCodeFiles = searchCodeFiles;
const embedder_1 = require("../../lib/embedder");
const defaults_1 = require("../../lib/defaults");
const bm25_1 = require("../../lib/search/bm25");
function searchDocFiles(graph, queryEmbedding, options = {}) {
    const { topK = defaults_1.FILE_SEARCH_TOP_K, minScore = defaults_1.SEARCH_MIN_SCORE_FILES, queryText, bm25Index } = options;
    // Vector scoring: root chunks (level=1) with fileEmbedding
    const vectorScores = new Map();
    const titleMap = new Map();
    if (queryEmbedding.length > 0) {
        graph.forEachNode((_, attrs) => {
            if (attrs.level !== 1 || attrs.fileEmbedding.length === 0)
                return;
            vectorScores.set(attrs.fileId, (0, embedder_1.cosineSimilarity)(queryEmbedding, attrs.fileEmbedding));
            titleMap.set(attrs.fileId, attrs.title);
        });
    }
    // BM25 scoring
    const bm25Scores = queryText && bm25Index ? bm25Index.score(queryText) : new Map();
    // Collect titles from BM25 hits not already in titleMap
    for (const fileId of bm25Scores.keys()) {
        if (!titleMap.has(fileId)) {
            graph.forEachNode((_, attrs) => {
                if (attrs.fileId === fileId && attrs.level === 1)
                    titleMap.set(fileId, attrs.title);
            });
        }
    }
    // Fuse or use single source
    let scored;
    if (vectorScores.size > 0 && bm25Scores.size > 0) {
        const fused = (0, bm25_1.rrfFuse)(vectorScores, bm25Scores, defaults_1.RRF_K);
        scored = [...fused.entries()].map(([fileId, score]) => ({ fileId, score }));
        // Normalize fused scores to 0–1
        const maxScore = scored.reduce((m, s) => Math.max(m, s.score), 0);
        if (maxScore > 0)
            for (const s of scored)
                s.score /= maxScore;
    }
    else if (bm25Scores.size > 0) {
        scored = [...bm25Scores.entries()].map(([fileId, score]) => ({ fileId, score }));
        const maxScore = scored.reduce((m, s) => Math.max(m, s.score), 0);
        if (maxScore > 0)
            for (const s of scored)
                s.score /= maxScore;
    }
    else {
        scored = [...vectorScores.entries()].map(([fileId, score]) => ({ fileId, score }));
    }
    // Count chunks per file
    const chunkCounts = new Map();
    graph.forEachNode((_, attrs) => {
        chunkCounts.set(attrs.fileId, (chunkCounts.get(attrs.fileId) ?? 0) + 1);
    });
    return scored
        .filter(s => s.score >= minScore)
        .sort((a, b) => b.score - a.score)
        .slice(0, topK)
        .map(s => ({
        fileId: s.fileId,
        title: titleMap.get(s.fileId) ?? '',
        chunks: chunkCounts.get(s.fileId) ?? 0,
        score: s.score,
    }));
}
function searchCodeFiles(graph, queryEmbedding, options = {}) {
    const { topK = defaults_1.FILE_SEARCH_TOP_K, minScore = defaults_1.SEARCH_MIN_SCORE_FILES, queryText, bm25Index } = options;
    // Vector scoring: file nodes with fileEmbedding
    const vectorScores = new Map();
    if (queryEmbedding.length > 0) {
        graph.forEachNode((_, attrs) => {
            if (attrs.kind !== 'file' || attrs.fileEmbedding.length === 0)
                return;
            vectorScores.set(attrs.fileId, (0, embedder_1.cosineSimilarity)(queryEmbedding, attrs.fileEmbedding));
        });
    }
    // BM25 scoring
    const bm25Scores = queryText && bm25Index ? bm25Index.score(queryText) : new Map();
    // Fuse or use single source
    let scored;
    if (vectorScores.size > 0 && bm25Scores.size > 0) {
        const fused = (0, bm25_1.rrfFuse)(vectorScores, bm25Scores, defaults_1.RRF_K);
        scored = [...fused.entries()].map(([fileId, score]) => ({ fileId, score }));
        const maxScore = scored.reduce((m, s) => Math.max(m, s.score), 0);
        if (maxScore > 0)
            for (const s of scored)
                s.score /= maxScore;
    }
    else if (bm25Scores.size > 0) {
        scored = [...bm25Scores.entries()].map(([fileId, score]) => ({ fileId, score }));
        const maxScore = scored.reduce((m, s) => Math.max(m, s.score), 0);
        if (maxScore > 0)
            for (const s of scored)
                s.score /= maxScore;
    }
    else {
        scored = [...vectorScores.entries()].map(([fileId, score]) => ({ fileId, score }));
    }
    // Count symbols per file
    const symbolCounts = new Map();
    graph.forEachNode((_, attrs) => {
        symbolCounts.set(attrs.fileId, (symbolCounts.get(attrs.fileId) ?? 0) + 1);
    });
    return scored
        .filter(s => s.score >= minScore)
        .sort((a, b) => b.score - a.score)
        .slice(0, topK)
        .map(s => ({
        fileId: s.fileId,
        symbolCount: symbolCounts.get(s.fileId) ?? 0,
        score: s.score,
    }));
}
