// ── Kadastrale vergelijking: eigendomstitel ↔ recent uittreksel ↔ plan ───────
// Pure functies (geen React, geen I/O) die de kadastrale aanduidingen uit de
// eigendomstitel, het recente kadastrale uittreksel en het opmetingsplan
// naast elkaar leggen. Titel en recent uittreksel gebruiken voor hetzelfde
// perceel vaak een ander formaat ("60 F 22" tegenover "0060F22P0000") en een
// andere oppervlakte-notatie ("1a 80ca" tegenover "180 m²"): deze module
// normaliseert beide kanten en besluit "zelfde", "verschillend" of
// "onvergelijkbaar" — nooit gokken: wat niet parsebaar is, wordt als
// onvergelijkbaar gemeld en blijft dus een expliciet nazichtpunt.

/** Ontleed Belgisch perceelnummer: grondnummer[/bisnummer] [letterexponent][cijferexponent] [partitie]. */
export interface Perceelnummer {
  grondnummer: number;
  bisnummer?: number;
  letterexponent?: string;
  cijferexponent?: number;
  /** Partitienummer uit het recente formaat (bv. "P0000"); ontbreekt in oude titels. */
  partitie?: string;
}

/**
 * Parseert zowel het historische titelformaat ("60 F 22", "60/2 F 3", "125 B")
 * als het recente uittrekselformaat ("0060F22P0000", "0060/02F022P0001").
 * Retourneert null wanneer de tekst niet met zekerheid te ontleden is.
 */
export function parsePerceelnummer(tekst: string): Perceelnummer | null {
  const t = tekst.trim().toUpperCase();
  if (!t) return null;

  // Recent formaat: 4 cijfers grond, optioneel /2 cijfers bis, 1 letter,
  // 2 cijfers exponent, P + 4 cijfers partitie (bv. "0060F22P0000").
  const recent = t.match(/^(\d{4})(?:\/(\d{2}))?([A-Z])(\d{2})(P\d{4})$/);
  if (recent) {
    return {
      grondnummer: Number(recent[1]),
      bisnummer: recent[2] ? Number(recent[2]) : undefined,
      letterexponent: recent[3] === "_" ? undefined : recent[3],
      cijferexponent: Number(recent[4]) || undefined,
      partitie: recent[5],
    };
  }

  // Historisch/vrij formaat: "60 F 22", "60/2F3", "125 B", "60 f/22", "319 E".
  const oud = t.match(/^(\d{1,4})(?:\s*\/\s*(\d{1,2}))?\s*([A-Z])?\s*\/?\s*(\d{1,2})?$/);
  if (oud && (oud[3] || oud[4] || oud[2])) {
    return {
      grondnummer: Number(oud[1]),
      bisnummer: oud[2] ? Number(oud[2]) : undefined,
      letterexponent: oud[3] || undefined,
      cijferexponent: oud[4] ? Number(oud[4]) : undefined,
    };
  }
  // Enkel een kaal grondnummer ("60"): geldig maar arm — toch ontleden.
  if (/^\d{1,4}$/.test(t)) return { grondnummer: Number(t) };
  return null;
}

/**
 * Parseert een oppervlakte naar vierkante meter (centiare). Ondersteunt het
 * titelformaat in hectare/are/centiare ("1ha 20a 35ca", "1 a 80 ca") en het
 * metrische formaat ("180 m²", "1.234,56 m²", "12035 m2"). Null bij twijfel.
 */
export function parseOppervlakteM2(tekst: string): number | null {
  const t = tekst.trim().toLowerCase().replace(/ /g, " ");
  if (!t) return null;

  // ha/a/ca-notatie (volgorde vrij, elk hoogstens één keer).
  const haacaRe = /(\d+(?:[.,]\d+)?)\s*(ha|a|ca)(?![a-z])/g;
  let m: RegExpExecArray | null;
  let totaal = 0;
  let gevonden = false;
  const gezien = new Set<string>();
  while ((m = haacaRe.exec(t)) !== null) {
    const eenheid = m[2];
    if (gezien.has(eenheid)) return null; // dubbelzinnig ("2a 3a")
    gezien.add(eenheid);
    gevonden = true;
    const waarde = Number(m[1].replace(",", "."));
    totaal += waarde * (eenheid === "ha" ? 10_000 : eenheid === "a" ? 100 : 1);
  }
  if (gevonden) {
    // De hele string moet uit ha/a/ca-termen bestaan (geen restjes zoals "m²").
    const rest = t.replace(haacaRe, "").replace(/[\s,;]+/g, "");
    return rest === "" ? totaal : null;
  }

  // m²-notatie: "180 m²", "1.234,56 m²", "12035" (kale waarde = m² aannemen is
  // gokken → enkel met expliciete eenheid).
  const m2 = t.match(/^([\d.., ]+)\s*(?:m²|m2|vierkante meter|m\^2)$/);
  if (m2) {
    const cijfers = m2[1].replace(/\s/g, "");
    // "1.234,56" (BE) → 1234.56; "1234.56" (int'l) → idem; "12.035" → 12035.
    const genormaliseerd = /,\d{1,2}$/.test(cijfers)
      ? cijfers.replace(/\./g, "").replace(",", ".")
      : cijfers.replace(/\.(?=\d{3}(\D|$))/g, "").replace(",", ".");
    const waarde = Number(genormaliseerd);
    return Number.isFinite(waarde) ? waarde : null;
  }
  return null;
}

/** Menselijke weergave van een oppervlakte in m², zonder onnodige decimalen. */
export function formatteerM2(m2: number): string {
  const afgerond = Math.round(m2 * 100) / 100;
  return `${afgerond.toLocaleString("nl-BE")} m²`;
}

export type VergelijkingsUitkomst = "zelfde" | "verschillend" | "onvergelijkbaar";

export interface KadasterVergelijking {
  sectie: VergelijkingsUitkomst;
  perceelnummer: VergelijkingsUitkomst;
  oppervlakte: VergelijkingsUitkomst;
  /** Verschil in m² (recent − titel), enkel bij twee parsebare oppervlaktes. */
  oppervlakteVerschilM2?: number;
  /** Leesbare meldingen per afwijking of onvergelijkbaarheid — leeg = alles zelfde. */
  meldingen: string[];
}

/** Kadastrale aanduiding van één bron (titel, recent uittreksel of plan). */
export interface KadastraleAanduiding {
  sectie?: string;
  perceelnummer?: string;
  oppervlakte?: string;
}

function vergelijkSectie(titel?: string, recent?: string): VergelijkingsUitkomst {
  const a = titel?.trim().toUpperCase();
  const b = recent?.trim().toUpperCase();
  if (!a || !b) return "onvergelijkbaar";
  return a === b ? "zelfde" : "verschillend";
}

function zelfdePerceel(a: Perceelnummer, b: Perceelnummer): boolean {
  return (
    a.grondnummer === b.grondnummer &&
    (a.bisnummer ?? 0) === (b.bisnummer ?? 0) &&
    (a.letterexponent ?? "") === (b.letterexponent ?? "") &&
    (a.cijferexponent ?? 0) === (b.cijferexponent ?? 0)
    // partitie bewust genegeerd: bestaat enkel in het recente formaat.
  );
}

/**
 * Legt de aanduiding uit de eigendomstitel naast die van het recente
 * kadastrale uittreksel. Elke afwijking of onvergelijkbaarheid levert een
 * leesbare melding op voor het nazicht door de notaris.
 */
export function vergelijkTitelMetRecent(
  titel: KadastraleAanduiding,
  recent: KadastraleAanduiding
): KadasterVergelijking {
  const meldingen: string[] = [];

  const sectie = vergelijkSectie(titel.sectie, recent.sectie);
  if (sectie === "verschillend") {
    meldingen.push(`Sectie wijkt af: titel "${titel.sectie}" ↔ recent uittreksel "${recent.sectie}".`);
  } else if (sectie === "onvergelijkbaar") {
    meldingen.push("Sectie niet vergelijkbaar: één van beide bronnen ontbreekt.");
  }

  let perceelnummer: VergelijkingsUitkomst = "onvergelijkbaar";
  const pTitel = titel.perceelnummer ? parsePerceelnummer(titel.perceelnummer) : null;
  const pRecent = recent.perceelnummer ? parsePerceelnummer(recent.perceelnummer) : null;
  if (pTitel && pRecent) {
    perceelnummer = zelfdePerceel(pTitel, pRecent) ? "zelfde" : "verschillend";
    if (perceelnummer === "verschillend") {
      meldingen.push(
        `Perceelnummer wijkt af: titel "${titel.perceelnummer}" ↔ recent uittreksel "${recent.perceelnummer}" (ook na normalisatie van het formaat).`
      );
    }
  } else if (titel.perceelnummer || recent.perceelnummer) {
    meldingen.push(
      `Perceelnummer niet automatisch vergelijkbaar (formaat niet herkend of bron ontbreekt): titel "${titel.perceelnummer ?? "—"}" ↔ recent "${recent.perceelnummer ?? "—"}" — manueel nakijken.`
    );
  }

  let oppervlakte: VergelijkingsUitkomst = "onvergelijkbaar";
  let oppervlakteVerschilM2: number | undefined;
  const oTitel = titel.oppervlakte ? parseOppervlakteM2(titel.oppervlakte) : null;
  const oRecent = recent.oppervlakte ? parseOppervlakteM2(recent.oppervlakte) : null;
  if (oTitel !== null && oTitel !== undefined && oRecent !== null && oRecent !== undefined) {
    oppervlakteVerschilM2 = Math.round((oRecent - oTitel) * 100) / 100;
    oppervlakte = oppervlakteVerschilM2 === 0 ? "zelfde" : "verschillend";
    if (oppervlakte === "verschillend") {
      meldingen.push(
        `Oppervlakte wijkt af: titel ${titel.oppervlakte} (= ${formatteerM2(oTitel)}) ↔ recent uittreksel ${recent.oppervlakte} (= ${formatteerM2(oRecent)}); verschil ${formatteerM2(Math.abs(oppervlakteVerschilM2))}.`
      );
    }
  } else if (titel.oppervlakte || recent.oppervlakte) {
    meldingen.push(
      `Oppervlakte niet automatisch vergelijkbaar (notatie niet herkend of bron ontbreekt): titel "${titel.oppervlakte ?? "—"}" ↔ recent "${recent.oppervlakte ?? "—"}" — manueel nakijken.`
    );
  }

  return { sectie, perceelnummer, oppervlakte, oppervlakteVerschilM2, meldingen };
}

/**
 * Vergelijkt daarnaast het opmetingsplan (indien voorhanden) met het recente
 * uittreksel: zelfde spel, andere bron. De planoppervlakte is doorgaans de
 * meest precieze — een afwijking met het kadaster is normaal maar moet gekend
 * zijn vóór het verlijden.
 */
export function vergelijkPlanMetRecent(
  plan: KadastraleAanduiding,
  recent: KadastraleAanduiding
): KadasterVergelijking {
  const v = vergelijkTitelMetRecent(plan, recent);
  return { ...v, meldingen: v.meldingen.map((m) => m.replace(/titel/g, "plan").replace(/^Titel/, "Plan")) };
}

/**
 * De afgeleide waarde voor {{oppervlakte_recent_kadaster_vermelding}} in de
 * verplichte beschrijvingsclausule: "dezelfde oppervlakte" wanneer titel en
 * recent uittreksel na omzetting exact overeenkomen, anders de voluit
 * geschreven vermelding met de oppervlakte van het recente uittreksel.
 */
export function oppervlakteRecentVermelding(oppervlakteTitel?: string, oppervlakteRecent?: string): string | undefined {
  if (!oppervlakteRecent?.trim()) return undefined;
  const oTitel = oppervlakteTitel ? parseOppervlakteM2(oppervlakteTitel) : null;
  const oRecent = parseOppervlakteM2(oppervlakteRecent);
  if (oTitel !== null && oRecent !== null && oTitel === oRecent) return "dezelfde oppervlakte";
  return `een oppervlakte van ${oppervlakteRecent.trim()}`;
}
