// ── Verbeterlus: de zelfverbeteringsfunctie van Notary.AI ────────────────────
// Pure functies (geen I/O, geen React) die de OPGEBOUWDE ERVARING van de app
// omzetten in concrete, gerangschikte verbetervoorstellen. De ervaring bestaat
// uit signalen die de app al verzamelt: de gebruikslog (waar agenten vastlopen
// op de bibliotheek), de bibliotheek-audit (kwaliteitsbevindingen) en de
// uitkomst van eerdere voorstellen. De motor clustert die signalen, scoort ze
// (frequentie × ernst × recentheid) en leidt er kandidaat-verbeteringen uit af,
// elk gerouteerd naar het juiste, BESTAANDE kanaal:
//
//   • bibliotheek → een wijzigingsvoorstel in "Te valideren";
//   • code        → een verbetervoorstel dat een AI-agent oppikt;
//   • kennis      → een kennisbank-item "te valideren".
//
// De motor MUTEERT nooit zelf: hij versnelt enkel de weg van ervaring naar
// voorstel; de notaris blijft de enige die valideert (zie AGENTS.md). Alle
// signalen zijn strikt geanonimiseerd — een `onderwerp` is een clausule-id,
// parameternaam, module of endpoint, nooit dossierinhoud.

import type { VoorstelCategorie, VoorstelPrioriteit } from "@/data/verbetervoorstellen";
import type { GebruikslogEvent, GebruikslogType } from "@/lib/gebruikslog";
import type { AuditBevinding, AuditCode } from "@/data/modeldocumenten";

/** Kanaal waarlangs een kandidaat wordt ingediend — altijd een bestaand validatiekanaal. */
export type VerbeterKanaal = "bibliotheek" | "code" | "kennis";

/** Eén geanonimiseerd ervaringssignaal. */
export interface ErvaringSignaal {
  bron: "gebruikslog" | "audit" | "voorstel-uitkomst";
  /** Machineleesbare soort (gebruikslogType, auditCode, …). */
  soort: string;
  /** Clausule-id, parameternaam, module of endpoint — nooit persoonsgegevens. */
  onderwerp: string;
  /** Relatief gewicht van dit signaal (ernst). */
  gewicht: number;
  /** ISO-datum van het signaal; stuurt de recentheidsweging. */
  datum: string;
}

/** Eén afgeleide verbeterkandidaat, klaar om via zijn kanaal te worden ingediend. */
export interface VerbetervoorstelKandidaat {
  /** Stabiele sleutel (kanaal∷soort∷onderwerp) voor deduplicatie en anti-terugkoppeling. */
  sleutel: string;
  kanaal: VerbeterKanaal;
  /** Categorie voor het codekanaal (verbetervoorstellen); onbenut voor de andere kanalen. */
  categorie: VoorstelCategorie;
  prioriteit: VoorstelPrioriteit;
  titel: string;
  omschrijving: string;
  /** Aggregatiescore (hoger = dringender): frequentie × ernst × recentheid. */
  score: number;
  /** De onderliggende signalen (bewijs), nieuwste eerst. */
  signalen: ErvaringSignaal[];
}

/** Routeringsregel per signaalsoort: kanaal, categorie, ernst en tekstsjablonen. */
interface Routering {
  kanaal: VerbeterKanaal;
  categorie: VoorstelCategorie;
  /** Basisernst van één signaal van deze soort. */
  ernst: number;
  titel: (onderwerp: string) => string;
  omschrijving: (onderwerp: string, aantal: number) => string;
}

// Gebruikslog-soorten → routering. Bibliotheekgaten leiden tot een
// bibliotheekvoorstel; connector-/perceptieproblemen tot een codevoorstel.
const GEBRUIKSLOG_ROUTERING: Partial<Record<GebruikslogType, Routering>> = {
  onbekend_modeldocument: {
    kanaal: "bibliotheek", categorie: "data", ernst: 4,
    titel: (o) => `Ontbrekend modeldocument opvragen: "${o}"`,
    omschrijving: (o, n) => `Een agent vroeg ${n}× het onbekende modeldocument "${o}" op. Voeg het model toe aan de bibliotheek (via "Te valideren").`,
  },
  onbekende_modelbrief: {
    kanaal: "bibliotheek", categorie: "data", ernst: 3,
    titel: (o) => `Ontbrekende modelbrief opvragen: "${o}"`,
    omschrijving: (o, n) => `Een agent vroeg ${n}× de onbekende modelbrief "${o}" op. Voeg de modelmail toe aan de bibliotheek.`,
  },
  onbekende_rechtshandeling: {
    kanaal: "bibliotheek", categorie: "data", ernst: 4,
    titel: (o) => `Onbekende rechtshandeling: "${o}"`,
    omschrijving: (o, n) => `Een agent vroeg ${n}× de niet-gemodelleerde rechtshandeling "${o}". Overweeg een nieuw gouden pad of model.`,
  },
  onbekende_parameter: {
    kanaal: "bibliotheek", categorie: "data", ernst: 2,
    titel: (o) => `Onbekende parameter: {{${o}}}`,
    omschrijving: (o, n) => `De parameter {{${o}}} werd ${n}× opgevraagd maar is nergens gedeclareerd. Declareer ze (naam + omschrijving) op de betrokken clausule.`,
  },
  genegeerd_facultatief_onderdeel: {
    kanaal: "bibliotheek", categorie: "data", ernst: 2,
    titel: (o) => `Facultatief onderdeel niet activeerbaar: "${o}"`,
    omschrijving: (o, n) => `Het facultatieve onderdeel "${o}" kon ${n}× niet geactiveerd worden. Controleer de conditie of de structuurverwijzing.`,
  },
  perceptie_veld_ontbreekt: {
    kanaal: "code", categorie: "functionaliteit", ernst: 3,
    titel: (o) => `Dossierveld structureel ontbrekend: "${o}"`,
    omschrijving: (o, n) => `Het veld "${o}" ontbrak ${n}× bij de perceptie van een brondocument. Breid de extractor of het intakeschema uit.`,
  },
  perceptie_veld_tegenstrijdig: {
    kanaal: "code", categorie: "functionaliteit", ernst: 4,
    titel: (o) => `Tegenstrijdige bron voor "${o}"`,
    omschrijving: (o, n) => `Het veld "${o}" was ${n}× tegenstrijdig tussen bronnen. Versterk de bronprioriteit of de tegenstrijdigheidsmelding.`,
  },
  perceptie_onbekende_bron: {
    kanaal: "code", categorie: "functionaliteit", ernst: 2,
    titel: (o) => `Onbekend brontype: "${o}"`,
    omschrijving: (o, n) => `Het brontype "${o}" werd ${n}× niet herkend. Voeg een extractor toe voor dit brontype.`,
  },
  connector_fout: {
    kanaal: "code", categorie: "integratie", ernst: 3,
    titel: (o) => `Connectorfout bij dienst "${o}"`,
    omschrijving: (o, n) => `De connector "${o}" faalde ${n}×. Onderzoek de adapter of de foutafhandeling.`,
  },
  connector_geescaleerd: {
    kanaal: "code", categorie: "integratie", ernst: 2,
    titel: (o) => `Connector escaleert vaak: "${o}"`,
    omschrijving: (o, n) => `De dienst "${o}" escaleerde ${n}× naar de notaris. Bekijk of de zekerheidsdrempel of dekking kan verbeteren.`,
  },
  connector_verboden: {
    kanaal: "code", categorie: "integratie", ernst: 3,
    titel: (o) => `Connector geweigerd: "${o}"`,
    omschrijving: (o, n) => `De dienst "${o}" werd ${n}× geweigerd door de grenzen. Controleer de vooraf goedgekeurde grenzen.`,
  },
};

// Audit-codes → routering. Fouten wegen zwaarder dan waarschuwingen; code-
// gerichte codes (rollen-mapping, generatie) gaan naar het codekanaal.
const AUDIT_ROUTERING: Record<AuditCode, Routering> = {
  "rol-mapping-ontbreekt": {
    kanaal: "code", categorie: "data", ernst: 5,
    titel: (o) => `Rollen-mapping ontbreekt voor akteType "${o}"`,
    omschrijving: (o) => `Voeg "${o}" toe aan aktetypeRollen (data/modeldocumenten/rollen.ts) — nu renderen de generieke rollen "overdrager"/"verkrijger".`,
  },
  "ontbrekend-onderdeel": {
    kanaal: "bibliotheek", categorie: "data", ernst: 5,
    titel: (o) => `Onbestaand onderdeel in structuur: "${o}"`,
    omschrijving: (o) => `De structuur verwijst naar het niet-bestaande onderdeel "${o}". Voeg het toe of corrigeer de verwijzing.`,
  },
  "generatie-restfout": {
    kanaal: "code", categorie: "functionaliteit", ernst: 4,
    titel: (o) => `Restfout in gegenereerd voorbeeld: "${o}"`,
    omschrijving: (o) => `Het gegenereerde voorbeeld van "${o}" houdt onopgeloste invulpunten over. Controleer de parameters of de generatie.`,
  },
  "dode-declaratie": {
    kanaal: "bibliotheek", categorie: "data", ernst: 3,
    titel: (o) => `Dode parameterdeclaratie in "${o}"`,
    omschrijving: (o) => `"${o}" declareert een parameter die nergens in de tekst voorkomt. Hernoem naar de werkelijke placeholder of verwijder ze.`,
  },
  "te-vertalen-markering": {
    kanaal: "bibliotheek", categorie: "data", ernst: 2,
    titel: (o) => `[TE VERTALEN] in "${o}"`,
    omschrijving: (o) => `"${o}" bevat nog een [TE VERTALEN]-markering. Lever de authentieke vertaling aan.`,
  },
  "onvertaalde-clausule": {
    kanaal: "bibliotheek", categorie: "data", ernst: 2,
    titel: (o) => `Clausule niet in modeltaal: "${o}"`,
    omschrijving: (o) => `"${o}" is niet volledig beschikbaar in de taal van het model. Vertaal de clausule of koppel de spiegel.`,
  },
  "tegenhanger-ontbreekt": {
    kanaal: "bibliotheek", categorie: "data", ernst: 3,
    titel: (o) => `NL/FR-tegenhanger ontbreekt: "${o}"`,
    omschrijving: (o) => `Model "${o}" mist zijn anderstalige spiegel. Voeg de tegenhanger toe (vertalingVanId).`,
  },
  "parameter-nergens-gedeclareerd": {
    kanaal: "bibliotheek", categorie: "data", ernst: 1,
    titel: (o) => `Parameter zonder omschrijving: {{${o}}}`,
    omschrijving: (o) => `{{${o}}} is nergens in de bibliotheek gedeclareerd. Geef ze een omschrijving (en liefst een voorbeeld) op de betrokken clausule.`,
  },
  "alias-parameternaam": {
    kanaal: "bibliotheek", categorie: "data", ernst: 4,
    titel: (o) => `Aliasnaam heringevoerd in "${o}"`,
    omschrijving: (o) => `"${o}" gebruikt een oude aliasnaam als parameter. Hernoem naar de canonieke naam (zie parameter-register.ts).`,
  },
  "parameter-taalpariteit": {
    kanaal: "bibliotheek", categorie: "data", ernst: 2,
    titel: (o) => `FR-parameternamen wijken af van de NL-spiegel: "${o}"`,
    omschrijving: (o) => `"${o}" gebruikt parameternamen die niet in de NL-spiegel staan. Hernoem naar de gedeelde (canonieke) namen zodat één waardenset beide talen vult.`,
  },
  "duplicaat-tekst": {
    kanaal: "bibliotheek", categorie: "data", ernst: 2,
    titel: (o) => `Mogelijk duplicaat: "${o}"`,
    omschrijving: (o) => `De tekst van "${o}" is identiek aan een ander onderdeel. Voeg samen of maak het onderscheid expliciet.`,
  },
};

/** Halfwaardetijd (dagen) van de recentheidsweging: een signaal van deze ouderdom telt half. */
const HALFWAARDETIJD_DAGEN = 30;

function recentheidsfactor(datum: string, nu: number): number {
  const dagen = Math.max(0, (nu - new Date(datum).getTime()) / 86_400_000);
  return Math.pow(0.5, dagen / HALFWAARDETIJD_DAGEN);
}

// ── Signaalbouwers uit de bestaande ervaringsbronnen ─────────────────────────

/** Zet gebruikslog-events om naar ervaringssignalen (bekende soorten met een routering). */
export function signalenUitGebruikslog(events: readonly GebruikslogEvent[]): ErvaringSignaal[] {
  const signalen: ErvaringSignaal[] = [];
  for (const event of events) {
    const routering = GEBRUIKSLOG_ROUTERING[event.type as GebruikslogType];
    if (!routering) continue;
    signalen.push({ bron: "gebruikslog", soort: event.type, onderwerp: event.waarde, gewicht: routering.ernst, datum: event.tijdstip });
  }
  return signalen;
}

/** Zet audit-bevindingen om naar ervaringssignalen (fouten wegen dubbel). */
export function signalenUitAudit(bevindingen: readonly AuditBevinding[], datum: string): ErvaringSignaal[] {
  return bevindingen.map((b) => ({
    bron: "audit" as const,
    soort: b.code,
    onderwerp: b.sleutel ?? b.onderwerp,
    gewicht: AUDIT_ROUTERING[b.code].ernst * (b.ernst === "fout" ? 2 : 1),
    datum,
  }));
}

function routeringVoor(soort: string): Routering | undefined {
  return AUDIT_ROUTERING[soort as AuditCode] ?? GEBRUIKSLOG_ROUTERING[soort as GebruikslogType];
}

function prioriteitUitScore(score: number): VoorstelPrioriteit {
  if (score >= 10) return "hoog";
  if (score >= 4) return "midden";
  return "laag";
}

/**
 * Aggregeert ervaringssignalen tot gerangschikte verbeterkandidaten: signalen
 * met dezelfde (soort, onderwerp) worden geclusterd, gescoord op
 * frequentie × ernst × recentheid, en gerouteerd naar hun kanaal. Kandidaten
 * waarvan de sleutel al in `reedsIngediend` zit (open verbetervoorstellen of
 * hangende bibliotheekvoorstellen), worden weggelaten — anti-terugkoppeling,
 * zodat dezelfde ervaring niet telkens opnieuw wordt voorgesteld.
 */
export function bouwVerbeterkandidaten(
  signalen: readonly ErvaringSignaal[],
  opties: { nu?: number; reedsIngediend?: ReadonlySet<string> } = {}
): VerbetervoorstelKandidaat[] {
  const nu = opties.nu ?? Date.now();
  const reedsIngediend = opties.reedsIngediend ?? new Set<string>();

  const clusters = new Map<string, { routering: Routering; signalen: ErvaringSignaal[]; score: number }>();
  for (const signaal of signalen) {
    const routering = routeringVoor(signaal.soort);
    if (!routering) continue;
    const sleutel = `${routering.kanaal}∷${signaal.soort}∷${signaal.onderwerp}`;
    const cluster = clusters.get(sleutel) ?? { routering, signalen: [], score: 0 };
    cluster.signalen.push(signaal);
    cluster.score += signaal.gewicht * recentheidsfactor(signaal.datum, nu);
    clusters.set(sleutel, cluster);
  }

  const kandidaten: VerbetervoorstelKandidaat[] = [];
  for (const [sleutel, cluster] of clusters) {
    if (reedsIngediend.has(sleutel)) continue;
    const { routering } = cluster;
    const onderwerp = cluster.signalen[0].onderwerp;
    const score = Math.round(cluster.score * 100) / 100;
    kandidaten.push({
      sleutel,
      kanaal: routering.kanaal,
      categorie: routering.categorie,
      prioriteit: prioriteitUitScore(score),
      titel: routering.titel(onderwerp),
      omschrijving: routering.omschrijving(onderwerp, cluster.signalen.length),
      score,
      signalen: [...cluster.signalen].sort((a, b) => b.datum.localeCompare(a.datum)),
    });
  }
  return kandidaten.sort((a, b) => b.score - a.score);
}

/**
 * De volledige verbeterlus in één aanroep: bouwt de signalen uit de
 * ervaringsbronnen en levert de gerangschikte kandidaten. `reedsIngediend` is
 * de verzameling sleutels van al openstaande voorstellen (anti-terugkoppeling).
 */
export function verbeterkandidatenUitErvaring(input: {
  gebruikslog?: readonly GebruikslogEvent[];
  auditBevindingen?: readonly AuditBevinding[];
  auditDatum?: string;
  nu?: number;
  reedsIngediend?: ReadonlySet<string>;
}): VerbetervoorstelKandidaat[] {
  const nu = input.nu ?? Date.now();
  const signalen: ErvaringSignaal[] = [
    ...signalenUitGebruikslog(input.gebruikslog ?? []),
    ...signalenUitAudit(input.auditBevindingen ?? [], input.auditDatum ?? new Date(nu).toISOString().slice(0, 10)),
  ];
  return bouwVerbeterkandidaten(signalen, { nu, reedsIngediend: input.reedsIngediend });
}
