// Gestructureerd dossierschema voor vastgoedverkoop (met of zonder krediet),
// schenking en nalatenschap. Elke waarde draagt zijn bron mee, zodat
// conflicten tussen documenten expliciet zichtbaar blijven in plaats van
// stilzwijgend opgelost te worden.

import type { GoedeType, Relatie } from "@/data/belastingen";

export type BronType =
  | "compromis"
  | "eigendomstitel"
  | "hypotheekattest"
  | "kadastraal-uittreksel"
  | "stedenbouwkundige-inlichtingen"
  | "bodemattest"
  | "epc"
  | "kredietofferte"
  | "identiteitsstuk"
  | "syndicusafrekening"
  | "verklaring-partij"
  | "manueel";

// Lager getal = hogere autoriteit. Authentieke bron > onderhandse bron.
export const bronPrioriteit: Record<BronType, number> = {
  eigendomstitel: 1,
  "hypotheekattest": 2,
  "kadastraal-uittreksel": 2,
  "stedenbouwkundige-inlichtingen": 2,
  bodemattest: 2,
  compromis: 3,
  kredietofferte: 3,
  identiteitsstuk: 3,
  epc: 4,
  syndicusafrekening: 4,
  "verklaring-partij": 5,
  manueel: 6,
};

/** Eén waarneming van een veldwaarde uit één bron(document). */
export interface Waarneming<T> {
  waarde: T;
  bron: BronType;
  brondocumentId?: string;
}

export interface Veld<T> {
  waarde: T;
  bron: BronType;
  brondocumentId?: string;
  /**
   * Afwijkende waarnemingen van ditzelfde veld uit andere bronnen. De primaire
   * waarde hierboven is steeds die met het hoogste brongezag (laagste
   * bronPrioriteit-getal); de alternatieven houden de tegenstrijdige
   * waarnemingen uit minder gezaghebbende bronnen expliciet zichtbaar.
   */
  alternatieven?: Waarneming<T>[];
}

/** Alle gekende brontypes, gesorteerd noch gefilterd; voor keuzelijsten. */
export const BRON_TYPES = Object.keys(bronPrioriteit) as BronType[];

export interface Partij {
  naam: string;
  voornaam?: string;
  geboortedatum?: string;
  geboorteplaats?: string;
  rijksregisternummer?: string;
  adres?: string;
  rol: "verkoper" | "koper" | "schenker" | "begiftigde" | "erflater" | "erfgenaam";
  /**
   * Geslacht van de natuurlijke persoon, voor de aanspreking ("De heer" /
   * "Mevrouw") in de identificatieclausule. Onbekend ⇒ de keuze-vorm
   * "De heer/Mevrouw" blijft staan ter keuze door de notaris.
   */
  geslacht?: "man" | "vrouw";
  /** Bepaalt welke identificatiehypothese (variant van het onderdeel "identiteit-partijen") wordt gekozen. */
  burgerlijkeStaat?: "ongehuwd" | "gehuwd" | "wettelijk-samenwonend" | "uit-de-echt-gescheiden";
  /** Naam van de echtgeno(o)t(e) (variant "gehuwd"). */
  echtgenoot?: string;
  /** Plaats van het huwelijk (variant "gehuwd"). */
  huwelijksplaats?: string;
  /** Datum van het huwelijk, ISO "JJJJ-MM-DD" (variant "gehuwd"). */
  huwelijksdatum?: string;
  /** Huwelijksvermogensstelsel (variant "gehuwd"). */
  huwelijksstelsel?: string;
  /** Notaris voor wie het huwelijkscontract werd verleden (variant "gehuwd"). */
  huwelijkscontractNotaris?: string;
  /** Standplaats van die notaris. */
  huwelijkscontractPlaats?: string;
  /** Datum van het huwelijkscontract, ISO "JJJJ-MM-DD". */
  huwelijkscontractDatum?: string;
  /**
   * Werd het huwelijksvermogensstelsel nadien gewijzigd bij één of meer latere
   * akten (bv. gebleken uit een CRH-opzoeking)? Chronologisch (oudste eerst).
   * Enkel invullen wanneer dit met zekerheid gekend is — onbekend/leeg laat de
   * volledige KIES-keuze (niet/wel gewijzigd) open in plaats van "niet
   * gewijzigd" te veronderstellen (nooit gokken). Vervangt de losse velden
   * notarisWijzigingStelsel/plaatsWijzigingStelsel/datumWijzigingStelsel/
   * omschrijvingWijzigingStelsel hieronder, die als eenmalige wijziging
   * blijven werken (achterwaartse compatibiliteit) maar geen meervoudige
   * wijziging kunnen uitdrukken.
   */
  wijzigingenStelsel?: { notaris: string; plaats?: string; datum: string; omschrijving?: string }[];
  /**
   * Werd het huwelijksvermogensstelsel nadien gewijzigd bij één latere akte?
   * Verouderd: gebruik wijzigingenStelsel voor nieuwe dossiers (ondersteunt
   * ook meerdere opeenvolgende wijzigingen); deze losse velden blijven werken
   * als wijzigingenStelsel niet is opgegeven.
   */
  notarisWijzigingStelsel?: string;
  /** Standplaats van de notaris die de wijzigende akte verleed. */
  plaatsWijzigingStelsel?: string;
  /** Datum van de wijzigende akte, ISO "JJJJ-MM-DD". */
  datumWijzigingStelsel?: string;
  /** Korte omschrijving van de wijziging, indien relevant (bv. "zonder het stelsel zelf te wijzigen"). */
  omschrijvingWijzigingStelsel?: string;
  /**
   * Bij testament- én nalatenschapdossiers (rol "erfgenaam"): de relatie van
   * deze partij tot de erflater, om naam_partner/namen_kinderen automatisch
   * te kunnen invullen (zie lib/dossier/parameters.ts) en, bij nalatenschap,
   * om de partner-vrijstellingen (gezinswoning/roerend) hard af te leiden in
   * plaats van aan te nemen (zie lib/dossier/afrekening.ts).
   */
  testamentRelatie?: "partner" | "kind" | "andere";
}

/** Gestructureerde gegevens van een vennootschap, voor statutenwijziging/-oprichting. */
export interface Vennootschap {
  /** Volledige maatschappelijke benaming. */
  naam: string;
  /** Huidig zetel-adres. */
  zetel?: string;
  ondernemingsnummer?: string;
  rechtsvorm?: string;
}

export interface Goed {
  adres: string;
  kadastraleGegevens?: string;
  gemeente?: string;
  /** Deelgemeente (indien afwijkend van de fusiegemeente), bv. "Duisburg". */
  deelgemeente?: string;
  afdeling?: string;
  sectie?: string;
  perceelnummer?: string;
  /**
   * Kadastrale sectie volgens de eigendomstitel, enkel indien expliciet
   * gekend en apart aangeleverd (bv. uit een kadastrale opzoeking die titel
   * en recent uittreksel naast elkaar toont). Geen fallback naar `sectie`:
   * titel en recent uittreksel kunnen (net als bij `perceelnummer`) een
   * verschillende aanduiding gebruiken — nooit gokken dat ze gelijk zijn.
   */
  sectieTitel?: string;
  /**
   * Perceelnummer volgens de eigendomstitel, letterlijk (bv. "60 F 22"),
   * enkel indien expliciet gekend. Geen fallback naar `perceelnummer` (het
   * recente-uittreksel-formaat, bv. "0060F22P0000"): beide formaten kunnen
   * voor hetzelfde perceel uiteenlopen — nooit gokken dat ze gelijk zijn.
   */
  perceelnummerTitel?: string;
  kadastraalInkomen?: number;
  type?: "woning" | "appartement" | "grond" | "ander";
  oppervlakte?: number;
  /** Oppervlakte volgens de eigendomstitel, letterlijk (bv. "1a 80ca"). */
  oppervlakteTitel?: string;
  /** Inleidende omschrijving van het gebouw of perceel uit de titel/basisakte, bv. "In een appartementsgebouw gelegen …". */
  inleidendeOmschrijving?: string;
  /**
   * Bouwjaar van de (toegankelijke) constructies, zoals vermeld in de
   * kadastrale legger/matrice cadastrale. Enkel gebruikt voor de
   * deterministische "niet vereist"-hypothese van het asbestinventarisattest
   * (Vlaanderen, ht-asbestinventarisattest-vlaanderen): bouwjaar 2001 of
   * recenter → asbestinventaris niet vereist. Geen fallback naar een ander
   * veld — enkel invullen wanneer het bouwjaar met zekerheid uit de stukken
   * blijkt (nooit gokken).
   */
  bouwjaar?: number;
}

/**
 * Gegevens uit de attesten/certificaten bij een vastgoedverkoop. Elk attest is
 * éénbronnig en gezaghebbend (het attest zelf), dus deze waarden worden — anders
 * dan prijs/goed — niet als bron-getrackte Veld<T> bijgehouden. Uitbreiden met
 * bijkomende attesten (stedenbouw, asbest, stookolietank, KLIM, …) gebeurt door
 * een veld toe te voegen, niet door de structuur te wijzigen.
 */
export interface Attesten {
  /** Energieprestatiecertificaat (EPC). `energiedeskundige` = naam van de erkende opsteller. */
  epc?: { datum?: string; nummer?: string; label?: string; geldigTot?: string; verbruik?: number; energiedeskundige?: string };
  /**
   * Bodemattest (OVAM). `refertenummer` = het dossier-/refertenummer van het
   * attest. `activiteiten`: "geen" = UITDRUKKELIJK verklaard dat op het goed
   * geen bodemvervuilende activiteiten (risico-inrichtingen) werden of worden
   * uitgevoerd — een apart feit van de attestinhoud zelf; enkel "geen" wordt
   * gedragen, nooit afgeleid uit `inhoud` (nooit gokken).
   */
  bodem?: { datum?: string; inhoud?: string; refertenummer?: string; activiteiten?: "geen" };
  /** Keuringsverslag van de elektrische installatie. */
  elektrischeKeuring?: { organisme?: string; datum?: string; conclusie?: string };
  /**
   * Stedenbouwkundige inlichtingen (gemeente). `onteigeningRooilijn` en
   * `erfgoed` zijn EXPLICIETE negatieve feiten uit de inlichtingen/opzoeking:
   * enkel "geen" wordt gedragen (geen onteigeningsmaatregel, rooilijn of
   * inneming resp. noch vastgesteld noch beschermd erfgoed) — elk ander geval
   * blijft weg zodat de clausulekeuze open blijft voor de notaris.
   */
  stedenbouw?: {
    datum?: string;
    bestemming?: string;
    vergunningen?: string;
    onteigeningRooilijn?: "geen";
    erfgoed?: "geen";
  };
  /** Asbestattest (verplicht voor gebouwen van vóór 2001). */
  asbest?: { datum?: string; nummer?: string; conclusie?: string };
  /** Overstromingsrapport/-attest (P- en G-score, datum en conclusie). */
  overstroming?: { pScore?: string; gScore?: string; datum?: string; conclusie?: string };
  /**
   * Natuur/bos (Vlaanderen): "geen" = het goed is geen bos, maakt geen deel
   * uit van een VEN/GEN en er bestaat geen natuurbeheerplan (uit de
   * opzoeking). Enkel "geen" wordt gedragen — nooit gokken.
   */
  natuurBos?: "geen";
  /** Herstelvordering woonkwaliteit (Vlaanderen): "geen" = geen vordering of veroordeling gekend. */
  herstelvordering?: "geen";
  /** Raadpleging van het Vlaamse maatregelenregister: datum + "blanco" wanneer het register geen informatie over het goed bevat. */
  maatregelenregister?: { datumRaadpleging?: string; inhoud?: "blanco" };
  /** Stookolietank: "geen" = uitdrukkelijk verklaard/vastgesteld dat er geen (ondergrondse of bovengrondse) stookolietank aanwezig is of was. */
  stookolietank?: "geen";
  /**
   * Lijst van ongeschiktheid/onbewoonbaarheid/verwaarlozing/leegstand
   * (Vlaanderen — inventaris/registers uit het Decreet Grond- en Pandenbeleid):
   * "geen" = het goed staat UITDRUKKELIJK op geen enkele van deze lijsten
   * (bevestigd door de inlichtingenbrief van de gemeente). Enkel "geen" wordt
   * gedragen — nooit gokken.
   */
  leegstand?: "geen";
  /**
   * PFAS-no-regret-zone: "geen" = het goed is UITDRUKKELIJK niet opgenomen in
   * de inventaris van risicosites voor PFAS-verontreiniging en ligt niet in
   * een PFAS-no-regret-zone. Enkel "geen" wordt gedragen — nooit gokken.
   */
  pfas?: "geen";
}

export interface HypothecaireLast {
  schuldeiser: string;
  bedrag: number;
  moetGeroyeerdWorden: boolean;
}

/**
 * Eén op de derdenrekening (kwaliteitsrekening) ontvangen betaling voor dit
 * dossier, door het kantoor geëncodeerd in de boekhoudmodule. Geen
 * bron-getrackt veld: dit is kantoor-eigen boekhouding, geen gegeven uit een
 * brondocument. `van` is een rol, geen naam — persoonsgegevens horen hier
 * niet thuis (de mededeling is vrije tekst voor het kantoor zelf).
 */
export interface DerdengeldOntvangst {
  id: string;
  /** Valutadatum, ISO "JJJJ-MM-DD". */
  datum: string;
  /** Bedrag in euro (positief). */
  bedrag: number;
  /** Wie stortte: de koperszijde (koper zelf of zijn kredietbank), het
   * bemiddelend agentschap (bv. doorstorting van het voorschot), de verkoper
   * of een andere partij. */
  van: "koper" | "kredietbank" | "makelaar" | "verkoper" | "andere";
  /** Vrije mededeling/referentie van de overschrijving. */
  mededeling?: string;
}

export interface Kredietofferte {
  bank: string;
  bedrag: number;
  aanvaard: boolean;
  /** Termijn (in weken) van de opschortende voorwaarde van financiering, indien overeengekomen. */
  termijnWeken?: number;
}

/** Dossiervelden waarop een tegenstrijdigheid kan slaan (sleutel op het Dossier-object). */
export type TegenstrijdigheidVeldSleutel =
  | "prijs"
  | "voorschot"
  | "compromisdatum"
  | "belastbareWaarde"
  | "goed";

export interface Tegenstrijdigheid {
  veld: string;
  /** Stabiele sleutel naar het dossierveld, voor validatie in de UI (optioneel voor oudere data). */
  veldSleutel?: TegenstrijdigheidVeldSleutel;
  waarden: { bron: BronType; waarde: string }[];
  opgelost: boolean;
}

// ── Dossierkoppeling: relaties tussen dossiers van het kantoor ───────────────

/**
 * Gerichte relatie van dit dossier naar een ander dossier. Elke relatie heeft
 * een inverse (zie RELATIE_INVERS in lib/dossier/koppeling.ts); koppelen
 * gebeurt altijd symmetrisch, zodat de band vanuit beide dossiers zichtbaar
 * is. Typische ketens: verkoop ↔ kredietdossier, nalatenschap → verkoop uit
 * de nalatenschap, een masterdossier dat deeldossiers omvat.
 */
export type DossierRelatie =
  | "volgt-op"
  | "gevolgd-door"
  | "kredietdossier-van"
  | "heeft-kredietdossier"
  | "deeldossier-van"
  | "omvat-deeldossier"
  | "verwant";

/** Koppeling van een dossier naar een ander dossier (DossierKern.koppelingen). */
export interface DossierKoppeling {
  /** Id van het gekoppelde dossier (DossierKern.id). */
  dossierId: string;
  relatie: DossierRelatie;
  /** Vrije toelichting bij de koppeling, bv. "aankoop na de nalatenschap X". */
  toelichting?: string;
  /** Tijdstip van koppelen, ISO-8601. */
  aangemaakt: string;
}

export type DossierStatus =
  | "compromis-ontvangen"
  | "opzoekingen-lopend"
  | "ontwerp-in-opmaak"
  | "ontwerp-verstuurd"
  | "klaar-voor-akte"
  | "verleden";

export type Dossiertype =
  | "verkoop-met-krediet"
  | "verkoop-zonder-krediet"
  | "schenking"
  | "nalatenschap"
  | "aanpassing-statuten-vennootschap"
  | "keuzetestament"
  | "testament-gezinswoning";

/** De twee verkoop-dossiertypes (het discriminant-bereik van VerkoopDossier). */
export type VerkoopDossiertype = "verkoop-met-krediet" | "verkoop-zonder-krediet";

/** De twee testament-dossiertypes (het discriminant-bereik van TestamentDossier). */
export type TestamentDossiertype = "keuzetestament" | "testament-gezinswoning";

/** Mensleesbaar label per dossierstatus — gedeeld tussen het dossieroverzicht
 * en de statusselect op de detailpagina (die toonden tot nu de rauwe
 * status-sleutel, bv. "compromis-ontvangen"). */
export const DOSSIERSTATUS_LABEL: Record<DossierStatus, string> = {
  "compromis-ontvangen": "Compromis ontvangen",
  "opzoekingen-lopend": "Opzoekingen lopend",
  "ontwerp-in-opmaak": "Ontwerp in opmaak",
  "ontwerp-verstuurd": "Ontwerp verstuurd",
  "klaar-voor-akte": "Klaar voor akte",
  verleden: "Akte verleden",
};

/** Mensleesbaar label per dossiertype — gedeeld tussen /dossier, /dossier/[id] en /gouden-paden. */
export const DOSSIERTYPE_LABEL: Record<Dossiertype, string> = {
  "verkoop-met-krediet": "Verkoop met krediet",
  "verkoop-zonder-krediet": "Verkoop zonder krediet",
  schenking: "Schenking",
  nalatenschap: "Nalatenschap",
  "aanpassing-statuten-vennootschap": "Aanpassing statuten vennootschap",
  keuzetestament: "Keuzetestament",
  "testament-gezinswoning": "Testament gezinswoning",
};

// ── Dossier als discriminated union per dossiertype ──────────────────────────
// Eén vlakke Dossier-interface werd bij tientallen gouden paden onleesbaar:
// welk veld hoort bij welk pad? De velden zijn daarom gegroepeerd per pad
// (DossierKern + *Velden-groepen) en Dossier is een discriminated union op
// `dossiertype`. Zo narrowt een switch/if op het dossiertype automatisch naar
// de juiste velden, en is het TOEKENNEN van een pad-vreemd veld (bv. een prijs
// op een schenkingsdossier) een compile-fout.
//
// LEZEN blijft overal mogelijk: elk unielid draagt de veldnamen van de andere
// paden als `?: never` (zie Zonder<T>), zodat generieke lezers (parameter-
// mapping, kenmerken, UI) `dossier.prijs?.waarde` kunnen blijven schrijven
// zonder eerst te narrowen — het resultaat is dan gewoon `undefined`.
//
// Nieuw dossierveld toevoegen = het veld in de juiste *Velden-groep zetten
// (of een nieuwe groep maken en die aan de juiste unieleden hangen); nieuw
// dossiertype = een nieuw unielid met de groepen die bij dat pad horen.

/** Gedeelde kern van elk dossier, ongeacht het dossiertype. */
export interface DossierKern {
  id: string;
  status: DossierStatus;
  /** Kantoorreferentie/dossiernummer; vult {{referte}} (modelmails) en {{dossiernummer}} (akte). */
  referentie?: string;
  /**
   * Uitdrukkelijke taal van de op te stellen documenten ("indication contraire
   * claire"). Ontbreekt dit veld, dan worden de documenten in de taal van de
   * gebruiker opgesteld (de kantoorvoorkeurstaal).
   */
  taal?: "nl" | "fr";
  partijen: Veld<Partij[]>;
  ontbrekendeStukken: string[];
  tegenstrijdigheden: Tegenstrijdigheid[];
  /**
   * Koppelingen naar andere dossiers van het kantoor (vervolgdossier,
   * kredietdossier, deeldossier, verwant). Altijd symmetrisch beheren via
   * lib/dossier/koppeling.ts (koppelDossiers/ontkoppelDossiers) — nooit
   * rechtstreeks muteren, zodat beide zijden gelijk blijven lopen. Dit is
   * ook het aanknopingspunt voor latere lagen die over een dossierketen
   * heen werken (dossierregie, de boekhoudmodule van laag 6).
   */
  koppelingen?: DossierKoppeling[];
  /**
   * Afvinkstatus van de akte-checklist (data/kennisbank-checklists.ts) voor
   * DIT dossier: puntId → afgevinkt. Dossierspecifiek (twee dossiers van
   * hetzelfde akteType houden onafhankelijk hun voortgang bij); de gedeelde
   * checklist zelf (welke punten, welk gewicht) blijft ongewijzigd.
   */
  checklistVoortgang?: Record<string, boolean>;
  aangemaakt: string;
  aangepast: string;
}

/**
 * Velden van elk dossier met een ONROEREND GOED als voorwerp (verkoop,
 * schenking van onroerend goed, nalatenschap met onroerend actief): het goed
 * zelf, de attesten, de eigendomstitel en de mede-eigendomsgegevens — de
 * beschrijvings- en attestclausules zijn dezelfde ongeacht de rechtshandeling.
 */
export interface OnroerendGoedVelden {
  goed?: Veld<Goed>;
  /** Gegevens uit de attesten/certificaten (EPC, bodem, elektrische keuring, …). */
  attesten?: Attesten;
  /**
   * Gegevens uit de eigendomstitel: datum van de akte en de erfdienstbaarheden/
   * bijzondere voorwaarden die de titel vermeldt (vrije tekst, letterlijk over
   * te nemen). Vullen de heldere-taal-erfdienstbaarhedenclausule in.
   */
  eigendomstitel?: { datum?: string; erfdienstbaarheden?: string };
  /**
   * Gegevens van de mede-eigendom (enkel bij een kavel in mede-eigendom):
   * datum van de statuten (basisakte + reglement), datum van het bericht van de
   * syndicus (samen met syndicusStatus "geantwoord"), de gewone
   * gemeenschappelijke lasten per trimester, de beschrijving van de kavel uit
   * de basisakte en de identificatie van de basisakte zelf. Alles vrije tekst,
   * letterlijk over te nemen uit basisakte/compromis.
   */
  medeEigendom?: {
    datumStatuten?: string;
    datumBerichtSyndicus?: string;
    lastenPerTrimester?: number;
    /** Benaming/nummer van de kavel, bv. "appartement 1". */
    nummerKavel?: string;
    /** Ligging in het gebouw, bv. "op de kelderverdieping, gelijkvloers en tussenverdieping". */
    liggingInGebouw?: string;
    /** Verdieping(en), bv. "gelijkvloers" of "derde verdieping". */
    verdieping?: string;
    /** Beschrijving van de privatieve delen, letterlijk uit basisakte/compromis. */
    beschrijvingPrivatief?: string;
    /** Tuin/terras/koer/… in exclusief genot, indien van toepassing. */
    exclusiefGenot?: string;
    /** Aandeel in de gemene delen, bv. "39/100sten (waaronder de grond)". */
    aandeelGemeneDelen?: string;
    /** Gereserveerd perceelsidentificatienummer van de kavel, bv. "0250V2P0002". */
    perceelsidentificatie?: string;
    /** Datum van de basisakte (ISO "JJJJ-MM-DD"). */
    datumBasisakte?: string;
    /** Notaris die de basisakte verleed. */
    notarisBasisakte?: string;
    /** Standplaats van die notaris. */
    plaatsNotarisBasisakte?: string;
  };
}

/** Velden die uitsluitend bij een verkoopdossier (compromis/verkoopakte) horen. */
export interface VerkoopVelden {
  /**
   * Welk document het kantoor opstelt.
   * - "compromis": de onderhandse verkoopovereenkomst (voordien is enkel een
   *   bod/aankoopbelofte getekend);
   * - "verkoopakte": de authentieke akte (meestal binnen de vier maanden na de
   *   ondertekening van het compromis).
   * Bepaalt expliciet het akteType en het model. Is dit veld niet gezet, dan
   * valt de akteType-detectie terug op de aanwezigheid van een compromisdatum.
   */
  verkoopdocument?: "compromis" | "verkoopakte";
  /**
   * Verkoopregime: "gewoon" (standaard, geldt impliciet wanneer dit veld
   * ontbreekt), "wet-breyne" (verkoop van een te bouwen of in aanbouw zijnde
   * woning, wet van 9 juli 1971) of "lijfrente" (verkoop tegen lijfrente/
   * bail à rente viagère, art. 1968 e.v. oud BW). Orthogonaal op
   * `verkoopdocument` — elk regime kent zowel een compromis- als een
   * aktefase, dus dit is GEEN derde waarde van `verkoopdocument` en GEEN
   * apart dossiertype. Kruist met `verkoopdocument` in `bepaalAkteType` naar
   * de regime-specifieke akteTypes ("verkoopovereenkomst (compromis) - wet
   * breyne"/"verkoopakte - wet breyne" resp. "… - lijfrente"), die hun eigen
   * verplichte vermeldingen en afrekeningslogica meebrengen — zie
   * ARCHITECTURE.md, "Nieuwe gouden (sub-)paden".
   */
  verkoopRegime?: "gewoon" | "wet-breyne" | "lijfrente";
  /**
   * Gegevens van de lijfrente, enkel bij `verkoopRegime: "lijfrente"`: de
   * prijs wordt niet in één keer betaald maar via een bouquet (optioneel
   * eenmalig bedrag bij de akte) + een periodieke rente zolang de
   * verkoper/crediteur leeft. `genotsrecht` bepaalt of de verkoper na de
   * overdracht nog in het goed woont (vruchtgebruik of enkel gebruik/
   * bewoning) of het onmiddellijk vrij overdraagt. Nooit gokken op een
   * ontbrekend bedrag: de rente-/bouquetclausule blijft dan open
   * `[AAN TE VULLEN]`.
   */
  lijfrente?: {
    /** Eenmalig bedrag bij de akte, bovenop de rente (optioneel — niet elke lijfrenteverkoop kent een bouquet). */
    bouquet?: number;
    /** Periodiek rentebedrag (jaarbedrag, ongeacht de betaalfrequentie). */
    jaarlijkseRente?: number;
    /** Betaalfrequentie van de rente. */
    frequentie?: "maandelijks" | "trimestrieel" | "jaarlijks";
    /** Voorbehouden genotsrecht van de verkoper/crediteur na de overdracht. */
    genotsrecht?: "geen" | "vruchtgebruik" | "gebruik-en-bewoning";
  };
  /**
   * Gebruik/genot van het goed bij de overdracht (bepaalt de hypothese van
   * "eigendomsoverdracht-genot" / de verhuurd-clausule).
   */
  gebruik?: "vrij" | "koper-is-huurder" | "verhuurd";
  /**
   * Zijn er zonnepanelen op het goed? Stuurt de heldere-taal-clausule
   * reclamepanelen/zonnepanelen: false → clausule weglaten (geen panelen),
   * true → opnemen (notaris/agent kiest de groenestroomcertificaten-hypothese).
   */
  zonnepanelen?: boolean;
  /**
   * Bij een kavel in mede-eigendom (appartement): antwoordde de syndicus op de
   * vraag om documenten/informatie? Stuurt de heldere-taal-hypothese van de
   * clausule "statuten van mede-eigendom" (ht-mede-eigendom-statuten):
   * "geantwoord" / "geen-antwoord" / "geen-syndicus". Zonder deze waarde blijft
   * die hypothesekeuze open (alle varianten), ook bij een appartement.
   */
  syndicusStatus?: "geantwoord" | "geen-antwoord" | "geen-syndicus";
  /**
   * Rust er een WETTELIJK voorkooprecht, voorkeurrecht of recht van
   * wederinkoop op het goed (te onderscheiden van een conventioneel/
   * contractueel voorkooprecht)? Stuurt de heldere-taal-clausule
   * "voorkooprecht/voorkeurrecht/recht van wederinkoop" (ht-voorkooprecht-
   * algemeen): true/false kiest de overeenkomstige hypothese; onbekend laat
   * de volledige keuze open.
   */
  wettelijkVoorkooprecht?: boolean;
  /**
   * Fiscaal regime van de verkoop: (uitsluitend) registratiebelasting, btw, of
   * een combinatie (deels btw op de constructiewaarde, deels registratie-
   * belasting op de grondwaarde). Stuurt de heldere-taal-clausule
   * "registratiebelasting en btw" (ht-registratiebelasting-btw): een gekend
   * regime kiest de overeenkomstige hypothese; onbekend laat de volledige
   * A/B/C-keuze open.
   */
  fiscaalRegime?: "registratiebelasting" | "btw" | "gemengd";
  /**
   * Bij mede-eigendom: betwist de verkoper het bedrag van de lasten/
   * achterstallen dat de syndicus meedeelt? Stuurt de heldere-taal-clausule
   * "gemeenschappelijke lasten en voorrecht van de VME" (ht-mede-eigendom-
   * lasten-voorrecht): true/false kiest de overeenkomstige hypothese; onbekend
   * laat de volledige keuze open.
   */
  medeEigendomLastenBetwist?: boolean;
  /**
   * Betalingsgegevens uit het compromis (geen bron-getrackte velden — het
   * compromis is er de enige bron van): de rekening waarvan de koper betaalt en
   * de derdenrekening waarop de waarborg wordt geconsigneerd. Vullen de
   * heldere-taal-prijsclausule deterministisch in.
   */
  betaling?: {
    rekeningKoper?: string;
    naamRekeningKoper?: string;
    rekeningDerden?: string;
    naamRekeningDerden?: string;
  };
  /** Gekozen notaris per partij (heldere-taal §6); vrije naam + standplaats. */
  notarissen?: { verkoper?: string; koper?: string };
  /**
   * Commerciële naam van het vastgoedkantoor/agentschap dat de verkoop
   * bemiddelde (heldere-taal §25), bv. "ENGEL & VÖLKERS". Enkel de
   * kantoornaam — nooit de naam of het B.I.V./I.P.I.-nummer van de
   * individuele makelaar (natuurlijk persoon): de kantoornaam is geen
   * persoonsgegeven, de individuele identiteit wel. Onbekend/leeg laat de
   * vermelding open in plaats van te gokken dat er geen makelaar was.
   */
  makelaarNaam?: string;
  /**
   * Meeverkochte roerende goederen (heldere-taal §2): omschrijving (vrije
   * lijst) en de door partijen geschatte waarde, inbegrepen in de prijs.
   */
  roerendeGoederen?: { omschrijving?: string; waarde?: number };
  /**
   * Verdeling van de aangekochte delen tussen de kopers, letterlijk uit het
   * compromis (bv. "elk voor de onverdeelde helft" of "voor 60% in volle
   * eigendom door X en voor 40% door Y").
   */
  verdelingGekochteDelen?: string;
  /**
   * Koopt de koper zijn enige eigen woning (met vestiging van het
   * hoofdverblijf)? Stuurt het verkooprecht in de afrekening: true → verlaagd
   * tarief (Vlaanderen 2%, binnen de waardegrens), false → algemeen tarief
   * zonder voorbehoud, onbekend → algemeen tarief mét de melding dat het
   * verlaagd tarief mogelijk van toepassing is.
   */
  koperEnigeEigenWoning?: boolean;
  /** Verkoopprijs. */
  prijs?: Veld<number>;
  voorschot?: Veld<number>;
  compromisdatum?: Veld<string>;
  /**
   * Contractueel bedongen aktetermijn uit het compromis: een afwijkend aantal
   * maanden na de ondertekening (`maanden`) of een vaste uiterste datum
   * (`uiterlijkeDatum`, ISO). Zonder dit veld geldt de standaardtermijn van
   * vier maanden. De FISCALE registratietermijn van vier maanden blijft een
   * aparte vervaldag zodra de contractuele termijn er voorbij ligt (twee
   * verschillende gevolgen: registratiebelasting-boete versus wanprestatie).
   */
  aktetermijn?: Veld<{ maanden?: number; uiterlijkeDatum?: string }>;
  hypothecaireLasten?: Veld<HypothecaireLast[]>;
  kredietofferte?: Veld<Kredietofferte>;
  /** True wanneer het kantoor de stedenbouwkundige inlichtingen effectief heeft opgevraagd/ontvangen. */
  stedenbouwInlichtingenOntvangen?: Veld<boolean>;
  /**
   * Commissiefactuur van het bemiddelend agentschap (boekhoudmodule).
   * `ingehoudenOpVoorschot: true` = het agentschap hield zijn factuur al in op
   * het voorschot dat het onder zich had en stort enkel het nettosaldo door —
   * het kantoor mag de factuur dan NIET nogmaals betalen (ze vervalt in het
   * betalingsoverzicht en de verwachte voorschot-doorstorting daalt met het
   * factuurbedrag).
   */
  makelaarsFactuur?: { bedrag?: number; ingehoudenOpVoorschot?: boolean };
  /**
   * Op de derdenrekening ontvangen betalingen voor dit dossier, geëncodeerd in
   * de boekhoudmodule (chronologisch). Voedt het décompte en de
   * dekkingscontrole van de kantoorbetalingen (het dossiersaldo mag nooit
   * negatief — zie lib/dossier/derdengelden.ts).
   */
  derdengeldenOntvangsten?: DerdengeldOntvangst[];
}

/**
 * Fiscale velden van een overdracht om niet (schenking én nalatenschap): de
 * grondslag en parameters van de schenk-/erfbelastingberekening.
 */
export interface OverdrachtsbelastingVelden {
  /** Belastbare waarde van de schenking/nalatenschap. */
  belastbareWaarde?: Veld<number>;
  /** Verwantschap van de begiftigde/erfgenaam t.o.v. de schenker/erflater. */
  relatieBelasting?: Veld<Relatie>;
  /** Aard van het geschonken/nagelaten goed, voor de belastingberekening. */
  goedTypeBelasting?: Veld<GoedeType>;
}

/** Velden die uitsluitend bij een nalatenschapsdossier horen. */
export interface NalatenschapVelden {
  /** Overige goederen van de nalatenschap (basis voor de progressiviteit). */
  andereGoederenWaarde?: Veld<number>;
}

/** Velden die uitsluitend bij een vennootschapsdossier horen. */
export interface VennootschapVelden {
  /** Gegevens van de vennootschap. */
  vennootschap?: Veld<Vennootschap>;
}

/**
 * Sluit de velden van een andere groep uit op dit unielid: de veldnaam bestaat
 * (zodat generiek lezen op het brede Dossier-type blijft compileren en
 * `undefined` oplevert), maar een waarde toekennen is een compile-fout.
 */
type Zonder<T> = { [K in keyof T]?: never };

/** Verkoopdossier (compromis of verkoopakte), met of zonder krediet. */
export interface VerkoopDossier
  extends DossierKern,
    OnroerendGoedVelden,
    VerkoopVelden,
    Zonder<OverdrachtsbelastingVelden>,
    Zonder<NalatenschapVelden>,
    Zonder<VennootschapVelden> {
  dossiertype: VerkoopDossiertype;
}

/** Schenkingsdossier (roerend of onroerend). */
export interface SchenkingDossier
  extends DossierKern,
    OnroerendGoedVelden,
    OverdrachtsbelastingVelden,
    Zonder<VerkoopVelden>,
    Zonder<NalatenschapVelden>,
    Zonder<VennootschapVelden> {
  dossiertype: "schenking";
}

/** Nalatenschapsdossier (aangifte van nalatenschap, erfbelasting). */
export interface NalatenschapDossier
  extends DossierKern,
    OnroerendGoedVelden,
    OverdrachtsbelastingVelden,
    NalatenschapVelden,
    Zonder<VerkoopVelden>,
    Zonder<VennootschapVelden> {
  dossiertype: "nalatenschap";
}

/** Vennootschapsdossier (statutenwijziging/-aanpassing). */
export interface Vennootschapsdossier
  extends DossierKern,
    VennootschapVelden,
    Zonder<OnroerendGoedVelden>,
    Zonder<VerkoopVelden>,
    Zonder<OverdrachtsbelastingVelden>,
    Zonder<NalatenschapVelden> {
  dossiertype: "aanpassing-statuten-vennootschap";
}

/** Testamentdossier (keuzetestament, testament gezinswoning): enkel de kern — de erflater/erfgenamen zitten in `partijen` (met `testamentRelatie`). */
export interface TestamentDossier
  extends DossierKern,
    Zonder<OnroerendGoedVelden>,
    Zonder<VerkoopVelden>,
    Zonder<OverdrachtsbelastingVelden>,
    Zonder<NalatenschapVelden>,
    Zonder<VennootschapVelden> {
  dossiertype: TestamentDossiertype;
}

export type Dossier =
  | VerkoopDossier
  | SchenkingDossier
  | NalatenschapDossier
  | Vennootschapsdossier
  | TestamentDossier;

/** Opzoektabel dossiertype → unielid, voor generieke bouwers (DossierVanType). */
interface DossierPerType {
  "verkoop-met-krediet": VerkoopDossier;
  "verkoop-zonder-krediet": VerkoopDossier;
  schenking: SchenkingDossier;
  nalatenschap: NalatenschapDossier;
  "aanpassing-statuten-vennootschap": Vennootschapsdossier;
  keuzetestament: TestamentDossier;
  "testament-gezinswoning": TestamentDossier;
}

/** Het unielid van Dossier dat bij het gegeven dossiertype hoort. */
export type DossierVanType<T extends Dossiertype> = DossierPerType[T];

/**
 * Bouwt een leeg dossier van het gegeven type (enkel de gedeelde kern) — dé
 * manier om vanuit een runtime-dossiertype een correct getypeerd unielid te
 * maken. Pad-specifieke velden ken je daarna toe ná narrowing (type guards
 * hieronder). De cast hierbinnen is bewust en veilig: de kern bevat exact de
 * gedeelde velden en het dossiertype bepaalt het unielid één-op-één.
 */
export function maakLeegDossier<T extends Dossiertype>(dossiertype: T, kern: DossierKern): DossierVanType<T> {
  return { ...kern, dossiertype } as DossierVanType<T>;
}

// ── Type guards: dé plaats om op dossiertype te narrowen ─────────────────────

export function isVerkoopType(type: Dossiertype): type is VerkoopDossiertype {
  return type === "verkoop-met-krediet" || type === "verkoop-zonder-krediet";
}

export function isVerkoopDossier(dossier: Dossier): dossier is VerkoopDossier {
  return isVerkoopType(dossier.dossiertype);
}

export function isSchenkingDossier(dossier: Dossier): dossier is SchenkingDossier {
  return dossier.dossiertype === "schenking";
}

export function isNalatenschapDossier(dossier: Dossier): dossier is NalatenschapDossier {
  return dossier.dossiertype === "nalatenschap";
}

/** Dossiers met een onroerend goed als (mogelijk) voorwerp (verkoop, schenking, nalatenschap). */
export function heeftOnroerendGoedVelden(
  dossier: Dossier
): dossier is VerkoopDossier | SchenkingDossier | NalatenschapDossier {
  return isVerkoopDossier(dossier) || isSchenkingDossier(dossier) || isNalatenschapDossier(dossier);
}

/** Dossiers met schenk-/erfbelastingvelden (schenking, nalatenschap). */
export function heeftOverdrachtsbelastingVelden(
  dossier: Dossier
): dossier is SchenkingDossier | NalatenschapDossier {
  return isSchenkingDossier(dossier) || isNalatenschapDossier(dossier);
}

// Ids uit data/vastgoed/verkoop.json -> opzoekingen, gebruikt om
// ontbrekende stukken te bepalen zonder een apart regelsysteem te bouwen.
export const VERPLICHTE_STUKKEN_VERKOOP = [
  "hyp-attest",
  "kad-uittreksel",
  "stedenbouw",
  "bodem-vl",
] as const;

export function maakVeld<T>(waarde: T, bron: BronType, brondocumentId?: string): Veld<T> {
  return { waarde, bron, brondocumentId };
}
