// api/search/bm25.js

/**
 * BM25 ranking. Returns top-K docs sorted by relevance score descending.
 * @param {string[]} queryTokens - tokenized query
 * @param {Array<{tokens: string[], text: string, breadcrumb: string, url: string}>} docs
 * @param {number} topK
 */
function bm25(queryTokens, docs, topK = 5) {
  const k1 = 1.5;
  const b = 0.75;

  // Average document length
  const avgDocLen = docs.reduce((sum, d) => sum + d.tokens.length, 0) / docs.length;

  // Document frequency for each token
  const df = {};
  for (const token of queryTokens) {
    df[token] = docs.filter(d => d.tokens.includes(token)).length;
  }

  const N = docs.length;
  const scored = docs.map(doc => {
    let score = 0;
    const docLen = doc.tokens.length;
    for (const token of queryTokens) {
      const tf = doc.tokens.filter(t => t === token).length;
      const idf = Math.log((N - (df[token] || 0) + 0.5) / ((df[token] || 0) + 0.5) + 1);
      const numerator = tf * (k1 + 1);
      const denominator = tf + k1 * (1 - b + b * (docLen / avgDocLen));
      score += idf * (numerator / denominator);
    }
    return { ...doc, score };
  });

  return scored
    .filter(d => d.score > 0)
    .sort((a, b) => b.score - a.score)
    .slice(0, topK);
}

module.exports = { bm25 };
