// ── Vierde pijler van het Einddoel: opvolging tot aan de ondertekening ───────
// Een ervaren notariële medewerker stelt niet alleen het compromis op — hij
// volgt het dossier ook op tot de authentieke akte getekend is: welke termijnen
// lopen er (opschortende voorwaarde van financiering, uiterste aktedatum),
// welke stukken ontbreken nog, en wat is — gegeven de dossierfase — de
// eerstvolgende stap met de bijbehorende modelmail-categorie.
//
// Kernprincipes (AGENTS.md):
//  • Puur en deterministisch: dezelfde dossierfeiten leveren altijd dezelfde
//    opvolging. "Vandaag" is een parameter (ISO-datum) die de aanroeper
//    meegeeft; zonder die parameter worden geen resterende dagen berekend.
//  • Nooit doen-alsof-zeker: een termijn wordt enkel berekend wanneer de
//    grondslag (compromisdatum, overeengekomen termijn) in het dossier zit.
//    Ontbreekt die, dan komt dat eerlijk in `onzekerheden` terecht.

import type { Dossier, DossierStatus } from "./types";
import type { BriefCategorie } from "@/data/modelbrieven";
import { berekenOntbrekendeStukken, stukLabel } from "./afleiding";
import { formatteerDatumVoluit } from "@/lib/format";

// ── Deterministische datumhulpfuncties (ISO "JJJJ-MM-DD") ────────────────────

/**
 * Strikte ISO-datumcontrole (JJJJ-MM-DD). De datumrekenkunde hieronder werkt
 * enkel op dit formaat; elke andere invoer (bv. "12 mei 2026" uit een agent-
 * import) zou op NaN uitkomen en de hele werkdossier-samenstelling laten
 * crashen. Wie rekent, controleert dus eerst met deze functie en degradeert
 * anders naar een expliciete onzekerheid — nooit een crash, nooit een gok.
 */
export function isIsoDatum(datum: string): boolean {
  return /^\d{4}-\d{2}-\d{2}$/.test(datum);
}

function splits(iso: string): [number, number, number] {
  const [j, m, d] = iso.split("-").map(Number);
  return [j, m, d];
}

function naarIso(jaar: number, maandIndex: number, dag: number): string {
  const d = new Date(Date.UTC(jaar, maandIndex, dag));
  return d.toISOString().slice(0, 10);
}

/** Telt hele dagen op bij een ISO-datum. */
export function voegDagenToe(iso: string, dagen: number): string {
  const [j, m, d] = splits(iso);
  return naarIso(j, m - 1, d + dagen);
}

/** Telt weken op bij een ISO-datum. */
export function voegWekenToe(iso: string, weken: number): string {
  return voegDagenToe(iso, weken * 7);
}

/**
 * Telt maanden op "van datum tot datum" (burgerlijke termijnberekening): de
 * vervaldag valt op dezelfde dag van de maand; bestaat die dag niet (bv.
 * 31 oktober + 4 maanden), dan op de laatste dag van de doelmaand.
 */
export function voegMaandenToe(iso: string, maanden: number): string {
  const [j, m, d] = splits(iso);
  const doelIndex = m - 1 + maanden;
  const doelJaar = j + Math.floor(doelIndex / 12);
  const doelMaand = ((doelIndex % 12) + 12) % 12;
  const laatsteDag = new Date(Date.UTC(doelJaar, doelMaand + 1, 0)).getUTCDate();
  return naarIso(doelJaar, doelMaand, Math.min(d, laatsteDag));
}

/** Hele dagen van `van` tot `tot` (negatief wanneer `tot` vóór `van` ligt). */
export function dagenTussen(van: string, tot: string): number {
  const [vj, vm, vd] = splits(van);
  const [tj, tm, td] = splits(tot);
  return Math.round((Date.UTC(tj, tm - 1, td) - Date.UTC(vj, vm - 1, vd)) / 86_400_000);
}

// ── Types ─────────────────────────────────────────────────────────────────────

/** Eén lopende termijn in het dossier, met herleidbare berekeningsgrondslag. */
export interface OpvolgingTermijn {
  id: "opschortende-voorwaarde-krediet" | "uiterste-datum-akte" | "fiscale-registratietermijn";
  titel: string;
  /** Vervaldag, ISO "JJJJ-MM-DD". */
  vervaldatum: string;
  /** Vervaldag voluit, voor het werkdocument. */
  vervaldatumVoluit: string;
  /** Herleidbare grondslag van de berekening (datum + termijn + bron). */
  grondslag: string;
  /** Wat er vóór de vervaldag moet gebeuren. */
  toelichting: string;
  /** Enkel wanneer `vandaag` werd meegegeven. */
  dagenResterend?: number;
  verstreken?: boolean;
}

/** Eén stap op de vaste route van compromis tot (en met) de akte. */
export interface OpvolgingStap {
  status: DossierStatus;
  titel: string;
  /** Concrete, uit het dossier afgeleide acties voor deze stap. */
  acties: string[];
  /** Modelmail-categorieën die bij deze stap horen. */
  mailCategorieen: BriefCategorie[];
  toestand: "afgerond" | "huidig" | "te-doen";
}

/** De volledige opvolging van een dossier tot aan de ondertekening. */
export interface AutonomeOpvolging {
  /** De vaste route (alle fasen) met de huidige fase gemarkeerd. */
  stappen: OpvolgingStap[];
  /** De stap waarin het dossier zich nu bevindt. */
  huidigeStap?: OpvolgingStap;
  /** Lopende termijnen, berekend uit de dossierfeiten. */
  termijnen: OpvolgingTermijn[];
  /** Nog ontbrekende verplichte stukken (id + label). */
  ontbrekendeStukken: { id: string; label: string }[];
  /** Alles wat de opvolging niet met zekerheid kon bepalen. */
  onzekerheden: string[];
}

const STATUS_VOLGORDE: DossierStatus[] = [
  "compromis-ontvangen",
  "opzoekingen-lopend",
  "ontwerp-in-opmaak",
  "ontwerp-verstuurd",
  "klaar-voor-akte",
  "verleden",
];

const STAP_TITELS: Record<DossierStatus, string> = {
  "compromis-ontvangen": "Compromis ontvangen — opzoekingen starten",
  "opzoekingen-lopend": "Opzoekingen opvolgen",
  "ontwerp-in-opmaak": "Ontwerp opstellen en vervolledigen",
  "ontwerp-verstuurd": "Ontwerp verstuurd — opmerkingen en ondertekening voorbereiden",
  "klaar-voor-akte": "Klaar voor de akte — ondertekening organiseren",
  verleden: "Akte verleden — formaliteiten na de akte",
};

function isVerkoop(dossier: Dossier): boolean {
  return dossier.dossiertype === "verkoop-met-krediet" || dossier.dossiertype === "verkoop-zonder-krediet";
}

// ── Termijnen ─────────────────────────────────────────────────────────────────

function berekenTermijnen(dossier: Dossier, vandaag: string | undefined, onzekerheden: string[]): OpvolgingTermijn[] {
  const termijnen: OpvolgingTermijn[] = [];
  const compromisdatum = dossier.compromisdatum?.waarde;
  const naAkte = dossier.status === "verleden";

  if (!compromisdatum) {
    // Zonder ondertekend compromis lopen er nog geen termijnen; dat is geen
    // fout, maar de notaris moet het weten zodra het compromis getekend is.
    onzekerheden.push(
      "Geen compromisdatum gekend — de termijnen (opschortende voorwaarde, uiterste aktedatum) kunnen nog niet worden berekend. Vul de compromisdatum aan zodra het compromis ondertekend is."
    );
    return termijnen;
  }
  if (!isIsoDatum(compromisdatum)) {
    // Een niet-ISO-datum (bv. vrije tekst uit een import) kan niet als
    // rekenbasis dienen; expliciet melden in plaats van te crashen of te gokken.
    onzekerheden.push(
      `De compromisdatum "${compromisdatum}" staat niet in het formaat JJJJ-MM-DD — de termijnen (opschortende voorwaarde, uiterste aktedatum) kunnen er niet mee worden berekend. Corrigeer de datum op de dossierpagina.`
    );
    return termijnen;
  }

  function verrijk(termijn: OpvolgingTermijn): OpvolgingTermijn {
    if (!vandaag || !isIsoDatum(vandaag)) return termijn;
    const resterend = dagenTussen(vandaag, termijn.vervaldatum);
    return { ...termijn, dagenResterend: resterend, verstreken: resterend < 0 };
  }

  // 1. Opschortende voorwaarde van financiering (enkel verkoop-met-krediet,
  //    zolang de kredietofferte niet aanvaard is).
  if (dossier.dossiertype === "verkoop-met-krediet" && !naAkte) {
    const offerte = dossier.kredietofferte?.waarde;
    if (offerte?.aanvaard) {
      // Voorwaarde vervuld — geen lopende termijn meer.
    } else if (offerte?.termijnWeken) {
      const vervaldatum = voegWekenToe(compromisdatum, offerte.termijnWeken);
      termijnen.push(
        verrijk({
          id: "opschortende-voorwaarde-krediet",
          titel: "Opschortende voorwaarde van financiering",
          vervaldatum,
          vervaldatumVoluit: formatteerDatumVoluit(vervaldatum),
          grondslag: `compromisdatum ${formatteerDatumVoluit(compromisdatum)} + ${offerte.termijnWeken} weken (overeengekomen krediettermijn)`,
          toelichting:
            "Vóór deze vervaldag moet de koper het krediet verkrijgen (of de verkoper verwittigen). Volg de kredietaanvraag actief op en noteer de aanvaarding in het dossier.",
        })
      );
    } else {
      onzekerheden.push(
        "De termijn van de opschortende voorwaarde van financiering is niet gekend — de vervaldag kan niet worden bewaakt. Vraag de overeengekomen termijn (in weken) op uit het compromis of bij de partijen."
      );
    }
  }

  // 2. Uiterste datum van de authentieke akte. Standaard: vier maanden na de
  //    ondertekening (tevens de fiscale registratietermijn van de onderhandse
  //    overeenkomst). Een contractueel bedongen aktetermijn (afwijkende
  //    maanden of vaste uiterste datum) heeft voorrang; ligt die VOORBIJ de
  //    fiscale vervaldag, dan blijft de fiscale registratietermijn als aparte
  //    vervaldag bewaakt — laattijdige registratie kost een belastingverhoging,
  //    los van de contractuele wanprestatie.
  if (!naAkte) {
    const fiscaleVervaldag = voegMaandenToe(compromisdatum, 4);
    const contractueel = dossier.aktetermijn?.waarde;
    const contractueleVervaldag =
      contractueel?.uiterlijkeDatum && isIsoDatum(contractueel.uiterlijkeDatum)
        ? contractueel.uiterlijkeDatum
        : contractueel?.maanden && contractueel.maanden > 0
          ? voegMaandenToe(compromisdatum, contractueel.maanden)
          : undefined;
    const vervaldatum = contractueleVervaldag ?? fiscaleVervaldag;
    const grondslag = contractueleVervaldag
      ? contractueel?.uiterlijkeDatum
        ? `contractueel bedongen uiterste datum ${formatteerDatumVoluit(contractueleVervaldag)} (compromis)`
        : `compromisdatum ${formatteerDatumVoluit(compromisdatum)} + ${contractueel!.maanden} maanden (contractueel bedongen aktetermijn)`
      : `compromisdatum ${formatteerDatumVoluit(compromisdatum)} + vier maanden (standaardtermijn van de verkoopovereenkomst; tevens de fiscale registratietermijn)`;
    termijnen.push(
      verrijk({
        id: "uiterste-datum-akte",
        titel: "Uiterste datum authentieke akte",
        vervaldatum,
        vervaldatumVoluit: formatteerDatumVoluit(vervaldatum),
        grondslag,
        toelichting:
          "De authentieke akte moet vóór deze dag verleden worden. Plan de ondertekening tijdig en hou rekening met de doorlooptijd van de opzoekingen.",
      })
    );
    if (contractueleVervaldag && contractueleVervaldag > fiscaleVervaldag) {
      termijnen.push(
        verrijk({
          id: "fiscale-registratietermijn",
          titel: "Fiscale registratietermijn onderhandse overeenkomst",
          vervaldatum: fiscaleVervaldag,
          vervaldatumVoluit: formatteerDatumVoluit(fiscaleVervaldag),
          grondslag: `compromisdatum ${formatteerDatumVoluit(compromisdatum)} + vier maanden (wettelijke registratietermijn)`,
          toelichting:
            "De contractuele aktetermijn ligt voorbij deze fiscale vervaldag: wordt de akte niet vóór deze dag verleden, dan moet de onderhandse overeenkomst apart ter registratie worden aangeboden om een belastingverhoging te vermijden.",
        })
      );
    }
  }

  // Waarschuw uitdrukkelijk bij verstreken of nakende termijnen (enkel
  // berekenbaar wanneer de aanroeper "vandaag" meegeeft).
  for (const t of termijnen) {
    if (t.verstreken) {
      onzekerheden.push(
        `De termijn "${t.titel}" is verstreken op ${t.vervaldatumVoluit} — controleer onmiddellijk de stand van zaken en verwittig de notaris.`
      );
    } else if (t.dagenResterend !== undefined && t.dagenResterend <= 14) {
      onzekerheden.push(
        `De termijn "${t.titel}" vervalt binnen ${t.dagenResterend} dag(en) (${t.vervaldatumVoluit}) — onderneem nu actie.`
      );
    }
  }

  return termijnen;
}

// ── Stappen (vaste route) ─────────────────────────────────────────────────────

function actiesVoorStap(
  status: DossierStatus,
  dossier: Dossier,
  ontbrekend: { id: string; label: string }[],
  termijnen: OpvolgingTermijn[]
): string[] {
  const acties: string[] = [];
  const metKrediet = dossier.dossiertype === "verkoop-met-krediet";
  const appartement = dossier.goed?.waarde.type === "appartement";
  const ontbrekendLabels = ontbrekend.map((s) => s.label).join(", ");
  const kredietTermijn = termijnen.find((t) => t.id === "opschortende-voorwaarde-krediet");
  const akteTermijn = termijnen.find((t) => t.id === "uiterste-datum-akte");

  switch (status) {
    case "compromis-ontvangen":
      acties.push(
        ontbrekend.length > 0
          ? `Vraag de verplichte opzoekingen aan: ${ontbrekendLabels}.`
          : "Alle verplichte stukken zijn binnen — de opzoekingsfase kan worden afgesloten."
      );
      acties.push("Stuur de eerste-contactmail naar verkoper en koper met de opvraging van de nodige stukken en gegevens.");
      if (appartement && dossier.syndicusStatus !== "geantwoord" && dossier.syndicusStatus !== "geen-syndicus") {
        acties.push("Vraag de syndicus de wettelijke inlichtingen en documenten van de mede-eigendom op.");
      }
      break;
    case "opzoekingen-lopend":
      acties.push(
        ontbrekend.length > 0
          ? `Volg de lopende opzoekingen op en rappelleer waar nodig; nog te ontvangen: ${ontbrekendLabels}.`
          : "Alle verplichte stukken zijn binnen — zet het dossier door naar de ontwerpfase."
      );
      if (appartement && dossier.syndicusStatus === "geen-antwoord") {
        acties.push("Rappelleer de syndicus: zonder zijn inlichtingen blijft de mede-eigendomsclausule onvolledig.");
      }
      break;
    case "ontwerp-in-opmaak":
      acties.push("Stel het ontwerp autonoom samen (pijler 1) en werk de gemelde onzekerheden weg.");
      acties.push("Vraag ontbrekende gegevens bij de partijen op via de passende modelmail (pijler 2).");
      break;
    case "ontwerp-verstuurd":
      acties.push("Verzamel de opmerkingen van de partijen en van de confrater-notaris op het ontwerp.");
      if (metKrediet) {
        acties.push(
          kredietTermijn
            ? `Volg de opschortende voorwaarde van financiering op (vervaldag ${kredietTermijn.vervaldatumVoluit}).`
            : "Volg de opschortende voorwaarde van financiering op en noteer de aanvaarding van het krediet in het dossier."
        );
      }
      acties.push(
        akteTermijn
          ? `Plan de ondertekening van de akte vóór de uiterste datum (${akteTermijn.vervaldatumVoluit}).`
          : "Plan de ondertekening van de akte binnen de overeengekomen termijn."
      );
      break;
    case "klaar-voor-akte":
      if (metKrediet && !dossier.kredietofferte?.waarde?.aanvaard) {
        acties.push("Controleer dat de opschortende voorwaarde van financiering vervuld is (aanvaarde kredietofferte in het dossier).");
      }
      acties.push("Vraag de koper het saldo van de prijs en de aktekosten vóór het verlijden op de derdenrekening te storten.");
      acties.push("Nodig alle partijen uit voor de ondertekening en bevestig datum, uur en plaats.");
      break;
    case "verleden":
      acties.push("Vervul de formaliteiten na de akte: registratie en overschrijving, uitbetaling aan de verkoper en afrekening van de kosten.");
      if (dossier.hypothecaireLasten?.waarde.some((l) => l.moetGeroyeerdWorden)) {
        acties.push("Volg het royement (doorhaling) van de af te lossen hypothecaire inschrijvingen op.");
      }
      break;
  }
  return acties;
}

/** Modelmail-categorieën die bij een dossierfase horen (verkooproute). */
function mailCategorieenVoorStap(status: DossierStatus, metKrediet: boolean): BriefCategorie[] {
  switch (status) {
    case "compromis-ontvangen":
    case "opzoekingen-lopend":
      return ["verkoop"];
    case "ontwerp-in-opmaak":
      return ["verkoop", "akteontwerp"];
    case "ontwerp-verstuurd":
      return metKrediet ? ["akteontwerp", "kredietdossier", "betaling"] : ["akteontwerp", "betaling"];
    case "klaar-voor-akte":
      return ["betaling", "digitale_ondertekening"];
    case "verleden":
      return ["betaling"];
  }
}

// ── Samenstelling ─────────────────────────────────────────────────────────────

/**
 * Stelt autonoom de opvolging van een verkoopdossier samen: de vaste route van
 * compromis tot akte met de huidige fase gemarkeerd, de lopende termijnen en de
 * nog ontbrekende stukken. `vandaag` (ISO "JJJJ-MM-DD") is optioneel; enkel
 * daarmee worden resterende dagen en verstreken-waarschuwingen berekend, zodat
 * de functie zelf puur en reproduceerbaar blijft.
 */
export function stelOpvolgingSamenAutonoom(dossier: Dossier, vandaag?: string): AutonomeOpvolging {
  const onzekerheden: string[] = [];

  if (!isVerkoop(dossier)) {
    onzekerheden.push(
      "De opvolging tot aan de ondertekening is momenteel enkel gemodelleerd voor verkoopdossiers — voor dit dossiertype bepaalt de notaris zelf de vervolgstappen."
    );
    return { stappen: [], termijnen: [], ontbrekendeStukken: [], onzekerheden };
  }

  const ontbrekendeStukken = berekenOntbrekendeStukken(dossier).map((id) => ({ id, label: stukLabel(id) }));
  const termijnen = berekenTermijnen(dossier, vandaag, onzekerheden);
  const metKrediet = dossier.dossiertype === "verkoop-met-krediet";

  const huidigeIndex = STATUS_VOLGORDE.indexOf(dossier.status);
  const stappen: OpvolgingStap[] = STATUS_VOLGORDE.map((status, i) => ({
    status,
    titel: STAP_TITELS[status],
    acties: actiesVoorStap(status, dossier, ontbrekendeStukken, termijnen),
    mailCategorieen: mailCategorieenVoorStap(status, metKrediet),
    toestand: i < huidigeIndex ? "afgerond" : i === huidigeIndex ? "huidig" : "te-doen",
  }));
  const huidigeStap = stappen[huidigeIndex];

  // Consistentiebewaking: een dossier dat (bijna) aktegereed heet te zijn maar
  // waar nog verplichte stukken ontbreken, verdient een uitdrukkelijke vlag.
  if (ontbrekendeStukken.length > 0 && (dossier.status === "ontwerp-verstuurd" || dossier.status === "klaar-voor-akte")) {
    onzekerheden.push(
      `Het dossier staat op "${dossier.status}" maar er ontbreken nog verplichte stukken: ${ontbrekendeStukken.map((s) => s.label).join(", ")}. Vraag ze op vóór het verlijden van de akte.`
    );
  }

  return { stappen, huidigeStap, termijnen, ontbrekendeStukken, onzekerheden };
}

// ── Urgentie per dossier (voor het overzicht) ─────────────────────────────────

export type UrgentieNiveau = "verstreken" | "nabij" | "ok" | "geen-termijnen";

/** De meest dringende lopende termijn van een dossier, samengevat voor het
 * dossieroverzicht (badge + sortering). */
export interface DossierUrgentie {
  niveau: UrgentieNiveau;
  /** Kort badge-label, bv. "Termijn verstreken" of "Akte binnen 9 d". */
  label?: string;
  /** De termijn waarop het niveau/label gebaseerd is. */
  termijn?: OpvolgingTermijn;
  /** Lager = dringender; stabiele sorteersleutel voor het overzicht. */
  sorteersleutel: number;
}

const KORTE_TERMIJN_TITEL: Record<OpvolgingTermijn["id"], string> = {
  "opschortende-voorwaarde-krediet": "Krediettermijn",
  "uiterste-datum-akte": "Akte",
  "fiscale-registratietermijn": "Registratie",
};

/**
 * Vat de lopende termijnen van een dossier samen tot één urgentieniveau voor
 * het overzicht: de meest nabije termijn bepaalt niveau en label. Puur:
 * `vandaag` (ISO "JJJJ-MM-DD") is een parameter. Dossiers zonder berekenbare
 * termijnen (geen compromisdatum, niet-verkooptype, of al verleden) krijgen
 * "geen-termijnen" — geen badge, achteraan in de urgentiesortering.
 *
 * Sorteersleutel: verstreken termijnen eerst (meest verstreken bovenaan),
 * daarna nakende/lopende oplopend op resterende dagen, daarna alles zonder
 * termijnen (onderling te ordenen door de aanroeper, bv. op `aangepast`).
 */
export function bepaalDossierUrgentie(dossier: Dossier, vandaag: string): DossierUrgentie {
  const { termijnen } = stelOpvolgingSamenAutonoom(dossier, vandaag);
  const meetbaar = termijnen.filter((t) => t.dagenResterend !== undefined);
  if (meetbaar.length === 0) return { niveau: "geen-termijnen", sorteersleutel: Number.MAX_SAFE_INTEGER };

  const dringendst = meetbaar.reduce((a, b) => (a.dagenResterend! <= b.dagenResterend! ? a : b));
  const dagen = dringendst.dagenResterend!;
  const kort = KORTE_TERMIJN_TITEL[dringendst.id];

  if (dagen < 0) {
    return {
      niveau: "verstreken",
      label: `${kort}: termijn verstreken`,
      termijn: dringendst,
      sorteersleutel: dagen,
    };
  }
  // Zelfde drempel als de onzekerheden-waarschuwing in berekenTermijnen (14 d).
  if (dagen <= 14) {
    return {
      niveau: "nabij",
      label: `${kort} binnen ${dagen} d`,
      termijn: dringendst,
      sorteersleutel: dagen,
    };
  }
  return { niveau: "ok", termijn: dringendst, sorteersleutel: dagen };
}
