/**
 * Score boosts (Task 308).
 *
 * Two post-fetch score adjustments that re-rank hybrid hits using
 * properties that the typed-edge hook (Task 305) and compiled-truth
 * migration (Task 306) populate. Both are no-ops on the current corpus
 * until those tasks land — the boost reads a missing property as zero
 * and adds nothing.
 *
 * Boosts are pure functions over the scored hit array. They never query
 * Neo4j; the data they read is already on each hit's `properties` map.
 */

export interface ScoredHit {
  nodeId: string;
  properties: Record<string, unknown>;
  score: number;
}

/**
 * Add a flat fractional bump to any hit whose node carries a non-null
 * `compiledTruth` property. Authoritative summaries beat noisy timeline
 * fragments — this puts compiled rows ahead when the score is close.
 *
 * Default weight 0.15 (+15%); the brief reserves room to tune.
 */
export function applyCompiledTruthBoost<T extends ScoredHit>(
  hits: T[],
  weight = 0.15,
): T[] {
  if (weight < 0 || weight > 1) {
    throw new Error(`applyCompiledTruthBoost: weight must be in [0,1], got ${weight}`);
  }
  return hits.map((hit) => {
    const compiled = hit.properties["compiledTruth"];
    if (compiled === null || compiled === undefined || compiled === "") return hit;
    return { ...hit, score: hit.score * (1 + weight) };
  });
}

/**
 * Log-weighted boost scaled by inbound MENTIONS edge count.
 *
 * Formula: `bump = clamp(0.05 + 0.05 * log10(count), 0.05, 0.25)` when
 * count > 0; zero otherwise. So 1 backlink → +5%, 10 → +10%, 100 → +15%,
 * 1000 → +20%, 10000+ → capped at +25%. Backlink-rich nodes (the
 * graph's natural authorities) surface higher.
 *
 * Reads from `hit.properties.backlinkCount` — a property Task 305's
 * post-write hook maintains. Until 305 lands the count is absent and
 * the boost is identity.
 */
export function applyBacklinkBoost<T extends ScoredHit>(hits: T[]): T[] {
  return hits.map((hit) => {
    const raw = hit.properties["backlinkCount"];
    const count = typeof raw === "number" ? raw : Number(raw);
    if (!Number.isFinite(count) || count <= 0) return hit;
    const bump = Math.max(0.05, Math.min(0.25, 0.05 + 0.05 * Math.log10(count)));
    return { ...hit, score: hit.score * (1 + bump) };
  });
}
