// ── Akte sui generis: graceful-degradation-contract ──────────────────────────
// Voor atypische akten (onbenoemde overeenkomsten, gemengde rechtshandelingen,
// uitzonderlijke clausulecombinaties) bestaat per definitie geen kant-en-klaar
// model. De typegebonden lagen — modelkeuze, checklist, opvolging, afrekening,
// rollen — mogen daar niet HARD op falen, maar moeten GRACEFUL DEGRADEREN naar
// een zinvolle terugval, zichtbaar voor de notaris. Twee grenzen degraderen
// echter NOOIT en blijven onaantastbaar, ongeacht hoe atypisch de akte is:
//
//   1. de VERPLICHTE BESCHRIJVINGSCLAUSULE (beschrijving-onroerend-goed) voor
//      elk onroerend goed dat het voorwerp is van de akte (AGENTS.md);
//   2. de VOORBEHOUDGRENS: de ambtelijke handeling (identiteits-/wilscontrole,
//      voorlezing, verlijden) wordt nooit geautomatiseerd — het resultaat is
//      "klaar voor het verlijden", nooit het verlijden zelf.
//
// Deze module is puur: ze berekent het contract uit de dekking van de lagen
// (als sets meegegeven), zodat ze testbaar en laagonafhankelijk blijft.

/** De aktefamilie bepaalt het skeletmodel waarop een sui-generis-akte terugvalt. */
export type Aktefamilie = "vastgoed" | "familie" | "vennootschap" | "algemeen";

/** De typegebonden lagen die kunnen degraderen bij een atypische akte. */
export type TypegebondenLaag = "model" | "checklist" | "opvolging" | "afrekening" | "rollen";

export const TYPEGEBONDEN_LAGEN: readonly TypegebondenLaag[] = [
  "model",
  "checklist",
  "opvolging",
  "afrekening",
  "rollen",
];

/** Skeletmodel per aktefamilie (aanhef/partijen/kern/slot); null = nog geen skelet, vrije-vorm-samensteller (punt 54). */
export const SKELETMODEL_PER_FAMILIE: Record<Aktefamilie, string | null> = {
  vastgoed: "algemeen-kader-vastgoedakte",
  familie: null,
  vennootschap: null,
  algemeen: null,
};

/** Onaantastbare invariant-codes die nooit degraderen. */
export type HardeInvariant =
  | "verplichte-beschrijvingsclausule"
  | "voorbehoudgrens-ambtelijke-handeling";

/** Dekkingsstatus van één typegebonden laag voor deze akte. */
export interface LaagStatus {
  laag: TypegebondenLaag;
  dekking: "gedekt" | "gedegradeerd";
  /** Bij degradatie: waarnaar teruggevallen wordt (voor de notaris zichtbaar). */
  uitleg: string;
}

/** Het volledige sui-generis-contract voor één akteType. */
export interface SuiGenerisContract {
  akteType: string;
  /** True wanneer er geen eigen modeldocument voor dit akteType bestaat. */
  isSuiGeneris: boolean;
  betreftOnroerendGoed: boolean;
  /** Skeletmodel waarop de generatie terugvalt; null = vrije-vorm-samensteller. */
  skeletModelId: string | null;
  aktefamilie: Aktefamilie;
  lagen: LaagStatus[];
  /** Invarianten die voor deze akte hoe dan ook gelden (nooit gedegradeerd). */
  hardeInvarianten: HardeInvariant[];
}

/** Meegegeven dekking per laag: bevat het akteType een eigen model/checklist/…? */
export interface Dekking {
  gedekteModellen: ReadonlySet<string>;
  gedekteChecklists: ReadonlySet<string>;
  gedekteOpvolging: ReadonlySet<string>;
  gedekteAfrekening: ReadonlySet<string>;
  gedekteRollen: ReadonlySet<string>;
}

const DEGRADATIE_UITLEG: Record<TypegebondenLaag, string> = {
  model: "Geen eigen modeldocument — terugval op het skeletmodel van de aktefamilie of de vrije-vorm-samensteller; de kern wordt samengesteld uit bestaande clausules.",
  checklist: "Geen typespecifieke checklist — terugval op de generieke aandachtspunten; vul aan via de kennisbank.",
  opvolging: "Geen gemodelleerde opvolgingsroute — geen automatische termijnen/urgentie; de notaris bepaalt de opvolging.",
  afrekening: "Geen barema voor dit akteType — geen automatische afrekening; ereloon en rechten manueel bepalen.",
  rollen: "Geen rollen-mapping — {{rol_*}} rendert de generieke rollen 'overdrager'/'verkrijger'; controleer de partijbenamingen.",
};

const GEDEKT_UITLEG: Record<TypegebondenLaag, string> = {
  model: "Eigen modeldocument aanwezig.",
  checklist: "Typespecifieke checklist aanwezig.",
  opvolging: "Opvolgingsroute gemodelleerd.",
  afrekening: "Afrekening-barema aanwezig.",
  rollen: "Rollen-mapping aanwezig.",
};

/**
 * Leidt een aktefamilie af uit het akteType (heuristiek op trefwoorden). Enkel
 * gebruikt wanneer de aanroeper er geen meegeeft; bij twijfel "algemeen".
 */
export function afleidenAktefamilie(akteType: string): Aktefamilie {
  const t = akteType.toLowerCase();
  if (/verkoop|vente|schenk.*onroerend|erfpacht|opstal|basisakte|verkaveling|hypothe|krediet|verdeling|ruil|lijfrente|breyne|onroerend|immobil/.test(t)) {
    return "vastgoed";
  }
  if (/vennootschap|soci[eé]t[eé]|statuten|oprichting|ontbinding|omzetting|aandeel/.test(t)) return "vennootschap";
  if (/schenk|testament|huwelijk|zorgvolmacht|nalatenschap|erf|eot|echtscheiding|donation/.test(t)) return "familie";
  return "algemeen";
}

/**
 * Berekent het graceful-degradation-contract voor een akteType. Elke
 * typegebonden laag die het akteType niet dekt, degradeert met een uitleg;
 * de harde invarianten blijven altijd staan (beschrijvingsclausule bij een
 * onroerend goed, en steeds de voorbehoudgrens).
 */
export function bepaalSuiGenerisContract(
  akteType: string,
  opties: {
    dekking: Dekking;
    betreftOnroerendGoed: boolean;
    aktefamilie?: Aktefamilie;
  }
): SuiGenerisContract {
  const { dekking, betreftOnroerendGoed } = opties;
  const aktefamilie = opties.aktefamilie ?? afleidenAktefamilie(akteType);

  const gedektePerLaag: Record<TypegebondenLaag, ReadonlySet<string>> = {
    model: dekking.gedekteModellen,
    checklist: dekking.gedekteChecklists,
    opvolging: dekking.gedekteOpvolging,
    afrekening: dekking.gedekteAfrekening,
    rollen: dekking.gedekteRollen,
  };

  const lagen: LaagStatus[] = TYPEGEBONDEN_LAGEN.map((laag) => {
    const gedekt = gedektePerLaag[laag].has(akteType);
    return {
      laag,
      dekking: gedekt ? "gedekt" : "gedegradeerd",
      uitleg: gedekt ? GEDEKT_UITLEG[laag] : DEGRADATIE_UITLEG[laag],
    };
  });

  const hardeInvarianten: HardeInvariant[] = ["voorbehoudgrens-ambtelijke-handeling"];
  if (betreftOnroerendGoed) hardeInvarianten.unshift("verplichte-beschrijvingsclausule");

  return {
    akteType,
    isSuiGeneris: !dekking.gedekteModellen.has(akteType),
    betreftOnroerendGoed,
    skeletModelId: SKELETMODEL_PER_FAMILIE[aktefamilie],
    aktefamilie,
    lagen,
    hardeInvarianten,
  };
}

/** De gedegradeerde lagen van een contract (voor een beknopte notaris-melding). */
export function gedegradeerdeLagen(contract: SuiGenerisContract): LaagStatus[] {
  return contract.lagen.filter((l) => l.dekking === "gedegradeerd");
}

/**
 * Verifieert dat een contract de harde invarianten respecteert: bij een
 * onroerend goed MOET de beschrijvingsclausule erin zitten, en de
 * voorbehoudgrens altijd. Geeft de ontbrekende invarianten terug (leeg = ok) —
 * bedoeld als guard tegen een toekomstige regressie die een invariant zou
 * laten degraderen.
 */
export function ontbrekendeInvarianten(contract: SuiGenerisContract): HardeInvariant[] {
  const vereist: HardeInvariant[] = ["voorbehoudgrens-ambtelijke-handeling"];
  if (contract.betreftOnroerendGoed) vereist.push("verplichte-beschrijvingsclausule");
  return vereist.filter((inv) => !contract.hardeInvarianten.includes(inv));
}
