/**
 * Querverweise zwischen verwandten Fragen.
 *
 * **Der Anlass.** 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 nach der Umkehrung – die Erklärung zu II-36 sagt das selbst
 * im Text. Wer eine davon verstanden hat, erfuhr bis 0.22.0 nie, dass es die
 * anderen gibt.
 *
 * Geprüft wird die Sammelregel an gebauten Fällen und der **ausgelieferte
 * Bestand** auf Beidseitigkeit: Ein einseitiger Verweis zeigt die Verbindung
 * nur dem, der zufällig von der richtigen Seite kommt.
 */

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

import { describe, expect, it } from 'vitest';

import { verwandteSammeln, type Erklaerung, type Erklaerungen } from '../src/shared/erklaerungen';
import type { Katalog } from '../src/shared/katalog';

const WURZEL = join(process.cwd(), '..');
const ERKLAERUNGEN = JSON.parse(
  readFileSync(join(WURZEL, 'content', 'erklaerungen.json'), 'utf8'),
) as Erklaerungen;
const KATALOG = JSON.parse(
  readFileSync(join(WURZEL, 'content', 'katalog', 'katalog.json'), 'utf8'),
) as Katalog;

function nachschlagen(bestand: Record<string, readonly string[]>) {
  return (frageId: string): Erklaerung | null => {
    const verwandt = bestand[frageId];
    return verwandt === undefined
      ? null
      : { kurz: 'k', text: 't', fundstellen: [], ohneFundstelleGrund: 'g', verwandt };
  };
}

describe('verwandteSammeln', () => {
  it('sammelt die Verwandten mehrerer Fragen', () => {
    const zu = nachschlagen({ 'II-34': ['II-33', 'II-36'], 'I.5-02': ['I.5-03'] });

    expect(verwandteSammeln(zu, ['II-34', 'I.5-02'])).toEqual(['II-33', 'II-36', 'I.5-03']);
  });

  it('lässt weg, was schon in der Sitzung stand', () => {
    // Wer II-33 gerade bearbeitet hat, braucht sie nicht als „verwandt“
    // vorgeschlagen zu bekommen.
    const zu = nachschlagen({ 'II-34': ['II-33', 'II-36'], 'II-33': ['II-34', 'II-36'] });

    expect(verwandteSammeln(zu, ['II-34', 'II-33'])).toEqual(['II-36']);
  });

  it('nennt keine Frage zweimal', () => {
    const zu = nachschlagen({ 'II-34': ['II-36'], 'II-33': ['II-36'] });

    expect(verwandteSammeln(zu, ['II-34', 'II-33'])).toEqual(['II-36']);
  });

  it('behält die Reihenfolge der Begegnung bei', () => {
    /* Nach Kennung zu sortieren ginge schief: „I.2-99“ kommt im Katalog vor
       „I.2-135“, lexikografisch steht es dahinter. */
    const zu = nachschlagen({ a: ['I.2-135'], b: ['I.2-99'] });

    expect(verwandteSammeln(zu, ['a', 'b'])).toEqual(['I.2-135', 'I.2-99']);
  });

  it('kommt mit Fragen ohne Erklärung zurecht', () => {
    expect(verwandteSammeln(() => null, ['II-34'])).toEqual([]);
    expect(verwandteSammeln(nachschlagen({}), [])).toEqual([]);
  });
});

describe('Der ausgelieferte Bestand', () => {
  const mitVerwandten = Object.entries(ERKLAERUNGEN.zuFrage).filter(
    ([, eintrag]) => eintrag.verwandt !== undefined,
  );
  const bekannt = new Set(KATALOG.fragen.map((frage) => frage.id));

  it('führt überhaupt Querverweise', () => {
    expect(mitVerwandten.length).toBeGreaterThan(50);
  });

  it('verweist nur auf Fragen, die es gibt', () => {
    const unbekannt = mitVerwandten.flatMap(([frageId, eintrag]) =>
      (eintrag.verwandt ?? [])
        .filter((ziel) => !bekannt.has(ziel))
        .map((ziel) => `${frageId} -> ${ziel}`),
    );

    expect(unbekannt).toEqual([]);
  });

  it('verweist nirgends auf sich selbst', () => {
    const selbstbezug = mitVerwandten
      .filter(([frageId, eintrag]) => (eintrag.verwandt ?? []).includes(frageId))
      .map(([frageId]) => frageId);

    expect(selbstbezug).toEqual([]);
  });

  it('hält jeden Verweis beidseitig', () => {
    /* Die eigentliche Zusage. Ein einseitiger Verweis ist fast immer ein
       Versehen beim Schreiben – und er zeigt die Verbindung nur dem, der
       zufällig von der richtigen Seite kommt. */
    const einseitig = mitVerwandten.flatMap(([frageId, eintrag]) =>
      (eintrag.verwandt ?? [])
        .filter((ziel) => !(ERKLAERUNGEN.zuFrage[ziel]?.verwandt ?? []).includes(frageId))
        .map((ziel) => `${frageId} nennt ${ziel}, aber nicht zurück`),
    );

    expect(einseitig).toEqual([]);
  });

  it('nennt keine Frage doppelt in derselben Liste', () => {
    const doppelt = mitVerwandten
      .filter(([, eintrag]) => {
        const liste = eintrag.verwandt ?? [];
        return new Set(liste).size !== liste.length;
      })
      .map(([frageId]) => frageId);

    expect(doppelt).toEqual([]);
  });

  it('kennt das Schalldämpfer-Bündel, mit dem der Befund begann', () => {
    // II-36 nennt sich in der eigenen Erklärung „die Umkehrung von Frage 34“.
    expect(ERKLAERUNGEN.zuFrage['II-36']?.verwandt).toContain('II-34');
    expect(ERKLAERUNGEN.zuFrage['II-34']?.verwandt).toContain('II-36');
  });

  it('verbindet auch über Kapitelgrenzen hinweg', () => {
    /* I.1-07 fragt nach der waffenrechtlichen Einordnung des
       Double-Action-Revolvers, II-47 nach der Technik der Double-Action-
       Pistole. Genau solche Paare findet man im Katalog nicht von allein. */
    expect(ERKLAERUNGEN.zuFrage['I.1-07']?.verwandt).toContain('II-47');
  });
});
