// ── Tegenstrijdigheden tussen bronnen ────────────────────────────────────────
// Pure functies die meerdere waarnemingen van eenzelfde veld (uit verschillende
// brondocumenten) samenbrengen. De waarneming met het hoogste brongezag
// (laagste bronPrioriteit-getal — authentieke bron > onderhandse bron) wordt de
// effectieve waarde; afwijkende waarnemingen blijven als tegenstrijdigheid
// zichtbaar in plaats van stilzwijgend overschreven te worden.

import { euro } from "@/lib/format";
import {
  bronPrioriteit,
  type Dossier,
  type Goed,
  type Tegenstrijdigheid,
  type Veld,
  type Waarneming,
} from "./types";

/** Alle waarnemingen van een veld: de primaire plus de alternatieven. */
export function waarnemingen<T>(veld: Veld<T>): Waarneming<T>[] {
  return [
    { waarde: veld.waarde, bron: veld.bron, brondocumentId: veld.brondocumentId },
    ...(veld.alternatieven ?? []),
  ];
}

/** Sorteert waarnemingen op brongezag: hoogste gezag (laagste getal) eerst. */
function sorteerOpGezag<T>(ws: Waarneming<T>[]): Waarneming<T>[] {
  return [...ws].sort((a, b) => bronPrioriteit[a.bron] - bronPrioriteit[b.bron]);
}

/** De waarneming met het hoogste brongezag — de effectieve veldwaarde. */
export function effectieveWaarneming<T>(veld: Veld<T>): Waarneming<T> {
  return sorteerOpGezag(waarnemingen(veld))[0];
}

/**
 * Bouwt één Veld uit meerdere waarnemingen: de waarneming met het hoogste
 * brongezag wordt de primaire waarde; afwijkende waarnemingen uit minder
 * gezaghebbende bronnen blijven als alternatieven bewaard. Identieke
 * waarnemingen (zelfde geformatteerde waarde) worden niet als alternatief
 * bijgehouden — enkel echte tegenstrijdigheden.
 */
export function maakVeldUitWaarnemingen<T>(
  ws: Waarneming<T>[],
  format: (waarde: T) => string = (waarde) => JSON.stringify(waarde)
): Veld<T> {
  if (ws.length === 0) {
    throw new Error("maakVeldUitWaarnemingen vereist minstens één waarneming");
  }
  const [primair, ...rest] = sorteerOpGezag(ws);
  const alternatieven = rest.filter((w) => format(w.waarde) !== format(primair.waarde));
  return {
    waarde: primair.waarde,
    bron: primair.bron,
    brondocumentId: primair.brondocumentId,
    ...(alternatieven.length > 0 ? { alternatieven } : {}),
  };
}

/** True wanneer een veld waarnemingen uit verschillende bronnen heeft die niet overeenstemmen. */
export function isTegenstrijdig<T>(veld: Veld<T>, format: (waarde: T) => string): boolean {
  return veldTegenstrijdigheid("", veld, format) !== null;
}

/**
 * Bouwt een Tegenstrijdigheid voor één veld, of null wanneer alle waarnemingen
 * overeenstemmen. De afwijkende waarden worden, hoogste gezag eerst en ontdaan
 * van dubbels, opgesomd met hun bron.
 */
export function veldTegenstrijdigheid<T>(
  veldNaam: string,
  veld: Veld<T> | undefined,
  format: (waarde: T) => string,
  veldSleutel?: Tegenstrijdigheid["veldSleutel"]
): Tegenstrijdigheid | null {
  if (!veld) return null;
  const gesorteerd = sorteerOpGezag(waarnemingen(veld));
  const waarden: { bron: Tegenstrijdigheid["waarden"][number]["bron"]; waarde: string }[] = [];
  const gezien = new Set<string>();
  for (const w of gesorteerd) {
    const geformatteerd = format(w.waarde);
    if (gezien.has(geformatteerd)) continue;
    gezien.add(geformatteerd);
    waarden.push({ bron: w.bron, waarde: geformatteerd });
  }
  if (waarden.length < 2) return null;
  return { veld: veldNaam, veldSleutel, waarden, opgelost: false };
}

/**
 * Herschikt een veld zodat de waarneming van de gekozen bron de primaire waarde
 * wordt; de overige waarnemingen blijven als alternatief behouden. Gebruikt
 * wanneer de notaris bij een tegenstrijdigheid expliciet een andere bron dan de
 * gezaghebbendste als juist aanduidt. Onbekende bron → veld ongewijzigd.
 */
export function kiesPrimaireBron<T>(veld: Veld<T>, bron: Veld<T>["bron"]): Veld<T> {
  const alle = waarnemingen(veld);
  const gekozen = alle.find((w) => w.bron === bron);
  if (!gekozen) return veld;
  const rest = alle.filter((w) => w !== gekozen);
  return {
    waarde: gekozen.waarde,
    bron: gekozen.bron,
    brondocumentId: gekozen.brondocumentId,
    ...(rest.length > 0 ? { alternatieven: rest } : {}),
  };
}

/** Vergelijkingssleutel van een goed voor tegenstrijdigheids- en samenvoeglogica. */
export function formatGoed(goed: Goed): string {
  return [goed.adres, goed.kadastraleGegevens].filter(Boolean).join(" — ");
}

/**
 * Doorloopt de tegenstrijdigheidsgevoelige velden van een dossier en levert de
 * vastgestelde conflicten. Reeds als opgelost gemarkeerde tegenstrijdigheden
 * (op veldnaam) behouden hun status, zodat een handmatige validatie niet
 * verloren gaat bij herberekening.
 */
export function berekenTegenstrijdigheden(dossier: Dossier): Tegenstrijdigheid[] {
  const opgelostVoor = new Set(
    dossier.tegenstrijdigheden.filter((t) => t.opgelost).map((t) => t.veld)
  );
  const kandidaten = [
    veldTegenstrijdigheid("Verkoopprijs", dossier.prijs, euro, "prijs"),
    veldTegenstrijdigheid("Voorschot", dossier.voorschot, euro, "voorschot"),
    veldTegenstrijdigheid("Compromisdatum", dossier.compromisdatum, (d) => d, "compromisdatum"),
    veldTegenstrijdigheid("Belastbare waarde", dossier.belastbareWaarde, euro, "belastbareWaarde"),
    veldTegenstrijdigheid("Goed", dossier.goed, formatGoed, "goed"),
  ];
  return kandidaten
    .filter((t): t is Tegenstrijdigheid => t !== null)
    .map((t) => (opgelostVoor.has(t.veld) ? { ...t, opgelost: true } : t));
}
