// ── Gouden-pad-harnas: één meetlat voor élk gouden pad ───────────────────────
// Het einddoel vraagt tientallen gouden paden (één per aktetype). Elk pad
// apart bewaken met een eigen, gekloonde testfile schaalt niet; dit harnas
// maakt van "een gouden pad bewaken" één declaratief scenario-object:
// intake-JSON in, en de meting komt uit het WERKELIJKE gedrag van de volledige
// motor (stelWerkdossierSamenAutonoom) — detectie, model, open velden, open
// hypothesekeuzes, mails en afrekening in één keer.
//
// Gebruik (zie DRAAIBOEK-GOUDEN-PAD.md, laag 10): voeg een scenario toe aan
// GOUDEN_PADEN in `gouden-paden.ts`; `gouden-paden.test.ts` bewaakt het dan
// automatisch met ratchet-plafonds (enkel verlagen, nooit stilzwijgend
// verhogen). Padspecifieke inhouds-asserties (zoals de compromis-details in
// `gouden-pad-compromis.test.ts`) blijven mogelijk náást dit harnas, maar het
// basisniveau — werkt het pad, en regresseert het niet? — komt hiervandaan.

import type { Dossier } from "./types";
import { dossierVeldenUitIntake, maakDossierUitIntakeResultaat, DOSSIER_INTAKE_SCHEMA } from "./intake";
import { stelWerkdossierSamenAutonoom } from "./werkdossier";
import type { Gewest } from "@/lib/context/gewest";
import type { Modeldocument, Modelonderdeel } from "@/data/modeldocumenten";
import type { AkteTypeNaam } from "@/data/modeldocumenten/akte-types";
import type { Modelbrief } from "@/data/modelbrieven";

/** De open-hypothese-marker in gegenereerde documenten (NL of FR). */
export const OPEN_KEUZE_MARKER =
  /KIES DE TOEPASSELIJKE HYPOTHESE EN SCHRAP DE OVERIGE:|CHOISIR L'HYPOTHÈSE APPLICABLE ET SUPPRIMER LES AUTRES/g;

/**
 * Eén gouden pad, declaratief: het intake-JSON van een realistisch dossier en
 * de verwachte, geratchte uitkomst van de motor. De plafonds (`maxOpenVelden`,
 * `maxOpenKeuzes`) volgen het ratchet-principe: verlaag ze bij elke
 * verbetering, verhoog ze nooit stilzwijgend. Of de mail- en afrekeningpijler
 * het pad dekken is GEEN scenario-veld: dat wordt gemeten uit het werkelijke
 * gedrag (`GoudenPadMeting.heeftMails`/`heeftAfrekening`) — een levend feit
 * kan niet verouderen; de volwassenheids-ratchet bewaakt de bewuste omslag.
 */
export interface GoudenPadScenario {
  naam: string;
  /** Intake-JSON zoals een agent het aanlevert (zonder `schema`-veld; dat vult het harnas aan). */
  intake: Record<string, unknown>;
  gewest: Gewest;
  /** Verwachte deterministische detectie (zekerheid moet "zeker" zijn). */
  verwachtAkteType: AkteTypeNaam;
  verwachtModelId: string;
  /** Ratchet: maximum aantal unieke open [AAN TE VULLEN]-velden in het NL-ontwerp. */
  maxOpenVelden: number;
  /** Ratchet: maximum aantal open hypothesekeuzes in het NL-ontwerp. */
  maxOpenKeuzes: number;
  /** FR-spiegelmodel; weglaten = gekend gat (bewaakt door de uitbouw-matrix). */
  frModelId?: string;
  maxOpenVeldenFr?: number;
  maxOpenKeuzesFr?: number;
}

/** Meting van één taal-run van de motor op een scenario. */
export interface GoudenPadTaalMeting {
  modelId?: string;
  openVelden: string[];
  openKeuzes: number;
}

/** De volledige meting van één scenario, uit het werkelijke gedrag. */
export interface GoudenPadMeting {
  akteType: string;
  zekerheid: "zeker" | "onduidelijk";
  nl: GoudenPadTaalMeting;
  /** Enkel gemeten wanneer het scenario een FR-spiegel verwacht. */
  fr?: GoudenPadTaalMeting;
  heeftMails: boolean;
  heeftAfrekening: boolean;
}

export interface GoudenPadBibliotheken {
  modellen: Modeldocument[];
  onderdelen: Modelonderdeel[];
  brieven: Modelbrief[];
}

/** Bouwt het proefdossier van een scenario via de éne dossier-bouwer (intake.ts). */
export function bouwScenarioDossier(scenario: GoudenPadScenario): Dossier {
  return maakDossierUitIntakeResultaat(
    dossierVeldenUitIntake({ schema: DOSSIER_INTAKE_SCHEMA, ...scenario.intake }),
    {
      id: `gouden-pad-${scenario.naam}`,
      status: "ontwerp-in-opmaak",
      aangemaakt: "2026-06-01T00:00:00.000Z",
      aangepast: "2026-06-01T00:00:00.000Z",
    }
  );
}

function meetTekst(tekst: string | undefined): Pick<GoudenPadTaalMeting, "openVelden" | "openKeuzes"> {
  if (!tekst) return { openVelden: [], openKeuzes: 0 };
  return {
    openVelden: [...new Set([...tekst.matchAll(/\[AAN TE VULLEN: ([^\]]+)\]/g)].map((m) => m[1]))],
    openKeuzes: (tekst.match(OPEN_KEUZE_MARKER) ?? []).length,
  };
}

/**
 * Meet één gouden pad end-to-end via de volledige autonome motor. Puur en
 * deterministisch: zelfde scenario + bibliotheken → zelfde meting.
 */
export function beoordeelGoudenPad(
  scenario: GoudenPadScenario,
  bibliotheken: GoudenPadBibliotheken
): GoudenPadMeting {
  const dossier = bouwScenarioDossier(scenario);
  const wd = stelWerkdossierSamenAutonoom(dossier, bibliotheken, scenario.gewest, "nl");

  const meting: GoudenPadMeting = {
    akteType: wd.ontwerpakte.akteTypeKeuze.akteType,
    zekerheid: wd.ontwerpakte.akteTypeKeuze.zekerheid,
    nl: { modelId: wd.ontwerpakte.model?.id, ...meetTekst(wd.ontwerpakte.tekst) },
    heeftMails: wd.modelmails.mails.length > 0,
    // Zelfde definitie als de uitbouw-dekkingsmatrix (uitbouw.ts).
    heeftAfrekening: Boolean(wd.afrekening.aktekosten || wd.afrekening.belasting),
  };

  if (scenario.frModelId) {
    const wdFr = stelWerkdossierSamenAutonoom(dossier, bibliotheken, scenario.gewest, "fr");
    meting.fr = { modelId: wdFr.ontwerpakte.model?.id, ...meetTekst(wdFr.ontwerpakte.tekst) };
  }

  return meting;
}

// ── Volwassenheidsrapport: hoe ver staat elk gouden pad van het einddoel? ────

/** De pijlers die een geregistreerd gouden pad nog kunnen ontbreken, in de
 * einddoel-prioriteitsvolgorde van AGENTS.md (ontwerp-NL is per definitie
 * gedekt: zonder werkend NL-ontwerp bestaat het scenario niet). */
export type GoudenPadPijlerGat = "ontwerp-fr" | "mails" | "afrekening";

const PIJLER_VOLGORDE: GoudenPadPijlerGat[] = ["ontwerp-fr", "mails", "afrekening"];

/** Eén geregistreerd gouden pad mét zijn motor-meting: de input voor het
 * volwassenheidsrapport (de mail-/afrekeningpijler komt uit de meting). */
export interface GemetenGoudenPad {
  scenario: GoudenPadScenario;
  meting: GoudenPadMeting;
}

/** Volwassenheid van één gouden pad: de FR-pijler uit het scenario (gekend
 * gat of niet), de mail- en afrekeningpijler uit het werkelijke gedrag. */
export interface GoudenPadVolwassenheid {
  naam: string;
  verwachtAkteType: AkteTypeNaam;
  /** Ontbrekende pijlers, in einddoel-prioriteitsvolgorde; leeg = volledig. */
  gaten: GoudenPadPijlerGat[];
  /** Gedekte pijlers van de vier (ontwerp-NL telt altijd mee). */
  gedekt: number;
  totaalPijlers: 4;
  /** Resterend invulwerk in het NL-ontwerp (de ratchet-plafonds). */
  openVelden: number;
  openKeuzes: number;
  volledig: boolean;
}

export interface GoudenPadVolwassenheidsRapport {
  /** Eén regel per pad, verst van het einddoel eerst (= werklijst-volgorde). */
  paden: GoudenPadVolwassenheid[];
  /** Gedekte pijlers over alle paden heen, als voortgangsteller. */
  gedektePijlers: number;
  totaalPijlers: number;
}

function padVolwassenheid({ scenario: s, meting }: GemetenGoudenPad): GoudenPadVolwassenheid {
  const gaten = PIJLER_VOLGORDE.filter((p) =>
    p === "ontwerp-fr" ? !s.frModelId : p === "mails" ? !meting.heeftMails : !meting.heeftAfrekening
  );
  return {
    naam: s.naam,
    verwachtAkteType: s.verwachtAkteType,
    gaten,
    gedekt: 4 - gaten.length,
    totaalPijlers: 4,
    openVelden: s.maxOpenVelden,
    openKeuzes: s.maxOpenKeuzes,
    volledig: gaten.length === 0,
  };
}

/**
 * Rangschikt alle geregistreerde gouden paden naar afstand tot het einddoel:
 * eerst het pad met de meeste ontbrekende pijlers, bij gelijke stand het pad
 * waarvan het eerste gat het vroegst in de einddoel-prioriteit valt
 * (FR-spiegel vóór mails vóór afrekening), daarna het pad met de meeste open
 * hypothesekeuzes. Puur en deterministisch: aggregatie over de scenario's en
 * hun motor-metingen (`beoordeelGoudenPad`) — de mail-/afrekeningpijler is
 * dus een gemeten feit, geen manueel bijgehouden vlag (backlogpunt 19) — als
 * geconsolideerd overzicht naast de per-pad-ratchets van gouden-paden.test.ts.
 */
export function berekenGoudenPadVolwassenheid(
  gemeten: GemetenGoudenPad[]
): GoudenPadVolwassenheidsRapport {
  const paden = gemeten.map(padVolwassenheid).sort((a, b) => {
    if (a.gaten.length !== b.gaten.length) return b.gaten.length - a.gaten.length;
    const eersteGat = (p: GoudenPadVolwassenheid) =>
      p.gaten.length ? PIJLER_VOLGORDE.indexOf(p.gaten[0]) : PIJLER_VOLGORDE.length;
    if (eersteGat(a) !== eersteGat(b)) return eersteGat(a) - eersteGat(b);
    if (a.openKeuzes !== b.openKeuzes) return b.openKeuzes - a.openKeuzes;
    return a.naam.localeCompare(b.naam);
  });
  const gedektePijlers = paden.reduce((som, p) => som + p.gedekt, 0);
  return { paden, gedektePijlers, totaalPijlers: paden.length * 4 };
}
