// ── Automatische bewaking van manifeste fouten in gegenereerde voorbeeldakten ─
// Schaalt de handmatige audit (taalvermenging, verdubbelde woorden, dubbele
// aanhef, onopgeloste placeholders) op naar de volledige modellenbibliotheek.
// Puur en deterministisch: leest enkel de reeds gegenereerde voorbeeldtekst
// (via `vulModelVoorbeeldIn`), muteert niets.
//
// Vier klassen bevindingen:
//   1. HERHAALDE WOORDEN ("euro euro", "verdieping verdieping", "notaris
//      notaris") — bijna altijd een parameterwaarde die een eenheid/woord
//      bevat dat de omringende clausuletekst er zelf ook al bijschrijft.
//      Dit is de klasse die deze module hard bewaakt: elke nieuwe herhaling
//      is een regressie of een nieuw gat in `voorbeeldwaarden.ts`.
//   2. TAALVERMENGING (een FR-trefwoord in een NL-akte of omgekeerd) — meestal
//      een taalneutrale parameter-Map die per ongeluk de andere taal laat
//      lekken (zie ModellenTab.tsx/vulModelVoorbeeldIn — de taalscheiding
//      per onderdeel is de structurele bewaking; deze woordenlijst is de
//      aanvullende, zichtbare rookmelder).
//   3. VERKEERDE-TAAL-ONDERDEEL: een FR-model waarvan de `structuur` een
//      onderdeel ZONDER `taal: "fr"` opneemt (of omgekeerd) — een structurele,
//      geen cosmetische fout: er ontbreekt een FR-tegenhanger en het model
//      rendert dan een heel blok in de verkeerde taal. Vond bij de eerste
//      audit twee echte gevallen (zie voorbeeld-audit.test.ts,
//      GEKENDE_TAALGATEN) — telkens een ontbrekende vertaling, geen
//      demo-waardebug, dus niet fixbaar via `voorbeeldwaarden.ts`.
//   4. OMSCHRIJVING-TAALMISMATCH: een onderdeel met `taal: "fr"` waarvan een
//      `parameters[].omschrijving` in het Nederlands is blijven staan. Deze
//      omschrijving is de fallback-waarde die `genereerVoorbeeldwaarde`
//      toont wanneer geen enkel PATROON matcht (zie voorbeeldwaarden.ts,
//      `kortUitOmschrijving`) — de taal van de omschrijving negeert daarbij
//      de gevraagde `taal` volledig, dus Nederlandse tekst lekt rechtstreeks
//      in de FR-voorbeeldtekst. Puur documentatie/parameterbug, nooit de
//      juridische clausuletekst zelf (`tekst`/`varianten` blijft onaangeroerd).

import type { Taal } from "./types";

/**
 * Vindt elk paar onmiddellijk opeenvolgende, identieke woorden
 * (hoofdletterongevoelig), met het fragment eromheen voor context.
 * Woordgrensmatch: `\s+` laat ook een regeleinde tussen de twee woorden toe
 * (vangt bv. een titel die zichzelf op de volgende regel herhaalt).
 */
export interface HerhaaldWoord {
  woord: string;
  positie: number;
  fragment: string;
}

const HERHALING_REGEX = /\b([a-zàâäéèêëïîôöùûüçœ]{2,})\s+\1\b/gi;

export function vindHerhaaldeWoorden(tekst: string): HerhaaldWoord[] {
  const resultaten: HerhaaldWoord[] = [];
  const re = new RegExp(HERHALING_REGEX.source, "gi");
  let match: RegExpExecArray | null;
  while ((match = re.exec(tekst))) {
    resultaten.push({
      woord: match[1].toLowerCase(),
      positie: match.index,
      fragment: tekst.slice(Math.max(0, match.index - 40), match.index + match[0].length + 25),
    });
  }
  return resultaten;
}

/**
 * Korte, veelvoorkomende functiewoorden die legitiem onmiddellijk kunnen
 * herhalen (Nederlandse/Franse grammatica of markeringsartefacten) en dus
 * geen "manifeste fout" zijn. Bewust kort gehouden: een woord hier bijzetten
 * moet je kunnen verantwoorden met een concreet, correct zinsvoorbeeld.
 * - "dat dat": "... dat dat gebruik correct verloopt" (voegwoord + aanwijzend
 *   voornaamwoord, correcte NL-zin).
 * - "soit soit": Franse correlatieve constructie "soit X soit Y" (= "ofwel
 *   X ofwel Y").
 * - "de de"/"le le": lidwoord/voorzetsel gevolgd door een eigennaam die
 *   toevallig met hetzelfde woord begint (bv. "de "De Vlaamse Waterweg nv"").
 */
export const TOEGELATEN_HERHALINGEN = new Set(["dat", "soit", "de", "le", "la", "het", "een"]);

/** Herhalingen die overblijven na de toegelaten-woordenlijst — de echte kandidaten. */
export function vindManifesteHerhalingen(tekst: string): HerhaaldWoord[] {
  return vindHerhaaldeWoorden(tekst).filter((h) => !TOEGELATEN_HERHALINGEN.has(h.woord));
}

/**
 * Onopgeloste `{{parameter}}`-placeholders in een voorbeeldtekst: mag nooit
 * voorkomen na `vulModelVoorbeeldIn`, die per definitie elke geëxtraheerde
 * parameter invult (desnoods met een fallback-waarde). Een treffer wijst op
 * een generatiefout (bv. een parameter die niet via `extraheerParameters`
 * werd meegenomen).
 */
export function vindOnopgelosteePlaceholders(tekst: string): string[] {
  return [...tekst.matchAll(/\{\{[^}]+\}\}/g)].map((m) => m[0]);
}

/**
 * Woorden die zo goed als zeker wijzen op de verkeerde taal in de tekst
 * (taalvermenging): een FR-woord in een NL-akte, of omgekeerd. Bewust een
 * kleine, hoog-precisie lijst — functiewoorden/vocabulaire die in de andere
 * taal geen legitieme lezing hebben (dus geen eigennamen, geen getallen).
 */
const NL_IN_FR_TREFWOORDEN = /\b(notaris|verdieping|onroerend|eigendom|vastgoed|wettelijke|standplaats)\b/i;
const FR_IN_NL_TREFWOORDEN = /\b(notaire|euros|jours|être|avec|dans|cette|leur)\b/i;

export interface TaalvermengingBevinding {
  woord: string;
  positie: number;
  fragment: string;
}

/**
 * Bracketinhoud ("[NAKIJKEN OF SCHRAPPEN: …]", "[NIEUW - TE VALIDEREN DOOR DE
 * NOTARIS: eigen vertaling…]", "[⚠ KNIPPERLICHT — EIGEN KANTOORREDACTIE…]")
 * is een redactionele meta-instructie AAN de notaris, niet de akte-inhoud
 * zelf — die instructies zijn bewust soms tweetalig (bv. een Nederlandse
 * "TE VALIDEREN DOOR DE NOTARIS"-vlag middenin een Franstalig onderdeel) en
 * mogen de taalvermengingscheck dus niet vals laten aanslaan.
 */
function zonderBracketinhoud(tekst: string): string {
  return tekst.replace(/\[[^\]]*\]/g, " ");
}

/** `taal` is de taal die de tekst HOORT te zijn. */
export function vindTaalvermenging(tekst: string, taal: "nl" | "fr"): TaalvermengingBevinding[] {
  const doorzoekbaar = zonderBracketinhoud(tekst);
  const regex = taal === "nl" ? FR_IN_NL_TREFWOORDEN : NL_IN_FR_TREFWOORDEN;
  const re = new RegExp(regex.source, "gi");
  const resultaten: TaalvermengingBevinding[] = [];
  let match: RegExpExecArray | null;
  while ((match = re.exec(doorzoekbaar))) {
    resultaten.push({
      woord: match[0],
      positie: match.index,
      fragment: doorzoekbaar.slice(Math.max(0, match.index - 40), match.index + match[0].length + 25),
    });
  }
  return resultaten;
}

/**
 * Zoekt modellen wiens `structuur` een onderdeel in de VERKEERDE taal opneemt
 * (een FR-model dat een onderdeel zonder `taal: "fr"` gebruikt, of
 * omgekeerd) — een ontbrekende vertaling die een heel tekstblok in de
 * verkeerde taal laat renderen. Puur structureel, dus preciezer dan de
 * woordheuristiek van `vindTaalvermenging`; controleert enkel onderdelen die
 * effectief in de bibliotheek bestaan (een ontbrekend onderdeel zelf bewaakt
 * `stelModelSamen`/`genereerDocument` al met "[ONTBREKEND ONDERDEEL: …]").
 */
export interface VerkeerdeTaalOnderdeel {
  modelId: string;
  onderdeelId: string;
  onderdeelTitel: string;
}

export function vindVerkeerdeTaalOnderdelen(
  modellen: { id: string; taal?: Taal; structuur: { onderdeelId: string }[] }[],
  onderdelen: { id: string; taal?: Taal; titel: string }[]
): VerkeerdeTaalOnderdeel[] {
  const onderdeelById = new Map(onderdelen.map((o) => [o.id, o]));
  const resultaten: VerkeerdeTaalOnderdeel[] = [];
  for (const model of modellen) {
    const modelTaal = model.taal === "fr" ? "fr" : "nl";
    for (const item of model.structuur) {
      const onderdeel = onderdeelById.get(item.onderdeelId);
      if (!onderdeel) continue;
      const onderdeelTaal = onderdeel.taal === "fr" ? "fr" : "nl";
      if (onderdeelTaal !== modelTaal) {
        resultaten.push({ modelId: model.id, onderdeelId: item.onderdeelId, onderdeelTitel: onderdeel.titel });
      }
    }
  }
  return resultaten;
}

/** Eén volledige audit van één gegenereerde voorbeeldtekst. */
export interface ModelAuditResultaat {
  modelId: string;
  herhalingen: HerhaaldWoord[];
  onopgelosteePlaceholders: string[];
  taalvermenging: TaalvermengingBevinding[];
}

export function auditeerVoorbeeldtekst(
  modelId: string,
  tekst: string,
  taal: "nl" | "fr"
): ModelAuditResultaat {
  return {
    modelId,
    herhalingen: vindManifesteHerhalingen(tekst),
    onopgelosteePlaceholders: vindOnopgelosteePlaceholders(tekst),
    taalvermenging: vindTaalvermenging(tekst, taal),
  };
}

/**
 * Hoog-precisie Nederlandse trefwoorden/uitdrukkingen die in een correcte
 * Franse zin niet legitiem voorkomen — bewust volledige woorden/frasen
 * (geen korte lidwoorden) om vals-positieven op Franse tekst te vermijden.
 * Gekalibreerd op de effectief aangetroffen omschrijvingen in de bibliotheek.
 */
const NL_OMSCHRIJVING_TREFWOORDEN =
  /\b(van de|van het|niet samenvatten|integraal en letterlijk|overnemen|indien van toepassing|blijkend uit|voor het|de koper|de verkoper|opmaakdatum|goedkeuringsdatum|unieke code|eventuele|inclusief|schermafbeeldingen|ontvangen|volledige|letterlijke|inhoud)\b/i;

export interface OmschrijvingTaalmismatch {
  onderdeelId: string;
  onderdeelTitel: string;
  parameterNaam: string;
  omschrijving: string;
}

/**
 * Zoekt onderdelen met `taal: "fr"` waarvan minstens één
 * `parameters[].omschrijving` Nederlandstalig aandoet (zie klasse 4
 * hierboven). Structureel, geen woordheuristiek op de gegenereerde tekst:
 * scant de brondata rechtstreeks, dus ook onafhankelijk van of een PATROON
 * in `voorbeeldwaarden.ts` de omschrijving-fallback toevallig al overschrijft.
 */
export function vindOmschrijvingTaalmismatch(
  onderdelen: {
    id: string;
    titel: string;
    taal?: Taal;
    parameters: { naam: string; omschrijving: string }[];
  }[]
): OmschrijvingTaalmismatch[] {
  const resultaten: OmschrijvingTaalmismatch[] = [];
  for (const onderdeel of onderdelen) {
    if (onderdeel.taal !== "fr") continue;
    for (const parameter of onderdeel.parameters) {
      if (NL_OMSCHRIJVING_TREFWOORDEN.test(parameter.omschrijving)) {
        resultaten.push({
          onderdeelId: onderdeel.id,
          onderdeelTitel: onderdeel.titel,
          parameterNaam: parameter.naam,
          omschrijving: parameter.omschrijving,
        });
      }
    }
  }
  return resultaten;
}
