// Pure lexical (keyword) ranking. Complements semantic search by catching exact
// term matches the embedding model might under-weight (names, acronyms, codes).
// No I/O, no platform deps — fully unit-testable.

/** Lowercase + split on non-word chars, dropping empties. */
function tokenize(s: string): string[] {
  return s
    .toLowerCase()
    .split(/\W+/)
    .filter((t) => t.length > 0);
}

/**
 * Rank `rows` by how often the query's terms occur in each row's text.
 *
 * Scoring: sum, over each query term, of the number of times that term appears
 * as a token in the row's text. Rows scoring zero are dropped. Result is sorted
 * by score descending (ties keep input order, since Array.sort is stable).
 */
export function lexicalRank(
  rows: { id: string; text: string }[],
  query: string,
): { id: string; score: number }[] {
  const terms = tokenize(query);
  if (terms.length === 0) return [];

  const out: { id: string; score: number }[] = [];
  for (const row of rows) {
    const tokens = tokenize(row.text);
    if (tokens.length === 0) continue;

    // Count occurrences per token once, then sum the query terms' counts.
    const counts = new Map<string, number>();
    for (const tok of tokens) counts.set(tok, (counts.get(tok) ?? 0) + 1);

    let score = 0;
    for (const term of terms) score += counts.get(term) ?? 0;

    if (score > 0) out.push({ id: row.id, score });
  }

  out.sort((a, b) => b.score - a.score);
  return out;
}
