// @vitest-environment node
/**
 * Der Nummernweg, gemessen am echten Katalog.
 *
 * Die beiden Fehler, die hier passieren, sind still: Eine Präfixsuche nach
 * „2.5“ liefert die Fragen 2.50 bis 2.59 – das Falsche statt nichts –, und
 * eine Teilzeichenkettensuche nach „05“ hebt sechs beliebige Fragen über
 * jeden Texttreffer. Beides fällt niemandem auf, der nicht danach sucht.
 */

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

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

import { nummerSchluessel, nummernindexBauen, nummernTreffer } from '../src/shared/fragennummer';
import type { Katalog } from '../src/shared/katalog';

const wurzel = fileURLToPath(new URL('..', import.meta.url));

const KATALOG = JSON.parse(
  readFileSync(join(wurzel, '..', 'content', 'katalog', 'katalog.json'), 'utf8'),
) as Katalog;

const INDEX = nummernindexBauen(KATALOG);

function treffer(eingabe: string): readonly string[] {
  return nummernTreffer(INDEX, eingabe);
}

describe('Nummern des Kapitels I', () => {
  it.each([
    ['1.87', 'I.1-87'],
    ['2.150', 'I.2-150'],
    ['1.01', 'I.1-01'],
    ['5.01', 'I.5-01'],
  ])('„%s“ führt zu %s', (eingabe, id) => {
    expect(treffer(eingabe)).toEqual([id]);
  });

  it('füllt führende Nullen auf, statt einen Präfix zu suchen', () => {
    /* Der teure Fehler: „2.5“ als Präfix genommen lieferte 2.50 bis 2.59 –
       zehn falsche Fragen statt der einen richtigen. */
    expect(treffer('2.5')).toEqual(['I.2-05']);
    expect(treffer('2.05')).toEqual(['I.2-05']);
    expect(treffer('2.50')).toEqual(['I.2-50']);
    expect(treffer('2.50')).not.toEqual(treffer('2.5'));
  });

  it('nimmt auch das Komma als Trenner', () => {
    // Deutsche Zehnertastatur, deutsche Gewohnheit.
    expect(treffer('2,05')).toEqual(['I.2-05']);
  });
});

describe('Nummern der Kapitel II bis IV', () => {
  it('zeigt bei einer mehrdeutigen Nummer alle Kapitel nebeneinander', () => {
    /* Von 437 Nummern sind 89 mehrdeutig. Heimlich eine auszuwählen wäre
       die schlechteste Antwort – der Nutzer sähe nicht, dass es drei gibt. */
    expect(treffer('45')).toEqual(['II-45', 'III-45', 'IV-45']);
  });

  it('behandelt „05“ und „5“ gleich', () => {
    expect(treffer('5')).toEqual(treffer('05'));
    expect(treffer('5')).toEqual(['II-05', 'III-05', 'IV-05']);
  });

  it('liefert nur die Kapitel, in denen es die Nummer gibt', () => {
    // Kapitel III endet bei 49, Kapitel IV bei 89, Kapitel II bei 92.
    expect(treffer('60')).toEqual(['II-60', 'IV-60']);
    expect(treffer('91')).toEqual(['II-91']);
  });
});

describe('Vollständige Kennungen', () => {
  it.each([
    ['I.2-150', 'I.2-150'],
    ['i.2-150', 'I.2-150'],
    ['II-45', 'II-45'],
    ['ii-45', 'II-45'],
    ['II 45', 'II-45'],
    ['iv 89', 'IV-89'],
  ])('„%s“ führt zu %s', (eingabe, id) => {
    expect(treffer(eingabe)).toEqual([id]);
  });

  it('versteht einen Vorspann', () => {
    expect(treffer('Frage 2.150')).toEqual(['I.2-150']);
    expect(treffer('Nr. 45')).toEqual(['II-45', 'III-45', 'IV-45']);
  });
});

describe('Was keine Nummer ist', () => {
  it.each(['waffe', '', '   ', 'notwehr angriff', 'abs. 1 waffg', '7,5 joule', '§ 3'])(
    '„%s“ ergibt keinen Nummerntreffer',
    (eingabe) => {
      expect(treffer(eingabe)).toEqual([]);
    },
  );

  it('erkennt gar nicht erst einen Schlüssel', () => {
    expect(nummerSchluessel('waffe')).toBeNull();
    expect(nummerSchluessel('')).toBeNull();
  });

  it('gibt es die Nummer nicht, bleibt die Liste leer', () => {
    expect(treffer('9.99')).toEqual([]);
    expect(treffer('999')).toEqual([]);
  });
});

describe('Vollständigkeit', () => {
  it('löst jede der 437 amtlichen Nummern auf', () => {
    /* Nicht „einige“, nicht „die geprüften“: jede. Eine Nummer, die ins
       Leere führt, ist in einem Nachschlagewerk ein Widerspruch in sich. */
    const nummern = [...new Set(KATALOG.fragen.map((f) => f.amtliche_nummer))];
    expect(nummern.length).toBe(437);

    const ausfaelle = nummern.filter((n) => treffer(n).length === 0);
    expect(ausfaelle, `Nicht auflösbar: ${ausfaelle.slice(0, 10).join(', ')}`).toEqual([]);
  });

  it('löst jede der 575 Kennungen auf – und zwar auf sich selbst', () => {
    const ausfaelle = KATALOG.fragen.filter((f) => !treffer(f.id).includes(f.id)).map((f) => f.id);
    expect(ausfaelle, `Nicht auflösbar: ${ausfaelle.slice(0, 10).join(', ')}`).toEqual([]);
  });

  it('führt eine Nummer immer auf Fragen, die es gibt', () => {
    const bekannt = new Set(KATALOG.fragen.map((f) => f.id));
    for (const [, ids] of INDEX.nach) {
      for (const id of ids) {
        expect(bekannt.has(id)).toBe(true);
      }
    }
  });

  it('zählt 89 mehrdeutige Nummern', () => {
    const mehrdeutig = [...new Set(KATALOG.fragen.map((f) => f.amtliche_nummer))].filter(
      (n) => treffer(n).length > 1,
    );
    expect(mehrdeutig.length).toBe(89);
  });
});
