/**
 * Die redaktionelle Feingliederung der Kapitel II bis IV.
 *
 * **Der Anlass.** Der amtliche Katalog gliedert nur Kapitel I in Abschnitte.
 * Für 230 Fragen steht `abschnitt` auf `null`; wer in der Simulation an
 * Munitionskunde scheiterte, sah bis 0.22.0 nur „Kapitel II schwach“ und
 * konnte nur das ganze Kapitel nachüben – 92 Fragen.
 *
 * Die eigentliche Zusage steht ganz unten: **Vollständigkeit**. Eine
 * lückenhafte Gliederung wäre schlimmer als keine, weil „Thema üben“ dann
 * Fragen stillschweigend überginge.
 */

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

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

import type { Katalog } from '../src/shared/katalog';
import { gruppenZuKapitel, gruppeZuFrage, THEMEN_LEER, type Themen } from '../src/shared/themen';

const WURZEL = join(process.cwd(), '..');
const THEMEN = JSON.parse(readFileSync(join(WURZEL, 'content', 'themen.json'), 'utf8')) as Themen;
const KATALOG = JSON.parse(
  readFileSync(join(WURZEL, 'content', 'katalog', 'katalog.json'), 'utf8'),
) as Katalog;

/** Kapitel ohne amtliche Abschnittsgliederung. */
const OHNE_ABSCHNITTE = ['II', 'III', 'IV'];

describe('gruppenZuKapitel', () => {
  it('liefert die Gruppen eines Kapitels in der Reihenfolge der Datei', () => {
    const gruppen = gruppenZuKapitel(THEMEN, 'III');

    expect(gruppen.length).toBeGreaterThan(3);
    expect(gruppen.every((gruppe) => gruppe.kapitel === 'III')).toBe(true);
  });

  it('liefert für Kapitel I nichts', () => {
    /* Dort gibt es die amtlichen Abschnitte. Eine zweite Gliederung daneben
       wäre die nächste Zahl, die still auseinanderläuft. */
    expect(gruppenZuKapitel(THEMEN, 'I')).toEqual([]);
  });

  it('kommt ohne geladene Gliederung zurecht', () => {
    expect(gruppenZuKapitel(THEMEN_LEER, 'II')).toEqual([]);
    expect(gruppeZuFrage(THEMEN_LEER, 'II-01')).toBeNull();
  });
});

describe('gruppeZuFrage', () => {
  it('findet die Gruppe einer Frage', () => {
    expect(gruppeZuFrage(THEMEN, 'II-34')?.titel).toBe('Schalldämpfer und Schallentstehung');
    expect(gruppeZuFrage(THEMEN, 'III-08')?.kapitel).toBe('III');
  });

  it('gibt für Fragen aus Kapitel I nichts zurück', () => {
    expect(gruppeZuFrage(THEMEN, 'I.1-01')).toBeNull();
  });
});

describe('Die ausgelieferte Gliederung', () => {
  const zugeordnet = new Map<string, string>();
  for (const gruppe of THEMEN.gruppen) {
    for (const frageId of gruppe.fragen) {
      zugeordnet.set(frageId, gruppe.id);
    }
  }

  it('ordnet jede Frage der Kapitel II bis IV genau einer Gruppe zu', () => {
    /* Die eigentliche Zusage. Ohne sie überginge „Thema üben“ Fragen, ohne es
       zu sagen – und niemandem fiele auf, welche. */
    const fehlend = KATALOG.fragen
      .filter((frage) => OHNE_ABSCHNITTE.includes(frage.kapitel) && !zugeordnet.has(frage.id))
      .map((frage) => frage.id);

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

  it('lässt Kapitel I in Ruhe', () => {
    const zuviel = KATALOG.fragen
      .filter((frage) => !OHNE_ABSCHNITTE.includes(frage.kapitel) && zugeordnet.has(frage.id))
      .map((frage) => frage.id);

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

  it('nennt keine Frage in zwei Gruppen', () => {
    const alle = THEMEN.gruppen.flatMap((gruppe) => gruppe.fragen);

    expect(alle.length).toBe(new Set(alle).size);
    expect(alle.length).toBe(230);
  });

  it('kennt nur Fragen, die es gibt', () => {
    const bekannt = new Set(KATALOG.fragen.map((frage) => frage.id));
    const erfunden = [...zugeordnet.keys()].filter((frageId) => !bekannt.has(frageId));

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

  it('mischt in keiner Gruppe zwei Kapitel', () => {
    const kapitelJeFrage = new Map(KATALOG.fragen.map((frage) => [frage.id, frage.kapitel]));
    const gemischt = THEMEN.gruppen
      .filter(
        (gruppe) =>
          !gruppe.fragen.every((frageId) => kapitelJeFrage.get(frageId) === gruppe.kapitel),
      )
      .map((gruppe) => gruppe.id);

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

  it('führt keine Gruppe mit weniger als drei Fragen', () => {
    // Eine Gruppe mit einer Frage ist kein Thema, sondern ein Einzelfall.
    const winzig = THEMEN.gruppen
      .filter((gruppe) => gruppe.fragen.length < 3)
      .map((gruppe) => `${gruppe.id} (${String(gruppe.fragen.length)})`);

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

  it('vergibt eindeutige Kennungen und nichtleere Titel', () => {
    const kennungen = THEMEN.gruppen.map((gruppe) => gruppe.id);

    expect(kennungen.length).toBe(new Set(kennungen).size);
    expect(THEMEN.gruppen.every((gruppe) => gruppe.titel.trim().length > 0)).toBe(true);
  });

  it('weist sich selbst als Ergänzung dieser Software aus', () => {
    /* Der amtliche Katalog darf nicht verändert wiedergegeben werden; was
       eigener Zusatz ist, muss erkennbar sein – auch in der Datei. */
    expect(THEMEN.meta.hinweis).toContain('nicht Teil des amtlichen');
  });
});
