// ── AUT-S1 · Gedeelde extractie-hulp voor de perceptielaag ───────────────────
// Pure parsing-primitieven die de extractors (lib/dossier/extractors/, AUT-O3)
// delen: Belgische bedragen en datums uit ruwe brontekst halen, en een
// GeextraheerdeWaarneming netjes bouwen. Bewust BUITEN de extractors-map: die
// map is een codegen-register (elk bestand hoort precies één `…Extractors` te
// exporteren), een hulpmodule zou de barrel-generator doen falen.
//
// Ontwerpregel (AUT-O3, beslissing 2): NOOIT GOKKEN. Een patroon dat niet
// betrouwbaar matcht, levert `undefined` (→ het veld ontbreekt en escaleert via
// perceptieGronden), nooit een verzonnen of half-geraden waarde.

import { parseBedrag } from "@/lib/format";
import type { Brondocument, GeextraheerdeWaarneming } from "./perceptie";
import type { Zekerheidsniveau } from "./autonomie";

/** Bouwt een waarneming met de bron/ref van het document eraan gehangen. */
export function waarneming(
  doc: Brondocument,
  veldpad: string,
  waarde: unknown,
  zekerheid: Zekerheidsniveau
): GeextraheerdeWaarneming {
  return { veldpad, waarde, bronType: doc.bronType, brondocumentRef: doc.ref, zekerheid };
}

/**
 * Geeft de eerste vanggroep van het eerste matchende patroon terug (getrimd),
 * of undefined. Handig om één tekstveld uit meerdere mogelijke bewoordingen te
 * halen. Elk patroon moet minstens één vanggroep hebben.
 */
export function eersteMatch(tekst: string, patronen: readonly RegExp[]): string | undefined {
  for (const p of patronen) {
    const m = p.exec(tekst);
    if (m && m[1] !== undefined) {
      const waarde = m[1].trim();
      if (waarde) return waarde;
    }
  }
  return undefined;
}

/**
 * Haalt een Belgisch bedrag uit de eerste matchende bewoording. De vanggroep
 * moet het ruwe bedrag bevatten (bv. "385.000,00" of "385000"); `parseBedrag`
 * (lib/format) past de Belgische conventie toe (punt = duizendtal, komma =
 * decimaal). Een niet-positief of onleesbaar bedrag → undefined (nooit 0 gokken).
 */
export function zoekBedrag(tekst: string, patronen: readonly RegExp[]): number | undefined {
  const rauw = eersteMatch(tekst, patronen);
  if (rauw === undefined) return undefined;
  const bedrag = parseBedrag(rauw);
  return Number.isFinite(bedrag) && bedrag > 0 ? bedrag : undefined;
}

/** Nederlandse (en enkele Franse) maandnamen → maandnummer (1-12). */
const MAANDEN: Record<string, number> = {
  januari: 1, februari: 2, maart: 3, april: 4, mei: 5, juni: 6,
  juli: 7, augustus: 8, september: 9, oktober: 10, november: 11, december: 12,
  janvier: 1, "février": 2, fevrier: 2, mars: 3, avril: 4, mai: 5, juin: 6,
  juillet: 7, "août": 8, aout: 8, septembre: 9, octobre: 10, novembre: 11, "décembre": 12, decembre: 12,
};

function isoUit(jaar: number, maand: number, dag: number): string | undefined {
  if (maand < 1 || maand > 12 || dag < 1 || dag > 31) return undefined;
  return `${jaar}-${String(maand).padStart(2, "0")}-${String(dag).padStart(2, "0")}`;
}

/**
 * Haalt de eerste herkenbare datum uit de tekst en normaliseert ze naar ISO
 * (JJJJ-MM-DD). Herkent ISO zelf, dag/maand/jaar met "/", "-" of "." én de
 * geschreven vorm "15 maart 2026" (NL/FR maandnamen). Voluit geschreven datums
 * ("vijftien maart tweeduizend zesentwintig", zoals in authentieke akten)
 * worden bewust NIET geraden → undefined.
 */
export function zoekDatumISO(tekst: string): string | undefined {
  const iso = /\b(\d{4})-(\d{2})-(\d{2})\b/.exec(tekst);
  if (iso) return isoUit(Number(iso[1]), Number(iso[2]), Number(iso[3]));

  const numeriek = /\b(\d{1,2})[/.\-](\d{1,2})[/.\-](\d{4})\b/.exec(tekst);
  if (numeriek) return isoUit(Number(numeriek[3]), Number(numeriek[2]), Number(numeriek[1]));

  const geschreven = /\b(\d{1,2})\s+([A-Za-zçéèûôî]+)\s+(\d{4})\b/.exec(tekst);
  if (geschreven) {
    const maand = MAANDEN[geschreven[2].toLowerCase()];
    if (maand) return isoUit(Number(geschreven[3]), maand, Number(geschreven[1]));
  }
  return undefined;
}

/**
 * Zoekt een datum die op een specifiek label volgt (bv. "Datum: 15/03/2026").
 * Doorloopt ALLE voorkomens van elk label (niet enkel het eerste): een label
 * dat toevallig vroeger matcht met een niet-datum in de vanggroep (bv. een
 * koptekst) mag de echte gelabelde datum verderop niet afschermen.
 */
export function zoekDatumNaLabel(tekst: string, labels: readonly RegExp[]): string | undefined {
  for (const label of labels) {
    const globaal = new RegExp(label.source, label.flags.includes("g") ? label.flags : `${label.flags}g`);
    for (const m of tekst.matchAll(globaal)) {
      if (m[1] !== undefined) {
        const datum = zoekDatumISO(m[1]);
        if (datum) return datum;
      }
    }
  }
  return undefined;
}
