import { describe, it, expect } from "vitest";
import { selectDiverseGenres, sampleAcross } from "./genreDiversity";

const g = (...names: string[]) => names.map(name => ({ name }));

describe("sampleAcross", () => {
  it("returns input unchanged when at or under the count", () => {
    expect(sampleAcross([1, 2, 3], 5)).toEqual([1, 2, 3]);
  });

  it("spreads the sample across the whole list rather than taking a prefix", () => {
    // 26 letters, want ~7: a prefix would be A–G; striding spans A→Z.
    const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
    const picked = sampleAcross(letters, 7);
    expect(picked[0]).toBe("A");
    // Reaches deep into the alphabet, not stuck near the front.
    expect(picked[picked.length - 1] >= "S").toBe(true);
    expect(picked.length).toBeLessThanOrEqual(7);
  });

  it("never exceeds the requested count", () => {
    const items = Array.from({ length: 100 }, (_, i) => i);
    expect(sampleAcross(items, 8).length).toBeLessThanOrEqual(8);
  });
});

describe("selectDiverseGenres", () => {
  it("returns input unchanged when at or under the limit", () => {
    const input = g("Rock", "Jazz");
    expect(selectDiverseGenres(input, 5)).toEqual(input);
  });

  it("seeds with the most populous genre (first in input)", () => {
    const out = selectDiverseGenres(g("Rock", "Jazz", "Hip Hop"), 1);
    expect(out.map(x => x.name)).toEqual(["Rock"]);
  });

  it("spreads the family instead of stacking near-synonyms", () => {
    // Count ranking puts all the Rock variants first; a naive top-N would
    // return four flavours of Rock. Diversity should pull in distinct genres.
    const input = g(
      "Rock",
      "Hard Rock",
      "Classic Rock",
      "Pop Rock",
      "Jazz",
      "Hip Hop",
      "Classical"
    );
    const names = selectDiverseGenres(input, 4).map(x => x.name);

    expect(names[0]).toBe("Rock"); // seeded by count
    expect(names).toContain("Jazz");
    expect(names).toContain("Hip Hop");
    expect(names).toContain("Classical");
    // Only the seed represents the Rock cluster.
    expect(names.filter(n => n.includes("Rock"))).toEqual(["Rock"]);
  });

  it("preserves count order as the tie-breaker among equally-distinct genres", () => {
    // All four are mutually distinct (no shared tokens), so every pick after
    // the seed is a distance tie and should follow input (count) order.
    const input = g("Rock", "Jazz", "Blues", "Folk");
    expect(selectDiverseGenres(input, 3).map(x => x.name)).toEqual([
      "Rock",
      "Jazz",
      "Blues",
    ]);
  });

  it("is case- and separator-insensitive when comparing", () => {
    const input = g("Hip Hop", "hip-hop", "Reggae");
    // "Hip Hop" and "hip-hop" share all tokens → the second is redundant.
    expect(selectDiverseGenres(input, 2).map(x => x.name)).toEqual([
      "Hip Hop",
      "Reggae",
    ]);
  });
});
