import { describe, it, expect } from "vitest";
import { deriveFootnoteId } from "./footnote-util";

/**
 * GOLDEN TABLE for `deriveFootnoteId` (and its private alphabetic `suffix`).
 *
 * deriveFootnoteId is DELIBERATELY duplicated in
 *   packages/mcp/src/lib/collaboration.ts
 * and the two copies MUST stay byte-for-byte equivalent in behavior so the same
 * markdown imported through the editor and through the MCP path yields identical
 * footnote ids. This table is the SHARED contract: the parity test
 *   packages/mcp/test/unit/derive-id-parity.test.mjs
 * pins the exact SAME (input -> expected) pairs against the COMPILED mcp build.
 * If either copy drifts, one of the two tests goes red.
 *
 * Keep this constant in sync with GOLDEN in the mcp parity test.
 */
export const DERIVE_GOLDEN: Array<{
  originalId: string;
  occurrence: number;
  taken: string[];
  expected: string;
  why: string;
}> = [
  // Base candidate `${id}__${occurrence}` when nothing collides.
  { originalId: "d", occurrence: 2, taken: [], expected: "d__2", why: "plain base, second occurrence" },
  { originalId: "d", occurrence: 3, taken: [], expected: "d__3", why: "plain base, third occurrence" },
  // The base is taken -> first alphabetic bump is "b" (NOT "a": suffix starts at 'b').
  { originalId: "d", occurrence: 2, taken: ["d__2"], expected: "d__2b", why: "base taken -> first bump 'b'" },
  // Base + first bump taken -> "c".
  { originalId: "d", occurrence: 2, taken: ["d__2", "d__2b"], expected: "d__2c", why: "base+b taken -> 'c'" },
  // A non-contiguous taken set still walks deterministically to the first free slot.
  {
    originalId: "d",
    occurrence: 2,
    taken: ["d__2", "d__2b", "d__2c", "d__2d"],
    expected: "d__2e",
    why: "base + b,c,d taken -> 'e'",
  },
  // >25 bump: base + b..z (the 25 single-letter suffixes) all taken -> "bb".
  // suffix(26) === "bb" (base-25 over b..z, carrying to a two-letter suffix).
  {
    originalId: "d",
    occurrence: 2,
    taken: ["d__2", ...singleLetterSuffixes().map((s) => `d__2${s}`)],
    expected: "d__2bb",
    why: ">25 collisions -> two-letter suffix 'bb'",
  },
];

/** The 25 single-letter suffixes the scheme uses: b, c, ..., z (n = 1..25). */
function singleLetterSuffixes(): string[] {
  // Mirror of the production suffix() for n in 1..25 (all single letters).
  // n=1 -> 'b' ... n=25 -> 'z'. Used only to BUILD the taken-set for the
  // >25 row; the EXPECTED value (d__2bb) is asserted against the real function.
  return Array.from({ length: 25 }, (_, i) => String.fromCharCode(98 + i));
}

describe("deriveFootnoteId golden table (cross-package drift guard)", () => {
  for (const row of DERIVE_GOLDEN) {
    it(`derive("${row.originalId}", ${row.occurrence}, {${row.taken.join(",")}}) === "${row.expected}" — ${row.why}`, () => {
      const got = deriveFootnoteId(
        row.originalId,
        row.occurrence,
        new Set(row.taken),
      );
      expect(got).toBe(row.expected);
    });
  }

  it("the >25 row's taken-set really contains b..z (25 single letters) plus the base", () => {
    // Sanity-pin the construction so a typo in singleLetterSuffixes() cannot make
    // the >25 assertion pass for the wrong reason.
    const letters = singleLetterSuffixes();
    expect(letters).toHaveLength(25);
    expect(letters[0]).toBe("b");
    expect(letters[24]).toBe("z");
  });

  it("is a PURE function: it never mutates the taken set it is given", () => {
    const taken = new Set(["d__2"]);
    const before = [...taken];
    deriveFootnoteId("d", 2, taken);
    expect([...taken]).toEqual(before);
  });

  it("is deterministic: same input -> same output across calls", () => {
    const mk = () => new Set(["d__2", "d__2b"]);
    expect(deriveFootnoteId("d", 2, mk())).toBe(deriveFootnoteId("d", 2, mk()));
  });
});
