// ── Conventie-nazicht — analyse-engine ───────────────────────────────────────
// Toetst een extern ontvangen ontwerptekst (onbekend model) aan de kennisbank
// (data/conventie-nazicht/kennisbank.ts) en levert een rapport van
// bevindingen. Elke bevinding vertaalt zich naar een concrete track-changes-
// actie: een clausule invoegen (punt "vereist" dat ontbreekt) of een alinea
// schrappen/vervangen (punt "verdacht" dat is aangetroffen). De Word-render
// zelf leeft in lib/word/genereerNazichtWordDocument.ts.
//
// Pure functies — geen React, geen browser-API's — zodat engine en render
// testbaar zijn en later ook via de MCP-/API-laag kunnen worden aangeboden.

import {
  KENNISBANK_CONTROLEPUNTEN,
  relevanteControlepunten,
} from "@/data/conventie-nazicht/kennisbank";
import type { KennisbankAanpassing } from "@/data/conventie-nazicht/aanpassingen";
import { stelKennisbankSamen } from "@/data/conventie-nazicht/aanpassingen";
import {
  NAZICHT_CATEGORIE_VOLGORDE,
  type Controlepunt,
  type NazichtCategorie,
} from "@/data/conventie-nazicht/types";
import { isGekendAkteType } from "@/data/modeldocumenten/akte-types";

/** Wat de analyse per controlepunt vaststelt. */
export type BevindingStatus = "in-orde" | "ontbreekt" | "aangetroffen";

/** Welke track-changes-actie de bevinding vraagt. */
export type BevindingActie = "invoegen" | "schrappen" | "vervangen" | "geen";

export interface NazichtBevinding {
  punt: Controlepunt;
  status: BevindingStatus;
  actie: BevindingActie;
  /** Bij "aangetroffen": index van de alinea (in de gesplitste brontekst) die de actie treft. */
  paragraafIndex?: number;
  /** Bij "aangetroffen": de getroffen alineatekst, ter verantwoording in het rapport. */
  fragment?: string;
}

export interface NazichtRapport {
  akteType: string;
  /** Bevindingen die een actie vragen, gesorteerd op zwaarte (te-vermijden → beter). */
  actiepunten: NazichtBevinding[];
  /** Punten die in orde zijn (vereist en aanwezig, of verdacht en afwezig). */
  inOrde: NazichtBevinding[];
  /** Telling per categorie: hoeveel getoetst, hoeveel actie nodig. */
  telling: Record<NazichtCategorie, { getoetst: number; actieNodig: number }>;
}

/** Verwijdert accenten, verkleint en vouwt witruimte samen — detectie-normalisatie. */
export function normaliseerVoorDetectie(tekst: string): string {
  return tekst
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .toLowerCase()
    .replace(/\s+/g, " ");
}

/** Splitst de brontekst in alinea's (regels); lege regels blijven bewaard voor de render. */
export function splitsAlineas(tekst: string): string[] {
  return tekst.replace(/\r\n/g, "\n").split("\n");
}

function groepMatcht(genormaliseerd: string, groep: readonly string[]): boolean {
  return groep.every((trefwoord) => genormaliseerd.includes(normaliseerVoorDetectie(trefwoord)));
}

/** Is minstens één signaalgroep volledig aanwezig in (de genormaliseerde) tekst? */
function signaalAanwezig(genormaliseerd: string, punt: Controlepunt): boolean {
  return punt.signalen.some((groep) => groepMatcht(genormaliseerd, groep));
}

/**
 * Zoekt de eerste alinea waarin één signaalgroep volledig voorkomt.
 * Verdachte punten vereisen de trefwoorden in DEZELFDE alinea, zodat de
 * schrapping een concrete alinea kan aanwijzen.
 */
function vindVerdachteAlinea(
  genormaliseerdeAlineas: readonly string[],
  punt: Controlepunt
): number | undefined {
  for (let i = 0; i < genormaliseerdeAlineas.length; i++) {
    if (signaalAanwezig(genormaliseerdeAlineas[i], punt)) return i;
  }
  return undefined;
}

/**
 * Toetst de ontwerptekst aan de kennisbank voor het opgegeven akteType.
 * Onbekend akteType is toegestaan (runtime-invoer): dan gelden enkel de
 * "alle"-punten. Geef `kennisbank` mee (de effectieve punten, seed +
 * kantooraanpassingen) om tegen de kantoorversie te toetsen; standaard de seed.
 */
export function analyseerConventie(
  tekst: string,
  akteType: string,
  kennisbank: readonly Controlepunt[] = KENNISBANK_CONTROLEPUNTEN
): NazichtRapport {
  const alineas = splitsAlineas(tekst);
  const genormaliseerdeAlineas = alineas.map(normaliseerVoorDetectie);
  const genormaliseerdGeheel = normaliseerVoorDetectie(tekst);

  const actiepunten: NazichtBevinding[] = [];
  const inOrde: NazichtBevinding[] = [];
  const telling = Object.fromEntries(
    NAZICHT_CATEGORIE_VOLGORDE.map((cat) => [cat, { getoetst: 0, actieNodig: 0 }])
  ) as NazichtRapport["telling"];

  for (const punt of relevanteControlepunten(akteType, kennisbank)) {
    telling[punt.categorie].getoetst += 1;

    if (punt.doel === "vereist") {
      if (signaalAanwezig(genormaliseerdGeheel, punt)) {
        inOrde.push({ punt, status: "in-orde", actie: "geen" });
      } else {
        telling[punt.categorie].actieNodig += 1;
        actiepunten.push({ punt, status: "ontbreekt", actie: "invoegen" });
      }
      continue;
    }

    // doel "verdacht": aangetroffen → schrappen of vervangen
    const index = vindVerdachteAlinea(genormaliseerdeAlineas, punt);
    if (index === undefined) {
      inOrde.push({ punt, status: "in-orde", actie: "geen" });
    } else {
      telling[punt.categorie].actieNodig += 1;
      actiepunten.push({
        punt,
        status: "aangetroffen",
        actie: punt.vervanging ? "vervangen" : "schrappen",
        paragraafIndex: index,
        fragment: alineas[index],
      });
    }
  }

  const rang = (b: NazichtBevinding) => NAZICHT_CATEGORIE_VOLGORDE.indexOf(b.punt.categorie);
  actiepunten.sort((a, b) => rang(a) - rang(b));
  inOrde.sort((a, b) => rang(a) - rang(b));

  return { akteType, actiepunten, inOrde, telling };
}

/** Trefwoorden per akteType, in afnemende specificiteit — eerste match wint. */
const AKTETYPE_SUGGESTIES: readonly { akteType: string; trefwoorden: readonly string[] }[] = [
  { akteType: "schenkingsakte-roerend-goed", trefwoorden: ["bankgift", "pacte adjoint", "handgift"] },
  { akteType: "onderhandse volmacht", trefwoorden: ["volmacht", "lastgever", "lasthebber"] },
  { akteType: "verkoopakte", trefwoorden: ["verkoopakte", "authentieke akte van verkoop", "kwijting"] },
  {
    akteType: "verkoopovereenkomst (compromis)",
    trefwoorden: ["compromis", "verkoopovereenkomst", "onderhandse verkoop", "verkoopbelofte", "verkoopt", "verkopen"],
  },
];

/**
 * Suggereert het akteType op basis van de tekst zelf (best effort, enkel voor
 * de types waarvoor de kennisbank punten bevat). `undefined` = geen suggestie;
 * de gebruiker/agent kiest dan zelf.
 */
export function suggereerAkteType(tekst: string): string | undefined {
  const genormaliseerd = normaliseerVoorDetectie(tekst);
  for (const kandidaat of AKTETYPE_SUGGESTIES) {
    if (kandidaat.trefwoorden.some((t) => genormaliseerd.includes(normaliseerVoorDetectie(t)))) {
      return kandidaat.akteType;
    }
  }
  return undefined;
}

/** Sanity-export voor tests/UI: de volledige kennisbank + geldigheidscheck van de akteTypes. */
export function kennisbankAkteTypesGeldig(): boolean {
  return KENNISBANK_CONTROLEPUNTEN.every(
    (punt) => punt.akteTypes === "alle" || punt.akteTypes.every((t) => isGekendAkteType(t))
  );
}

/**
 * De effectieve kennisbank: de seed met de kantooraanpassingen erover gelegd
 * (validaties/bewerkingen vervangen, schrappingen vallen weg, nieuwe punten
 * achteraan). Eén bron voor analyse én UI.
 */
export function effectieveKennisbank(
  aanpassingen: readonly KennisbankAanpassing[] = []
): Controlepunt[] {
  return stelKennisbankSamen(KENNISBANK_CONTROLEPUNTEN, aanpassingen);
}
