/**
 * Erklärungen zu den Fragen des amtlichen Katalogs.
 *
 * Der Fragenkatalog des Bundesverwaltungsamtes nennt bei Freitextfragen eine
 * Musterantwort und markiert bei Auswahlfragen die zutreffenden Antworten. Was
 * er nicht sagt: **warum**. Genau das leisten die Erklärungen – und das ist
 * der eine Punkt, an dem kein Wettbewerber etwas anbietet.
 *
 * ## Trennung vom amtlichen Werk
 *
 * Die Erklärungen sind eigener redaktioneller Inhalt und stehen deshalb in
 * einer eigenen Datei, nicht im Katalog. Der amtliche Katalog ist ein
 * amtliches Werk nach § 5 Abs. 2 UrhG und wird unverändert wiedergegeben
 * (§ 62 UrhG); eigene Zusätze dürfen ihn nicht vermischen. In der Oberfläche
 * sind sie sichtbar als Ergänzung gekennzeichnet.
 *
 * ## Zusage an die Richtigkeit
 *
 * Jede Fundstelle ist **strukturiert** angegeben, nicht als Fließtext. Nur so
 * lässt sie sich maschinell gegen den amtlichen Gesetzestext prüfen:
 * `data-pipeline/pruefe_erklaerungen.py` schlägt jede einzelne Angabe im Index
 * aus `content/gesetze/index.json` nach und bricht ab, wenn ein Paragraf, ein
 * Absatz, eine Nummer oder ein Buchstabe nicht existiert. Eine erfundene
 * Fundstelle kommt damit nicht in die Auslieferung.
 *
 * Was die Prüfung **nicht** leisten kann: ob die zitierte Norm die Aussage
 * auch trägt. Dafür gibt es die redaktionelle Durchsicht – und die
 * ausdrückliche Angabe {@link Erklaerung.ohneFundstelleGrund}, die verlangt,
 * das Fehlen einer Fundstelle zu begründen, statt eine zu erfinden.
 */

/** Gesetze, auf die sich eine Fundstelle beziehen darf. */
export const GESETZE = [
  'WaffG',
  'AWaffV',
  'BeschG',
  'BeschussV',
  'StGB',
  'SprengG',
  '1. SprengV',
] as const;

export type Gesetzeskuerzel = (typeof GESETZE)[number];

/** Ausgeschriebene Bezeichnung für die Anzeige. */
export const GESETZ_BEZEICHNUNG: Readonly<Record<Gesetzeskuerzel, string>> = Object.freeze({
  WaffG: 'Waffengesetz',
  AWaffV: 'Allgemeine Waffengesetz-Verordnung',
  BeschG: 'Beschussgesetz',
  BeschussV: 'Beschussverordnung',
  StGB: 'Strafgesetzbuch',
  SprengG: 'Sprengstoffgesetz',
  '1. SprengV': 'Erste Verordnung zum Sprengstoffgesetz',
});

/**
 * Eine Fundstelle im Gesetz – strukturiert, damit sie prüfbar bleibt.
 *
 * Bewusst kein Fließtext: „siehe § 12 WaffG" ließe sich nicht nachschlagen,
 * ohne den Satz zu zerlegen, und jede Abweichung in der Schreibweise wäre ein
 * neues Sonderproblem.
 */
export interface Fundstelle {
  readonly gesetz: Gesetzeskuerzel;
  /** „§ 12" oder „Anlage 1". */
  readonly norm: string;
  readonly absatz?: string;
  readonly nummer?: string;
  readonly buchstabe?: string;
  readonly satz?: string;
  /**
   * Nur bei Anlagen: die Stelle innerhalb der Anlage, etwa
   * „Abschnitt 1 Unterabschnitt 1 Nr. 1.1". Anlagen sind nicht in Absätze
   * gegliedert; geprüft wird deshalb durch Textsuche.
   */
  readonly stelle?: string;
}

/** Erklärung zu genau einer Frage. */
export interface Erklaerung {
  /**
   * Ein Satz, der die richtige Antwort trägt. Steht unmittelbar unter der
   * Rückmeldung und muss für sich allein verständlich sein.
   */
  readonly kurz: string;
  /**
   * Ausführliche Begründung: warum die richtige Antwort richtig ist und –
   * wo es hilft – warum die anderen es nicht sind.
   */
  readonly text: string;
  /** Belege im Gesetz. Darf leer sein, dann ist {@link ohneFundstelleGrund} nötig. */
  readonly fundstellen: readonly Fundstelle[];
  /**
   * Warum es zu dieser Frage keine Fundstelle gibt.
   *
   * Nicht jede Prüfungsfrage hat eine gesetzliche Grundlage: Die
   * Sicherheitsregeln zur Handhabung und weite Teile der Waffentechnik sind
   * fachliche Praxis, nicht Gesetzestext. Dieses Feld verlangt, das
   * auszusprechen – die Alternative wäre, eine Fundstelle zu erfinden.
   */
  readonly ohneFundstelleGrund?: string;
  /** Optionale Eselsbrücke. Nur, wo sie wirklich trägt. */
  readonly merksatz?: string;
  /**
   * Fragen, die denselben Gegenstand behandeln.
   *
   * **Wozu.** Der amtliche Katalog kennt keine Frage-zu-Frage-Beziehung, obwohl
   * die Bündel auf der Hand liegen: II-34 fragt, was ein Schalldämpfer bewirkt,
   * II-36 fragt nach der Umkehrung – die Erklärung zu II-36 sagt das selbst im
   * Text. Wer eine Frage gerade verstanden hat, ist im besten Moment, die
   * Nachbarfrage mitzunehmen; bis 0.22.0 erfuhr er nie, dass es sie gibt.
   *
   * **Redaktionell, nicht amtlich.** Die Zuordnung ist eine Einschätzung dieser
   * Software. Sie ist bewusst **beidseitig**: Steht B bei A, muss A bei B
   * stehen. Eine einseitige Verknüpfung wäre fast immer ein Versehen beim
   * Schreiben, und sie zeigte die Verbindung nur dem, der zufällig von der
   * richtigen Seite kommt. `pruefe_erklaerungen.py` besteht darauf.
   *
   * **Keine Wertung.** Die Liste sagt „das gehört zusammen“, nicht „das musst
   * du auch können“ – der Prüfungsstoff steht im Katalog, nicht hier.
   */
  readonly verwandt?: readonly string[];
  /**
   * Woran sich eine vollständige Antwort erkennen lässt – bei offenen Fragen.
   *
   * **Wozu.** Bei 63 der 104 offenen Fragen ist in der amtlichen
   * Musterantwort nichts unterstrichen, und die Anwendung gibt die
   * Unterstreichungen seit 0.17.0 zu Recht nicht mehr als Pflichtinhalt aus
   * (`docs/entscheidung-freitext.md`). Damit stand der Lernende bei der
   * vierstufigen Selbstbewertung – die unmittelbar die Wiedervorlage steuert
   * – ohne jedes Kriterium da: Die Hilfe warnt vor zu milder Bewertung, gab
   * aber kein Werkzeug, das Wesentliche zu erkennen.
   *
   * **Was das Feld nicht ist.** Es sagt **nicht**, was eine Antwort enthalten
   * muss. Über einer Liste wie dieser stand bis 0.17.0 „Diese Kernelemente
   * muss Ihre Antwort enthalten“, und genau das war unbelegt. Die Punkte sind
   * eine Einschätzung dieser Software, in eigenen Worten aus der amtlichen
   * Musterantwort erarbeitet – eine Erkennungshilfe, kein
   * Bewertungsversprechen. Wer prüft, ist weiterhin der Prüfungsausschuss;
   * wer die eigene Antwort bewertet, bleibt der Lernende.
   *
   * **Warum nicht überall.** Wo die Musterantwort aus einer einzigen Tatsache
   * besteht („Unbefristet“, „Nein“, „Mindestens alle drei Jahre“), fehlt das
   * Feld. Eine Prüfliste mit einem Punkt wäre kein Werkzeug, sondern Beiwerk.
   */
  readonly kernpunkte?: readonly string[];
}

export interface ErklaerungenMeta {
  readonly version: number;
  /** Tag, an dem der Bestand zuletzt geprüft wurde (ISO-Datum). */
  readonly stand: string;
  /**
   * Stand der Gesetze, gegen die geprüft wurde – wörtlich aus dem amtlichen
   * XML. Ändert sich ein Gesetz, ist erkennbar, worauf sich die Erklärungen
   * bezogen haben.
   */
  readonly gesetzesstand: Readonly<Record<string, string>>;
  readonly hinweis: string;
}

export interface Erklaerungen {
  readonly meta: ErklaerungenMeta;
  /** Frage-ID aus dem Katalog -> Erklärung. Nicht jede Frage muss eine haben. */
  readonly zuFrage: Readonly<Record<string, Erklaerung>>;
}

/**
 * Sammelt die verwandten Fragen zu einer Reihe von Fragen.
 *
 * Fragen, die schon in der Eingabe stehen, fallen heraus: Wer sie eben
 * bearbeitet hat, braucht sie nicht als „verwandt“ vorgeschlagen zu bekommen.
 * Die Reihenfolge bleibt die, in der man ihnen begegnet ist – nach Kennung zu
 * sortieren ginge schief, weil „I.2-99“ vor „I.2-135“ kommt, lexikografisch
 * aber dahinter steht.
 *
 * Der Zugriff kommt als Funktion herein und nicht als ganzer Bestand: Die
 * Oberfläche hält die Erklärungen ohnehin nur hinter einem Nachschlager, und
 * so lässt sich die Sammlung ohne Attrappe des vollen Vertrags prüfen.
 */
export function verwandteSammeln(
  zu: (frageId: string) => Erklaerung | null,
  frageIds: readonly string[],
): string[] {
  const schonDa = new Set(frageIds);
  const gesammelt: string[] = [];
  const gesehen = new Set<string>();

  for (const frageId of frageIds) {
    for (const verwandt of zu(frageId)?.verwandt ?? []) {
      if (!schonDa.has(verwandt) && !gesehen.has(verwandt)) {
        gesehen.add(verwandt);
        gesammelt.push(verwandt);
      }
    }
  }

  return gesammelt;
}

/**
 * Fundstelle als Zitat, wie es in der Oberfläche erscheint.
 *
 * Eine einzige Stelle, an der die Schreibweise festgelegt wird – sonst hätte
 * jede Erklärung ihre eigene.
 */
export function fundstelleText(fundstelle: Fundstelle): string {
  const teile: string[] = [fundstelle.norm];

  if (fundstelle.stelle !== undefined && fundstelle.stelle.length > 0) {
    teile.push(fundstelle.stelle);
  }
  if (fundstelle.absatz !== undefined) {
    teile.push(`Abs. ${fundstelle.absatz}`);
  }
  if (fundstelle.satz !== undefined) {
    teile.push(`Satz ${fundstelle.satz}`);
  }
  if (fundstelle.nummer !== undefined) {
    teile.push(`Nr. ${fundstelle.nummer}`);
  }
  if (fundstelle.buchstabe !== undefined) {
    teile.push(`Buchst. ${fundstelle.buchstabe}`);
  }

  return `${teile.join(' ')} ${fundstelle.gesetz}`;
}

/** Vollständiger Name für Bildschirmleser: Abkürzungen ausgeschrieben. */
export function fundstelleVorlesetext(fundstelle: Fundstelle): string {
  const teile: string[] = [fundstelle.norm.replace('§', 'Paragraf')];

  if (fundstelle.stelle !== undefined && fundstelle.stelle.length > 0) {
    teile.push(fundstelle.stelle);
  }
  if (fundstelle.absatz !== undefined) {
    teile.push(`Absatz ${fundstelle.absatz}`);
  }
  if (fundstelle.satz !== undefined) {
    teile.push(`Satz ${fundstelle.satz}`);
  }
  if (fundstelle.nummer !== undefined) {
    teile.push(`Nummer ${fundstelle.nummer}`);
  }
  if (fundstelle.buchstabe !== undefined) {
    teile.push(`Buchstabe ${fundstelle.buchstabe}`);
  }

  return `${teile.join(' ')} ${GESETZ_BEZEICHNUNG[fundstelle.gesetz]}`;
}

/**
 * Die ganze Erklärung als vorlesbarer Text.
 *
 * **Wozu.** Die ausführliche Begründung ist der längste Text der Anwendung –
 * gesprochen über eine Minute. Bis 0.22.0 gab es keinen Weg, sie auf Abruf zu
 * hören: Die Vorlesetaste der Sitzung liest sie bewusst nicht mit (die
 * nächste Frage wartet, siehe `shared/ipc.ts`), und die Automatik greift nur
 * bei falscher Antwort. Wer bei einer richtigen Antwort trotzdem wissen
 * wollte, warum – oder wer sie in der Prüfungsauswertung nachlas –, war aufs
 * stille Lesen zurückgeworfen. Ausgerechnet in der Nachbereitung, dem
 * lernwirksamsten Moment.
 *
 * Die Fundstellen kommen in der ausgeschriebenen Form mit: „§ 12 Abs. 1“
 * würde sonst buchstabiert oder falsch betont. Der Herkunftssatz bleibt
 * draußen – er steht am Bildschirm und ist keine Auskunft zur Sache.
 */
export function erklaerungVorlesetext(erklaerung: Erklaerung): string {
  const teile: string[] = [erklaerung.kurz, erklaerung.text];

  if (erklaerung.merksatz !== undefined) {
    teile.push(`Merksatz: ${erklaerung.merksatz}`);
  }

  if (erklaerung.fundstellen.length > 0) {
    const liste = erklaerung.fundstellen.map((fundstelle) => fundstelleVorlesetext(fundstelle));
    teile.push(`Im Gesetz nachlesen: ${liste.join('; ')}.`);
  }

  if (erklaerung.ohneFundstelleGrund !== undefined) {
    teile.push(erklaerung.ohneFundstelleGrund);
  }

  return teile.join('\n');
}
