import { describe, expect, it } from "vitest";
import { fuseRrf } from "../rrf-fusion.js";

describe("fuseRrf", () => {
  it("sums 1/(k+rank) per node across lists", () => {
    const listA = [{ nodeId: "a" }, { nodeId: "b" }, { nodeId: "c" }];
    const listB = [{ nodeId: "b" }, { nodeId: "d" }, { nodeId: "a" }];
    const fused = fuseRrf([listA, listB], 60);

    // a: list A rank 0 + list B rank 2 = 1/60 + 1/62
    // b: list A rank 1 + list B rank 0 = 1/61 + 1/60
    // c: list A rank 2 only           = 1/62
    // d: list B rank 1 only           = 1/61
    const byId = new Map(fused.map((f) => [f.nodeId, f.rrfScore]));
    expect(byId.get("a")).toBeCloseTo(1 / 60 + 1 / 62, 6);
    expect(byId.get("b")).toBeCloseTo(1 / 61 + 1 / 60, 6);
    expect(byId.get("c")).toBeCloseTo(1 / 62, 6);
    expect(byId.get("d")).toBeCloseTo(1 / 61, 6);
  });

  it("sorts by descending RRF score", () => {
    const listA = [{ nodeId: "a" }, { nodeId: "b" }];
    const listB = [{ nodeId: "b" }, { nodeId: "a" }];
    const fused = fuseRrf([listA, listB], 60);
    // Both appear in both lists at ranks (0,1) and (1,0) → same score; tie broken by first-seen → "a" first.
    expect(fused.map((f) => f.nodeId)).toEqual(["a", "b"]);
  });

  it("honours configurable k", () => {
    const listA = [{ nodeId: "a" }];
    const fused = fuseRrf([listA], 10);
    expect(fused[0].rrfScore).toBeCloseTo(1 / 10, 6);
  });

  it("returns [] for empty input", () => {
    expect(fuseRrf([])).toEqual([]);
    expect(fuseRrf([[], []])).toEqual([]);
  });
});
