import { openReadonlyOrFail } from '../../../db/index.js';
import { loadConfig } from '../../../infrastructure/config.js';
import { hasFtsIndex } from '../stores/fts5.js';
import { ftsSearchData } from './keyword.js';
import { searchData } from './semantic.js';
/** Parse a semicolon-delimited query string into individual queries. */
function parseQueries(query) {
    return query
        .split(';')
        .map((q) => q.trim())
        .filter((q) => q.length > 0);
}
/** Collect BM25 and semantic ranked lists for each query. */
async function collectRankedLists(queries, customDbPath, opts, topK) {
    const rankedLists = [];
    for (const q of queries) {
        const bm25Data = ftsSearchData(q, customDbPath, { ...opts, limit: topK });
        if (bm25Data?.results) {
            rankedLists.push(bm25Data.results.map((r, idx) => ({
                key: `${r.name}:${r.file}:${r.line}`,
                rank: idx + 1,
                source: 'bm25',
                ...r,
            })));
        }
        const semData = await searchData(q, customDbPath, {
            ...opts,
            limit: topK,
            minScore: opts.minScore ?? 0.2,
        });
        if (semData?.results) {
            rankedLists.push(semData.results.map((r, idx) => ({
                key: `${r.name}:${r.file}:${r.line}`,
                rank: idx + 1,
                source: 'semantic',
                ...r,
            })));
        }
    }
    return rankedLists;
}
/** Reciprocal Rank Fusion: merge ranked lists into a single scored result set. */
function fuseResults(rankedLists, k, limit) {
    const fusionMap = new Map();
    for (const list of rankedLists) {
        for (const item of list) {
            if (!fusionMap.has(item.key)) {
                fusionMap.set(item.key, {
                    name: item.name,
                    kind: item.kind,
                    file: item.file,
                    line: item.line,
                    endLine: item.endLine ?? null,
                    role: item.role ?? null,
                    fileHash: item.fileHash ?? null,
                    rrfScore: 0,
                    bm25Score: null,
                    bm25Rank: null,
                    similarity: null,
                    semanticRank: null,
                });
            }
            const entry = fusionMap.get(item.key);
            entry.rrfScore += 1 / (k + item.rank);
            if (item.source === 'bm25') {
                if (entry.bm25Rank === null || item.rank < entry.bm25Rank) {
                    entry.bm25Score = item.bm25Score ?? null;
                    entry.bm25Rank = item.rank;
                }
            }
            else {
                if (entry.semanticRank === null || item.rank < entry.semanticRank) {
                    entry.similarity = item.similarity ?? null;
                    entry.semanticRank = item.rank;
                }
            }
        }
    }
    return [...fusionMap.values()]
        .sort((a, b) => b.rrfScore - a.rrfScore)
        .slice(0, limit)
        .map((e) => ({
        name: e.name,
        kind: e.kind,
        file: e.file,
        line: e.line,
        endLine: e.endLine,
        role: e.role,
        fileHash: e.fileHash,
        rrf: e.rrfScore,
        bm25Score: e.bm25Score,
        bm25Rank: e.bm25Rank,
        similarity: e.similarity,
        semanticRank: e.semanticRank,
    }));
}
export async function hybridSearchData(query, customDbPath, opts = {}) {
    const config = opts.config || loadConfig();
    const searchCfg = config.search || {};
    const limit = opts.limit ?? searchCfg.topK ?? 15;
    const k = opts.rrfK ?? searchCfg.rrfK ?? 60;
    const topK = (opts.limit ?? searchCfg.topK ?? 15) * 5;
    const checkDb = openReadonlyOrFail(customDbPath);
    const ftsAvailable = hasFtsIndex(checkDb);
    checkDb.close();
    if (!ftsAvailable)
        return null;
    const queries = parseQueries(query);
    const rankedLists = await collectRankedLists(queries, customDbPath, opts, topK);
    const results = fuseResults(rankedLists, k, limit);
    return { results };
}
//# sourceMappingURL=hybrid.js.map