import type { ModelParameter, Modeldocument, Modelonderdeel, Taal } from "./types";
import { genereerVoorbeeldwaarde } from "./voorbeeldwaarden";
import { canoniekeParameterNaam } from "./parameter-register";

// ── Bibliotheekbrede parameter-catalogus ─────────────────────────────────────
// Parameternamen zijn per conventie gedeeld over de hele bibliotheek (dezelfde
// {{naam_koper}} in compromis, akte en FR-spiegel), maar de omschrijving/het
// voorbeeld is historisch maar bij één (of enkele) onderdelen gedeclareerd.
// De catalogus lost dat mappingsgat op: één opzoektabel naam → beste
// declaratie, zodat de UI en de voorbeeldgeneratie ook voor een lokaal
// niet-gedeclareerde parameter de omschrijving en het voorbeeld van elders
// kunnen tonen. Puur afgeleid — de seeds blijven de bron van waarheid.

/**
 * Bouwt de opzoektabel parameternaam → declaratie over alle onderdelen heen.
 * Bij meerdere declaraties van dezelfde naam wint de meest informatieve:
 * een declaratie mét `voorbeeld` verdringt er één zonder; verder wint de
 * eerste (stabiele volgorde van de seed-barrel).
 */
export function bouwParameterCatalogus(onderdelen: readonly Modelonderdeel[]): Map<string, ModelParameter> {
  const catalogus = new Map<string, ModelParameter>();
  for (const onderdeel of onderdelen) {
    for (const p of onderdeel.parameters) {
      if (!isInformatief(p)) continue;
      const bestaand = catalogus.get(p.naam);
      if (!bestaand || (!bestaand.voorbeeld?.trim() && p.voorbeeld?.trim())) {
        catalogus.set(p.naam, p);
      }
    }
  }
  return catalogus;
}

/**
 * Een declaratie telt pas als ze de gebruiker iets bijbrengt: een lege
 * omschrijving of de automatische "[AAN TE VULLEN: …]"-placeholder (uit de
 * onderdeel-editor resp. de MCP-intake) is geen informatie en houdt de
 * catalogus dus niet tegen om elders een betere declaratie te vinden — en de
 * audit niet tegen om het ontbreken te melden.
 */
export function isInformatief(p: ModelParameter): boolean {
  const omschrijving = p.omschrijving?.trim() ?? "";
  return (omschrijving !== "" && !omschrijving.startsWith("[AAN TE VULLEN")) || !!p.voorbeeld?.trim();
}

/**
 * Zoekt de declaratie van een parameter: eerst in de eigen declaraties van het
 * onderdeel (die kunnen taalspecifiek zijn, bv. een Franse omschrijving in een
 * FR-clausule), daarna in de bibliotheekbrede catalogus.
 */
export function vindParameterInfo(
  naam: string,
  eigenParameters: readonly ModelParameter[],
  catalogus: ReadonlyMap<string, ModelParameter>
): ModelParameter | undefined {
  return eigenParameters.find((p) => p.naam === naam) ?? catalogus.get(naam);
}

// ── Invulhulp voor (AI-)agenten ──────────────────────────────────────────────
// De API/MCP gaf de parameterlijst historisch als kale namen terug, terwijl de
// omschrijving/het voorbeeld enkel de UI bereikte (via de catalogus hierboven).
// Een agent moest de betekenis dus uit de naam raden — de belangrijkste bron
// van invulfouten. Deze invulhulp geeft élke parameter een omschrijving én een
// realistisch voorbeeld, met een expliciete herkomst zodat de agent weet hoe
// betrouwbaar de uitleg is.

/** Volledige invulinstructie voor één parameter, zoals de agent ze krijgt. */
export interface ParameterInvulhulp {
  naam: string;
  /** Wat hier precies moet komen; nooit leeg. */
  omschrijving: string;
  /** Realistische demo-waarde (nooit dossierdata); nooit leeg. */
  voorbeeld: string;
  /**
   * Betrouwbaarheid van de uitleg: "gedeclareerd" = declaratie in een
   * onderdeel van dit model zelf; "catalogus" = declaratie elders in de
   * bibliotheek (zelfde naamconventie); "afgeleid" = geen enkele declaratie —
   * de omschrijving is de verwoorde naam en het voorbeeld is patroongebaseerd:
   * leid de betekenis af uit de clausuletekst rond de placeholder en laat de
   * parameter bij twijfel leeg ([AAN TE VULLEN]) in plaats van te gokken.
   */
  herkomst: "gedeclareerd" | "catalogus" | "afgeleid";
}

const verwoord = (naam: string): string => naam.replace(/_/g, " ");

/**
 * Bouwt de invulhulp voor een lijst parameternamen: eigen declaraties eerst
 * (kunnen taalspecifiek zijn), dan de bibliotheekbrede catalogus, en als
 * laatste redmiddel de verwoorde naam + een patroongebaseerd voorbeeld —
 * zodat een agent nooit een parameter zonder uitleg te zien krijgt.
 */
export function beschrijfParameters(
  namen: readonly string[],
  eigenParameters: readonly ModelParameter[],
  catalogus: ReadonlyMap<string, ModelParameter>,
  taal: Taal
): ParameterInvulhulp[] {
  return namen.map((naam) => {
    // Aliasresolutie: een (gebruikers)clausule met een oude naam krijgt de
    // uitleg van de canonieke declaratie (parameter-register.ts).
    const canoniek = canoniekeParameterNaam(naam);
    const eigen = eigenParameters.find((p) => (p.naam === naam || p.naam === canoniek) && isInformatief(p));
    const info = eigen ?? catalogus.get(naam) ?? catalogus.get(canoniek);
    const herkomst: ParameterInvulhulp["herkomst"] = eigen ? "gedeclareerd" : info ? "catalogus" : "afgeleid";
    const omschrijving =
      info?.omschrijving?.trim() ||
      (herkomst === "afgeleid"
        ? `${verwoord(naam)} (niet gedeclareerd in de bibliotheek — leid de betekenis af uit de clausuletekst rond {{${naam}}}; bij twijfel leeg laten, nooit gokken)`
        : verwoord(naam));
    return {
      naam,
      omschrijving,
      voorbeeld: info?.voorbeeld?.trim() || genereerVoorbeeldwaarde(naam, taal, info),
      herkomst,
    };
  });
}

/**
 * Invulhulp voor alle parameternamen van één model: de eigen declaraties zijn
 * die van de onderdelen in de structuur (in structuurvolgorde, zodat een
 * taalspecifieke declaratie van het model zelf primeert), de rest komt uit de
 * bibliotheekbrede catalogus over `alleOnderdelen`.
 */
export function beschrijfParametersVoorModel(
  namen: readonly string[],
  model: Pick<Modeldocument, "structuur" | "taal">,
  alleOnderdelen: readonly Modelonderdeel[]
): ParameterInvulhulp[] {
  const perId = new Map(alleOnderdelen.map((o) => [o.id, o]));
  const eigen = model.structuur.flatMap((s) => perId.get(s.onderdeelId)?.parameters ?? []);
  return beschrijfParameters(namen, eigen, bouwParameterCatalogus(alleOnderdelen), model.taal ?? "nl");
}
