import { describe, it, expect } from "vitest";
import { seedModellen, seedOnderdelen, genereerDocument, pasKeuzesToe } from "@/data/modeldocumenten";
import { verwijderOpmaakMarkers } from "@/data/modeldocumenten/samenstellen";
import { dossierNaarKeuzes, dossierNaarParameterwaarden } from "@/lib/dossier/parameters";
import { maakVeld, type Dossier } from "@/lib/dossier/types";
import { kantoorHoofdingParagrafen, ontwerpInhoudParagrafen } from "./ontwerpOpmaak";
import { KANTOOR } from "@/data/kantoor";

function compromisOntwerp(
  modelId: string,
  gewest: "Vlaanderen" | "Wallonië" | "Brussel" = "Vlaanderen",
  goedType: "woning" | "appartement" | "grond" | "ander" = "woning"
): string {
  const d: Dossier = {
    id: "d", dossiertype: "verkoop-zonder-krediet", status: "compromis-ontvangen", verkoopdocument: "compromis",
    partijen: maakVeld([], "compromis"),
    goed: maakVeld({ adres: "x", type: goedType, gemeente: "Tervuren" }, "compromis"),
    ontbrekendeStukken: [], tegenstrijdigheden: [], aangemaakt: "x", aangepast: "x",
  };
  const m = seedModellen.find((x) => x.id === modelId)!;
  return genereerDocument(m, seedOnderdelen, pasKeuzesToe(m, dossierNaarKeuzes(d, gewest)), {}, []);
}

describe("compromis: sectiegroepen (koppen) in de gegenereerde tekst", () => {
  it("NL-compromis bevat de groepskoppen als '=== … ==='", () => {
    const t = compromisOntwerp("verkoopovereenkomst-heldere-taal");
    expect(t).toContain("=== Hoofdelementen van de verkoop ===");
    expect(t).toContain("=== Voorwaarden van de verkoop ===");
    expect(t).toContain("=== Verzekering en handtekeningen ===");
  });

  it("FR-compromis bevat de Franse groepskoppen", () => {
    const t = compromisOntwerp("verkoopovereenkomst-heldere-taal-fr");
    expect(t).toContain("=== Éléments principaux de la vente ===");
    expect(t).toContain("=== Assurance et signatures ===");
  });

  it("elke groepskop verschijnt maar één keer (bij groepwissel)", () => {
    const t = compromisOntwerp("verkoopovereenkomst-heldere-taal");
    const aantal = (t.match(/=== Hoofdelementen van de verkoop ===/g) ?? []).length;
    expect(aantal).toBe(1);
  });
});

describe("DIU/PID-clausule: geen dubbele subkop (regressie)", () => {
  it("NL: 'POSTINTERVENTIEDOSSIER (PID)' verschijnt maar één keer (als hoofdkop, geen dubbele subkop)", () => {
    const t = compromisOntwerp("verkoopovereenkomst-heldere-taal");
    const aantal = (t.match(/POSTINTERVENTIEDOSSIER \(PID\)/g) ?? []).length;
    expect(aantal).toBe(1);
  });

  it("FR: 'DOSSIER D'INTERVENTION ULTÉRIEURE (DIU)' verschijnt maar één keer (als hoofdkop, geen dubbele subkop zoals '23.1.')", () => {
    const t = compromisOntwerp("verkoopovereenkomst-heldere-taal-fr");
    const aantal = (t.match(/DOSSIER D'INTERVENTION ULTÉRIEURE \(DIU\)/g) ?? []).length;
    expect(aantal).toBe(1);
    expect(t).not.toMatch(/### \d+\.\d+\. DOSSIER D'INTERVENTION ULTÉRIEURE/);
  });
});

describe("FR: correcte werkwoordstijden en vereenvoudigde formuleringen (regressie)", () => {
  it("gebruikt de toekomende tijd voor verplichtingen bij de (nog niet ondertekende) akte", () => {
    const t = compromisOntwerp("verkoopovereenkomst-heldere-taal-fr");
    expect(t).toContain("L'acquéreur paiera le solde du prix");
    expect(t).toContain("proviendront du même compte");
    expect(t).toContain("il paiera aussi les frais liés au crédit");
    expect(t).not.toMatch(/L'acquéreur paie le solde/);
    expect(t).not.toMatch(/il paie aussi les frais liés au crédit/);
  });

  it("vereenvoudigt de bevoegdheid tot enkel de notaris (geen 'seul(s) son/ses notaire(s)/agent(s) immobilier(s) est/sont autorisé(s)')", () => {
    const t = compromisOntwerp("verkoopovereenkomst-heldere-taal-fr");
    expect(t).toContain("seul son notaire est autorisé");
    expect(t).not.toContain("seul(s) son/ses notaire(s)");
  });
});

describe("Logement/sol/préemption: standaard 'geen probleem' zonder open A/B-keuze (regressie)", () => {
  it("FR: de huisvestingscode-verklaringen (verhuurverbod/S.R.I./PV) staan default als opsomming zonder open [KIES: est / n'est pas]", () => {
    const t = compromisOntwerp("verkoopovereenkomst-heldere-taal-fr", "Brussel");
    expect(t).toContain("n'est pas frappé d'une interdiction de location");
    expect(t).toContain("n'est pas pourvu d'une attestation de contrôle de conformité");
    expect(t).toContain("n'a pas fait l'objet d'un PV de constat de logement inoccupé");
    expect(t).not.toMatch(/\[KIES: est \/ n'est pas\]/);
  });

  it("FR: 'immeuble abandonné' en 'informations supplémentaires sol' staan default zonder open KIES-keuze", () => {
    const t = compromisOntwerp("verkoopovereenkomst-heldere-taal-fr", "Brussel");
    expect(t).toContain("Le vendeur certifie que l'administration communale ne lui a pas notifié");
    expect(t).toContain("Le vendeur déclare qu'il ne détient pas d'informations supplémentaires susceptibles de modifier le contenu de cette attestation du sol.");
  });

  it("FR: droit de préemption/préférence/rachat dekt nu ook 'conventionnel' naast 'légal', en is geel gemarkeerd", () => {
    const t = compromisOntwerp("verkoopovereenkomst-heldere-taal-fr");
    expect(t).toContain("aucun droit de préemption légal ou conventionnel");
    expect(t).toContain("aucun droit de préférence légal ou conventionnel");
    expect(t).toContain("aucun droit de rachat légal ou conventionnel");
  });

  it("NL: dezelfde standaarden (geen verhuurverbod/attest/PV; geen extra bodeminfo)", () => {
    const t = compromisOntwerp("verkoopovereenkomst-heldere-taal", "Brussel");
    expect(t).toContain("geen verhuurverbod of boete voor niet-naleving");
    expect(t).toContain("geen conformiteitscontroleattest");
    expect(t).toContain("geen proces-verbaal van vaststelling van leegstand");
    expect(t).toContain("De verkoper verklaart niet over extra informatie te beschikken die de inhoud van het bodemattest zou kunnen veranderen.");
  });

  it("NL: voorkooprecht dekt ook contractueel naast wettelijk", () => {
    const t = compromisOntwerp("verkoopovereenkomst-heldere-taal");
    expect(t).toContain("geen wettelijk of contractueel voorkooprecht");
  });
});

describe("Facultatieve onderdelen met een deterministisch bevestigd gewest: geen overbodige 'facultatief'-markering (regressie, §43)", () => {
  it("FR: de registratiebelasting/btw-clausule (§43) toont geen 'clause facultative — uniquement pour ... Bruxelles ou ... wallonne' meer zodra het gewest gekend is", () => {
    const t = compromisOntwerp("verkoopovereenkomst-heldere-taal-fr", "Brussel");
    expect(t).not.toMatch(/clause facultative — .*Bruxelles.*wallonne/);
  });

  it("NL: dezelfde clausule toont geen 'facultatief onderdeel'-markering meer voor Vlaanderen (niet van toepassing, maar wel bevestigd)", () => {
    const t = compromisOntwerp("verkoopovereenkomst-heldere-taal", "Vlaanderen");
    expect(t).not.toContain("REGISTRATIEBELASTING – GEWESTELIJKE AANVULLINGEN");
  });
});

describe("Vet gemarkeerd: 'vier maanden'/'les quatre mois' en de verklaring zonder financieringsvoorwaarde (regressie)", () => {
  it("NL: 'vier maanden' is vet gemarkeerd (marker verwijderen laat de tekst ongewijzigd)", () => {
    const t = compromisOntwerp("verkoopovereenkomst-heldere-taal");
    expect(t).not.toBe(verwijderOpmaakMarkers(t));
    expect(verwijderOpmaakMarkers(t)).toContain("wordt ondertekend binnen vier maanden na de ondertekening");
  });

  it("FR: 'les quatre mois' is vet gemarkeerd", () => {
    const t = compromisOntwerp("verkoopovereenkomst-heldere-taal-fr");
    expect(verwijderOpmaakMarkers(t)).toContain("L'acte sera signé dans les quatre mois de la signature");
    expect(t).not.toBe(verwijderOpmaakMarkers(t));
  });

  it("FR: de verklaring 'pas de condition suspensive de financement' is integraal vet gemarkeerd wanneer gekozen", () => {
    const t = compromisOntwerp("verkoopovereenkomst-heldere-taal-fr");
    // Standaardkeuzes tonen alle hypotheses (variantId "alle"); de tekst van
    // de "geen opschortende voorwaarde"-hypothese moet dus aanwezig zijn.
    expect(verwijderOpmaakMarkers(t)).toContain("Cette vente n'est pas conclue sous la condition suspensive d'obtenir un financement par l'acquéreur.");
  });
});

describe("Vet gemarkeerd: de tijdstip-labels 'À la signature ...'/'Bij het ondertekenen ...' in de prijsclausule (regressie)", () => {
  it("FR: 'À la signature de ce compromis :' en 'À la signature de l'acte :' zijn vet gemarkeerd", () => {
    const t = compromisOntwerp("verkoopovereenkomst-heldere-taal-fr");
    expect(t).not.toBe(verwijderOpmaakMarkers(t));
    expect(verwijderOpmaakMarkers(t)).toContain("À la signature de ce compromis :");
    expect(verwijderOpmaakMarkers(t)).toContain("À la signature de l'acte :");
  });

  it("NL: 'Bij het ondertekenen van deze overeenkomst:' en 'Bij het ondertekenen van de notariële verkoopakte:' zijn vet gemarkeerd", () => {
    const t = compromisOntwerp("verkoopovereenkomst-heldere-taal");
    expect(t).not.toBe(verwijderOpmaakMarkers(t));
    expect(verwijderOpmaakMarkers(t)).toContain("Bij het ondertekenen van deze overeenkomst:");
    expect(verwijderOpmaakMarkers(t)).toContain("Bij het ondertekenen van de notariële verkoopakte:");
  });
});

describe("Premies (§41): website(s) per gewest, geen 'nouveau certificat EPC/PEB'-zin meer (regressie)", () => {
  it("FR: Bruxelles toont enkel de RENOLUTION/Homegrade-links, niet de Vlaamse/Waalse", () => {
    const t = compromisOntwerp("verkoopovereenkomst-heldere-taal-fr", "Brussel");
    expect(t).toContain("RENOLUTION");
    expect(t).toContain("homegrade.brussels");
    expect(t).not.toContain("premiezoeker.be");
    expect(t).not.toContain("logement.wallonie.be");
    expect(t).not.toMatch(/s'engage à demander un nouveau certificat EPC\/PEB/);
  });

  it("NL: Vlaanderen toont enkel premiezoeker.be/vlaio.be, niet de Brusselse/Waalse links, en geen EPC-herkeuringszin", () => {
    const t = compromisOntwerp("verkoopovereenkomst-heldere-taal", "Vlaanderen");
    expect(t).toContain("premiezoeker.be");
    expect(t).toContain("vlaio.be/nl/subsidies-financiering/subsidiedatabank");
    expect(t).not.toContain("renolution.brussels");
    expect(t).not.toContain("energie.wallonie.be");
    expect(t).not.toMatch(/nieuw EPC\/PEB-certificaat aan te vragen/);
  });
});

describe("§43 fiscale verklaringen: geen verwijzing meer naar een bijgevoegd formulier/schema (regressie)", () => {
  it("FR: Bruxelles en Wallonie bevatten geen 'il complète le(s) formulaire(s) en annexe'", () => {
    const bruxelles = compromisOntwerp("verkoopovereenkomst-heldere-taal-fr", "Brussel");
    expect(bruxelles).toContain("Abattement (Bruxelles)");
    expect(bruxelles).not.toMatch(/complète (le|les) formulaire/);
    const wallonie = compromisOntwerp("verkoopovereenkomst-heldere-taal-fr", "Wallonië");
    expect(wallonie).toContain("Réduction des droits d'enregistrement (Wallonie)");
    expect(wallonie).not.toMatch(/complète (le|les) formulaire/);
  });

  it("NL: Brussels Hoofdstedelijk Gewest en Waals Gewest bevatten geen verwijzing naar een bijgevoegd formulier/schema", () => {
    const brussel = compromisOntwerp("verkoopovereenkomst-heldere-taal", "Brussel");
    expect(brussel).toContain("Abattement (Brussels Hoofdstedelijk Gewest)");
    expect(brussel).not.toMatch(/formulieren in bijlage/);
    const wallonie = compromisOntwerp("verkoopovereenkomst-heldere-taal", "Wallonië");
    expect(wallonie).toContain("Vermindering (Waals Gewest)");
    expect(wallonie).not.toMatch(/schema in bijlage/);
  });
});

describe("Responsabilité décennale/Tienjarige aansprakelijkheid: zelfde kopniveau als Vices/Gebreken (regressie)", () => {
  it("FR: 'Responsabilité décennale' wordt een echte Kop 3, net als 'Vices' (zonder de parenthese die de 60-tekens-grens overschreed)", () => {
    const t = compromisOntwerp("verkoopovereenkomst-heldere-taal-fr");
    expect(t).toMatch(/### \d+\.\d+\. Vices/);
    expect(t).toMatch(/### \d+\.\d+\. Responsabilité décennale$/m);
    expect(t).not.toContain("Responsabilité décennale (uniquement pour les biens d'habitation)");
  });

  it("NL: 'Tienjarige aansprakelijkheid' blijft een echte Kop 3, net als 'Gebreken'", () => {
    const t = compromisOntwerp("verkoopovereenkomst-heldere-taal");
    expect(t).toMatch(/### \d+\.\d+\. Gebreken/);
    expect(t).toMatch(/### \d+\.\d+\. Tienjarige aansprakelijkheid$/m);
  });
});

describe("Word-opmaak: koppenstijlen en kantoorhoofding", () => {
  it("kantoorhoofding toont de kantoornaam (vetgedrukt) en de zetel", () => {
    const p = kantoorHoofdingParagrafen();
    const json = JSON.stringify(p);
    expect(json).toContain(KANTOOR.naam);
    expect(json).toContain(KANTOOR.zetel);
  });

  it("mapt titel → Title, groep → Heading 1, onderdeel → Heading 2", () => {
    const ontwerp = "VERKOOPOVEREENKOMST ONROEREND GOED\n\n=== Hoofdelementen van de verkoop ===\n\n## KOPER EN VERKOPER\n\ngewone tekst\n\n▸ HYPOTHESE — iets:";
    const p = ontwerpInhoudParagrafen(ontwerp);
    // docx Paragraph bewaart de stijl in zijn opties; serialiseer en controleer.
    const styles = p.map((par) => JSON.stringify(par));
    expect(styles.some((s) => s.includes("Title"))).toBe(true);
    expect(styles.some((s) => s.includes("Heading1"))).toBe(true);
    expect(styles.some((s) => s.includes("Heading2"))).toBe(true);
  });

  it("ster-markeert commentaren in de inhoud", () => {
    const p = ontwerpInhoudParagrafen("TITEL\n\n## IETS\n\nTekst met [AAN TE VULLEN: x].");
    expect(JSON.stringify(p)).toContain("*[*AAN TE VULLEN: x*]*");
  });

  it("nest de subkop ONDER de clausulekop (Heading2 → Heading3), nooit op hetzelfde niveau", () => {
    const p = ontwerpInhoudParagrafen("TITEL\n\n## CLAUSULE\n\n### HET KADASTRAAL INKOMEN\n\nTekst.\n\n#### Fijnere titel\n\nMeer tekst.");
    const json = JSON.stringify(p);
    expect(json).toContain("Heading2"); // clausulekop
    expect(json).toContain("Heading3"); // HOOFDLETTER-tussentitel, genest onder de clausule
    expect(json).toContain("Heading4"); // fijnere tussentitel, genest onder de HOOFDLETTER-tussentitel
  });

  it("toont het kantoorlogo (afbeelding) bovenaan de hoofding", () => {
    const p = kantoorHoofdingParagrafen();
    expect(JSON.stringify(p)).toMatch(/ImageRun|graphic|blip|image/i);
  });
});

describe("Asbestinventaris gemene delen (§42, Vlaanderen): enkel bij een appartement (regressie)", () => {
  it("NL: bij een huis (woning) verschijnt de gemene-delen-clausule niet", () => {
    const t = compromisOntwerp("verkoopovereenkomst-heldere-taal", "Vlaanderen", "woning");
    expect(t).toContain("ASBESTINVENTARISATTEST (Vlaams Gewest)");
    expect(t).not.toContain("ASBESTINVENTARIS GEMENE DELEN");
    expect(t).not.toMatch(/gemene delen of gemeenschappelijk gebruikte delen/i);
  });

  it("NL: bij een appartement verschijnt de gemene-delen-clausule wél, als aparte clausule", () => {
    const t = compromisOntwerp("verkoopovereenkomst-heldere-taal", "Vlaanderen", "appartement");
    expect(t).toContain("ASBESTINVENTARISATTEST (Vlaams Gewest)");
    expect(t).toMatch(/## \d+\. ASBESTINVENTARIS GEMENE DELEN/);
  });

  it("FR: bij een maison (woning) verschijnt de clausule parties communes niet", () => {
    const t = compromisOntwerp("verkoopovereenkomst-heldere-taal-fr", "Vlaanderen", "woning");
    expect(t).toContain("CERTIFICAT D'INVENTAIRE D'AMIANTE (Région flamande)");
    expect(t).not.toContain("INVENTAIRE AMIANTE PARTIES COMMUNES");
  });

  it("FR: bij een appartement verschijnt de clausule parties communes wél, als aparte clausule", () => {
    const t = compromisOntwerp("verkoopovereenkomst-heldere-taal-fr", "Vlaanderen", "appartement");
    expect(t).toMatch(/## \d+\. INVENTAIRE AMIANTE PARTIES COMMUNES/);
  });
});

describe("Asbestinventarisattest (§42, Vlaanderen): 'niet vereist' op basis van het bouwjaar (regressie)", () => {
  function compromisOntwerpMetBouwjaar(modelId: string, bouwjaar?: number): string {
    const d: Dossier = {
      id: "d", dossiertype: "verkoop-zonder-krediet", status: "compromis-ontvangen", verkoopdocument: "compromis",
      partijen: maakVeld([], "compromis"),
      goed: maakVeld({ adres: "x", type: "woning", gemeente: "Tervuren", ...(bouwjaar !== undefined ? { bouwjaar } : {}) }, "compromis"),
      ontbrekendeStukken: [], tegenstrijdigheden: [], aangemaakt: "x", aangepast: "x",
    };
    const m = seedModellen.find((x) => x.id === modelId)!;
    return genereerDocument(m, seedOnderdelen, pasKeuzesToe(m, dossierNaarKeuzes(d, "Vlaanderen")), dossierNaarParameterwaarden(d), []);
  }

  it("NL: bouwjaar 2001 of recenter kiest deterministisch 'niet vereist', met het bouwjaar en de kadastrale legger ingevuld", () => {
    const t = compromisOntwerpMetBouwjaar("verkoopovereenkomst-heldere-taal", 2015);
    expect(t).toMatch(/Niet vereist: het bouwjaar van de toegankelijke constructies is 2015/);
    expect(t).toContain("blijkend uit de kadastrale legger");
    expect(t).not.toContain("[KIES - A. Niet vereist");
  });

  it("NL: een bouwjaar vóór 2001 laat de volledige keuze open (bewijst niet dat een attest vereist is)", () => {
    const t = compromisOntwerpMetBouwjaar("verkoopovereenkomst-heldere-taal", 1975);
    expect(t).toContain("[KIES - A. Niet vereist");
    expect(t).not.toMatch(/Niet vereist: het bouwjaar/);
  });

  it("NL: onbekend bouwjaar laat de volledige keuze open", () => {
    const t = compromisOntwerpMetBouwjaar("verkoopovereenkomst-heldere-taal");
    expect(t).toContain("[KIES - A. Niet vereist");
  });

  it("FR: année de construction 2001 ou plus récente choisit déterministiquement 'pas requis'", () => {
    const t = compromisOntwerpMetBouwjaar("verkoopovereenkomst-heldere-taal-fr", 2015);
    expect(t).toMatch(/Pas requis : l'année de construction des constructions accessibles est 2015/);
    expect(t).toContain("attestée par la matrice cadastrale");
    expect(t).not.toContain("[KIES - A. Pas requis");
  });
});

describe("clausulekop: interne titel verdwijnt, nette kop verschijnt", () => {
  it("'## …' clausulekoppen komen uit de modeltekst, niet de interne titel", () => {
    const t = compromisOntwerp("verkoopovereenkomst-heldere-taal");
    // Clausulekoppen komen uit de modeltekst en zijn doorlopend genummerd.
    expect(t).toMatch(/## \d+\. GEBRUIK – GENOT/);
    // De interne administratieve titel mag niet als kop verschijnen.
    expect(t).not.toContain("GEBRUIK EN GENOT VAN HET GOED");
    expect(t).toMatch(/## \d+\. /); // clausulekoppen zijn genummerd
  });
});
