import { describe, it, expect } from "vitest";
import {
  normaliseerVoorVingerafdruk,
  berekenParallelVingerafdruk,
  detecteerParallelDivergentie,
  parallelWaarschuwingen,
  genereerDocument,
  standaardKeuzes,
  GEEL_OPEN,
  type Modeldocument,
  type Modelonderdeel,
} from "./index";
import { seedOnderdelen } from "./onderdelen";

// ── Parallelclausule-mechanisme: kern (backlog punt 13) ──────────────────────
// Bewaakt de vingerafdruk-functie (stabiel onder cosmetische edits, gevoelig
// voor woordwijzigingen) en de symmetrische drift-detectie over de hele
// seed-bibliotheek. Zolang er nog geen paren gekoppeld zijn (parallelMetIds),
// is de bibliotheekcontrole triviaal leeg; zodra een opvolgwerf (punt 12) paren
// koppelt, faalt deze test bij elke stille divergentie of asymmetrie.

function onderdeel(over: Partial<Modelonderdeel> = {}): Modelonderdeel {
  return {
    id: "x",
    titel: "X",
    categorie: "attesten",
    omschrijving: "",
    tekst: "De verkoper verklaart dat het goed vrij is van lasten.",
    varianten: [],
    parameters: [],
    toepasbaarOp: [],
    versie: "1.0",
    datum: "2026-07-04",
    ...over,
  };
}

describe("vingerafdruk-normalisatie", () => {
  it("is stabiel onder cosmetische wijzigingen (witruimte, hoofdletters, commentaren, placeholders)", () => {
    const a = onderdeel({ tekst: "De prijs bedraagt {{prijs}} EUR. [NAKIJKEN OF SCHRAPPEN]" });
    const b = onderdeel({
      tekst: "de  PRIJS   bedraagt   *** eur.\n[A VERIFIER: iets anders]",
    });
    expect(berekenParallelVingerafdruk(a)).toBe(berekenParallelVingerafdruk(b));
  });

  it("verandert wél bij een echte woordwijziging (andere wetsverwijzing)", () => {
    const a = onderdeel({ tekst: "overeenkomstig artikel 3.94 van het Burgerlijk Wetboek" });
    const b = onderdeel({ tekst: "overeenkomstig artikel 3.95 van het Burgerlijk Wetboek" });
    expect(berekenParallelVingerafdruk(a)).not.toBe(berekenParallelVingerafdruk(b));
  });

  it("verandert bij het toevoegen/verwijderen van een variant", () => {
    const zonder = onderdeel({ varianten: [] });
    const met = onderdeel({
      varianten: [{ id: "v1", hypothese: "H", tekst: "Bijkomende hypothese." }],
    });
    expect(berekenParallelVingerafdruk(zonder)).not.toBe(berekenParallelVingerafdruk(met));
  });

  it("negeert de brute volgorde van varianten (op id gesorteerd)", () => {
    const v1 = { id: "a", hypothese: "A", tekst: "eerste" };
    const v2 = { id: "b", hypothese: "B", tekst: "tweede" };
    expect(berekenParallelVingerafdruk(onderdeel({ varianten: [v1, v2] }))).toBe(
      berekenParallelVingerafdruk(onderdeel({ varianten: [v2, v1] }))
    );
  });

  it("laat losse haken niet ontsporen (geen inhoud lekt door)", () => {
    expect(normaliseerVoorVingerafdruk("[a [b] c] tekst")).toBe("tekst");
  });
});

describe("detecteerParallelDivergentie", () => {
  const A = onderdeel({ id: "a", tekst: "gedeelde clausule" });
  const B = onderdeel({ id: "b", tekst: "gedeelde clausule (andere akte)" });
  const fpA = berekenParallelVingerafdruk(A);
  const fpB = berekenParallelVingerafdruk(B);

  it("meldt niets voor een correct, symmetrisch, up-to-date paar", () => {
    const paar: Modelonderdeel[] = [
      { ...A, parallelMetIds: ["b"], parallelVingerafdrukken: { b: fpB } },
      { ...B, parallelMetIds: ["a"], parallelVingerafdrukken: { a: fpA } },
    ];
    expect(detecteerParallelDivergentie(paar)).toEqual([]);
  });

  it("meldt asymmetrie wanneer de partner niet terugverwijst", () => {
    const lijst: Modelonderdeel[] = [
      { ...A, parallelMetIds: ["b"], parallelVingerafdrukken: { b: fpB } },
      { ...B }, // verwijst niet terug
    ];
    const p = detecteerParallelDivergentie(lijst);
    expect(p.map((x) => x.soort)).toContain("asymmetrisch");
  });

  it("meldt een onbekende parallel", () => {
    const p = detecteerParallelDivergentie([{ ...A, parallelMetIds: ["bestaat-niet"] }]);
    expect(p[0].soort).toBe("onbekende-parallel");
  });

  it("meldt drift zodra de partner wijzigt, met de nieuwe vingerafdruk als hersteltip", () => {
    const gewijzigdeB = { ...B, tekst: "gedeelde clausule — na een WETSWIJZIGING" };
    const lijst: Modelonderdeel[] = [
      { ...A, parallelMetIds: ["b"], parallelVingerafdrukken: { b: fpB } },
      { ...gewijzigdeB, parallelMetIds: ["a"], parallelVingerafdrukken: { a: fpA } },
    ];
    const p = detecteerParallelDivergentie(lijst);
    const drift = p.find((x) => x.soort === "gedivergeerd" && x.onderdeelId === "a");
    expect(drift).toBeDefined();
    expect(drift!.actueleVingerafdruk).toBe(berekenParallelVingerafdruk(gewijzigdeB));
  });
});

describe("knipperlicht in de generator (opvolgwerf punt 12)", () => {
  const A = onderdeel({ id: "a", tekst: "gedeelde clausule (compromis)" });
  const B = onderdeel({ id: "b", tekst: "gedeelde clausule (akte)" });
  const fpA = berekenParallelVingerafdruk(A);
  const fpB = berekenParallelVingerafdruk(B);
  const gekoppeldA: Modelonderdeel = { ...A, parallelMetIds: ["b"], parallelVingerafdrukken: { b: fpB } };
  const gekoppeldB: Modelonderdeel = { ...B, parallelMetIds: ["a"], parallelVingerafdrukken: { a: fpA } };
  const model: Modeldocument = {
    id: "test-model",
    titel: "Testdocument",
    akteType: "verkoopovereenkomst (compromis)",
    omschrijving: "Testmodel voor het knipperlicht.",
    structuur: [{ onderdeelId: "a", verplicht: true }],
    versie: "1.0",
    datum: "2026-07-06",
  };

  it("zwijgt zolang de parallellen gelijk lopen", () => {
    const bibliotheek = [gekoppeldA, gekoppeldB];
    expect(parallelWaarschuwingen(gekoppeldA, bibliotheek)).toEqual([]);
    const doc = genereerDocument(model, bibliotheek, standaardKeuzes(model), {});
    expect(doc).not.toContain("PARALLELLE CLAUSULE GEWIJZIGD");
  });

  it("zet een geel gearceerde melding in het werkdocument zodra de partner divergeert", () => {
    // De notaris wijzigt de akteclausule (bv. na een wetswijziging), de
    // compromisvariant is nog niet mee nagekeken.
    const gewijzigdeB: Modelonderdeel = { ...gekoppeldB, tekst: "gedeelde clausule (akte) — NA WETSWIJZIGING" };
    const bibliotheek = [gekoppeldA, gewijzigdeB];
    const meldingen = parallelWaarschuwingen(gekoppeldA, bibliotheek);
    expect(meldingen).toHaveLength(1);
    expect(meldingen[0]).toContain(`"${gewijzigdeB.titel}" (b)`);
    const doc = genereerDocument(model, bibliotheek, standaardKeuzes(model), {});
    expect(doc).toContain("PARALLELLE CLAUSULE GEWIJZIGD");
    expect(doc).toContain(GEEL_OPEN);
  });

  it("negeert een onbestaande partner (die bewaakt de ratchet-test al)", () => {
    const wees: Modelonderdeel = { ...A, parallelMetIds: ["bestaat-niet"] };
    expect(parallelWaarschuwingen(wees, [wees])).toEqual([]);
  });
});

describe("seed-bibliotheek: parallelkoppelingen lopen gelijk (ratchet)", () => {
  it("heeft geen enkele gedivergeerde, asymmetrische of ontbrekende parallel", () => {
    const problemen = detecteerParallelDivergentie(seedOnderdelen);
    expect(
      problemen,
      problemen.length
        ? `Parallelkoppelingen uit balans:\n` + problemen.map((p) => ` - ${p.melding}`).join("\n")
        : ""
    ).toEqual([]);
  });
});
