import { describe, it, expect } from "vitest";
import JSZip from "jszip";
import { seedModellen, seedOnderdelen, genereerDocument, pasKeuzesToe } from "@/data/modeldocumenten";
import { dossierNaarKeuzes } from "@/lib/dossier/parameters";
import { maakVeld, type Dossier } from "@/lib/dossier/types";
import { genereerCompromisUitSjabloon } from "./compromisSjabloon";

async function compromisDocx(modelId: string, taal: "nl" | "fr", kader?: { verkopers?: string; kopers?: string; goed?: string; referte?: 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" }, "compromis"),
    ontbrekendeStukken: [], tegenstrijdigheden: [], aangemaakt: "x", aangepast: "x",
  };
  const m = seedModellen.find((x) => x.id === modelId)!;
  const ontwerp = genereerDocument(m, seedOnderdelen, pasKeuzesToe(m, dossierNaarKeuzes(d, "Vlaanderen")), {}, []);
  const buf = await genereerCompromisUitSjabloon(ontwerp, { taal, kader });
  expect(buf).not.toBeNull();
  return JSZip.loadAsync(buf!);
}

const frCompromisDocx = () => compromisDocx("verkoopovereenkomst-heldere-taal-fr", "fr");

describe("compromis template-merge: kantoorlay-out overgenomen", () => {
  it("levert een geldig .docx met welgevormde document.xml", async () => {
    const zip = await frCompromisDocx();
    const xml = await zip.file("word/document.xml")!.async("string");
    // welgevormd: parse via DOMParser-vrije check — minstens gebalanceerde body.
    expect(xml).toContain("<w:body>");
    expect(xml).toContain("</w:body>");
  });

  it("behoudt de benoemde stijlen, de decoratieve icoontjes en het omkaderde vak", async () => {
    const zip = await frCompromisDocx();
    const media = Object.keys(zip.files).filter((n) => n.startsWith("word/media"));
    expect(media.length).toBeGreaterThanOrEqual(5); // logo + decoratieve icoontjes
    const styles = await zip.file("word/styles.xml")!.async("string");
    for (const s of ['"Kop1"', '"Inhoud"', '"Hoofdstukken"', '"Titel"']) expect(styles).toContain(s);
    const xml = await zip.file("word/document.xml")!.async("string");
    expect(xml).toContain("Tabelraster"); // het omkaderde vak rond partijen/goed
    expect(xml).toContain("Signatures"); // handtekeningenblok behouden
  });

  it("zet 'Wijzigingen bijhouden' (track changes) aan", async () => {
    const zip = await frCompromisDocx();
    const settings = await zip.file("word/settings.xml")!.async("string");
    expect(settings).toContain("<w:trackChanges");
  });

  it("injecteert de gegenereerde clausules in de sjabloonstijlen (Kop1-groepen)", async () => {
    const zip = await frCompromisDocx();
    const xml = await zip.file("word/document.xml")!.async("string");
    expect(xml).toContain("Éléments principaux de la vente");
    expect(xml).toContain("Conditions administratives de la vente");
    // Werkblad met (FR) ontbrekende stukken + pagina-einde vóór het compromis.
    expect(xml).toContain("Éléments encore à fournir");
    expect(xml).toContain('w:type="page"');
  });
});

describe("klikbare inhoudsopgave (backlog punt 31)", () => {
  it("NL: bevat een TOC-veld ná het werkblad en vóór de akte, met updateFields aan", async () => {
    const zip = await compromisDocx("verkoopovereenkomst-heldere-taal", "nl");
    const xml = await zip.file("word/document.xml")!.async("string");
    expect(xml).toContain("Inhoudsopgave");
    expect(xml).toContain('TOC \\o "1-4" \\h \\z \\u');
    // De inhoudsopgave staat ná het werkblad ("Nog aan te leveren gegevens") en
    // vóór de akte ("Tabelraster" = het omkaderde vak op de eerste aktepagina).
    const posWerkblad = xml.indexOf("Nog aan te leveren gegevens");
    const posToc = xml.indexOf('TOC \\o "1-4"');
    const posAkte = xml.indexOf("Tabelraster");
    expect(posWerkblad).toBeGreaterThan(-1);
    expect(posToc).toBeGreaterThan(posWerkblad);
    expect(posAkte).toBeGreaterThan(posToc);
    const settings = await zip.file("word/settings.xml")!.async("string");
    expect(settings).toContain('<w:updateFields w:val="true"/>');
  });

  it("FR: bevat 'Table des matières' als titel van het TOC-veld", async () => {
    const zip = await frCompromisDocx();
    const xml = await zip.file("word/document.xml")!.async("string");
    expect(xml).toContain("Table des matières");
    expect(xml).toContain('TOC \\o "1-4" \\h \\z \\u');
  });
});

describe("NL-compromis template-merge + omkaderd vak", () => {
  it("neemt het NL-sjabloon over met zijn groepskoppen (Hoofdstukken-stijl)", async () => {
    const zip = await compromisDocx("verkoopovereenkomst-heldere-taal", "nl");
    const xml = await zip.file("word/document.xml")!.async("string");
    expect(xml).toContain("Hoofdelementen van de verkoop");
    expect(xml).toContain("Handtekeningen");
    const media = Object.keys(zip.files).filter((n) => n.startsWith("word/media"));
    expect(media.length).toBeGreaterThanOrEqual(5);
  });

  it("vult het omkaderde vak in maar behoudt ALTIJD de identificatieclausule", async () => {
    const zip = await compromisDocx("verkoopovereenkomst-heldere-taal", "nl", {
      verkopers: "Jan PEETERS",
      kopers: "Anna JANSSENS",
      goed: "Bosstraat 45, 3080 Tervuren",
      referte: "2026/0456",
    });
    const xml = await zip.file("word/document.xml")!.async("string");
    // Het vak is ingevuld (geen ** meer voor de drie placeholders) ...
    expect(xml).toContain("Jan PEETERS");
    expect(xml).toContain("Bosstraat 45, 3080 Tervuren");
    // De referte draagt het taaljuiste label: "Ref." in het NL-document
    // (regressie: voorheen stond het Franse "Réf." ook op het NL-voorblad).
    expect(xml).toContain("Ref. 2026/0456");
    expect(xml).not.toContain("Réf. 2026/0456");
    // ... en de identificatieclausule blijft behouden: het vak is enkel
    // informatief (voorblad) en vervangt nooit (een deel van) het compromis.
    expect(xml).toContain("KOPER EN VERKOPER");
  });

  it("laat het vak ongemoeid en behoudt de clausule wanneer er geen kadergegevens zijn", async () => {
    const zip = await compromisDocx("verkoopovereenkomst-heldere-taal", "nl");
    const xml = await zip.file("word/document.xml")!.async("string");
    expect(xml).toContain("KOPER EN VERKOPER"); // identificatieclausule behouden
  });

  it("vult de drie kaderplaceholders in de juiste volgorde in (verkopers, kopers, goed)", async () => {
    const zip = await compromisDocx("verkoopovereenkomst-heldere-taal", "nl", {
      verkopers: "VERKOPER_MARKER",
      kopers: "KOPER_MARKER",
      goed: "GOED_MARKER",
    });
    const xml = await zip.file("word/document.xml")!.async("string");
    const tbl = xml.match(/<w:tbl>(?:(?!<\/w:tbl>)[\s\S])*?Tabelraster[\s\S]*?<\/w:tbl>/)![0];
    const iV = tbl.indexOf("VERKOPER_MARKER");
    const iK = tbl.indexOf("KOPER_MARKER");
    const iG = tbl.indexOf("GOED_MARKER");
    expect(iV).toBeGreaterThan(-1);
    expect(iK).toBeGreaterThan(iV);
    expect(iG).toBeGreaterThan(iK);
  });

  it("gebruikt echte SEQ-velden voor tussentitelnummers, geen statisch gebakken 'N.M.'-tekst", async () => {
    // Regressietest: de clausulekop (## N.) werd al eerder omgezet naar een
    // live SEQ-veld (zie ooxml.ts seqVeldRuns), maar de tussentitels (### /
    // ####, "N.M.") bleven statische tekst — die verouderde stil zodra de
    // notaris een clausule schrapte/toevoegde en het hoofdnummer via het
    // SEQ-veld herberekend werd. Bewaak dat beide nu live velden zijn.
    const zip = await compromisDocx("verkoopovereenkomst-heldere-taal", "nl");
    const xml = await zip.file("word/document.xml")!.async("string");
    expect(xml).toContain("SEQ Clausule");
    expect(xml).toContain("SEQ Tussentitel");
    expect(xml).toContain("SEQ Tussentitel \\r 1"); // herstart per clausule
    // Geen platte "N.M. TITEL"-tekst meer als losstaande run (zou wijzen op een
    // tussentitel die niet via seqSubVeld liep).
    expect(xml).not.toMatch(/<w:t xml:space="preserve">\d+\.\d+\. /);
  });
});

describe("compromis template-merge: spellingtaal genormaliseerd (backlog punt 29)", () => {
  const docDefaultsTaal = async (zip: JSZip) => {
    const styles = await zip.file("word/styles.xml")!.async("string");
    const dd = styles.match(/<w:docDefaults>[\s\S]*?<\/w:docDefaults>/)?.[0] ?? "";
    return dd.match(/<w:lang\b[^>]*\bw:val="([^"]+)"/)?.[1] ?? "";
  };

  it("corrigeert de docDefaults-taal naar nl-BE voor een NL-compromis (sjabloon stond op fr-FR)", async () => {
    expect(await docDefaultsTaal(await compromisDocx("verkoopovereenkomst-heldere-taal", "nl"))).toBe("nl-BE");
  });

  it("corrigeert de docDefaults-taal naar fr-BE voor een FR-compromis (sjabloon stond op fr-FR)", async () => {
    expect(await docDefaultsTaal(await compromisDocx("verkoopovereenkomst-heldere-taal-fr", "fr"))).toBe("fr-BE");
  });
});

// ── w:noProof op het werkblad (backlog punt 30) ─────────────────────────────
describe("noProof op de werkblad-runs (sjabloon-merge-pad)", () => {
  it("elke tekstdragende run van het werkblad draagt w:noProof; de clausule-zone niet", async () => {
    const { werkbladParagrafen } = await import("./compromisSjabloon");
    const werkblad = werkbladParagrafen("tekst met [AAN TE VULLEN: prijs]", "nl", "een notitie");
    // Alle runs met zichtbare tekst op het werkblad dragen noProof.
    const runs = werkblad.match(/<w:r>[\s\S]*?<\/w:r>/g) ?? [];
    const tekstRuns = runs.filter((r) => /<w:t[ >](?!<\/w:t>)/.test(r) && !/<w:t[^>]*\/>/.test(r));
    expect(tekstRuns.length).toBeGreaterThan(20);
    for (const run of tekstRuns) {
      expect(run, `werkblad-run zonder w:noProof: ${run.slice(0, 120)}`).toContain("<w:noProof/>");
    }
    // Integratie: in het volledige document is noProof aanwezig, maar níet op
    // alle runs — de eigenlijke compromis-tekst blijft spellingsgecontroleerd.
    const zip = await frCompromisDocx();
    const xml = await zip.file("word/document.xml")!.async("string");
    const aantalNoProof = (xml.match(/<w:noProof\/>/g) ?? []).length;
    const aantalTekst = (xml.match(/<w:t[ >]/g) ?? []).length;
    expect(aantalNoProof).toBeGreaterThan(0);
    expect(aantalNoProof).toBeLessThan(aantalTekst);
  });
});
