import { describe, it, expect } from "vitest";
import { bouwBibliotheekOverzicht, bouwNetwerk, documentAard } from "./overzicht";
import type { Modeldocument, Modelonderdeel } from "./types";

function onderdeel(id: string, titel = id): Modelonderdeel {
  return {
    id,
    titel,
    categorie: "vorm",
    omschrijving: "",
    tekst: "",
    varianten: [],
    parameters: [],
    toepasbaarOp: [],
    versie: "1.0",
    datum: "2026-06-26",
  };
}

function model(id: string, onderdeelIds: string[], thema?: Modeldocument["thema"]): Modeldocument {
  return {
    id,
    titel: id,
    akteType: id,
    thema,
    omschrijving: "",
    structuur: onderdeelIds.map((oid) => ({ onderdeelId: oid, verplicht: true })),
    versie: "1.0",
    datum: "2026-06-26",
  };
}

describe("bouwBibliotheekOverzicht", () => {
  const onderdelen = [onderdeel("a"), onderdeel("b"), onderdeel("c"), onderdeel("ongebruikt")];
  const modellen = [
    model("m1", ["a", "b"], "vastgoed"),
    model("m2", ["a", "c"], "familie"),
    model("m3", ["a", "ontbreekt"]),
  ];
  const overzicht = bouwBibliotheekOverzicht(modellen, onderdelen);

  it("telt onderdelen en modellen", () => {
    expect(overzicht.aantalOnderdelen).toBe(4);
    expect(overzicht.aantalModellen).toBe(3);
  });

  it("rangschikt gedeelde onderdelen op aflopend hergebruik", () => {
    expect(overzicht.gedeeld[0].onderdeelId).toBe("a");
    expect(overzicht.gedeeld[0].modellen).toHaveLength(3);
    // 'b' en 'c' komen elk maar in één model voor → niet gedeeld.
    expect(overzicht.gedeeld.map((g) => g.onderdeelId)).toEqual(["a"]);
  });

  it("vindt ongebruikte onderdelen", () => {
    expect(overzicht.ongebruikt.map((o) => o.id)).toEqual(["ongebruikt"]);
  });

  it("detecteert verwijzingen naar onbestaande onderdelen", () => {
    expect(overzicht.ontbrekend).toHaveLength(1);
    expect(overzicht.ontbrekend[0].onderdeelId).toBe("ontbreekt");
    expect(overzicht.ontbrekend[0].onderdeel).toBeNull();
    expect(overzicht.ontbrekend[0].modellen[0].id).toBe("m3");
  });

  it("registreert positie en verplicht per verwijzing", () => {
    const a = overzicht.gebruikPerOnderdeel.find((g) => g.onderdeelId === "a");
    const inM1 = a?.modellen.find((m) => m.id === "m1");
    expect(inM1?.positie).toBe(1);
    expect(inM1?.verplicht).toBe(true);
  });
});

describe("documentAard", () => {
  it("herkent onderhandse documenten aan de ondertekeningsclausule", () => {
    const m = model("compromis", ["a", "ondertekening-onderhands-document"]);
    expect(documentAard(m)).toBe("onderhands");
  });

  it("herkent onderhandse documenten aan het akteType-patroon", () => {
    expect(documentAard(model("x", ["a"]))).toBe("akte");
    expect(documentAard({ ...model("x", ["a"]), akteType: "verkoopovereenkomst (compromis)" })).toBe(
      "onderhands",
    );
    expect(documentAard({ ...model("x", ["a"]), akteType: "onderhandse volmacht" })).toBe(
      "onderhands",
    );
  });

  it("beschouwt een authentieke akte standaard als 'akte'", () => {
    expect(documentAard({ ...model("x", ["a"]), akteType: "verkoopakte" })).toBe("akte");
    expect(documentAard({ ...model("x", ["a"]), akteType: "schenkingsakte" })).toBe("akte");
  });
});

describe("bouwNetwerk", () => {
  const onderdelen = [onderdeel("a"), onderdeel("b"), onderdeel("c"), onderdeel("ongebruikt")];
  const modellen = [
    model("akte1", ["a", "b", "c"]),
    { ...model("comp", ["a", "ondertekening-onderhands-document"]), akteType: "compromis" },
  ];
  const netwerk = bouwNetwerk(modellen, onderdelen);

  it("maakt enkel knopen voor gebruikte, bestaande onderdelen", () => {
    const ids = netwerk.knopen.map((k) => k.id).sort();
    // 'ongebruikt' en 'ondertekening-onderhands-document' (onbestaand onderdeel) vallen weg.
    expect(ids).toEqual(["a", "b", "c"]);
  });

  it("verbindt opeenvolgende onderdelen per document", () => {
    const akteVerbindingen = netwerk.verbindingen.filter((v) => v.modelId === "akte1");
    expect(akteVerbindingen).toHaveLength(2);
    expect(akteVerbindingen.map((v) => [v.bron, v.doel])).toEqual([
      ["a", "b"],
      ["b", "c"],
    ]);
  });

  it("kleurt verbindingen volgens de aard van het document", () => {
    expect(netwerk.verbindingen.find((v) => v.modelId === "akte1")?.aard).toBe("akte");
    // Het compromis heeft maar één bestaand onderdeel ('a') → geen verbinding,
    // maar de aard staat wel op de knoop-modellen.
    const a = netwerk.knopen.find((k) => k.id === "a");
    expect(a?.modellen.find((m) => m.id === "comp")?.aard).toBe("onderhands");
  });

  it("telt het gebruik per knoop", () => {
    expect(netwerk.knopen.find((k) => k.id === "a")?.gebruik).toBe(2);
    expect(netwerk.knopen.find((k) => k.id === "b")?.gebruik).toBe(1);
  });
});
