import type { Hoofdthema, Modeldocument, Modelonderdeel, OnderdeelCategorie } from "./types";

// ── Afgeleid bibliotheekoverzicht ────────────────────────────────────────────
// Pure functies (geen React, geen browser-API's) die uit de modeldocumenten en
// modelonderdelen een kruisverwijzing afleiden: welke onderdelen in welke
// modellen voorkomen, welke onderdelen gedeeld worden over meerdere modellen,
// welke nergens gebruikt worden en welke gerefereerd maar niet gedefinieerd
// zijn. Wordt door de UI (OverzichtTab) telkens opnieuw berekend, zodat het
// overzicht automatisch meebeweegt met elke wijziging aan de seeds of de eigen
// items van de gebruiker.

/** Beknopte verwijzing naar een modeldocument dat een onderdeel gebruikt. */
export interface ModelVerwijzing {
  id: string;
  titel: string;
  akteType: string;
  thema?: Hoofdthema;
  /** Volgnummer (1-gebaseerd) van het onderdeel in de structuur van dit model. */
  positie: number;
  verplicht: boolean;
}

/** Gebruik van één onderdeel over alle modeldocumenten heen. */
export interface OnderdeelGebruik {
  onderdeelId: string;
  /** Het onderdeel zelf, of null wanneer het gerefereerd maar niet gedefinieerd is. */
  onderdeel: Modelonderdeel | null;
  modellen: ModelVerwijzing[];
}

/** Volledig afgeleid overzicht van de bibliotheekstructuur. */
export interface BibliotheekOverzicht {
  /** Eén regel per gerefereerd onderdeel-id, gesorteerd op aflopend hergebruik. */
  gebruikPerOnderdeel: OnderdeelGebruik[];
  /** Onderdelen die in twee of meer modeldocumenten voorkomen (gedeeld). */
  gedeeld: OnderdeelGebruik[];
  /** Gedefinieerde onderdelen die in geen enkel modeldocument voorkomen. */
  ongebruikt: Modelonderdeel[];
  /** Id's die een structuur aanroept maar die niet (meer) als onderdeel bestaan. */
  ontbrekend: OnderdeelGebruik[];
  /** Totaal aantal gedefinieerde onderdelen. */
  aantalOnderdelen: number;
  /** Totaal aantal modeldocumenten. */
  aantalModellen: number;
}

/**
 * Leidt de kruisverwijzing tussen modeldocumenten en modelonderdelen af.
 * Zuiver afgeleide data — verandert automatisch mee wanneer een model of
 * onderdeel wordt toegevoegd, gewijzigd of verwijderd.
 */
export function bouwBibliotheekOverzicht(
  modellen: Modeldocument[],
  onderdelen: Modelonderdeel[],
): BibliotheekOverzicht {
  const onderdeelPerId = new Map(onderdelen.map((o) => [o.id, o]));

  // onderdeelId → lijst van modellen die het gebruiken (met positie en verplicht).
  const gebruik = new Map<string, ModelVerwijzing[]>();
  for (const model of modellen) {
    model.structuur.forEach((item, index) => {
      const verwijzing: ModelVerwijzing = {
        id: model.id,
        titel: model.titel,
        akteType: model.akteType,
        thema: model.thema,
        positie: index + 1,
        verplicht: item.verplicht,
      };
      const bestaand = gebruik.get(item.onderdeelId);
      if (bestaand) bestaand.push(verwijzing);
      else gebruik.set(item.onderdeelId, [verwijzing]);
    });
  }

  const gebruikPerOnderdeel: OnderdeelGebruik[] = [...gebruik.entries()]
    .map(([onderdeelId, modellenLijst]) => ({
      onderdeelId,
      onderdeel: onderdeelPerId.get(onderdeelId) ?? null,
      modellen: modellenLijst,
    }))
    .sort(
      (a, b) =>
        b.modellen.length - a.modellen.length ||
        (a.onderdeel?.titel ?? a.onderdeelId).localeCompare(b.onderdeel?.titel ?? b.onderdeelId),
    );

  const gedeeld = gebruikPerOnderdeel.filter((g) => g.onderdeel && g.modellen.length >= 2);
  const ontbrekend = gebruikPerOnderdeel.filter((g) => !g.onderdeel);
  const ongebruikt = onderdelen
    .filter((o) => !gebruik.has(o.id))
    .sort((a, b) => a.titel.localeCompare(b.titel));

  return {
    gebruikPerOnderdeel,
    gedeeld,
    ongebruikt,
    ontbrekend,
    aantalOnderdelen: onderdelen.length,
    aantalModellen: modellen.length,
  };
}

// ── Aard van een document: authentieke akte of onderhands document ───────────
// De aard bepaalt de kleur in de netwerkgrafiek (akte = blauw, onderhands =
// oranje). Het meest betrouwbare signaal is de aanwezigheid van de verplichte
// slotclausule "ondertekening-onderhands-document" (zie AGENTS.md / Taak
// onderhandse ondertekening): elk onderhands document — en geen enkele akte —
// eindigt met die clausule. Daarnaast vangt het akteType-patroon de gevallen op
// waar die clausule (nog) niet in de structuur staat (bv. een compromis in
// heldere taal). Standaard = "akte".

export type DocumentAard = "akte" | "onderhands";

export function documentAard(model: Modeldocument): DocumentAard {
  const heeftOnderhandseOndertekening = model.structuur.some(
    (s) => s.onderdeelId === "ondertekening-onderhands-document",
  );
  if (heeftOnderhandseOndertekening) return "onderhands";
  if (/onderhands|compromis|overeenkomst/i.test(model.akteType)) return "onderhands";
  return "akte";
}

// ── Netwerk: onderdelen als knopen, documenten als verbindingen ──────────────
// Elk gebruikt modelonderdeel is een knoop; elk modeldocument verbindt zijn
// onderdelen (opeenvolgend in de structuur) met elkaar. Zuiver afgeleide data —
// de grafiek beweegt automatisch mee met elke wijziging aan de bibliotheek.

/** Knoop in de netwerkgrafiek: één gebruikt modelonderdeel. */
export interface NetwerkKnoop {
  id: string;
  titel: string;
  categorie: OnderdeelCategorie;
  /** Aantal modeldocumenten waarin het onderdeel voorkomt. */
  gebruik: number;
  modellen: { id: string; titel: string; aard: DocumentAard }[];
}

/** Verbinding tussen twee knopen, behorend bij één modeldocument. */
export interface NetwerkVerbinding {
  modelId: string;
  titel: string;
  akteType: string;
  thema?: Hoofdthema;
  aard: DocumentAard;
  /** onderdeel-id's van de twee verbonden knopen. */
  bron: string;
  doel: string;
}

export interface Netwerk {
  knopen: NetwerkKnoop[];
  verbindingen: NetwerkVerbinding[];
}

/**
 * Bouwt de knoop-/verbindingsstructuur voor de netwerkgrafiek. Enkel onderdelen
 * die in minstens één modeldocument voorkomen worden knopen; ontbrekende
 * verwijzingen worden overgeslagen.
 */
export function bouwNetwerk(modellen: Modeldocument[], onderdelen: Modelonderdeel[]): Netwerk {
  const onderdeelPerId = new Map(onderdelen.map((o) => [o.id, o]));
  const knoopPerId = new Map<string, NetwerkKnoop>();
  const verbindingen: NetwerkVerbinding[] = [];

  for (const model of modellen) {
    const aard = documentAard(model);
    // Enkel bestaande onderdelen, in structuurvolgorde.
    const aanwezig = model.structuur
      .map((s) => s.onderdeelId)
      .filter((id) => onderdeelPerId.has(id));

    for (const id of aanwezig) {
      const onderdeel = onderdeelPerId.get(id)!;
      let knoop = knoopPerId.get(id);
      if (!knoop) {
        knoop = {
          id,
          titel: onderdeel.titel,
          categorie: onderdeel.categorie,
          gebruik: 0,
          modellen: [],
        };
        knoopPerId.set(id, knoop);
      }
      knoop.gebruik += 1;
      knoop.modellen.push({ id: model.id, titel: model.titel, aard });
    }

    // Verbind opeenvolgende onderdelen tot één draad door het document.
    for (let i = 0; i < aanwezig.length - 1; i++) {
      verbindingen.push({
        modelId: model.id,
        titel: model.titel,
        akteType: model.akteType,
        thema: model.thema,
        aard,
        bron: aanwezig[i],
        doel: aanwezig[i + 1],
      });
    }
  }

  const knopen = [...knoopPerId.values()].sort((a, b) => b.gebruik - a.gebruik);
  return { knopen, verbindingen };
}
