/**
 * Laden und Prüfen der Themengliederung.
 *
 * Wie Erklärungen, Glossar und Normtexte eine eigene Datei:
 *
 *  - Entwicklung: `<repo>/content/themen.json`
 *  - Gepackt:     `<app>/resources/themen.json`
 *
 * Fehlt sie oder ist sie unbrauchbar, läuft die Anwendung ohne. Dann steht in
 * den Kapiteln II bis IV nur der Knopf für das ganze Kapitel – also genau der
 * Zustand vor 0.22.0.
 *
 * Die Prüfung hier betrifft nur die Form. Dass jede Frage der Kapitel II bis
 * IV in genau einer Gruppe liegt, sichert `data-pipeline/themen_bauen.py` vor
 * der Auslieferung – dort steht der Katalog zur Verfügung, hier nicht.
 */

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

import { app } from 'electron';

import { THEMEN_LEER, type Themen, type Themengruppe } from '../shared/themen';
import { sucheAufwaerts } from './katalog';

const DATEI = 'themen.json';

function ungueltig(nachricht: string): never {
  throw new Error(`Themen 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.trim().length === 0) {
    ungueltig(`${pfad} fehlt oder ist leer.`);
  }
  return wert;
}

function gruppeLesen(roh: unknown, pfad: string): Themengruppe {
  const o = objekt(roh, pfad);

  const fragenRoh = o['fragen'];
  if (!Array.isArray(fragenRoh)) {
    ungueltig(`${pfad}.fragen fehlt oder ist keine Liste.`);
  }
  const fragen = (fragenRoh as readonly unknown[]).map((eintrag, i) =>
    pflichttext(eintrag, `${pfad}.fragen[${String(i)}]`),
  );
  if (fragen.length === 0) {
    /* Eine leere Gruppe erschiene als Knopf, der eine Sitzung ohne Fragen
       startet – schlimmer als eine fehlende Gruppe. */
    ungueltig(`${pfad}.fragen ist leer.`);
  }

  return {
    id: pflichttext(o['id'], `${pfad}.id`),
    kapitel: pflichttext(o['kapitel'], `${pfad}.kapitel`),
    titel: pflichttext(o['titel'], `${pfad}.titel`),
    fragen,
  };
}

function themenValidieren(roh: unknown): Themen {
  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 gruppenRoh = o['gruppen'];
  if (!Array.isArray(gruppenRoh)) {
    ungueltig('gruppen fehlt oder ist keine Liste.');
  }
  const gruppen = (gruppenRoh as readonly unknown[]).map((gruppe, i) =>
    gruppeLesen(gruppe, `gruppen[${String(i)}]`),
  );

  /* Doppelte Kennungen wären in der Anzeige mehrdeutig, doppelte Fragen
     hießen, dass ein Thema Fragen eines anderen mitzieht. */
  const kennungen = new Set<string>();
  const fragen = new Set<string>();
  for (const gruppe of gruppen) {
    if (kennungen.has(gruppe.id)) {
      ungueltig(`Die Gruppenkennung „${gruppe.id}“ kommt mehrfach vor.`);
    }
    kennungen.add(gruppe.id);
    for (const frageId of gruppe.fragen) {
      if (fragen.has(frageId)) {
        ungueltig(`${frageId} steht in mehr als einer Gruppe.`);
      }
      fragen.add(frageId);
    }
  }

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

/** Pfad der Themendatei – gepackt wie ungepackt. */
export function themenPfad(): 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: Themen | null = null;

export function themenLaden(): Themen {
  if (speicher !== null) {
    return speicher;
  }

  const pfad = themenPfad();
  if (!existsSync(pfad)) {
    speicher = THEMEN_LEER;
    return speicher;
  }

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

  return speicher;
}

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