/**
 * Laden und Prüfen der mitgelieferten Normtexte.
 *
 * Wie Erklärungen und Glossar eine eigene Datei:
 *
 *  - Entwicklung: `<repo>/content/normtexte.json`
 *  - Gepackt:     `<app>/resources/normtexte.json`
 *
 * Fehlt sie oder ist sie unbrauchbar, läuft die Anwendung ohne. Die
 * Fundstellen stehen dann wie bisher als Zitat da – der Lernbetrieb hängt
 * nicht daran.
 *
 * Erzeugt wird die Datei von `data-pipeline/normtexte_bauen.py` aus
 * `content/gesetze/index.json`. Die Prüfung hier betrifft nur die Form; dass
 * der Wortlaut der amtliche ist, sichert die Pipeline, die ihn unverändert
 * übernimmt und die verlustfreie Zerlegung der Anlagen nachrechnet.
 */

import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';

import { app } from 'electron';

import {
  NORMTEXTE_LEER,
  type Anlagenblock,
  type Gesetzestext,
  type Normtext,
  type Normtexte,
} from '../shared/normtexte';
import { sucheAufwaerts } from './katalog';

const DATEI = 'normtexte.json';

function ungueltig(nachricht: string): never {
  throw new Error(`Normtexte ungültig: ${nachricht}`);
}

function objekt(wert: unknown, pfad: string): Record<string, unknown> {
  if (typeof wert !== 'object' || wert === null || Array.isArray(wert)) {
    ungueltig(`${pfad} ist kein Objekt.`);
  }
  return wert as Record<string, unknown>;
}

function pflichttext(wert: unknown, pfad: string): string {
  if (typeof wert !== 'string' || wert.length === 0) {
    ungueltig(`${pfad} fehlt oder ist leer.`);
  }
  return wert;
}

function textabbildung(wert: unknown, pfad: string): Record<string, string> {
  const roh = objekt(wert, pfad);
  const abbildung: Record<string, string> = {};
  for (const [schluessel, text] of Object.entries(roh)) {
    /* Leere Absätze gibt es im amtlichen Text: „(weggefallen)" steht dort
       teils als leere Zeichenkette. Sie zu verwerfen wäre eine Lücke, die
       niemand erwartet – sie durchzulassen ist die Wahrheit. */
    if (typeof text !== 'string') {
      ungueltig(`${pfad}.${schluessel} ist kein Text.`);
    }
    abbildung[schluessel] = text;
  }
  return abbildung;
}

function blockLesen(roh: unknown, pfad: string): Anlagenblock {
  const o = objekt(roh, pfad);

  const marke = o['marke'];
  if (marke !== null && typeof marke !== 'string') {
    ungueltig(`${pfad}.marke muss Text oder null sein.`);
  }

  const wegRoh = o['pfad'];
  if (!Array.isArray(wegRoh)) {
    ungueltig(`${pfad}.pfad fehlt oder ist keine Liste.`);
  }
  const weg = (wegRoh as readonly unknown[]).map((teil, i) =>
    pflichttext(teil, `${pfad}.pfad[${String(i)}]`),
  );

  const text = o['text'];
  if (typeof text !== 'string') {
    ungueltig(`${pfad}.text fehlt.`);
  }

  return { marke, pfad: weg, text };
}

function normLesen(roh: unknown, pfad: string): Normtext {
  const o = objekt(roh, pfad);

  const istAnlage = o['istAnlage'];
  if (typeof istAnlage !== 'boolean') {
    ungueltig(`${pfad}.istAnlage fehlt.`);
  }

  /* Der Titel darf leer sein: Manche Anlagen tragen im amtlichen XML gar
     keinen – SprengG Anlage II etwa. Einen zu erfinden wäre schlechter. */
  const titel = o['titel'];
  if (typeof titel !== 'string') {
    ungueltig(`${pfad}.titel fehlt.`);
  }

  if (istAnlage) {
    const bloeckeRoh = o['bloecke'];
    if (!Array.isArray(bloeckeRoh)) {
      ungueltig(`${pfad}.bloecke fehlt oder ist keine Liste.`);
    }
    return {
      titel,
      istAnlage: true,
      bloecke: (bloeckeRoh as readonly unknown[]).map((block, i) =>
        blockLesen(block, `${pfad}.bloecke[${String(i)}]`),
      ),
    };
  }

  const text = o['text'];
  if (text !== undefined && typeof text !== 'string') {
    ungueltig(`${pfad}.text ist kein Text.`);
  }

  return {
    titel,
    istAnlage: false,
    absaetze: textabbildung(o['absaetze'], `${pfad}.absaetze`),
    ...(text === undefined ? {} : { text }),
  };
}

function gesetzLesen(roh: unknown, pfad: string): Gesetzestext {
  const o = objekt(roh, pfad);
  const normenRoh = objekt(o['normen'], `${pfad}.normen`);

  const normen: Record<string, Normtext> = {};
  for (const [name, norm] of Object.entries(normenRoh)) {
    normen[name] = normLesen(norm, `${pfad}.normen["${name}"]`);
  }

  return {
    bezeichnung: pflichttext(o['bezeichnung'], `${pfad}.bezeichnung`),
    stand: pflichttext(o['stand'], `${pfad}.stand`),
    quelle: pflichttext(o['quelle'], `${pfad}.quelle`),
    normen,
  };
}

function normtexteValidieren(roh: unknown): Normtexte {
  const o = objekt(roh, 'Die Datei');
  const meta = objekt(o['meta'], 'meta');

  const version = meta['version'];
  if (typeof version !== 'number' || !Number.isInteger(version) || version < 1) {
    ungueltig('meta.version fehlt oder ist keine ganze Zahl ab 1.');
  }

  const standRoh = objekt(meta['gesetzesstand'], 'meta.gesetzesstand');
  const gesetzesstand: Record<string, string> = {};
  for (const [kuerzel, stand] of Object.entries(standRoh)) {
    gesetzesstand[kuerzel] = pflichttext(stand, `meta.gesetzesstand.${kuerzel}`);
  }

  const gesetzeRoh = objekt(o['gesetze'], 'gesetze');
  const gesetze: Record<string, Gesetzestext> = {};
  for (const [kuerzel, gesetz] of Object.entries(gesetzeRoh)) {
    gesetze[kuerzel] = gesetzLesen(gesetz, `gesetze.${kuerzel}`);
  }

  return {
    meta: {
      version,
      stand: pflichttext(meta['stand'], 'meta.stand'),
      hinweis: pflichttext(meta['hinweis'], 'meta.hinweis'),
      gesetzesstand,
    },
    gesetze,
  };
}

/** Pfad der Normtextdatei – gepackt wie ungepackt. */
export function normtextePfad(): string {
  if (app.isPackaged) {
    return join(process.resourcesPath, DATEI);
  }

  const relativ = join('content', DATEI);
  for (const start of [app.getAppPath(), __dirname, process.cwd()]) {
    const treffer = sucheAufwaerts(start, relativ);
    if (treffer) {
      return treffer;
    }
  }
  return join(app.getAppPath(), '..', relativ);
}

let speicher: Normtexte | null = null;

export function normtexteLaden(): Normtexte {
  if (speicher !== null) {
    return speicher;
  }

  const pfad = normtextePfad();
  if (!existsSync(pfad)) {
    speicher = NORMTEXTE_LEER;
    return speicher;
  }

  try {
    speicher = normtexteValidieren(JSON.parse(readFileSync(pfad, 'utf8')));
  } catch (fehler: unknown) {
    console.error('Normtexte konnten nicht gelesen werden:', fehler);
    speicher = NORMTEXTE_LEER;
  }

  return speicher;
}

/** Gegenstück für Tests. */
export function normtexteZuruecksetzen(): void {
  speicher = null;
}
