// ── OpenAPI 3.0-beschrijving van de Notary.AI-API ────────────────────────────
// Eén bron van waarheid voor de machineleesbare API-beschrijving, gedeeld door
// /api/openapi.json (REST-plugin voor Copilot Studio, custom GPT's, …) én
// /api/mcp (Model Context Protocol-server). De enum-waarden worden uit de
// datalaag gegenereerd zodat de spec automatisch synchroon blijft; elke nieuwe
// endpoint die hier wordt toegevoegd, verschijnt vanzelf als MCP-tool.

import { actieveAkteTypen } from "@/data/ereloon";
import { rechtshandelingen } from "@/data/vastgoed-opzoekingen";
import { seedModellen } from "@/data/modeldocumenten";
import { seedBrieven } from "@/data/modelbrieven";
import { relatieOpties } from "@/data/belastingen";
import { AKTE_CHECKLISTS_SEED } from "@/data/kennisbank-checklists";
import {
  KENMERKEN_HANDLEIDING,
  GEWESTEN_KENMERK,
  GOED_KENMERK_TYPES,
  ELEKTRISCHE_KEURING_KENMERK,
  SYNDICUS_INFO_KENMERK,
  GROENESTROOM_KENMERK,
} from "@/lib/dossier/kenmerken";

// ── Kenmerken-schema, gegenereerd uit de handleiding ─────────────────────────
// Eén bron van waarheid: elk deterministisch kenmerk dat de motor kent
// (KENMERKEN_HANDLEIDING) verschijnt automatisch in het API-/MCP-schema, met
// zijn effect als omschrijving. Voorheen werd deze lijst handmatig bijgehouden
// en ontbraken de recentere kenmerken (epcAanwezig, syndicusInfo,
// fiscaalRegime, ...), waardoor agents ze nooit aanleverden.
/**
 * Kenmerken als OpenAPI-queryparameters (voor de GET-varianten
 * haalModeldocument / haalModeldocumentAlsWord), gegenereerd uit dezelfde
 * handleiding. `behalve` = kenmerken die de operatie al handmatig declareert
 * met een rijkere omschrijving.
 */
function kenmerkenQueryParameters(behalve: readonly string[]): OpenApiParameter[] {
  return KENMERKEN_HANDLEIDING.kenmerken
    .filter((k) => !behalve.includes(k.naam as string))
    .map((k) => ({
      name: k.naam as string,
      in: "query" as const,
      required: false,
      schema: k.type === "boolean" ? ({ type: "boolean" } as const) : ({ type: "string", enum: k.toegelaten ?? [] } as const),
      description: k.effect,
    }));
}

function kenmerkenSchemaEigenschappen(): Record<string, { type: "string" | "boolean"; enum?: readonly string[]; description: string }> {
  return Object.fromEntries(
    KENMERKEN_HANDLEIDING.kenmerken.map((k) => [
      k.naam,
      k.type === "boolean"
        ? ({ type: "boolean", description: k.effect } as const)
        : ({ type: "string", enum: k.toegelaten ?? [], description: k.effect } as const),
    ])
  );
}

// ── Minimale, getypeerde deelverzameling van OpenAPI 3.0 ─────────────────────
// Enkel de velden die wij gebruiken en die de MCP-toolgenerator nodig heeft.

export interface OpenApiParameterSchema {
  type: "string" | "number" | "integer" | "boolean" | "object";
  enum?: readonly string[];
  default?: string | number | boolean;
  /** Mag de waarde null zijn (bv. base64 dat voor grote bestanden wordt weggelaten). */
  nullable?: boolean;
  /** Geneste eigenschappen wanneer type "object" is (bv. een kenmerken-blok). */
  properties?: Record<string, OpenApiParameterSchema & { description?: string }>;
}

export interface OpenApiParameter {
  name: string;
  in: "path" | "query";
  required?: boolean;
  schema: OpenApiParameterSchema;
  description?: string;
}

/** Object-schema voor een JSON request body (eigenschappen + verplichte velden). */
export interface OpenApiObjectSchema {
  type: "object";
  properties: Record<string, OpenApiParameterSchema & { description?: string }>;
  required?: string[];
}

/** JSON request body van een (toekomstige) POST-operatie. */
export interface OpenApiRequestBody {
  required?: boolean;
  description?: string;
  content: { "application/json": { schema: OpenApiObjectSchema } };
}

export interface OpenApiResponse {
  description: string;
  content?: { "application/json": { schema: OpenApiObjectSchema } };
}

export interface OpenApiOperation {
  operationId: string;
  summary: string;
  description?: string;
  parameters?: OpenApiParameter[];
  requestBody?: OpenApiRequestBody;
  responses: Record<string, OpenApiResponse>;
}

/**
 * Eén pad ondersteunt GET (lezen/berekenen) en/of POST (schrijven/genereren).
 * De MCP-toolgenerator leest beide, zodat nieuwe functies — ongeacht hun
 * HTTP-methode — automatisch als tool verschijnen zonder herconfiguratie.
 */
export interface OpenApiPathItem {
  get?: OpenApiOperation;
  post?: OpenApiOperation;
}

export interface OpenApiSpec {
  openapi: string;
  info: { title: string; version: string; description: string };
  servers?: { url: string; description?: string }[];
  paths: Record<string, OpenApiPathItem>;
}

/**
 * Bouwt de volledige OpenAPI-spec. Pure functie: geen request- of
 * omgevingsafhankelijkheden, zodat ze zowel server-side (route handlers) als in
 * scripts/tests bruikbaar is.
 */
export function bouwOpenApiSpec(): OpenApiSpec {
  return {
    openapi: "3.0.3",
    info: {
      title: "Notary.AI API — notariskantoor Tervuren",
      version: "1.0",
      description:
        "Einddoel: Notary.AI is een autonome notariële medewerker die op basis " +
        "van de bronstukken zelfstandig ontwerpdocumenten (akte of onderhands " +
        "document) opstelt, modelmails klaarmaakt en afrekeningen berekent " +
        "(aktekosten/ereloon én schenk-/erfbelasting), telkens als werkdocument " +
        "dat de notaris naleest en valideert. " +
        "Twee regimes: (1) GENEREREN/BEREKENEN/OPZOEKEN — modellen/brieven " +
        "opvragen en invullen, afrekeningen maken, opzoekingen/regelgeving " +
        "raadplegen — wijzigt geen gedeelde data en mag volledig vrij en " +
        "proactief; (2) MUTEREN VAN DE KANTOORBIBLIOTHEEK (modelclausules/" +
        "-documenten toevoegen of wijzigen) — uitsluitend via een " +
        "wijzigingsvoorstel (voegModelonderdeelVoorstelToe / " +
        "voegModeldocumentVoorstelToe), nooit rechtstreeks; de notaris keurt " +
        "goed. Privacy: stuur nooit dossierspecifieke persoonsgegevens naar de " +
        "server; de invoer geldt als geanonimiseerd en de agent herstelt " +
        "persoonsgegevens pas lokaal in het eindresultaat. " +
        "Modules: modellenbibliotheek, modelbrieven, aktekosten en de matrix " +
        "van verplichte vastgoedopzoekingen per gewest. Alle bedragen zijn " +
        "indicatief (BETA); de notaris valideert elk resultaat.",
    },
    paths: {
      "/api/werkwijze": {
        get: {
          operationId: "haalWerkwijze",
          summary: "Stappenplan: hoe combineer je deze tools tot een volledig werkdossier",
          description:
            "Machineleesbaar stappenplan voor een AI-agent die, op basis van " +
            "aangeleverde (geanonimiseerde) brondocumenten, een ontwerpdocument, " +
            "modelmail(s) en afrekening(en) samenstelt. Roep dit als eerste op " +
            "bij twijfel over de juiste volgorde of samenhang van de andere tools.",
          responses: {
            "200": { description: "Doel, uitgangspunten en een genummerd stappenplan met per stap de bijbehorende operationId's." },
          },
        },
      },
      "/api/modeldocumenten": {
        get: {
          operationId: "lijstModeldocumenten",
          summary: "Lijst van alle modeldocumenten in de kantoorbibliotheek",
          responses: { "200": { description: "Lijst met id, titel, akteType, thema, soort en detail-URL per model." } },
        },
      },
      "/api/modeldocumenten/{id}": {
        get: {
          operationId: "haalModeldocument",
          summary: "Eén modeldocument opvragen en/of invullen tot een ontwerpdocument",
          parameters: [
            {
              name: "id",
              in: "path",
              required: true,
              schema: { type: "string", enum: seedModellen.map((m) => m.id) },
              description: "Id van het modeldocument (zie lijstModeldocumenten).",
            },
            {
              name: "facultatief",
              in: "query",
              required: false,
              schema: { type: "string" },
              description:
                "Kommagescheiden lijst van facultatieve onderdeel-id's (zie 'structuur' met verplicht=false) die toch opgenomen moeten worden in het ontwerp. " +
                "Verplichte onderdelen zitten er standaard in; onbekende of niet-facultatieve id's komen in 'genegeerdFacultatief'.",
            },
            // Deterministische kenmerken (zelfde bron als
            // genereerOntwerpUitKenmerken): elk zeker gekend feit kiest
            // automatisch de juiste hypothese in het ontwerp.
            ...kenmerkenQueryParameters([]),
            {
              name: "<parameternaam>",
              in: "query",
              required: false,
              schema: { type: "string" },
              description:
                "Eén queryparameter per {{parameter}} uit het model (zie 'parameters' in het antwoord: per parameter naam, omschrijving, realistisch voorbeeld en herkomst — lees die vóór het invullen), bv. ?prijs_letters=...&prijs_cijfers=.... " +
                "Zodra er invulwaarden of een facultatief-keuze zijn, wordt ook het ingevulde ontwerpdocument ('ontwerp') teruggegeven; niet-ingevulde parameters worden [AAN TE VULLEN: ...]. Hypotheses blijven gemarkeerd ter keuze. " +
                "Bij herkomst 'afgeleid' bestaat er geen declaratie: leid de betekenis af uit de clausuletekst rond de placeholder en laat de parameter bij twijfel leeg — nooit gokken.",
            },
          ],
          responses: {
            "200": { description: "Modeltekst (met hypotheses en {{parameters}}), parameterlijst als invulhulp (per parameter: naam, omschrijving, voorbeeld en herkomst 'gedeclareerd'/'catalogus'/'afgeleid') en structuur; mét queryparameters ook het ingevulde ontwerpdocument ('ontwerp') als werkdocument." },
            "404": { description: "Onbekend model-id." },
          },
        },
      },
      "/api/modeldocumenten/{id}/word": {
        get: {
          operationId: "haalModeldocumentAlsWord",
          summary: "Ingevuld ontwerpdocument als Word-bestand (.docx) genereren",
          description:
            "Vult het modeldocument in (zelfde parameters als haalModeldocument) en geeft het resultaat terug als base64-gecodeerd .docx-bestand ('base64', 'mimeType', 'bestandsnaam') én als 'downloadUrl' (24 uur geldig). " +
            "TAAL: kies standaard het model in de taal van de gebruiker (FR of NL) — de modellen bestaan in beide talen (zie het '-fr'-achtervoegsel in de id en het 'taal'-veld in lijstModeldocumenten). " +
            "Het compromis in heldere taal wordt uit het kantoorsjabloon opgebouwd (briefhoofd, omkaderd vak, stijlen). De bestandsnaam bevat de vermelding (Notary.AI), de ligging van het goed en de datum. " +
            "Decodeer 'base64' lokaal naar een .docx-bestand, of deel 'downloadUrl' als klikbare Markdown-link (bv. [bestandsnaam](downloadUrl), nooit de kale URL als platte tekst) wanneer base64 in de chatomgeving niet naar een bijlage kan worden omgezet — stuur er nooit persoonsgegevens van het dossier naartoe (zie privacy-instructies). " +
            "Het antwoord bevat ook 'aanTeVullen': elke [AAN TE VULLEN: …]-parameter die in het Word-bestand achterblijft, mét omschrijving, realistisch voorbeeld en herkomst — gebruik die invulhulp bij het lokaal afwerken en gok nooit bij herkomst 'afgeleid'.",
          parameters: [
            {
              name: "id",
              in: "path",
              required: true,
              schema: { type: "string", enum: seedModellen.map((m) => m.id) },
              description: "Id van het modeldocument (zie lijstModeldocumenten).",
            },
            {
              name: "taal",
              in: "query",
              required: false,
              schema: { type: "string", enum: ["nl", "fr"] },
              description:
                "Taal van de gebruiker ('nl' of 'fr'). Sterk aangeraden: zo wordt het document gegarandeerd in die taal opgesteld en schakelt Notary.AI zo nodig naar het overeenkomstige model (bv. het Franstalige heldere-taal-compromis). Een eventuele modelwissel staat in 'taalMelding'.",
            },
            {
              name: "facultatief",
              in: "query",
              required: false,
              schema: { type: "string" },
              description:
                "Kommagescheiden lijst van facultatieve onderdeel-id's (zie 'structuur' met verplicht=false) die toch opgenomen moeten worden in het ontwerp. " +
                "Verplichte onderdelen zitten er standaard in; onbekende of niet-facultatieve id's komen in 'genegeerdFacultatief'.",
            },
            {
              name: "notities",
              in: "query",
              required: false,
              schema: { type: "string" },
              description:
                "Vrije tekst met nuttige informatie die de gebruiker in de chat heeft meegedeeld en die geen eigen {{parameter}} heeft (bv. bijzondere afspraken, context, opmerkingen). Komt als aparte, opvallend gemarkeerde sectie ('Aanvullende informatie van de gebruiker') vooraan in het Word-bestand, zodat de AI-agent die het document in Word M365 Copilot afwerkt er rekening mee kan houden.",
            },
            {
              name: "gewest",
              in: "query",
              required: false,
              schema: { type: "string", enum: GEWESTEN_KENMERK },
              description:
                "STERK AANGERADEN bij het heldere-taal-compromis: gewest van het goed. Schakelt automatisch de juiste gewestvariant in (energieprestatie, renovatieplicht, stedenbouw, fiscale bepalingen, …) EN sluit de niet-toepasselijke gewestvarianten volledig uit (titel inbegrepen) — zonder dit veld toont het document alle gewestvarianten na elkaar, ter keuze.",
            },
            {
              name: "goedType",
              in: "query",
              required: false,
              schema: { type: "string", enum: GOED_KENMERK_TYPES },
              description: "Type onroerend goed. 'appartement' schakelt de clausule mede-eigendom (statuten/lasten) in; elk ander type sluit ze volledig uit (titel inbegrepen).",
            },
            {
              name: "metKrediet",
              in: "query",
              required: false,
              schema: { type: "boolean" },
              description: "Wordt de aankoop (deels) met een hypothecair krediet gefinancierd? Kiest de toepasselijke financieringsvoorwaarde-hypothese.",
            },
            {
              name: "zonnepanelen",
              in: "query",
              required: false,
              schema: { type: "boolean" },
              description: "Zijn er zonnepanelen op het goed? false sluit de volledige clausule 'panneaux/enseignes' uit (titel inbegrepen) — gebruik dit enkel wanneer ZOWEL geen zonnepanelen ALS geen reclamepaneel/enseigne van toepassing is.",
            },
            {
              name: "gezinswoningVanToepassing",
              in: "query",
              required: false,
              schema: { type: "boolean" },
              description: "Is de clausule 'gezinswoning' van toepassing (een verkoper gedomicilieerd in het goed, met een echtgeno(o)t(e)/wettelijk samenwonende partner die zelf geen verkoper is)? false sluit de volledige clausule uit (titel inbegrepen) in plaats van ze als [NAKIJKEN OF SCHRAPPEN] te laten staan.",
            },
            {
              name: "elektrischeKeuring",
              in: "query",
              required: false,
              schema: { type: "string", enum: ELEKTRISCHE_KEURING_KENMERK },
              description: "Kiest zelf de hypothese voor de keuring van de elektrische installatie op basis van het keuringsverslag (geen verslag / conform / niet-conform / vrijstelling); 'niet-van-toepassing' geeft enkel 'Pas d'application.' (bv. een niet-residentieel goed).",
            },
            {
              name: "syndicusInfo",
              in: "query",
              required: false,
              schema: { type: "string", enum: SYNDICUS_INFO_KENMERK },
              description: "Enkel bij een appartement: stand van het antwoord van de syndicus over de mede-eigendom.",
            },
            {
              name: "groenestroomcertificaten",
              in: "query",
              required: false,
              schema: { type: "string", enum: GROENESTROOM_KENMERK },
              description: "Enkel bij zonnepanelen: regeling van de groenestroomcertificaten.",
            },
            // Overige deterministische kenmerken (zelfde bron als
            // genereerOntwerpUitKenmerken) — elk zeker gekend feit sluit een
            // open hypothese.
            ...kenmerkenQueryParameters([
              "gewest", "goedType", "metKrediet", "zonnepanelen",
              "gezinswoningVanToepassing", "elektrischeKeuring", "syndicusInfo",
              "groenestroomcertificaten",
            ]),
            {
              name: "<parameternaam>",
              in: "query",
              required: false,
              schema: { type: "string" },
              description:
                "Eén queryparameter per {{parameter}} uit het model, bv. ?prijs_letters=...&prijs_cijfers=.... Niet-ingevulde parameters worden [AAN TE VULLEN: ...] in het Word-bestand; hypotheses blijven gemarkeerd ter keuze. Een aangeleverde naam die niet als {{parameter}} in het model bestaat (typfout), verschijnt in 'genegeerdeParameters' — controleer die lijst in het antwoord.",
            },
          ],
          responses: {
            "200": {
              description: "Base64-gecodeerd .docx-bestand ('base64') én een 'downloadUrl' (24 uur geldig), mét bestandsnaam en mimeType, klaar om door te geven aan de gebruiker.",
              content: {
                "application/json": {
                  schema: {
                    type: "object",
                    properties: {
                      bestandsnaam: { type: "string", description: "Voorgestelde bestandsnaam, bv. \"Compromis (Notary.AI) - <plaats> - <datum>.docx\"." },
                      mimeType: { type: "string", description: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" },
                      base64: { type: "string", nullable: true, description: "Het .docx-bestand, base64-gecodeerd. NULL voor grote bestanden (bv. compromis uit het kantoorsjabloon): gebruik dan 'downloadUrl'." },
                      downloadUrl: { type: "string", description: "Directe downloadlink naar het bestand (24 uur geldig) — gebruik deze altijd wanneer 'base64' null is, en deel ze met de gebruiker als klikbare Markdown-link (bv. [bestandsnaam](downloadUrl)), nooit als kale URL." },
                    },
                    required: ["bestandsnaam", "mimeType", "downloadUrl"],
                  },
                },
              },
            },
            "404": { description: "Onbekend model-id." },
          },
        },
      },
      "/api/modeldocumenten/{id}/ontwerp": {
        post: {
          operationId: "genereerOntwerpUitKenmerken",
          summary: "Ontwerp deterministisch aanpassen op basis van gestructureerde feiten (kenmerken)",
          description:
            "Stuur NIET-persoonsgebonden feiten over de rechtshandeling ('kenmerken'); Notary.AI vertaalt die " +
            "DETERMINISTISCH naar de toepasselijke hypotheses (welke varianten/clausules) en genereert het ontwerp. " +
            "Zelfde kenmerken → exact dezelfde tekst: twee agents met dezelfde feiten krijgen hetzelfde resultaat. " +
            "VEILIGHEID — bij twijfel niets schrappen: lever enkel feiten aan die je met zekerheid kent; laat een feit weg als je twijfelt. " +
            "Een weggelaten feit laat alle hypotheses staan ter keuze van de notaris; een clausule verdwijnt enkel door een uitdrukkelijk feit. " +
            "Het antwoord bevat: 'tekst' (het ontwerp), 'toegepasteKeuzes' (wat beslist werd), 'aanTeVullen' (per resterende parameter: naam, omschrijving, realistisch voorbeeld en herkomst — dé invulhulp voor het lokaal aanvullen; gok nooit bij herkomst 'afgeleid'), 'openHypothese', " +
            "'impact' (welke hypotheses gekozen/geschrapt en welke clausules weggelaten), 'waarschuwingen' (uitdrukkelijk bij elke schrapping — altijd controleren) " +
            "en 'handleiding' (welke feiten je kan aanleveren, met toegelaten waarden en effect — lees dit eerst). " +
            "Het optionele 'parameters'-object mag enkel placeholders/algemene waarden bevatten — vul echte persoonsgegevens lokaal in, stuur ze nooit naar deze API.",
          parameters: [
            {
              name: "id",
              in: "path",
              required: true,
              schema: { type: "string", enum: seedModellen.map((m) => m.id) },
              description: "Id van het modeldocument (zie lijstModeldocumenten).",
            },
          ],
          requestBody: {
            required: true,
            content: {
              "application/json": {
                schema: {
                  type: "object",
                  properties: {
                    kenmerken: {
                      type: "object",
                      description:
                        "Gestructureerde, niet-persoonsgebonden feiten die de hypotheses bepalen. " +
                        "Volledige lijst met toegelaten waarden en effect: zie 'handleiding' in het antwoord " +
                        "(gegenereerd uit dezelfde bron als deze schema-eigenschappen). Lever ALLE feiten aan " +
                        "die je met zekerheid kent — elk aangeleverd feit sluit een open hypothese.",
                      properties: kenmerkenSchemaEigenschappen(),
                    },
                    parameters: {
                      type: "object",
                      description: "Optioneel: {{parameter}}-waarden (placeholders/algemene waarden, geen persoonsgegevens).",
                    },
                  },
                  required: ["kenmerken"],
                },
              },
            },
          },
          responses: {
            "200": { description: "Het ontwerp met deterministisch gekozen hypotheses ('tekst'), 'toegepasteKeuzes', 'impact', 'waarschuwingen', 'handleiding' en de resterende [AAN TE VULLEN]/open keuzes." },
            "400": { description: "Ongeldige JSON of onbekende kenmerkwaarde (zie 'problemen')." },
            "404": { description: "Onbekend model-id." },
          },
        },
      },
      "/api/bestanden/{token}": {
        get: {
          operationId: "haalGegenereerdBestand",
          summary: "Een via 'downloadUrl' gegenereerd .docx-bestand downloaden",
          description:
            "Geeft het bestand terug als ruwe binaire bestandsinhoud (geen JSON/base64) — bedoeld om de " +
            "'downloadUrl' uit haalModeldocumentAlsWord/genereerWerkdossierAlsWord rechtstreeks (bv. via " +
            "een browser of chatbijlage) te openen, niet om als los aan te roepen tool te gebruiken. Een " +
            "link is 24 uur geldig na het genereren.",
          parameters: [
            {
              name: "token",
              in: "path",
              required: true,
              schema: { type: "string" },
              description: "Token uit 'downloadUrl'.",
            },
          ],
          responses: {
            "200": { description: "De ruwe .docx-bestandsinhoud." },
            "404": { description: "Onbekende of verlopen downloadlink." },
          },
        },
      },
      "/api/dossier/word": {
        post: {
          operationId: "genereerWerkdossierAlsWord",
          summary: "Volledig werkdossier (ontwerp + modelmail(s) + afrekening) als één Word-bestand bundelen",
          description:
            "Bundelt het ingevulde modeldocument, één of meer ingevulde modelmails en de afrekening " +
            "(aktekosten en/of schenk-/erfbelasting) in ÉÉN base64-gecodeerd .docx-bestand — het volledige " +
            "werkdossier (zie haalWerkwijze). Geef ook de deterministische kenmerken mee (zelfde velden als " +
            "bij genereerOntwerpUitKenmerken, hier als top-level parameters): elk met zekerheid gekend feit " +
            "kiest automatisch de juiste hypothese in het gebundelde ontwerp — laat een feit weg als je twijfelt. " +
            "Het antwoord bevat ook 'downloadUrl' (24 uur geldig): decodeer " +
            "'base64' lokaal, of deel 'downloadUrl' als klikbare Markdown-link (bv. [bestandsnaam](downloadUrl), " +
            "nooit de kale URL als platte tekst) wanneer base64 in de chatomgeving niet naar een bijlage kan " +
            "worden omgezet. Geef het bestand ongewijzigd door; stuur er nooit persoonsgegevens van het dossier naartoe.",
          requestBody: {
            required: true,
            content: {
              "application/json": {
                schema: {
                  type: "object",
                  properties: {
                    // Alle deterministische kenmerken (zelfde bron als
                    // genereerOntwerpUitKenmerken): elk met zekerheid gekend
                    // feit sluit een open hypothese in het gebundelde ontwerp.
                    ...kenmerkenSchemaEigenschappen(),
                    modeldocumentId: { type: "string", enum: seedModellen.map((m) => m.id), description: "Id van het modeldocument (zie lijstModeldocumenten)." },
                    modeldocumentParametersJson: { type: "string", description: "JSON-object {parameternaam: waarde} voor de {{parameters}} van het modeldocument." },
                    modeldocumentFacultatief: { type: "string", description: "Kommagescheiden lijst van op te nemen facultatieve onderdeel-id's." },
                    modelbrievenJson: { type: "string", description: "JSON-array van {id, parameters: {...}, varianten: [...]}, één object per mee te bundelen modelbrief." },
                    akteType: { type: "string", enum: actieveAkteTypen, description: "Optioneel: aktetype voor de aktekosten-afrekening (zie berekenAktekosten)." },
                    bedrag: { type: "number", description: "Vereist wanneer akteType is opgegeven: bedrag in euro." },
                    gewest: { type: "string", enum: ["Vlaanderen", "Brussel", "Wallonië"], description: "Gewest van het goed: stuurt zowel de gewest-hypotheses in het ontwerp als de aktekosten-afrekening (default Vlaanderen)." },
                    schenkingsWijze: { type: "string", enum: ["voorschot", "buiten_deel"], description: "Enkel bij een schenking van bedrag/voorschot." },
                    eigenWoning: { type: "boolean", description: "Of het de eigen woning betreft (beïnvloedt het barema)." },
                    aantalOverdragers: { type: "integer", description: "Aantal overdragers/schenkers (default 1)." },
                    aantalPandregister: { type: "integer", description: "Aantal te raadplegen pandregisters (default 2)." },
                    extraLeveringskosten: { type: "string", description: "Kommagescheiden id's van niet-standaard leveringskosten die effectief werden aangevraagd." },
                    belastingJson: { type: "string", description: "Optioneel: JSON-object {soort, gewest, goedType, relatie, waarde, andereGoederen, isPartner} voor de schenk-/erfbelasting (zie berekenBelasting)." },
                    bevindingenJson: {
                      type: "string",
                      description:
                        "STERK AANGERADEN: JSON-array met je bevindingen uit de dossierstukken — elke geïdentificeerde parameter/nuttige informatie mét zekerheidsgraad: " +
                        "[{parameter, waarde, zekerheid (0-100 of '95%'/'hoog'), bron?, toelichting?}]. Komt als tabel 'Bevindingen van de voorbereidende agent' in het werkblad " +
                        "van het .docx, zodat de AI-agent die het document in Word M365 Copilot afwerkt jouw identificatiewerk niet opnieuw doet (enkel dubbelcheckt). " +
                        "Per GDPR-conventie geldt de invoer als geanonimiseerd — persoonsgegevens vervang je door placeholders.",
                    },
                    notities: { type: "string", description: "Vrije tekst met nuttige informatie die de gebruiker in de chat heeft meegedeeld en die geen eigen {{parameter}} heeft (bv. bijzondere afspraken, context, opmerkingen). Komt als aparte, opvallend gemarkeerde sectie ('Aanvullende informatie van de gebruiker') vooraan in het Word-bestand, zodat de AI-agent die het document in Word M365 Copilot afwerkt er rekening mee kan houden." },
                    afbeeldingenJson: {
                      type: "string",
                      description:
                        "JSON-array van bijgevoegde schermafbeeldingen om letterlijk in het werkdossier op te nemen (bv. de pagina's van een PDF met stedenbouwkundige inlichtingen): [{bestandsnaam, base64, bijschrift?}]. " +
                        "'base64' is de ruwe PNG- of JPEG-inhoud, base64-gecodeerd (geen data-URL-voorvoegsel). Max. 10 afbeeldingen, elk max. 5 MB (gedecodeerd); een afbeelding die niet gelezen kan worden of te groot is, wordt overgeslagen met een duidelijke markering in het document — de generatie wordt daardoor nooit geweigerd.",
                    },
                  },
                  required: ["modeldocumentId"],
                },
              },
            },
          },
          responses: {
            "200": {
              description: "Base64-gecodeerd .docx-bestand ('base64') én een 'downloadUrl' (24 uur geldig), mét bestandsnaam en mimeType, klaar om ongewijzigd door te geven aan de gebruiker.",
              content: {
                "application/json": {
                  schema: {
                    type: "object",
                    properties: {
                      bestandsnaam: { type: "string", description: "Voorgestelde bestandsnaam, bv. \"werkdossier-<model-id>.docx\"." },
                      mimeType: { type: "string", description: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" },
                      base64: { type: "string", description: "Het .docx-bestand, base64-gecodeerd. Decodeer lokaal als de omgeving dat ondersteunt." },
                      downloadUrl: { type: "string", description: "Directe downloadlink naar het bestand (24 uur geldig) — deel deze link met de gebruiker als klikbare Markdown-link (bv. [bestandsnaam](downloadUrl)), nooit als kale URL, als 'base64' hier niet naar een bijlage kan worden omgezet." },
                    },
                    required: ["bestandsnaam", "mimeType", "base64", "downloadUrl"],
                  },
                },
              },
            },
            "400": { description: "Validatiefout (foutboodschap vermeldt wat ontbreekt of ongeldig is)." },
          },
        },
      },
      "/api/modelbrieven": {
        get: {
          operationId: "lijstModelbrieven",
          summary: "Lijst van alle modelmails voor dossierbeheer",
          responses: { "200": { description: "Lijst met id, titel, categorie, ontvanger, taal, onderwerp en detail-URL per modelbrief." } },
        },
      },
      "/api/modelbrieven/{id}": {
        get: {
          operationId: "haalModelbrief",
          summary: "Eén modelbrief opvragen en/of invullen tot een verzendklare e-mail",
          parameters: [
            {
              name: "id",
              in: "path",
              required: true,
              schema: { type: "string", enum: seedBrieven.map((b) => b.id) },
              description: "Id van de modelbrief (zie lijstModelbrieven).",
            },
            {
              name: "varianten",
              in: "query",
              required: false,
              schema: { type: "string" },
              description:
                "Kommagescheiden lijst van te gebruiken variant-id's (zie 'varianten' in het antwoord zonder deze parameter). " +
                "Weglaten = alle varianten; lege waarde = geen varianten.",
            },
            {
              name: "<parameternaam>",
              in: "query",
              required: false,
              schema: { type: "string" },
              description:
                "Eén queryparameter per {{parameter}} uit de modelbrief (zie 'parameters' in het antwoord), bv. ?naam=...&referte=.... " +
                "Niet-ingevulde parameters worden [AAN TE VULLEN: ...] in het resultaat.",
            },
          ],
          responses: {
            "200": { description: "Sjabloon, parameters, varianten en — als er queryparameters zijn meegegeven — het ingevulde onderwerp/tekst/bijlagen." },
            "404": { description: "Onbekend modelbrief-id." },
          },
        },
      },
      "/api/aktekosten": {
        get: {
          operationId: "berekenAktekosten",
          summary: "Indicatieve aktekostenafrekening (honorarium, kosten, btw, btw-vrije posten)",
          parameters: [
            { name: "akteType", in: "query", required: true, schema: { type: "string", enum: actieveAkteTypen }, description: "Type akte." },
            { name: "bedrag", in: "query", required: true, schema: { type: "number" }, description: "Waarde/prijs/kredietbedrag in euro." },
            { name: "gewest", in: "query", required: false, schema: { type: "string", enum: ["Vlaanderen", "Brussel", "Wallonië"], default: "Vlaanderen" }, description: "Gewest." },
            { name: "schenkingsWijze", in: "query", required: false, schema: { type: "string", enum: ["voorschot", "buiten_deel"] }, description: "Enkel bij schenking: als voorschot op erfdeel (schaal F) of buiten erfdeel (schaal H)." },
            { name: "eigenWoning", in: "query", required: false, schema: { type: "boolean" }, description: "Enkel bij hypotheek: krediet voor de enige eigen woning." },
            { name: "aantalOverdragers", in: "query", required: false, schema: { type: "integer", default: 1 }, description: "Aantal verkopers/schenkers — bepaalt 'per overdrager'-kosten." },
            { name: "aantalPandregister", in: "query", required: false, schema: { type: "integer", default: 2 }, description: "Aantal pandregisteropzoekingen (€7 per stuk)." },
            {
              name: "extraLeveringskosten",
              in: "query",
              required: false,
              schema: { type: "string" },
              description:
                "Kommagescheiden lijst van id's van niet-standaard leveringskosten " +
                "(zie 'levKosten' in het antwoord, bv. 'lev-syndicus') die toch in de " +
                "afrekening moeten verschijnen omdat de bijbehorende opzoeking/het attest " +
                "effectief werd aangevraagd voor dit dossier — bepaal dit via haalOpzoekingen " +
                "vóór je de afrekening maakt.",
            },
          ],
          responses: {
            "200": { description: "Afrekening: honorarium, forfaitaire en individualiseerbare kosten, leveringskosten (incl. extraLeveringskosten), btw-vrije posten (KB 14.09.2016), btw 21% en totaal." },
            "400": { description: "Ontbrekende of ongeldige parameter (foutboodschap vermeldt de toegelaten waarden)." },
          },
        },
      },
      "/api/belasting": {
        get: {
          operationId: "berekenBelasting",
          summary: "Indicatieve schenk- of erfbelasting per gewest, met gunsttarieven en abattementen",
          description:
            "Berekent de progressieve schenk- of erfbelasting op basis van de officiële " +
            "barema's per gewest (Vlaanderen, Brussel, Wallonië), inclusief toepasselijke " +
            "abattementen en gunsttarieven (o.a. gezinswoning, bescheiden erfdeel). " +
            "Bij soort=erfenis zijn 'andereGoederen' en 'isPartner' relevant; bij " +
            "soort=schenking worden die genegeerd.",
          parameters: [
            { name: "soort", in: "query", required: true, schema: { type: "string", enum: ["schenking", "erfenis"] }, description: "Type belasting." },
            { name: "gewest", in: "query", required: false, schema: { type: "string", enum: ["Vlaanderen", "Brussel", "Wallonië"], default: "Vlaanderen" }, description: "Gewest." },
            { name: "goedType", in: "query", required: false, schema: { type: "string", enum: ["roerend", "onroerend", "gezinswoning"], default: "onroerend" }, description: "Type goed. 'gezinswoning' enkel relevant voor de woning zelf." },
            { name: "relatie", in: "query", required: true, schema: { type: "string", enum: relatieOpties }, description: "Verwantschap tussen schenker/erflater en begunstigde." },
            { name: "waarde", in: "query", required: true, schema: { type: "number" }, description: "Waarde van het geschonken/vererfde goed in euro." },
            { name: "andereGoederen", in: "query", required: false, schema: { type: "number", default: 0 }, description: "Enkel bij soort=erfenis: waarde van de overige (reeds belaste) goederen van dezelfde erfgenaam, voor de marginale/progressieve berekening." },
            { name: "isPartner", in: "query", required: false, schema: { type: "boolean", default: true }, description: "Enkel bij soort=erfenis en relatie=rechte_lijn_partner: of de begunstigde de langstlevende partner is (true) dan wel een afstammeling (false) — bepaalt welke gunsttarieven/vrijstellingen gelden." },
          ],
          responses: {
            "200": { description: "Berekend totaal, detail per belastingschijf, effectief tarief en context (geldigVanaf/bron)." },
            "400": { description: "Ontbrekende of ongeldige parameter (foutboodschap vermeldt de toegelaten waarden)." },
          },
        },
      },
      "/api/modeldocumenten/richtlijnen": {
        get: {
          operationId: "haalModelonderdeelRichtlijnen",
          summary: "Richtlijnen en beste praktijken voor nieuwe of gewijzigde modelonderdelen (clausules)",
          description:
            "Raadpleeg dit vóór voegModelonderdeelVoorstelToe: conventies voor " +
            "{{parameters}}, rolnamen ({{rol_overdrager}}/{{rol_verkrijger}}), " +
            "hypotheses/varianten, categorieën, thema's, soorten, talen, privacy " +
            "en het wijzigingsvoorstel-patroon, met een JSON-voorbeeld van " +
            "'onderdeelJson'.",
          responses: { "200": { description: "Richtlijnen, conventies, toegelaten enum-waarden en een JSON-voorbeeld." } },
        },
      },
      "/api/modeldocumenten/voorstellen": {
        get: {
          operationId: "lijstModelonderdeelVoorstellen",
          summary: "Lijst van via MCP ingediende wijzigingsvoorstellen voor modelonderdelen",
          description:
            "Best-effort sessiecache van deze serverinstantie (niet persistent " +
            "over deployments of meerdere instanties).",
          responses: { "200": { description: "Lijst van ingediende wijzigingsvoorstellen." } },
        },
        post: {
          operationId: "voegModelonderdeelVoorstelToe",
          summary: "Dien een nieuw of gewijzigd modelonderdeel (clausule) in als wijzigingsvoorstel",
          description:
            "Raadpleeg eerst haalModelonderdeelRichtlijnen voor de conventies en " +
            "het verwachte JSON-formaat van 'onderdeelJson'. Levert geen " +
            "rechtstreekse wijziging op, maar een Wijzigingsvoorstel dat de " +
            "notaris overneemt via het tabblad 'Te valideren'.",
          requestBody: {
            required: true,
            content: {
              "application/json": {
                schema: {
                  type: "object",
                  properties: {
                    titel: { type: "string", description: "Titel van het voorstel (samenvatting van de wijziging)." },
                    omschrijving: { type: "string", description: "Wat verandert er en waarom." },
                    prioriteit: { type: "string", enum: ["hoog", "midden", "laag"], description: "Prioriteit voor het tabblad 'Te valideren'." },
                    onderdeelId: { type: "string", description: "Id van het bestaande modelonderdeel dat wordt gewijzigd; weglaten = nieuw onderdeel." },
                    bron: { type: "string", description: "Herkomst van het voorstel (bv. brontekst of dossier)." },
                    onderdeelJson: {
                      type: "string",
                      description:
                        "JSON-tekst van het voorgestelde modelonderdeel (titel, categorie, omschrijving, " +
                        "tekst/varianten, parameters, toepasbaarOp, thema, soort, taal, wetsbasis). " +
                        "Zie haalModelonderdeelRichtlijnen voor het formaat en een voorbeeld.",
                    },
                  },
                  required: ["titel", "omschrijving", "prioriteit", "onderdeelJson"],
                },
              },
            },
          },
          responses: {
            "200": { description: "Het opgebouwde wijzigingsvoorstel (compleet modelonderdeel met gegenereerde id, versie en datum)." },
            "400": { description: "Validatiefout (foutboodschap vermeldt wat ontbreekt of ongeldig is)." },
          },
        },
      },
      "/api/modeldocumenten/modelrichtlijnen": {
        get: {
          operationId: "haalModeldocumentRichtlijnen",
          summary: "Richtlijnen en beste praktijken voor nieuwe of gewijzigde volledige modeldocumenten",
          description:
            "Raadpleeg dit vóór voegModeldocumentVoorstelToe: hoe een volledig " +
            "modeldocument wordt opgebouwd uit (bestaande of nieuwe) " +
            "modelonderdelen, de structuurverwijzingen, wijziging vs. nieuw, " +
            "privacy en een JSON-voorbeeld van 'modelJson'.",
          responses: { "200": { description: "Richtlijnen, conventies, toegelaten enum-waarden en een JSON-voorbeeld van een modeldocument." } },
        },
      },
      "/api/modeldocumenten/modelvoorstellen": {
        get: {
          operationId: "lijstModeldocumentVoorstellen",
          summary: "Lijst van ingediende voorstellen voor volledige modeldocumenten",
          description:
            "De ingediende modeldocument-voorstellen die nog wachten op validatie " +
            "door de notaris in het tabblad 'Te valideren'.",
          responses: { "200": { description: "Lijst van ingediende modeldocument-voorstellen." } },
        },
        post: {
          operationId: "voegModeldocumentVoorstelToe",
          summary: "Dien een nieuw of gewijzigd volledig modeldocument in als voorstel",
          description:
            "Raadpleeg eerst haalModeldocumentRichtlijnen (en haalModelonderdeelRichtlijnen " +
            "voor de onderdelen) voor de conventies en het JSON-formaat van 'modelJson'. " +
            "Levert geen rechtstreekse wijziging op, maar een ModeldocumentVoorstel dat de " +
            "notaris overneemt via het tabblad 'Te valideren' — model én nieuwe onderdelen " +
            "in één keer.",
          requestBody: {
            required: true,
            content: {
              "application/json": {
                schema: {
                  type: "object",
                  properties: {
                    titel: { type: "string", description: "Titel van het voorstel (samenvatting van de wijziging/toevoeging)." },
                    omschrijving: { type: "string", description: "Wat het model is/verandert en waarom." },
                    prioriteit: { type: "string", enum: ["hoog", "midden", "laag"], description: "Prioriteit voor het tabblad 'Te valideren'." },
                    modelId: { type: "string", description: "Id van het bestaande modeldocument dat wordt gewijzigd; weglaten = nieuw model." },
                    bron: { type: "string", description: "Herkomst van het voorstel (bv. brontekst of dossier)." },
                    modelJson: {
                      type: "string",
                      description:
                        "JSON-tekst van het voorgestelde modeldocument (titel, akteType, omschrijving, " +
                        "thema, soort, inleiding, structuur met onderdeelId-verwijzingen, slot, taal, en " +
                        "optioneel nieuweOnderdelen). Zie haalModeldocumentRichtlijnen voor het formaat en een voorbeeld.",
                    },
                  },
                  required: ["titel", "omschrijving", "prioriteit", "modelJson"],
                },
              },
            },
          },
          responses: {
            "200": { description: "Het opgebouwde modeldocument-voorstel (compleet model met gegenereerde id, versie, datum en nieuwe onderdelen)." },
            "400": { description: "Validatiefout (foutboodschap vermeldt wat ontbreekt of ongeldig is, bv. een onbekende structuurverwijzing)." },
          },
        },
      },
      "/api/dossier/aktetype": {
        post: {
          operationId: "bepaalAkteType",
          summary: "Bepaal het te gebruiken akteType uit uitsluitend structurele dossierkenmerken",
          description:
            "Leidt het akteType af op basis van enkel het dossiertype en (bij " +
            "verkoop) of er al een compromisdatum gekend is — geen namen, " +
            "adressen of bedragen nodig of toegelaten. Geeft 'zekerheid: " +
            "onduidelijk' met alternatieven terug wanneer het dossiertype de " +
            "keuze niet eenduidig vastlegt (bv. nalatenschap). Geeft ook aan of " +
            "er voor dat akteType een passend modeldocument in de " +
            "seed-bibliotheek bestaat ('modelGevonden'/'modelId'), zodat je " +
            "meteen weet of stap 3/4 (haalModeldocument) mogelijk is of dat " +
            "eerst een wijzigingsvoorstel nodig is. Vul vervolgens zelf, lokaal, " +
            "de echte parameters in via haalModeldocument — stuur die nooit naar " +
            "deze of een andere endpoint.",
          requestBody: {
            required: true,
            content: {
              "application/json": {
                schema: {
                  type: "object",
                  properties: {
                    dossiertype: {
                      type: "string",
                      enum: [
                        "verkoop-met-krediet",
                        "verkoop-zonder-krediet",
                        "schenking",
                        "nalatenschap",
                        "aanpassing-statuten-vennootschap",
                        "keuzetestament",
                        "testament-gezinswoning",
                      ],
                      description: "Het type dossier — bevat geen dossierinhoud, enkel het soort akte/transactie.",
                    },
                    heeftCompromisdatum: {
                      type: "boolean",
                      description: "Bij verkoop-met-krediet/verkoop-zonder-krediet: is er al een datum voor de onderhandse verkoopovereenkomst (compromis) gekend? Negeer voor andere dossiertypes.",
                    },
                  },
                  required: ["dossiertype"],
                },
              },
            },
          },
          responses: {
            "200": { description: "akteType, zekerheid ('zeker'/'onduidelijk'), eventuele alternatieven, reden, en modelGevonden/modelId." },
            "400": { description: "Validatiefout (ongeldig of ontbrekend 'dossiertype')." },
          },
        },
      },
      "/api/dossier/sessies": {
        get: {
          operationId: "lijstWerkdossierSessies",
          summary: "Lijst van de bewaarde werkdossier-sessies (24 uur raadpleegbaar en bijwerkbaar)",
          description:
            "Elke Word-generatie (genereerWerkdossierAlsWord of " +
            "haalModeldocumentAlsWord) bewaart automatisch haar VOLLEDIGE " +
            "generatie-invoer 24 uur onder een 'dossierToken' (zie dat veld in " +
            "hun antwoord). Deze lijst toont de actieve sessies — recentst " +
            "bijgewerkt eerst — met token, bestandsnaam, modeldocumentId, " +
            "aantal generaties en vervaltijd. Gebruik haalWerkdossierSessie " +
            "voor het detail en werkWerkdossierSessieBij om aanvullende " +
            "stukken toe te voegen en te regenereren. Ook voor de gebruiker " +
            "zichtbaar in de app onder /werkdossiers.",
          responses: {
            "200": { description: "aantal + sessies[] (token, soort, modeldocumentId, bestandsnaam, downloadUrl, aantalGeneraties, aangemaakt, bijgewerkt, verlooptOm)." },
          },
        },
      },
      "/api/dossier/sessies/{token}": {
        get: {
          operationId: "haalWerkdossierSessie",
          summary: "Eén werkdossier-sessie raadplegen: de volledige bewaarde invoer + historiek",
          description:
            "Geeft de volledige generatie-invoer van een sessie terug " +
            "(modeldocument, parameters, kenmerken, facultatieve onderdelen, " +
            "notities, …) plus de historiek van alle (re)generaties met hun " +
            "downloadUrl. Zo zie je exact met welke gegevens het laatste .docx " +
            "is gemaakt, vóór je bijwerkt met werkWerkdossierSessieBij.",
          parameters: [
            { name: "token", in: "path", required: true, schema: { type: "string" }, description: "Het 'dossierToken' uit het generatie-antwoord of uit lijstWerkdossierSessies." },
          ],
          responses: {
            "200": { description: "De sessie: soort, invoer, bestandsnaam, downloadUrl, historiek[], aangemaakt/bijgewerkt/verlooptOm." },
            "404": { description: "Onbekende of verlopen sessie (24 uur na de laatste wijziging)." },
          },
        },
        post: {
          operationId: "werkWerkdossierSessieBij",
          summary: "Aanvullende stukken toevoegen aan een werkdossier-sessie en het .docx regenereren",
          description:
            "Lever ALLEEN de aanvullingen/wijzigingen aan — de rest van de " +
            "bewaarde invoer blijft staan. 'parameters' wordt per naam " +
            "samengevoegd met de bestaande {{parameter}}-waarden (bv. de " +
            "gegevens uit een nagekomen attest of bodemonderzoek), 'kenmerken' " +
            "idem voor de deterministische feiten, 'facultatief' en 'notities' " +
            "vervangen hun vorige waarde. Het antwoord bevat het geregenereerde " +
            ".docx (base64/downloadUrl, 24 uur geldig) én de bijgewerkte " +
            "historiek; het token blijft hetzelfde en de vervaltijd schuift " +
            "opnieuw 24 uur op. Regeneratie gebruikt letterlijk dezelfde " +
            "generator als een verse aanroep — resultaat gegarandeerd " +
            "consistent. Zelfde privacy-regel: enkel geanonimiseerde waarden.",
          parameters: [
            { name: "token", in: "path", required: true, schema: { type: "string" }, description: "Het 'dossierToken' van de bij te werken sessie." },
          ],
          requestBody: {
            required: true,
            content: {
              "application/json": {
                schema: {
                  type: "object",
                  properties: {
                    parameters: { type: "object", description: "Aanvullende of gecorrigeerde {{parameter}}-waarden (naam → waarde); wordt samengevoegd met de bestaande. Geanonimiseerd per conventie." },
                    verwijderParameters: { type: "string", description: "Kommagescheiden parameternamen die uit de bewaarde invoer moeten verdwijnen." },
                    kenmerken: { type: "object", description: "Aanvullende deterministische kenmerken (zelfde velden als genereerOntwerpUitKenmerken); wordt samengevoegd." },
                    facultatief: { type: "string", description: "Nieuwe kommagescheiden lijst van op te nemen facultatieve onderdeel-id's (VERVANGT de vorige lijst)." },
                    notities: { type: "string", description: "Nieuwe vrije notities voor het werkblad (VERVANGT de vorige)." },
                    veldenJson: { type: "string", description: "JSON-object voor overige top-level generatievelden (bv. modelbrievenJson, belastingJson, akteType/bedrag/gewest) — shallow merge over de bewaarde invoer." },
                    wijziging: { type: "string", description: "Korte omschrijving van de aanvulling (bv. 'bodemattest ontvangen en verwerkt') — komt in de sessie-historiek." },
                  },
                },
              },
            },
          },
          responses: {
            "200": { description: "Het geregenereerde .docx (bestandsnaam, base64, downloadUrl) + dossierToken, historiek en toegepasteWijziging." },
            "400": { description: "Ongeldige JSON of regeneratiefout (foutboodschap vermeldt de oorzaak)." },
            "404": { description: "Onbekende of verlopen sessie." },
          },
        },
      },
      "/api/dossier/termijnen": {
        post: {
          operationId: "berekenDossierTermijnen",
          summary: "Bereken de lopende termijnen en het urgentieniveau van een verkoopdossier",
          description:
            "Berekent uit uitsluitend structurele data — de compromisdatum en " +
            "(optioneel) de overeengekomen krediettermijn in weken — de twee " +
            "wettelijke/contractuele vervaldagen van een verkoopdossier: de " +
            "opschortende voorwaarde van financiering (compromisdatum + " +
            "krediettermijnWeken) en de uiterste datum van de authentieke akte " +
            "(compromisdatum + vier maanden standaard, of de contractueel " +
            "bedongen aktetermijnMaanden/aktetermijnUiterlijkeDatum; ligt de " +
            "contractuele termijn voorbij de vier maanden, dan wordt de fiscale " +
            "registratietermijn als aparte vervaldag bewaakt). Het antwoord bevat per termijn de vervaldag, " +
            "de herleidbare berekeningsgrondslag, de resterende dagen en een " +
            "samengevat urgentieniveau ('verstreken'/'nabij'/'ok') — gebruik " +
            "dat om de notaris proactief te waarschuwen en de ondertekening " +
            "tijdig te plannen. Geen namen, adressen of bedragen nodig of " +
            "toegelaten. Zelfde motor als de dossiermodule in de app: MCP en " +
            "app geven gegarandeerd dezelfde vervaldagen.",
          requestBody: {
            required: true,
            content: {
              "application/json": {
                schema: {
                  type: "object",
                  properties: {
                    compromisdatum: { type: "string", description: "Ondertekeningsdatum van de onderhandse verkoopovereenkomst, ISO 'JJJJ-MM-DD'." },
                    krediettermijnWeken: { type: "number", description: "Enkel bij verkoop met krediet: de overeengekomen termijn van de opschortende voorwaarde van financiering, in weken (staat in het compromis)." },
                    kredietAanvaard: { type: "boolean", description: "Is de kredietofferte al aanvaard? true = de opschortende voorwaarde is vervuld en die termijn vervalt uit de bewaking." },
                    vandaag: { type: "string", description: "Referentiedatum ISO 'JJJJ-MM-DD' voor de resterende dagen; weglaten = vandaag (servertijd)." },
                    aktetermijnMaanden: { type: "number", description: "Contractueel bedongen aktetermijn in maanden (bv. 3 of 6), wanneer het compromis afwijkt van de standaard vier maanden. Krijgt voorrang; ligt hij voorbij de vier maanden, dan verschijnt de fiscale registratietermijn als aparte vervaldag." },
                    aktetermijnUiterlijkeDatum: { type: "string", description: "Contractueel bedongen vaste uiterste aktedatum, ISO 'JJJJ-MM-DD' (alternatief voor aktetermijnMaanden)." },
                  },
                  required: ["compromisdatum"],
                },
              },
            },
          },
          responses: {
            "200": { description: "referentiedatum, termijnen[] (id, vervaldatum, grondslag, toelichting, dagenResterend, verstreken), urgentie (niveau/label/sorteersleutel) en onzekerheden." },
            "400": { description: "Ontbrekende of ongeldige parameter (foutboodschap vermeldt het verwachte formaat)." },
          },
        },
      },
      "/api/dossier/volgende-stappen": {
        post: {
          operationId: "bepaalVolgendeStappen",
          summary: "Bepaal de volgende stappen (acties en mailcategorieën) van een verkoopdossier",
          description:
            "Geeft uit uitsluitend structurele data — dossiertype en status, met optionele " +
            "vlaggen (appartement, kredietAanvaard) — de volledige opvolgingsroute van de " +
            "verkoopketen terug: per fase de titel, de concrete acties en de bijhorende " +
            "modelmail-categorieën (zie lijstModelbrieven), met de huidige stap gemarkeerd " +
            "('huidigeStap'). Geef optioneel 'compromisdatum' mee om ook de lopende " +
            "termijnen te ontvangen (zelfde motor als berekenDossierTermijnen). Geen namen, " +
            "adressen of bedragen nodig of toegelaten. Gebruik dit om na elke statuswijziging " +
            "te weten wat er nu moet gebeuren en welke modelmail je best klaarmaakt.",
          requestBody: {
            required: true,
            content: {
              "application/json": {
                schema: {
                  type: "object",
                  properties: {
                    dossiertype: { type: "string", enum: ["verkoop-met-krediet", "verkoop-zonder-krediet"], description: "Type dossier (de stappenmotor modelleert vandaag de verkoopketen)." },
                    status: { type: "string", enum: ["compromis-ontvangen", "opzoekingen-lopend", "ontwerp-in-opmaak", "ontwerp-verstuurd", "klaar-voor-akte", "verleden"], description: "Huidige dossierstatus." },
                    appartement: { type: "boolean", description: "true = het goed is een kavel in mede-eigendom (voegt de syndicus-acties toe)." },
                    kredietAanvaard: { type: "boolean", description: "Enkel bij verkoop-met-krediet: is de kredietofferte al aanvaard?" },
                    compromisdatum: { type: "string", description: "Optioneel, ISO 'JJJJ-MM-DD': ondertekeningsdatum — voegt de lopende termijnen toe aan het antwoord." },
                    vandaag: { type: "string", description: "Referentiedatum ISO 'JJJJ-MM-DD' voor de resterende dagen; weglaten = vandaag (servertijd)." },
                  },
                  required: ["dossiertype", "status"],
                },
              },
            },
          },
          responses: {
            "200": { description: "huidigeStap (titel, acties, mailCategorieen), de volledige stappenroute en — mét compromisdatum — de lopende termijnen." },
            "400": { description: "Ontbrekende of ongeldige structurele invoer." },
          },
        },
      },
      "/api/dossier/intake-schema": {
        get: {
          operationId: "haalIntakeInstructies",
          summary: "Instructies + schema om een brondocument LOKAAL om te zetten naar importeerbaar dossier-JSON",
          description:
            "Geeft de volledige extractie-instructies en het schema " +
            "(notary-ai-dossier-intake) waarmee je één brondocument (compromis, " +
            "eigendomstitel, attesten, …) lokaal omzet naar het JSON-object dat " +
            "de notaris rechtstreeks importeert in de module Dossiers (/dossier " +
            "→ 'Importeer uit brondocument'). LET OP de omgekeerde privacy-flow: " +
            "het resulterende intake-JSON bevat wél persoonsgegevens en blijft " +
            "LOKAAL tussen jou en de notaris — verstuur het nooit naar een " +
            "Notary.AI-API of -tool; deze endpoint geeft enkel de statische " +
            "instructies terug en ontvangt zelf nooit dossierdata. Gebruik dit " +
            "wanneer de gebruiker een dossier wil aanmaken/aanvullen in de app " +
            "in plaats van (enkel) een los document te laten genereren.",
          responses: {
            "200": { description: "doel, privacy-regels, schemas (v1/v2), dossiertypes en de volledige 'instructies' (extractieprompt met JSON-voorbeeld)." },
          },
        },
      },
      "/api/dossier/modelmail-categorieen": {
        post: {
          operationId: "bepaalModelmailCategorieen",
          summary: "Bepaal de relevante modelmail-categorieën uit enkel het dossiertype",
          description:
            "Leidt op basis van uitsluitend het dossiertype af welke " +
            "modelbrief-categorieën (hoofdstukken) relevant zijn, met per " +
            "categorie de beschikbare NL-modelbrieven (id + titel). Geen namen, " +
            "adressen of bedragen nodig of toegelaten. Gebruik dit om snel het " +
            "juiste hoofdstuk te kiezen; vul de echte parameters daarna lokaal " +
            "in via haalModelbrief. Een lege lijst betekent dat het dossiertype " +
            "geen eenduidige categorie oplevert.",
          requestBody: {
            required: true,
            content: {
              "application/json": {
                schema: {
                  type: "object",
                  properties: {
                    dossiertype: {
                      type: "string",
                      enum: [
                        "verkoop-met-krediet",
                        "verkoop-zonder-krediet",
                        "schenking",
                        "nalatenschap",
                        "aanpassing-statuten-vennootschap",
                        "keuzetestament",
                        "testament-gezinswoning",
                      ],
                      description: "Het type dossier — bevat geen dossierinhoud, enkel het soort akte/transactie.",
                    },
                  },
                  required: ["dossiertype"],
                },
              },
            },
          },
          responses: {
            "200": { description: "Lijst van relevante categorieën met label en de beschikbare modelbrieven (id + titel) per categorie." },
            "400": { description: "Validatiefout (ongeldig of ontbrekend 'dossiertype')." },
          },
        },
      },
      "/api/verbetervoorstellen": {
        get: {
          operationId: "lijstVerbetervoorstellen",
          summary: "Lijst van verbetervoorstellen voor de Notary.AI-applicatie",
          description:
            "Geeft de openstaande verbetervoorstellen terug die de notaris in de applicatie " +
            "heeft ingediend, gesorteerd op aanmaakdatum. Gebruik 'prioriteit' (hoog/midden/laag), " +
            "'technischeDetails' en 'acceptatiecriteria' om de implementatievolgorde en -scope te " +
            "bepalen. Bevat enkel functionele beschrijvingen — nooit persoonsgegevens.",
          parameters: [
            {
              name: "status",
              in: "query",
              required: false,
              schema: {
                type: "string",
                enum: ["open", "in_behandeling", "gedaan", "afgewezen"],
                default: "open",
              },
              description: "Filter op status. Weglaten = alle statussen.",
            },
          ],
          responses: {
            "200": {
              description:
                "Lijst van verbetervoorstellen met id, titel, omschrijving, categorie, " +
                "prioriteit, status, technischeDetails, acceptatiecriteria en datum.",
            },
          },
        },
      },
      "/api/kennisbank/checklists": {
        get: {
          operationId: "haalAkteChecklists",
          summary: "Akte-checklists (cruciaal/belangrijk/nuttig/overbodig) + track-changes-werkwijze",
          description:
            "Per rechtshandeling een volledige controlelijst waarin elk punt " +
            "een gewicht draagt: cruciaal (moet aanwezig én correct zijn), " +
            "belangrijk (controleren en corrigeren), nuttig (toevoegen als het " +
            "weinig moeite kost) of overbodig (mag gemotiveerd geschrapt " +
            "worden uit teksten van derden). Het antwoord bevat ook de vaste " +
            "werkwijze om een tekst van een derde (makelaar, bank, confrater) " +
            "aan te passen in track changes, met de voorgeschreven handeling " +
            "per gewicht. Raadpleeg dit VÓÓR je zo'n tekst nakijkt of aanpast.",
          parameters: [
            {
              name: "akteType",
              in: "query",
              required: false,
              schema: {
                type: "string",
                enum: AKTE_CHECKLISTS_SEED.map((c) => c.akteType),
              },
              description:
                "Rechtshandeling waarvoor je de checklist wil; sub-varianten " +
                "(heldere taal, Wet Breyne, lijfrente) vallen automatisch " +
                "terug op de basischecklist. Weglaten = alle checklists.",
            },
          ],
          responses: {
            "200": { description: "De checklist(s) met gewogen punten (incl. gewest en wetsbasis) en de track-changes-werkwijze." },
            "404": { description: "Geen checklist voor het gevraagde akteType; het antwoord vermeldt de beschikbare types." },
          },
        },
      },
      "/api/kennisbank": {
        get: {
          operationId: "lijstKennisbank",
          summary: "Gedeelde kantoorkennis: werkwijzen, juridische aandachtspunten en kantoorgebruiken",
          description:
            "Raadpleeg de door de notaris gevalideerde kantoorkennis en pas ze " +
            "toe bij het samenstellen van dossiers. Doorzoekbaar en filterbaar; " +
            "standaard enkel status 'gevalideerd'. Bevat uitsluitend generieke " +
            "kantoorkennis — nooit persoonsgegevens.",
          parameters: [
            {
              name: "status",
              in: "query",
              required: false,
              schema: {
                type: "string",
                enum: ["gevalideerd", "te_valideren", "gearchiveerd", "alle"],
                default: "gevalideerd",
              },
              description: "Filter op status. Weglaten = enkel gevalideerde kennis.",
            },
            {
              name: "categorie",
              in: "query",
              required: false,
              schema: {
                type: "string",
                enum: ["werkwijze", "juridisch", "kantoorgebruik", "aandachtspunt", "faq"],
              },
              description: "Filter op categorie. Weglaten = alle categorieën.",
            },
            {
              name: "zoek",
              in: "query",
              required: false,
              schema: { type: "string" },
              description: "Vrije zoekterm(en); elke term moet voorkomen in titel, inhoud, thema's of bronnen.",
            },
          ],
          responses: {
            "200": { description: "Lijst van kennisitems met id, titel, inhoud, categorie, themas, bronnen en status." },
            "400": { description: "Ongeldige status of categorie." },
          },
        },
        post: {
          operationId: "voegKennisVoorstelToe",
          summary: "Dien nieuwe of verbeterde kantoorkennis in als voorstel (te valideren door de notaris)",
          description:
            "Levert geen rechtstreekse wijziging op: het item krijgt altijd " +
            "status 'te_valideren' en verschijnt in de module Kennisbank, waar " +
            "de notaris het goedkeurt of afwijst. Wijzig je bestaande kennis, " +
            "geef dan 'itemId' van het bestaande item mee — het blijft " +
            "onaangeroerd tot de goedkeuring. Stuur nooit persoonsgegevens mee.",
          requestBody: {
            required: true,
            content: {
              "application/json": {
                schema: {
                  type: "object",
                  properties: {
                    titel: { type: "string", description: "Korte, sprekende titel van het kennisitem." },
                    inhoud: {
                      type: "string",
                      description:
                        "De kennis zelf, in platte tekst; een lege regel begint een nieuwe alinea, \"- \" een opsommingsteken. " +
                        "Geen bedragen of tarieven — verwijs daarvoor naar de rekenmodules.",
                    },
                    categorie: {
                      type: "string",
                      enum: ["werkwijze", "juridisch", "kantoorgebruik", "aandachtspunt", "faq"],
                      description: "Soort kennis.",
                    },
                    themas: {
                      type: "string",
                      description: "Komma-gescheiden thema's/trefwoorden, bv. \"verkoop, gewest, bodem\".",
                    },
                    bronnen: {
                      type: "string",
                      description: "Komma-gescheiden wetsbasis of bronvermeldingen, bv. \"art. 12 OWN\".",
                    },
                    motivering: {
                      type: "string",
                      description: "Waarom is deze kennis nuttig voor het kantoor? Helpt de notaris bij het valideren.",
                    },
                    itemId: {
                      type: "string",
                      description: "Id van het bestaande kennisitem dat wordt gewijzigd; weglaten = nieuw item.",
                    },
                  },
                  required: ["titel", "inhoud", "categorie", "motivering"],
                },
              },
            },
          },
          responses: {
            "200": { description: "Het opgebouwde kennisvoorstel (status 'te_valideren')." },
            "400": { description: "Validatiefout (foutboodschap vermeldt wat ontbreekt of ongeldig is)." },
            "503": { description: "Geen persistente opslag geconfigureerd — voorstel kon niet worden bewaard." },
          },
        },
      },
      "/api/opzoekingen": {
        get: {
          operationId: "haalOpzoekingen",
          summary: "Verplichte attesten en opzoekingen per rechtshandeling en gewest",
          parameters: [
            {
              name: "rechtshandeling",
              in: "query",
              required: false,
              schema: { type: "string", enum: rechtshandelingen.map((r) => r.id) },
              description: "Weglaten = lijst van alle rechtshandeling-id's (discovery).",
            },
            { name: "gewest", in: "query", required: false, schema: { type: "string", enum: ["vl", "br", "wa"] }, description: "Vereist zodra rechtshandeling is opgegeven." },
          ],
          responses: {
            "200": { description: "Per verplichting: status (ja/nee/nuance/nvt), toelichting en voorwaarden." },
            "400": { description: "Gewest ontbreekt of is ongeldig." },
            "404": { description: "Onbekende rechtshandeling." },
          },
        },
      },
      "/api/gebruikslog": {
        get: {
          operationId: "lijstGebruikslog",
          summary: "Signalen over ontbrekende of onvolledige bibliotheekdekking",
          description:
            "Toont waar agenten vastlopen: onbekende modeldocument-/modelbrief-/" +
            "rechtshandeling-id's en genegeerde facultatieve onderdelen. Raadpleeg " +
            "dit om te bepalen of een wijzigingsvoorstel (voegModelonderdeelVoorstelToe " +
            "/ voegModeldocumentVoorstelToe) nodig is. Bevat geen persoonsgegevens.",
          responses: {
            "200": { description: "Aantal events, een telling per type en de losse events (type, waarde, tijdstip)." },
          },
        },
      },
      "/api/dossier/verbeterlus": {
        get: {
          operationId: "bepaalVerbeterkandidaten",
          summary: "Gerangschikte verbeterkandidaten uit de opgebouwde ervaring van het kantoor (AUT-S9)",
          description:
            "Combineert de gebruikslog (lijstGebruikslog) en de bibliotheek-audit tot een " +
            "gerangschikte lijst verbeterkandidaten (score = frequentie × ernst × recentheid), " +
            "elk gerouteerd naar zijn bestaande kanaal: 'bibliotheek' (dien in via " +
            "voegModelonderdeelVoorstelToe/voegModeldocumentVoorstelToe met een uitgewerkte " +
            "clausuletekst — deze tool levert enkel titel/omschrijving, geen tekst), 'code' " +
            "(maak een verbetervoorstel aan) of 'kennis' (voegKennisVoorstelToe). MUTEERT niets: " +
            "raadpleeg dit als vertrekpunt in plaats van de gebruikslog/audit zelf te clusteren en " +
            "te scoren — dat werk is al gedaan. De notaris valideert elk kanaal apart.",
          responses: {
            "200": { description: "aantal, kandidaten[] (sleutel, kanaal, categorie, prioriteit, titel, omschrijving, score, signalen[]), gesorteerd op score." },
          },
        },
      },
    },
  };
}
