// ── Uitbouw-dekkingsmatrix: waar staat het einddoel, wat is de volgende stap? ─
// Maakt het uitbouwrecept (STAPPENPLAN.md, Fase 2) machineleesbaar, zodat een
// bouwer (mens of AI-agent) in één functieaanroep ziet welke receptstappen per
// dossiertype al gedekt zijn en wat — in prioriteitsvolgorde — het volgende gat
// is. Zo gaan bouwcredits naar het dichten van échte gaten in plaats van naar
// het telkens opnieuw verkennen van de codebase.
//
// De dekking wordt zoveel mogelijk uit het WERKELIJKE gedrag afgeleid (de
// detectie-, model-, mail- en afrekeningslagen worden effectief aangeroepen op
// een minimaal proefdossier), niet uit een parallel bijgehouden lijstje dat kan
// scheefgroeien. Enkel de uitbreidingskandidaten (rechtshandelingen die nog
// geen dossiertype hebben) zijn declaratief: hun `akteType`-string is de
// conventie — wie het model met exact dat akteType toevoegt, ziet de dekking
// automatisch omslaan.
//
// Prioriteit volgt het einddoel: het OPSTELLEN VAN HET ONTWERP eerst, daarna
// de FR-spiegel, de detectie, de mailketen en de afrekening.

import type { Dossier, Dossiertype, DossierVanType } from "./types";
import { maakVeld, maakLeegDossier } from "./types";
import { bepaalAkteType } from "./akteType";
import { relevanteBriefCategorieen } from "./modelmails";
import { stelAfrekeningSamenAutonoom } from "./afrekening";
import type { Modeldocument } from "@/data/modeldocumenten";
import type { AkteTypeNaam } from "@/data/modeldocumenten/akte-types";
import type { Modelbrief } from "@/data/modelbrieven";
import { akteMeta, type AkteType as EreloonAkteType, type OnbepaaldeWaardeSoort } from "@/data/ereloon";

export type UitbouwPijler = "ontwerp" | "ontwerp-fr" | "detectie" | "mails" | "afrekening";

/** Eén concreet gat in de dekking, met de receptstap die het dicht. */
export interface UitbouwGat {
  /** Dossiertype of kandidaat-rechtshandeling waarop het gat slaat. */
  onderwerp: string;
  pijler: UitbouwPijler;
  omschrijving: string;
  /** Receptstap uit STAPPENPLAN.md Fase 2 die dit gat dicht. */
  receptStap: string;
  /** Lager = eerst aanpakken. Ontwerp-gaten komen vóór alle andere pijlers. */
  prioriteit: number;
}

/** Ontwerp-dekking van één gedetecteerd akteType binnen een dossiertype.
 * Eén dossiertype kan meerdere akteTypes dekken (compromis én verkoopakte;
 * schenking roerend én onroerend) en — met sub-paden zoals wet Breyne — ook
 * combinaties waarvan er maar een deel al een model heeft. Dit veld drukt
 * die gedeeltelijke dekking uit, waar de dossiertype-brede vlaggen
 * (`ontwerpNL`/`ontwerpFR`) enkel alles-of-niets kennen. */
export interface AkteTypeDekking {
  akteType: string;
  ontwerpNL: boolean;
  ontwerpFR: boolean;
}

/** Dekking van de receptstappen voor één bestaand dossiertype. */
export interface DossiertypeDekking {
  dossiertype: Dossiertype;
  /** De akteTypes die de detectie voor de proefscenario's teruggeeft. */
  akteTypes: string[];
  /** Ontwerp-dekking per gedetecteerd akteType (zelfde volgorde als `akteTypes`). */
  perAkteType: AkteTypeDekking[];
  ontwerpNL: boolean;
  ontwerpFR: boolean;
  detectieZeker: boolean;
  mails: boolean;
  afrekening: boolean;
  gaten: UitbouwGat[];
}

/** Dekking voor een uitbreidingskandidaat (nog geen dossiertype). */
export interface KandidaatDekking {
  akteType: string;
  omschrijving: string;
  ontwerpNL: boolean;
  ontwerpFR: boolean;
  brieven: boolean;
  afrekeningActief: boolean;
  gaten: UitbouwGat[];
}

export interface UitbouwDekking {
  perDossiertype: DossiertypeDekking[];
  kandidaten: KandidaatDekking[];
  /** Alle gaten samen, gesorteerd op prioriteit (ontwerp eerst). */
  gaten: UitbouwGat[];
}

// ── Prioriteitsgewichten ──────────────────────────────────────────────────────

// Einddoel-prioriteit: het ontwerp eerst, dan de rest van het recept.
const PIJLER_GEWICHT: Record<UitbouwPijler, number> = {
  ontwerp: 1,
  "ontwerp-fr": 2,
  detectie: 3,
  mails: 4,
  afrekening: 5,
};

// Frequentie/impact-volgorde van de onderwerpen: eerst de bestaande
// dossiertypes (volgorde van ALLE_DOSSIERTYPES), daarna de kandidaten in de
// volgorde van UITBOUW_KANDIDATEN. Zo is de declaratievolgorde van de
// catalogus meteen de prioriteitsvolgorde — geen tweede lijstje dat mee moet
// evolueren bij elke nieuwe kandidaat.
function onderwerpGewicht(onderwerp: string): number {
  const dossierIdx = ALLE_DOSSIERTYPES.indexOf(onderwerp as Dossiertype);
  if (dossierIdx >= 0) return dossierIdx + 1;
  const kandidaatIdx = UITBOUW_KANDIDATEN.findIndex((k) => k.akteType === onderwerp);
  if (kandidaatIdx >= 0) return ALLE_DOSSIERTYPES.length + kandidaatIdx + 1;
  return 99;
}

function prioriteit(onderwerp: string, pijler: UitbouwPijler): number {
  return PIJLER_GEWICHT[pijler] * 100 + onderwerpGewicht(onderwerp);
}

const RECEPT_STAP: Record<UitbouwPijler, string> = {
  ontwerp: "recept 1-2: model + onderdelen in data/modeldocumenten (via wijzigingsvoorstel)",
  "ontwerp-fr": "recept 3: FR-spiegel met identieke variant-id's + pariteitstest",
  detectie: "recept 5: akteType-detectie in lib/dossier/akteType.ts",
  mails: "recept 6: modelmail-keten in data/modelbrieven + mapping in modelmails.ts",
  afrekening: "recept 7: afrekening in data/ereloon.ts / data/belastingen.ts + afrekening.ts",
};

// ── Uitbreidingskandidaten (nog geen dossiertype) ─────────────────────────────

/** Praktijkdomein van een rechtshandeling, voor groepering in de catalogus. */
export type UitbouwDomein = "vastgoed" | "familie" | "vennootschappen" | "algemeen";

export interface UitbouwKandidaat {
  /** Geregistreerd akteType (data/modeldocumenten/akte-types.ts): een nieuw
   * model met exact deze naam laat de dekking automatisch omslaan. */
  akteType: AkteTypeNaam;
  omschrijving: string;
  domein: UitbouwDomein;
  briefCategorie?: Modelbrief["categorie"];
  ereloonAkteType?: EreloonAkteType;
  /**
   * Akte van onbepaalde waarde (geen waarde-grondslag): de afrekening beperkt
   * zich tot de forfaitaire aktekosten (art. 2 §2), het honorarium bepaalt de
   * notaris. Sluit elkaar uit met `ereloonAkteType` (waarde-gebaseerd).
   */
  forfaitAkte?: OnbepaaldeWaardeSoort;
}

/**
 * De volledige catalogus van rechtshandelingen die het kantoor nog niet (of
 * nog niet volledig) autonoom kan produceren, in prioriteitsvolgorde
 * (frequentie/impact, cf. STAPPENPLAN Fase 2). Dit is de machineleesbare
 * ambitie van het einddoel: tientallen gouden paden over de hele notariële
 * praktijk. Het `akteType` is de conventie — een nieuw model met exact dit
 * akteType laat de dekking automatisch omslaan; `briefCategorie` en
 * `ereloonAkteType` verwijzen naar bestaande bouwstenen die het pad meteen
 * kan hergebruiken.
 */
export const UITBOUW_KANDIDATEN: UitbouwKandidaat[] = [
  // ── Eerste ring (hoogste frequentie/impact) ──
  { akteType: "kredietakte", omschrijving: "Kredietakte / hypotheekvestiging", domein: "vastgoed", briefCategorie: "kredietdossier", ereloonAkteType: "hypotheek" },
  { akteType: "basisakte", omschrijving: "Basisakte / statuten mede-eigendom", domein: "vastgoed", briefCategorie: "basisakte", forfaitAkte: "hoofdakte" },
  { akteType: "verdeling-uit-onverdeeldheid", omschrijving: "Verdeling uit onverdeeldheid", domein: "vastgoed", briefCategorie: "vastgoed_diversen", ereloonAkteType: "verdeling" },
  { akteType: "erfovereenkomst", omschrijving: "Erfovereenkomst (familieplanning)", domein: "familie", briefCategorie: "erfovereenkomst", forfaitAkte: "hoofdakte" },
  // "aangifte-nalatenschap" is geen kandidaat meer maar dekking: de detectie wijst
  // ze toe aan het nalatenschapsdossier (zie bepaalAkteType) en het NL/FR-model
  // bestaat — ze telt dus mee in de dossiertype-dekking, niet in deze catalogus.
  { akteType: "huwelijkscontract", omschrijving: "Huwelijkscontract", domein: "familie", briefCategorie: "huwelijk_samenwoning", forfaitAkte: "hoofdakte" },
  // ── Familie ──
  { akteType: "zorgvolmacht", omschrijving: "Zorgvolmacht (buitengerechtelijke bescherming)", domein: "familie", briefCategorie: "zorgvolmacht", forfaitAkte: "hoofdakte" },
  { akteType: "wijziging-huwelijksstelsel", omschrijving: "Wijziging van het huwelijksvermogensstelsel", domein: "familie", briefCategorie: "huwelijk_samenwoning", forfaitAkte: "hoofdakte" },
  { akteType: "samenlevingsovereenkomst", omschrijving: "Samenlevingsovereenkomst (wettelijk samenwonenden)", domein: "familie", briefCategorie: "huwelijk_samenwoning", forfaitAkte: "hoofdakte" },
  { akteType: "echtscheiding-onderlinge-toestemming", omschrijving: "Regelingsakte echtscheiding door onderlinge toestemming", domein: "familie", briefCategorie: "echtscheiding", forfaitAkte: "hoofdakte" },
  { akteType: "akte-erfopvolging", omschrijving: "Akte/attest van erfopvolging", domein: "familie", briefCategorie: "nalatenschap", forfaitAkte: "hoofdakte" },
  // ── Vennootschappen ──
  { akteType: "oprichting-vennootschap", omschrijving: "Oprichting van een vennootschap", domein: "vennootschappen", briefCategorie: "vennootschappen", forfaitAkte: "bv-standaard" },
  { akteType: "ontbinding-vereffening-vennootschap", omschrijving: "Ontbinding en vereffening van een vennootschap", domein: "vennootschappen", briefCategorie: "vennootschappen", forfaitAkte: "hoofdakte" },
  { akteType: "omzetting-vennootschap", omschrijving: "Omzetting van een vennootschap (andere rechtsvorm)", domein: "vennootschappen", briefCategorie: "vennootschappen", forfaitAkte: "hoofdakte" },
  // ── Vastgoed (tweede ring) ──
  // Een openbare verkoop is fiscaal en qua honorarium een verkoop: zelfde
  // verkooprecht (VCF art. 2.9.1.0.1) en zelfde ereloonschaal (KB 1950 schaal J)
  // als een verkoop uit de hand — vandaar hetzelfde ereloon-akteType.
  { akteType: "openbare-verkoop", omschrijving: "Openbare verkoop (incl. Biddit)", domein: "vastgoed", briefCategorie: "openbare_verkoop", ereloonAkteType: "verkoop" },
  { akteType: "ruil", omschrijving: "Ruil van onroerende goederen", domein: "vastgoed", briefCategorie: "vastgoed_diversen", ereloonAkteType: "ruil" },
  { akteType: "vestiging-erfpacht-opstal", omschrijving: "Vestiging van erfpacht- of opstalrecht", domein: "vastgoed", briefCategorie: "vastgoed_diversen", ereloonAkteType: "erfpacht" },
  { akteType: "hypothecair-mandaat", omschrijving: "Hypothecair mandaat / volmacht tot hypothekeren", domein: "vastgoed", briefCategorie: "kredietdossier", ereloonAkteType: "hypotheek-mandaat" },
  { akteType: "handlichting", omschrijving: "Handlichting (doorhaling hypothecaire inschrijving)", domein: "vastgoed", briefCategorie: "vastgoed_diversen", forfaitAkte: "hoofdakte" },
  // Officiële VlaNot-brontekst (NL+FR) beschikbaar sinds 2026-07-05
  // (integratie/drive-backup, "Verkavelingsakte.docx"/"Acte de lotissement.docx");
  // nog geen model. Eén partij (de verkavelaar) vóór de notaris — géén koper op
  // dit ogenblik — dus geen bestaand ereloon-AkteType/briefCategorie herbruikbaar.
  { akteType: "verkavelingsakte", omschrijving: "Verkavelingsakte (art. 4.2.16 VCRO)", domein: "vastgoed", briefCategorie: "vastgoed_diversen", forfaitAkte: "hoofdakte" },
  // ── Algemeen ──
  { akteType: "volmacht", omschrijving: "Notariële (authentieke) volmacht", domein: "algemeen", briefCategorie: "volmacht", forfaitAkte: "hoofdakte" },
];

// ── Proefscenario's per dossiertype ───────────────────────────────────────────

function basisDossier<T extends Dossiertype>(
  dossiertype: T,
  extra: Partial<DossierVanType<T>> = {}
): DossierVanType<T> {
  return Object.assign(
    maakLeegDossier(dossiertype, {
      id: `uitbouw-${dossiertype}`,
      status: "ontwerp-in-opmaak",
      partijen: maakVeld([], "manueel"),
      ontbrekendeStukken: [],
      tegenstrijdigheden: [],
      aangemaakt: "2026-01-01T00:00:00.000Z",
      aangepast: "2026-01-01T00:00:00.000Z",
    }),
    extra
  );
}

/**
 * Minimale maar besliste proefdossiers per dossiertype: genoeg feiten om de
 * detectie en de afrekening hun zekere pad te laten kiezen. Meerdere
 * scenario's wanneer één dossiertype meerdere ontwerpen kent (compromis én
 * verkoopakte; schenking roerend én onroerend).
 */
function proefScenarios(dossiertype: Dossiertype): Dossier[] {
  switch (dossiertype) {
    case "verkoop-met-krediet":
      return [
        basisDossier(dossiertype, {
          verkoopdocument: "compromis",
          prijs: maakVeld(300000, "compromis"),
          kredietofferte: maakVeld({ bank: "Bank", bedrag: 250000, aanvaard: false }, "kredietofferte"),
        }),
        basisDossier(dossiertype, {
          verkoopdocument: "verkoopakte",
          prijs: maakVeld(300000, "compromis"),
          compromisdatum: maakVeld("2026-01-01", "compromis"),
          kredietofferte: maakVeld({ bank: "Bank", bedrag: 250000, aanvaard: false }, "kredietofferte"),
        }),
        // Wet Breyne (sub-pad via verkoopRegime, backlog punt 27): beide fasen
        // meten mee in de per-akteType-dekking (punt 28), zodat een regressie
        // van de Breyne-modellen als deelgat zichtbaar wordt zonder het hele
        // dossiertype vals te laten omslaan.
        basisDossier(dossiertype, {
          verkoopdocument: "compromis",
          verkoopRegime: "wet-breyne",
          prijs: maakVeld(300000, "compromis"),
          kredietofferte: maakVeld({ bank: "Bank", bedrag: 250000, aanvaard: false }, "kredietofferte"),
        }),
        basisDossier(dossiertype, {
          verkoopdocument: "verkoopakte",
          verkoopRegime: "wet-breyne",
          prijs: maakVeld(300000, "compromis"),
          compromisdatum: maakVeld("2026-01-01", "compromis"),
          kredietofferte: maakVeld({ bank: "Bank", bedrag: 250000, aanvaard: false }, "kredietofferte"),
        }),
      ];
    case "verkoop-zonder-krediet":
      return [
        basisDossier(dossiertype, { verkoopdocument: "compromis", prijs: maakVeld(300000, "compromis") }),
        basisDossier(dossiertype, {
          verkoopdocument: "verkoopakte",
          prijs: maakVeld(300000, "compromis"),
          compromisdatum: maakVeld("2026-01-01", "compromis"),
        }),
      ];
    case "schenking":
      return (["onroerend", "roerend"] as const).map((goedType) =>
        basisDossier(dossiertype, {
          partijen: maakVeld([{ naam: "X", rol: "schenker" }, { naam: "Y", rol: "begiftigde" }], "manueel"),
          belastbareWaarde: maakVeld(200000, "verklaring-partij"),
          relatieBelasting: maakVeld("rechte_lijn_partner", "verklaring-partij"),
          goedTypeBelasting: maakVeld(goedType, "verklaring-partij"),
        })
      );
    case "nalatenschap":
      return [
        basisDossier(dossiertype, {
          partijen: maakVeld([{ naam: "X", rol: "erflater" }, { naam: "Y", rol: "erfgenaam", testamentRelatie: "partner" }], "manueel"),
          belastbareWaarde: maakVeld(200000, "verklaring-partij"),
          relatieBelasting: maakVeld("rechte_lijn_partner", "verklaring-partij"),
        }),
      ];
    case "aanpassing-statuten-vennootschap":
      return [basisDossier(dossiertype, { vennootschap: maakVeld({ naam: "X BV" }, "manueel") })];
    case "keuzetestament":
    case "testament-gezinswoning":
      return [basisDossier(dossiertype)];
    default: {
      const _onbekend: never = dossiertype;
      void _onbekend;
      return [];
    }
  }
}

const ALLE_DOSSIERTYPES: Dossiertype[] = [
  "verkoop-met-krediet",
  "verkoop-zonder-krediet",
  "schenking",
  "nalatenschap",
  "testament-gezinswoning",
  "keuzetestament",
  "aanpassing-statuten-vennootschap",
];

// ── Dekking berekenen ─────────────────────────────────────────────────────────

function heeftFrSpiegel(modellen: Modeldocument[], nlModel: Modeldocument): boolean {
  return modellen.some((m) => (m.taal ?? "nl") === "fr" && m.vertalingVanId === nlModel.id);
}

/**
 * Berekent de volledige uitbouw-dekkingsmatrix uit de aangeleverde bibliotheken
 * (seed + eventuele gebruikersitems). Pure functie: dezelfde bibliotheken
 * leveren altijd dezelfde matrix en dezelfde prioriteitenlijst.
 */
export function bepaalUitbouwDekking(bibliotheken: {
  modellen: Modeldocument[];
  brieven: Modelbrief[];
}): UitbouwDekking {
  const { modellen, brieven } = bibliotheken;
  const perDossiertype: DossiertypeDekking[] = [];

  for (const dossiertype of ALLE_DOSSIERTYPES) {
    const gaten: UitbouwGat[] = [];
    const scenarios = proefScenarios(dossiertype);
    const keuzes = scenarios.map((s) => bepaalAkteType(s));
    const akteTypes = [...new Set(keuzes.map((k) => k.akteType).filter(Boolean))];

    // Detectie: elk proefscenario moet een zeker, niet-leeg akteType opleveren.
    const detectieZeker = keuzes.every((k) => k.akteType !== "" && k.zekerheid === "zeker");
    if (!detectieZeker) {
      gaten.push({
        onderwerp: dossiertype,
        pijler: "detectie",
        omschrijving: `De akteType-detectie is voor "${dossiertype}" niet in elk scenario zeker en niet-leeg.`,
        receptStap: RECEPT_STAP.detectie,
        prioriteit: prioriteit(dossiertype, "detectie"),
      });
    }

    // Ontwerp NL: voor elk gedetecteerd akteType bestaat een NL-model. De
    // per-akteType-lijst drukt gedeeltelijke dekking uit (relevant zodra een
    // sub-pad zoals wet Breyne extra akteTypes aan hetzelfde dossiertype
    // toevoegt); de dossiertype-brede vlaggen blijven de alles-of-niets-
    // samenvatting daarvan.
    const nlModellen = akteTypes.map((t) => modellen.find((m) => m.akteType === t && (m.taal ?? "nl") === "nl"));
    const perAkteType: AkteTypeDekking[] = akteTypes.map((t, i) => ({
      akteType: t,
      ontwerpNL: Boolean(nlModellen[i]),
      ontwerpFR: Boolean(nlModellen[i] && heeftFrSpiegel(modellen, nlModellen[i]!)),
    }));
    const ontbrekendNL = perAkteType.filter((p) => !p.ontwerpNL).map((p) => p.akteType);
    const ontwerpNL = akteTypes.length > 0 && ontbrekendNL.length === 0;
    if (!ontwerpNL) {
      gaten.push({
        onderwerp: dossiertype,
        pijler: "ontwerp",
        omschrijving:
          akteTypes.length === 0
            ? `Geen enkel akteType afleidbaar voor "${dossiertype}", dus ook geen model te kiezen.`
            : `Geen NL-model voor akteType(s): ${ontbrekendNL.join(", ")}.`,
        receptStap: RECEPT_STAP.ontwerp,
        prioriteit: prioriteit(dossiertype, "ontwerp"),
      });
    }

    // Ontwerp FR: elke aanwezige NL-variant heeft een FR-spiegel.
    const zonderFr = nlModellen.filter(
      (m, i): m is Modeldocument => Boolean(m) && !perAkteType[i].ontwerpFR
    );
    const ontwerpFR = ontwerpNL && zonderFr.length === 0;
    if (ontwerpNL && !ontwerpFR) {
      gaten.push({
        onderwerp: dossiertype,
        pijler: "ontwerp-fr",
        omschrijving: `Geen FR-spiegel voor model(len): ${zonderFr.map((m) => m.id).join(", ")}.`,
        receptStap: RECEPT_STAP["ontwerp-fr"],
        prioriteit: prioriteit(dossiertype, "ontwerp-fr"),
      });
    }

    // Mails: er is minstens één relevante categorie én elke categorie heeft
    // minstens één NL-modelbrief in de bibliotheek.
    const categorieen = relevanteBriefCategorieen(dossiertype);
    const legeCategorieen = categorieen.filter((c) => !brieven.some((b) => b.categorie === c && (b.taal ?? "nl") === "nl"));
    const mails = categorieen.length > 0 && legeCategorieen.length === 0;
    if (!mails) {
      gaten.push({
        onderwerp: dossiertype,
        pijler: "mails",
        omschrijving:
          categorieen.length === 0
            ? `Geen modelmail-categorie gemapt voor "${dossiertype}" (bepaalBasisCategorieen).`
            : `Categorie(ën) zonder NL-modelbrief: ${legeCategorieen.join(", ")}.`,
        receptStap: RECEPT_STAP.mails,
        prioriteit: prioriteit(dossiertype, "mails"),
      });
    }

    // Afrekening: minstens één proefscenario levert aktekosten of belasting op.
    const afrekening = scenarios.some((s) => {
      const a = stelAfrekeningSamenAutonoom(s, "Vlaanderen");
      return Boolean(a.aktekosten || a.belasting || a.forfaitaireAfrekening);
    });
    if (!afrekening) {
      gaten.push({
        onderwerp: dossiertype,
        pijler: "afrekening",
        omschrijving: `Geen automatische afrekening (aktekosten/belasting) voor "${dossiertype}".`,
        receptStap: RECEPT_STAP.afrekening,
        prioriteit: prioriteit(dossiertype, "afrekening"),
      });
    }

    perDossiertype.push({ dossiertype, akteTypes, perAkteType, ontwerpNL, ontwerpFR, detectieZeker, mails, afrekening, gaten });
  }

  // Uitbreidingskandidaten: dekking op basis van conventie-akteType.
  const kandidaten: KandidaatDekking[] = UITBOUW_KANDIDATEN.map((k) => {
    const gaten: UitbouwGat[] = [];
    const nlModel = modellen.find((m) => m.akteType === k.akteType && (m.taal ?? "nl") === "nl");
    const ontwerpNL = Boolean(nlModel);
    const ontwerpFR = Boolean(nlModel && heeftFrSpiegel(modellen, nlModel));
    const heeftBrieven = k.briefCategorie
      ? brieven.some((b) => b.categorie === k.briefCategorie && (b.taal ?? "nl") === "nl")
      : false;
    const afrekeningActief =
      (k.ereloonAkteType ? akteMeta[k.ereloonAkteType].actief : false) || Boolean(k.forfaitAkte);

    if (!ontwerpNL) {
      gaten.push({
        onderwerp: k.akteType,
        pijler: "ontwerp",
        omschrijving: `${k.omschrijving}: nog geen NL-model met akteType "${k.akteType}".`,
        receptStap: RECEPT_STAP.ontwerp,
        prioriteit: prioriteit(k.akteType, "ontwerp"),
      });
    } else if (!ontwerpFR) {
      gaten.push({
        onderwerp: k.akteType,
        pijler: "ontwerp-fr",
        omschrijving: `${k.omschrijving}: NL-model bestaat maar de FR-spiegel ontbreekt.`,
        receptStap: RECEPT_STAP["ontwerp-fr"],
        prioriteit: prioriteit(k.akteType, "ontwerp-fr"),
      });
    }
    if (!heeftBrieven) {
      gaten.push({
        onderwerp: k.akteType,
        pijler: "mails",
        omschrijving: `${k.omschrijving}: geen modelbrieven${k.briefCategorie ? ` in categorie "${k.briefCategorie}"` : " (nog geen categorie)"}.`,
        receptStap: RECEPT_STAP.mails,
        prioriteit: prioriteit(k.akteType, "mails"),
      });
    }
    if (!afrekeningActief) {
      gaten.push({
        onderwerp: k.akteType,
        pijler: "afrekening",
        omschrijving: `${k.omschrijving}: geen actieve afrekening${k.ereloonAkteType ? ` (akteMeta.${k.ereloonAkteType})` : " (nog geen ereloon-akteType)"}.`,
        receptStap: RECEPT_STAP.afrekening,
        prioriteit: prioriteit(k.akteType, "afrekening"),
      });
    }

    return { akteType: k.akteType, omschrijving: k.omschrijving, ontwerpNL, ontwerpFR, brieven: heeftBrieven, afrekeningActief, gaten };
  });

  const gaten = [...perDossiertype.flatMap((d) => d.gaten), ...kandidaten.flatMap((c) => c.gaten)].sort(
    (a, b) => a.prioriteit - b.prioriteit
  );

  return { perDossiertype, kandidaten, gaten };
}

// ── Percentages voor de UI ("gouden paden"-module) ────────────────────────────
// Zuivere afgeleiden van UitbouwDekking — geen React, herbruikbaar in tests.

/** Pijler-controles van één gouden pad (bestaand dossiertype), voor percentage/weergave. */
function dossiertypeControles(d: DossiertypeDekking): boolean[] {
  return [d.ontwerpNL, d.ontwerpFR, d.detectieZeker, d.mails, d.afrekening];
}

/** Pijler-controles van één uitbreidingskandidaat (nog geen dossiertype). */
function kandidaatControles(k: KandidaatDekking): boolean[] {
  return [k.ontwerpNL, k.ontwerpFR, k.brieven, k.afrekeningActief];
}

function percentage(controles: boolean[]): number {
  if (controles.length === 0) return 0;
  return Math.round((controles.filter(Boolean).length / controles.length) * 100);
}

/** Percentage van de pijler-controles die voor dit gouden pad al gedekt zijn. */
export function percentageDossiertypeDekking(d: DossiertypeDekking): number {
  return percentage(dossiertypeControles(d));
}

/** Percentage van de pijler-controles die voor deze uitbreidingskandidaat al gedekt zijn. */
export function percentageKandidaatDekking(k: KandidaatDekking): number {
  return percentage(kandidaatControles(k));
}

/** Samenvatting van de volledige dekkingsmatrix, voor de voortgangsindicator naar het einddoel. */
export interface UitbouwSamenvatting {
  percentageVoltooid: number;
  aantalControlesVoltooid: number;
  aantalControlesTotaal: number;
}

export function samenvattingUitbouwDekking(dekking: UitbouwDekking): UitbouwSamenvatting {
  const alleControles = [
    ...dekking.perDossiertype.flatMap(dossiertypeControles),
    ...dekking.kandidaten.flatMap(kandidaatControles),
  ];
  return {
    aantalControlesTotaal: alleControles.length,
    aantalControlesVoltooid: alleControles.filter(Boolean).length,
    percentageVoltooid: percentage(alleControles),
  };
}
