// ── Wijzigingsvoorstellen van modelonderdelen (extern aangeleverd) ───────────
// Pure functies om een door een AI-agent (MCP) aangeleverd ontwerp van een
// modelonderdeel te valideren en om te zetten naar een volwaardig
// Wijzigingsvoorstel, in hetzelfde formaat als de "Te valideren"-wachtrij.

import type {
  Hoofdthema,
  Modeldocument,
  ModeldocumentVoorstel,
  Modelonderdeel,
  ModelParameter,
  ModelVariant,
  OnderdeelCategorie,
  StructuurItem,
  DocumentSoort,
  Taal,
  VoorstelPrioriteit,
  Wijzigingsvoorstel,
} from "./types";
import {
  onderdeelCategorieLabels,
  hoofdthemaLabels,
  documentSoortLabels,
  taalLabels,
} from "./types";
import { extraheerParameters } from "./samenstellen";

/** Ontwerp van een modelonderdeel zoals aangeleverd door een externe AI, nog zonder id/versie/datum. */
export interface ModelonderdeelOntwerp {
  titel: string;
  categorie: OnderdeelCategorie;
  omschrijving: string;
  /** Basistekst met {{parameter}}-placeholders; mag leeg zijn als alles in varianten zit. */
  tekst?: string;
  varianten?: { hypothese: string; tekst: string }[];
  /** Weglaten = automatisch afgeleid uit de {{parameters}} in tekst/varianten. */
  parameters?: ModelParameter[];
  toepasbaarOp: string[];
  thema?: Hoofdthema;
  soort?: DocumentSoort;
  taal?: Taal;
  bron?: string;
  wetsbasis?: string[];
}

export interface VoorstelInvoer {
  titel: string;
  omschrijving: string;
  prioriteit: VoorstelPrioriteit;
  /** Id van het bestaande onderdeel dat wordt gewijzigd; weglaten = nieuw onderdeel. */
  onderdeelId?: string;
  bron?: string;
  onderdeel: ModelonderdeelOntwerp;
}

/** Valideert een ontwerp-modelonderdeel; retourneert foutmeldingen (leeg = geldig). */
export function valideerOnderdeelOntwerp(ontwerp: ModelonderdeelOntwerp): string[] {
  const fouten: string[] = [];
  if (!ontwerp.titel?.trim()) fouten.push("onderdeel.titel ontbreekt.");
  if (!ontwerp.omschrijving?.trim()) fouten.push("onderdeel.omschrijving ontbreekt.");
  if (!(ontwerp.categorie in onderdeelCategorieLabels)) {
    fouten.push(
      `onderdeel.categorie "${ontwerp.categorie}" is ongeldig. Toegelaten: ${Object.keys(onderdeelCategorieLabels).join(", ")}.`
    );
  }
  if (!ontwerp.tekst?.trim() && (!ontwerp.varianten || ontwerp.varianten.length === 0)) {
    fouten.push("onderdeel: geef minstens 'tekst' of één variant in 'varianten' op.");
  }
  if (!Array.isArray(ontwerp.toepasbaarOp) || ontwerp.toepasbaarOp.length === 0) {
    fouten.push("onderdeel.toepasbaarOp ontbreekt: geef minstens één modeltype op.");
  }
  if (ontwerp.thema && !(ontwerp.thema in hoofdthemaLabels)) {
    fouten.push(`onderdeel.thema "${ontwerp.thema}" is ongeldig. Toegelaten: ${Object.keys(hoofdthemaLabels).join(", ")}.`);
  }
  if (ontwerp.soort && !(ontwerp.soort in documentSoortLabels)) {
    fouten.push(`onderdeel.soort "${ontwerp.soort}" is ongeldig. Toegelaten: ${Object.keys(documentSoortLabels).join(", ")}.`);
  }
  if (ontwerp.taal && !(ontwerp.taal in taalLabels)) {
    fouten.push(`onderdeel.taal "${ontwerp.taal}" is ongeldig. Toegelaten: ${Object.keys(taalLabels).join(", ")}.`);
  }
  // Dode declaratie: een expliciet opgegeven parameter die nergens als
  // {{placeholder}} in tekst/varianten voorkomt is vrijwel zeker een verzonnen
  // naam naast de werkelijke placeholder — meteen terugkaatsen naar de agent,
  // met de namen die wél in de tekst staan als hint.
  if (ontwerp.parameters && ontwerp.parameters.length > 0) {
    const inTekst = new Set(
      extraheerParameters(ontwerp.tekst ?? "", ...(ontwerp.varianten ?? []).map((v) => v.tekst))
    );
    const dood = ontwerp.parameters.filter((p) => !inTekst.has(p.naam)).map((p) => p.naam);
    if (dood.length > 0) {
      fouten.push(
        `onderdeel.parameters bevat namen die nergens als {{placeholder}} in de tekst voorkomen: ${dood.join(", ")}. ` +
          `Gebruik exact de namen uit de tekst (gevonden: ${[...inTekst].join(", ") || "geen"}).`
      );
    }
  }
  return fouten;
}

/** Zet een titel om naar een id-slug ("Identificatie natuurlijke persoon" → "identificatie-natuurlijke-persoon"). */
function slug(titel: string): string {
  return titel
    .toLowerCase()
    .normalize("NFD")
    .replace(/[̀-ͯ]/g, "")
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-+|-+$/g, "");
}

/**
 * Bouwt één volwaardig Modelonderdeel uit een ontwerp: genereert variant-id's,
 * leidt ontbrekende parameters af uit de {{parameters}} en markeert het als nog
 * niet gevalideerd (nazicht "na_te_kijken"). `id` mag worden opgegeven (bv. bij
 * een wijziging van een bestaand onderdeel); anders wordt "mcp-<slug>" gebruikt.
 */
function bouwOnderdeelUitOntwerp(
  o: ModelonderdeelOntwerp,
  datum: string,
  id?: string,
  bronFallback?: string
): Modelonderdeel {
  const basisSlug = slug(o.titel);
  const varianten: ModelVariant[] = (o.varianten ?? []).map((v, i) => ({
    id: `${basisSlug}-${i + 1}`,
    hypothese: v.hypothese,
    tekst: v.tekst,
  }));

  const parameters: ModelParameter[] =
    o.parameters && o.parameters.length > 0
      ? o.parameters
      : extraheerParameters(o.tekst ?? "", ...varianten.map((v) => v.tekst)).map((naam) => ({
          naam,
          omschrijving: "[AAN TE VULLEN: omschrijving]",
        }));

  return {
    id: id ?? `mcp-${basisSlug}`,
    titel: o.titel,
    categorie: o.categorie,
    omschrijving: o.omschrijving,
    tekst: o.tekst ?? "",
    varianten,
    parameters,
    toepasbaarOp: o.toepasbaarOp,
    thema: o.thema,
    soort: o.soort ?? "MODCL",
    bron: o.bron ?? bronFallback,
    versie: "0.1",
    datum,
    taal: o.taal ?? "nl",
    wetsbasis: o.wetsbasis,
    nazicht: {
      status: "na_te_kijken",
      datum,
      aandachtspunten: "Voorstel ingediend via MCP — nog niet door de notaris gevalideerd.",
    },
    isGebruiker: true,
  };
}

/**
 * Bouwt een volledig Wijzigingsvoorstel uit een ontwerp-modelonderdeel: genereert
 * id's, versie en datum, leidt ontbrekende parameters af uit de {{parameters}}
 * in de tekst, en markeert het resultaat als nog niet door de notaris
 * gevalideerd (nazicht "na_te_kijken"). Gooit een fout als het ontwerp niet
 * voldoet aan valideerOnderdeelOntwerp.
 */
export function bouwWijzigingsvoorstel(invoer: VoorstelInvoer, datum: string): Wijzigingsvoorstel {
  const fouten = valideerOnderdeelOntwerp(invoer.onderdeel);
  if (fouten.length > 0) throw new Error(fouten.join(" "));

  const o = invoer.onderdeel;
  const basisSlug = slug(o.titel);
  const voorgesteldOnderdeel = bouwOnderdeelUitOntwerp(o, datum, invoer.onderdeelId, invoer.bron);

  return {
    id: `voorstel-mcp-${basisSlug}-${Date.now()}`,
    onderdeelId: invoer.onderdeelId,
    titel: invoer.titel,
    omschrijving: invoer.omschrijving,
    prioriteit: invoer.prioriteit,
    voorgesteldOnderdeel,
    status: "open",
    bron: invoer.bron ?? o.bron,
    datum,
  };
}

// ── Voorstellen van volledige modeldocumenten (extern aangeleverd) ───────────

/** Eén positie in de voorgestelde structuur; verwijst naar een bestaand of nieuw onderdeel. */
export interface StructuurItemOntwerp {
  /** Id van een bestaand seed-onderdeel, of de titel/slug van een meegestuurd nieuw onderdeel. */
  onderdeelId: string;
  /** Verplicht onderdeel (true) of facultatief (false); ontbreekt = verplicht. */
  verplicht?: boolean;
  /** Wanneer dit onderdeel op te nemen is, bv. "enkel bij appartement". */
  conditie?: string;
}

/** Ontwerp van een volledig modeldocument zoals aangeleverd door een externe AI, nog zonder id/versie/datum. */
export interface ModeldocumentOntwerp {
  titel: string;
  /** Aktetype/rechtshandeling, bv. "verkoopakte", "schenkingsakte". */
  akteType: string;
  omschrijving: string;
  thema?: Hoofdthema;
  soort?: DocumentSoort;
  inleiding?: string;
  /** Geordende lijst van onderdelen; elk verwijst naar een bestaand of meegestuurd nieuw onderdeel. */
  structuur: StructuurItemOntwerp[];
  slot?: string;
  taal?: Taal;
  bron?: string;
  /** Nieuwe onderdelen waarnaar de structuur verwijst en die nog niet in de bibliotheek bestaan. */
  nieuweOnderdelen?: ModelonderdeelOntwerp[];
}

export interface ModeldocumentVoorstelInvoer {
  titel: string;
  omschrijving: string;
  prioriteit: VoorstelPrioriteit;
  /** Id van het bestaande model dat wordt gewijzigd; weglaten = nieuw model. */
  modelId?: string;
  bron?: string;
  model: ModeldocumentOntwerp;
}

/**
 * Valideert een ontwerp-modeldocument tegenover de bestaande onderdeel-id's.
 * Retourneert foutmeldingen (leeg = geldig). Elke verwijzing in de structuur
 * moet ofwel een bestaand onderdeel-id zijn, ofwel verwijzen (op titel of slug)
 * naar een meegestuurd nieuw onderdeel.
 */
export function valideerModeldocumentOntwerp(
  ontwerp: ModeldocumentOntwerp,
  bestaandeOnderdeelIds: string[]
): string[] {
  const fouten: string[] = [];
  if (!ontwerp.titel?.trim()) fouten.push("model.titel ontbreekt.");
  if (!ontwerp.akteType?.trim()) fouten.push("model.akteType ontbreekt.");
  if (!ontwerp.omschrijving?.trim()) fouten.push("model.omschrijving ontbreekt.");
  if (ontwerp.thema && !(ontwerp.thema in hoofdthemaLabels)) {
    fouten.push(`model.thema "${ontwerp.thema}" is ongeldig. Toegelaten: ${Object.keys(hoofdthemaLabels).join(", ")}.`);
  }
  if (ontwerp.soort && !(ontwerp.soort in documentSoortLabels)) {
    fouten.push(`model.soort "${ontwerp.soort}" is ongeldig. Toegelaten: ${Object.keys(documentSoortLabels).join(", ")}.`);
  }
  if (ontwerp.taal && !(ontwerp.taal in taalLabels)) {
    fouten.push(`model.taal "${ontwerp.taal}" is ongeldig. Toegelaten: ${Object.keys(taalLabels).join(", ")}.`);
  }
  if (!Array.isArray(ontwerp.structuur) || ontwerp.structuur.length === 0) {
    fouten.push("model.structuur ontbreekt: geef minstens één onderdeel op in de gewenste volgorde.");
  }

  // Valideer elk meegestuurd nieuw onderdeel afzonderlijk.
  const nieuweSlugs = new Set<string>();
  for (const n of ontwerp.nieuweOnderdelen ?? []) {
    const onderdeelFouten = valideerOnderdeelOntwerp(n);
    if (onderdeelFouten.length > 0) {
      fouten.push(`nieuw onderdeel "${n.titel || "(zonder titel)"}": ${onderdeelFouten.join(" ")}`);
    }
    if (n.titel?.trim()) nieuweSlugs.add(slug(n.titel));
  }

  // Elke structuurverwijzing moet oplosbaar zijn.
  const bestaand = new Set(bestaandeOnderdeelIds);
  for (const item of ontwerp.structuur ?? []) {
    const ref = item.onderdeelId?.trim();
    if (!ref) {
      fouten.push("model.structuur bevat een item zonder onderdeelId.");
      continue;
    }
    if (bestaand.has(ref) || nieuweSlugs.has(ref) || nieuweSlugs.has(slug(ref))) continue;
    fouten.push(
      `model.structuur verwijst naar onbekend onderdeelId "${ref}": geef een bestaand onderdeel-id op (zie lijstModeldocumenten/haalModeldocument) of voeg het toe aan model.nieuweOnderdelen.`
    );
  }

  return fouten;
}

/**
 * Bouwt een volledig ModeldocumentVoorstel uit een ontwerp-modeldocument:
 * genereert id's, versie en datum, bouwt de meegestuurde nieuwe onderdelen op
 * en herschrijft de structuurverwijzingen naar de gegenereerde onderdeel-id's.
 * Gooit een fout als het ontwerp niet voldoet aan valideerModeldocumentOntwerp.
 */
export function bouwModeldocumentVoorstel(
  invoer: ModeldocumentVoorstelInvoer,
  datum: string,
  bestaandeOnderdeelIds: string[]
): ModeldocumentVoorstel {
  const fouten = valideerModeldocumentOntwerp(invoer.model, bestaandeOnderdeelIds);
  if (fouten.length > 0) throw new Error(fouten.join(" "));

  const m = invoer.model;
  const basisSlug = slug(m.titel);

  // Bouw de nieuwe onderdelen en houd een map titel/slug → gegenereerd id bij.
  const nieuweOnderdelen: Modelonderdeel[] = (m.nieuweOnderdelen ?? []).map((n) =>
    bouwOnderdeelUitOntwerp(n, datum, undefined, invoer.bron)
  );
  const slugNaarId = new Map<string, string>();
  for (const onderdeel of nieuweOnderdelen) {
    // Gegenereerde id is "mcp-<slug>"; map zowel slug als id op zichzelf.
    slugNaarId.set(slug(onderdeel.titel), onderdeel.id);
  }

  const bestaand = new Set(bestaandeOnderdeelIds);
  const structuur: StructuurItem[] = m.structuur.map((item) => {
    const ref = item.onderdeelId.trim();
    const onderdeelId = bestaand.has(ref)
      ? ref
      : slugNaarId.get(ref) ?? slugNaarId.get(slug(ref)) ?? ref;
    return {
      onderdeelId,
      verplicht: item.verplicht ?? true,
      conditie: item.conditie,
    };
  });

  const voorgesteldModel: Modeldocument = {
    id: invoer.modelId ?? `mcp-${basisSlug}`,
    titel: m.titel,
    akteType: m.akteType,
    thema: m.thema,
    soort: m.soort ?? "MOD",
    omschrijving: m.omschrijving,
    inleiding: m.inleiding,
    structuur,
    slot: m.slot,
    bron: m.bron ?? invoer.bron,
    versie: "0.1",
    datum,
    taal: m.taal ?? "nl",
    isGebruiker: true,
  };

  return {
    id: `modelvoorstel-mcp-${basisSlug}-${Date.now()}`,
    modelId: invoer.modelId,
    titel: invoer.titel,
    omschrijving: invoer.omschrijving,
    prioriteit: invoer.prioriteit,
    voorgesteldModel,
    nieuweOnderdelen,
    status: "open",
    bron: invoer.bron ?? m.bron,
    datum,
  };
}
