import { describe, it, expect } from "vitest";
// ── Uitbouw-dekkingsmatrix: het bouwdashboard van het einddoel ────────────────
// Deze test is bewust ook DOCUMENTATIE: de lijst HUIDIGE_GATEN hieronder is de
// actuele, geprioriteerde werklijst voor het uitbouwen van het einddoel
// (ontwerp eerst). Een bouwer die wil weten "wat is de volgende stap?" leest
// deze lijst — zonder iets te hoeven draaien.
//
// Ratchet-principe: dicht je een gat (bv. je voegt de FR-spiegel van de
// verkoopakte toe), dan faalt deze test tot je het gat hier schrapt — zo blijft
// de werklijst altijd synchroon met de werkelijkheid. Verschijnt er een NIEUW
// gat, dan faalt de test ook: dekking mag nooit stilzwijgend achteruitgaan.
import {
  bepaalUitbouwDekking,
  UITBOUW_KANDIDATEN,
  percentageDossiertypeDekking,
  percentageKandidaatDekking,
  samenvattingUitbouwDekking,
} from "./uitbouw";
import { seedModellen } from "@/data/modeldocumenten";
import { seedBrieven } from "@/data/modelbrieven";

const dekking = bepaalUitbouwDekking({ modellen: seedModellen, brieven: seedBrieven });

/**
 * De actuele werklijst (onderwerp:pijler), in prioriteitsvolgorde — ontwerp
 * eerst (einddoel-prioriteit), daarna FR-spiegel, detectie, mails, afrekening.
 * Binnen elke pijler: frequentie/impact-volgorde van STAPPENPLAN Fase 2.
 */
const HUIDIGE_GATEN: string[] = [
  // Alle pijlers gedekt voor de huidige dossiertypes en uitbreidingskandidaten.
  // Nieuwe gaten verschijnen automatisch zodra een dossiertype of kandidaat een
  // ontbrekende laag heeft; deze test faalt dan tot de lijst weer klopt.
];

describe("Uitbouw-dekkingsmatrix", () => {
  it("de werklijst klopt exact met de werkelijke dekking (ratchet: schrap gedichte gaten, nooit stilzwijgend nieuwe)", () => {
    const gaten = dekking.gaten.map((g) => `${g.onderwerp}:${g.pijler}`);
    expect(gaten).toEqual(HUIDIGE_GATEN);
  });

  it("prioriteert ontwerp-gaten vóór alle andere pijlers (einddoel-prioriteit)", () => {
    const pijlers = dekking.gaten.map((g) => g.pijler);
    const aantalOntwerp = pijlers.filter((p) => p === "ontwerp").length;
    // Alle ontwerp-gaten staan aaneengesloten vooraan in de lijst.
    expect(pijlers.slice(0, aantalOntwerp).every((p) => p === "ontwerp")).toBe(true);
    // En de lijst als geheel is gesorteerd op prioriteit.
    const prioriteiten = dekking.gaten.map((g) => g.prioriteit);
    expect([...prioriteiten].sort((a, b) => a - b)).toEqual(prioriteiten);
  });

  it("het gouden pad (verkoop) is volledig gedekt, inclusief de FR-verkoopakte", () => {
    for (const dt of ["verkoop-met-krediet", "verkoop-zonder-krediet"] as const) {
      const d = dekking.perDossiertype.find((x) => x.dossiertype === dt)!;
      expect(d.ontwerpNL).toBe(true);
      expect(d.detectieZeker).toBe(true);
      expect(d.mails).toBe(true);
      expect(d.afrekening).toBe(true);
      // verkoopakte-woning-fr (recept 3) dicht dit gat: geen gaten meer over.
      expect(d.ontwerpFR).toBe(true);
      expect(d.gaten).toEqual([]);
    }
  });

  it("drukt de ontwerp-dekking per akteType uit (gedeeltelijke dekking binnen één dossiertype, backlog punt 28)", () => {
    for (const d of dekking.perDossiertype) {
      // Eén regel per gedetecteerd akteType, in dezelfde volgorde.
      expect(d.perAkteType.map((p) => p.akteType)).toEqual(d.akteTypes);
      // De dossiertype-brede vlaggen zijn exact de alles-of-niets-samenvatting
      // van de per-akteType-dekking (en zonder detectie is er niets te dekken).
      expect(d.ontwerpNL).toBe(d.akteTypes.length > 0 && d.perAkteType.every((p) => p.ontwerpNL));
      expect(d.ontwerpFR).toBe(d.ontwerpNL && d.perAkteType.every((p) => p.ontwerpFR));
      // FR-dekking veronderstelt NL-dekking (een spiegel zonder bron bestaat niet).
      for (const p of d.perAkteType) {
        if (p.ontwerpFR) expect(p.ontwerpNL, `${d.dossiertype}/${p.akteType}`).toBe(true);
      }
    }
  });

  it("elk gat draagt de receptstap die het dicht (verwijzing naar STAPPENPLAN Fase 2)", () => {
    for (const g of dekking.gaten) {
      expect(g.receptStap, `${g.onderwerp}:${g.pijler}`).toMatch(/^recept \d/);
      expect(g.omschrijving.length).toBeGreaterThan(10);
    }
  });

  it("de kandidaat-conventie werkt: bestaande bouwstenen worden herkend (kredietdossier-brieven, hypotheek-afrekening)", () => {
    const krediet = dekking.kandidaten.find((k) => k.akteType === "kredietakte")!;
    expect(krediet.brieven).toBe(true); // categorie 'kredietdossier' bestaat al
    expect(krediet.afrekeningActief).toBe(true); // akteMeta.hypotheek is actief
    expect(krediet.ontwerpNL).toBe(true); // NL-model 'kredietakte' bestaat nu
    expect(krediet.ontwerpFR).toBe(true); // met FR-spiegel — kandidaat volledig gedekt
  });

  it("herkent bestaande modellen in de catalogus (zorgvolmacht, volmacht): NL-model én FR-spiegel volledig gedekt", () => {
    for (const akteType of ["zorgvolmacht", "volmacht"]) {
      const k = dekking.kandidaten.find((x) => x.akteType === akteType)!;
      expect(k.ontwerpNL, `${akteType}: NL-model bestaat al in de seed-bibliotheek`).toBe(true);
      expect(k.ontwerpFR, `${akteType}: FR-spiegel bestaat nu`).toBe(true);
      expect(k.gaten.some((g) => g.pijler === "ontwerp" || g.pijler === "ontwerp-fr")).toBe(false);
    }
  });

  it("de catalogus is gezond: unieke akteTypes, geen overlap met de dekking-akteTypes van bestaande dossiertypes", () => {
    const akteTypes = UITBOUW_KANDIDATEN.map((k) => k.akteType);
    expect(new Set(akteTypes).size).toBe(akteTypes.length);
    // Een kandidaat mag geen akteType claimen dat de detectie al aan een
    // bestaand dossiertype toewijst (dan is hij geen kandidaat maar dekking).
    const gedekt = new Set(dekking.perDossiertype.flatMap((d) => d.akteTypes));
    for (const t of akteTypes) expect(gedekt.has(t), `${t} is al gedekt door een dossiertype`).toBe(false);
  });

  it("is deterministisch (tweemaal dezelfde bibliotheken → identieke matrix)", () => {
    const opnieuw = bepaalUitbouwDekking({ modellen: seedModellen, brieven: seedBrieven });
    expect(opnieuw).toEqual(dekking);
  });

  it("percentageDossiertypeDekking en percentageKandidaatDekking geven 0-100 en kloppen met de gaten", () => {
    for (const d of dekking.perDossiertype) {
      const pct = percentageDossiertypeDekking(d);
      expect(pct).toBeGreaterThanOrEqual(0);
      expect(pct).toBeLessThanOrEqual(100);
      expect(pct === 100).toBe(d.gaten.length === 0);
    }
    for (const k of dekking.kandidaten) {
      const pct = percentageKandidaatDekking(k);
      expect(pct).toBeGreaterThanOrEqual(0);
      expect(pct).toBeLessThanOrEqual(100);
      // Kandidaten kunnen ook een detectie-onafhankelijk gat missen dat niet
      // in hun 4 controles zit; 100% impliceert dus enkel "geen gaten", niet
      // omgekeerd (hier gelden wel dezelfde 4 velden als gaten-bronnen).
      expect(pct === 100).toBe(k.gaten.length === 0);
    }
  });

  it("samenvattingUitbouwDekking telt alle pijler-controles van dossiertypes én kandidaten", () => {
    const samenvatting = samenvattingUitbouwDekking(dekking);
    const verwachtTotaal = dekking.perDossiertype.length * 5 + dekking.kandidaten.length * 4;
    expect(samenvatting.aantalControlesTotaal).toBe(verwachtTotaal);
    expect(samenvatting.aantalControlesVoltooid).toBeLessThanOrEqual(samenvatting.aantalControlesTotaal);
    expect(samenvatting.percentageVoltooid).toBe(
      Math.round((samenvatting.aantalControlesVoltooid / samenvatting.aantalControlesTotaal) * 100)
    );
  });
});
