// ── Lokale, deterministische invulling van [AAN TE VULLEN: …]-plaatshouders ──
// Vervangt niet de creatieve redactie van een afwerkende AI-agent, maar wél de
// mechanische stap "plaatshouder → echte waarde": exact, reproduceerbaar en
// als een oprechte Word-getrackte wijziging (invoeging + doorhaling), zonder
// dat een LLM de tekst zelf hoeft over te typen (en dus zonder het risico dat
// het een naam, bedrag of datum verkeerd overneemt).
//
// GDPR: deze functie doet geen enkele netwerkoproep. De aanroeper (bv. een
// Claude-agent met lokale bestandstoegang) heeft de dossierstukken al lokaal
// gelezen en de werkelijke waarden bepaald; die waarden gaan hier nooit naar
// Notary.AI — enkel het al gegenereerde .docx-bestand (met veilige,
// geanonimiseerde placeholders) wordt lokaal aangevuld. Zie
// scripts/vul-werkdossier-lokaal.ts voor de bijhorende CLI en
// .claude/skills/notary-werkdossier-invullen/SKILL.md voor de volledige
// werkwijze van zo'n agent.
//
// Werking: elke {{parameter}} die de server niet kon invullen, staat in het
// document als de letterlijke tekst "*[*AAN TE VULLEN: naam*]*" (Frans:
// "*[*À COMPLÉTER: naam*]*"), telkens als een geïsoleerde Word-run — zie
// vulParametersIn in data/modeldocumenten/samenstellen.ts en de ster-/geel-
// markering in lib/word/runs.ts. Deze functie zoekt die exacte runs op en
// vervangt ze door een `<w:del>` (de oorspronkelijke plaatshouder, doorgehaald)
// gevolgd door een `<w:ins>` (de nieuwe waarde) — precies zoals wanneer een
// notarieel medewerker het zelf had getypt met 'Wijzigingen bijhouden' aan.

import JSZip from "jszip";
import { escapeXml } from "@/lib/word/ooxml";
import { forceerTrackChanges } from "@/lib/word/trackChanges";

export interface PlaatshouderInvulling {
  /** De {{parameter}}-naam zoals in het model (zonder accolades). */
  parameter: string;
  /** De werkelijke waarde, lokaal bepaald uit de dossierstukken. */
  waarde: string;
}

export interface VulPlaceholdersResultaat {
  /** Het bijgewerkte .docx-bestand, met 'Wijzigingen bijhouden' gegarandeerd aan. */
  buffer: Buffer;
  /** Parameters die effectief in het document gevonden en vervangen zijn. */
  ingevuld: string[];
  /**
   * Opgegeven parameters die NERGENS in het document als
   * "[AAN TE VULLEN: …]" voorkwamen — vrijwel altijd een tikfout in de
   * parameternaam. Nooit stilzwijgend negeren: meld dit aan de gebruiker/agent
   * (zelfde conventie als "genegeerdeParameters" in de API, zie
   * lib/modeldocumenten/ontwerpUitQuery.ts).
   */
  nietGevonden: string[];
  /** Totaal aantal vervangen Word-runs (hoger dan ingevuld.length als een parameter meermaals voorkomt). */
  aantalVervangen: number;
}

function escapeRegExp(tekst: string): string {
  return tekst.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

/** Haalt de gele markering (het "nog aan te vullen"-signaal) uit een rPr-fragment. */
function zonderHighlight(rPr: string): string {
  return rPr.replace(/<w:highlight\b[^/]*\/>/g, "").replace(/<w:highlightCs\b[^/]*\/>/g, "");
}

/** Eén of meer `<w:t>`/`<w:br/>`-elementen voor een (mogelijk meerregelige) waarde. */
function waardeInhoud(waarde: string, attrs: string): string {
  const regels = waarde.split("\n");
  return regels
    .map((regel, i) => `<w:t${attrs}>${escapeXml(regel)}</w:t>${i < regels.length - 1 ? "<w:br/>" : ""}`)
    .join("");
}

const LABEL = "(?:AAN TE VULLEN|À COMPLÉTER)";
// Run-eigenschappen (<w:rPr>) bestaan in de gegenereerde documenten altijd
// uit een reeks ZELFSLUITENDE tags (<w:b/>, <w:highlight w:val="yellow"/>, …),
// nooit uit geneste open/sluit-elementen. Dit expliciet zo begrenzen — in
// plaats van een vrije "[\s\S]*?" — is essentieel: een vrije, niet-gebonden
// match "eet" bij een eerste mislukte poging dwars door ongerelateerde
// `<w:r>…</w:r>`-runs heen tot de eerstvolgende toevallige treffer verderop in
// het document (in de praktijk: tot bij de ingebedde AI-agentprompt), en
// vervangt dan een veel te groot, fout stuk van het document.
const RPR_INHOUD = "(?:<w:[a-zA-Z]+(?:\\s[^>]*)?\\/>)*";

/**
 * Vervangt in een gegenereerd .docx-bestand elke "[AAN TE VULLEN: parameter]"
 * (of de Franse tegenhanger "[À COMPLÉTER: parameter]") door de opgegeven
 * waarde, als een echte getrackte Word-wijziging. Pure bestandsbewerking:
 * geen netwerkverkeer, geen kennis van het onderliggende model nodig — enkel
 * van de vaste plaatshoudertekst uit samenstellen.ts.
 */
export async function vulPlaceholdersLokaalIn(
  docxBuffer: Buffer,
  invullingen: PlaatshouderInvulling[],
  opties: { auteur?: string; datum?: string } = {}
): Promise<VulPlaceholdersResultaat> {
  const auteur = opties.auteur ?? "Notary.AI — lokale invulling";
  const datum = opties.datum ?? new Date().toISOString();

  const zip = await JSZip.loadAsync(docxBuffer);
  const bestand = zip.file("word/document.xml");
  if (!bestand) {
    return { buffer: docxBuffer, ingevuld: [], nietGevonden: invullingen.map((i) => i.parameter), aantalVervangen: 0 };
  }
  let xml = await bestand.async("string");

  const ingevuld: string[] = [];
  const nietGevonden: string[] = [];
  let volgId = 9000;
  let aantalVervangen = 0;

  for (const { parameter, waarde } of invullingen) {
    const patroon = new RegExp(
      `<w:r>(?:<w:rPr>(${RPR_INHOUD})<\\/w:rPr>)?<w:t([^>]*)>(\\*\\[\\*${LABEL}: ${escapeRegExp(parameter)}\\*\\]\\*)<\\/w:t><\\/w:r>`,
      "g"
    );
    let gevonden = 0;
    xml = xml.replace(patroon, (_match, rPr: string | undefined, attrs: string, origineel: string) => {
      gevonden += 1;
      const delId = volgId++;
      const insId = volgId++;
      const rPrDel = rPr ? `<w:rPr>${rPr}</w:rPr>` : "";
      const rPrIns = rPr ? `<w:rPr>${zonderHighlight(rPr)}</w:rPr>` : "";
      return (
        `<w:del w:id="${delId}" w:author="${escapeXml(auteur)}" w:date="${datum}">` +
        `<w:r>${rPrDel}<w:delText${attrs}>${origineel}</w:delText></w:r>` +
        `</w:del>` +
        `<w:ins w:id="${insId}" w:author="${escapeXml(auteur)}" w:date="${datum}">` +
        `<w:r>${rPrIns}${waardeInhoud(waarde, attrs)}</w:r>` +
        `</w:ins>`
      );
    });
    if (gevonden > 0) {
      ingevuld.push(parameter);
      aantalVervangen += gevonden;
    } else {
      nietGevonden.push(parameter);
    }
  }

  zip.file("word/document.xml", xml);
  const nieuweBuffer = await zip.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
  return { buffer: await forceerTrackChanges(nieuweBuffer), ingevuld, nietGevonden, aantalVervangen };
}
