// @vitest-environment node
/**
 * Begriffserkennung im Fragetext.
 *
 * Der heikle Teil ist die Wortgrenze. `\b` kennt nur ASCII-Wortzeichen –
 * in „Schießstätte" gälte damit jedes ß und ä als Grenze. Und „Waffe" darf
 * nicht in „Waffenbesitzkarte" treffen, sonst hängt an jeder zweiten Frage
 * ein Begriff, der dort gar nicht steht.
 */

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

import { begriffssucheBauen, type Glossar, type Glossareintrag } from '../src/shared/glossar';

function eintrag(begriff: string, varianten: string[]): Glossareintrag {
  return {
    begriff,
    art: 'begriff',
    kurz: `Bedeutung von ${begriff} in einem Satz, lang genug zum Anzeigen.`,
    varianten,
    fundstellen: [],
    ohneFundstelleGrund: 'Prüfeintrag ohne gesetzliche Grundlage.',
  };
}

function glossar(...eintraege: Glossareintrag[]): Glossar {
  return {
    meta: { version: 1, stand: '2026-08-21', gesetzesstand: {}, hinweis: 'Prüfstand' },
    eintraege,
  };
}

describe('Begriffe im Text finden', () => {
  it('findet die Grundform', () => {
    const suche = begriffssucheBauen(glossar(eintrag('Führen', ['führen', 'führt', 'geführt'])));

    expect(suche.imText('Wer eine Waffe führt, braucht einen Waffenschein.')).toHaveLength(1);
  });

  it('findet alle angegebenen Wortformen', () => {
    const suche = begriffssucheBauen(glossar(eintrag('Führen', ['führen', 'führt', 'geführt'])));

    for (const satz of ['Darf man führen?', 'Er führt sie.', 'Sie wurde geführt.']) {
      expect(suche.imText(satz), satz).toHaveLength(1);
    }
  });

  it('achtet nicht auf Groß- und Kleinschreibung', () => {
    const suche = begriffssucheBauen(glossar(eintrag('Führen', ['führen'])));

    expect(suche.imText('Das Führen ist erlaubnispflichtig.')).toHaveLength(1);
  });

  it('trifft nicht innerhalb eines längeren Wortes', () => {
    /* Der wichtigste Fall: Sonst hinge an fast jeder Frage ein Begriff, der
       dort gar nicht steht. */
    const suche = begriffssucheBauen(glossar(eintrag('Waffe', ['Waffe', 'Waffen'])));

    expect(suche.imText('Die Waffenbesitzkarte wird ausgestellt.')).toHaveLength(0);
    expect(suche.imText('Eine Schusswaffe liegt vor.')).toHaveLength(0);
    expect(suche.imText('Die Waffe ist geladen.')).toHaveLength(1);
  });

  it('behandelt Umlaute und ß als Wortzeichen', () => {
    /* `\\b` würde in „Schießstätte" an jedem ß und ä eine Grenze sehen.
       Dann fände „Stätte" einen Treffer, den es nicht gibt. */
    const suche = begriffssucheBauen(
      glossar(
        eintrag('Schießstätte', ['Schießstätte', 'Schießstätten']),
        eintrag('Stätte', ['Stätte']),
      ),
    );
    const gefunden = suche.imText('Auf einer Schießstätte darf geschossen werden.');

    expect(gefunden.map((e) => e.begriff)).toEqual(['Schießstätte']);
  });

  it('findet mehrteilige Begriffe', () => {
    const suche = begriffssucheBauen(
      glossar(eintrag('Wesentliche Teile', ['wesentliche Teile', 'wesentlichen Teile'])),
    );

    expect(suche.imText('Auch die wesentlichen Teile sind erfasst.')).toHaveLength(1);
  });

  /*
    Deutsch schreibt Substantive groß, und daran hängt hier eine Bedeutung:
    „Geschossen" ist der Dativ Plural von „Geschoss", „geschossen" das
    Partizip von „schießen". Bis Fassung 0.24.1 suchte die Begriffssuche
    ausnahmslos ohne Rücksicht auf Groß- und Kleinschreibung; am Katalog
    nachgezählt bekamen zwölf Fragen mit dem Verb den Substantiveintrag
    angeboten und sieben mit dem Substantiv den Verbeintrag.
  */
  it('unterscheidet groß notierte Varianten von klein geschriebenen Wörtern', () => {
    const suche = begriffssucheBauen(
      glossar(
        eintrag('Geschoss', ['Geschoss', 'Geschosse', 'Geschossen']),
        eintrag('Schießen', ['schießen', 'geschossen', 'schießt']),
      ),
    );

    expect(
      suche.imText('Darf unter Alkoholeinfluss geschossen werden?').map((e) => e.begriff),
    ).toEqual(['Schießen']);
    expect(
      suche.imText('Der Gefährdungsbereich von Geschossen der Patrone.').map((e) => e.begriff),
    ).toEqual(['Geschoss']);
  });

  it('findet eine klein notierte Variante auch am Satzanfang', () => {
    /* Ein Verb steht dort groß, und die Variante ist klein notiert – hier
       muss die Suche weiterhin ohne Rücksicht arbeiten. */
    const suche = begriffssucheBauen(glossar(eintrag('Führen', ['führen', 'führt'])));

    expect(suche.imText('Führt jemand eine Waffe, braucht er einen Schein.')).toHaveLength(1);
  });

  it('gibt die Begriffe in deutscher Sortierung zurück', () => {
    // Ohne Locale stünde „Überlassen" hinter „Zuverlässigkeit".
    const suche = begriffssucheBauen(
      glossar(
        eintrag('Zuverlässigkeit', ['Zuverlässigkeit']),
        eintrag('Überlassen', ['überlässt']),
        eintrag('Bedürfnis', ['Bedürfnis']),
      ),
    );
    const text = 'Wer die Zuverlässigkeit besitzt und ein Bedürfnis hat, dem überlässt man sie.';

    expect(suche.imText(text).map((e) => e.begriff)).toEqual([
      'Bedürfnis',
      'Überlassen',
      'Zuverlässigkeit',
    ]);
  });

  it('meldet jeden Begriff höchstens einmal', () => {
    const suche = begriffssucheBauen(glossar(eintrag('Führen', ['führen', 'führt'])));

    expect(suche.imText('Wer führt, muss führen dürfen.')).toHaveLength(1);
  });

  it('übergeht Einträge ohne Wortformen', () => {
    /* Ein Ausdruck aus einer leeren Alternativenliste passt auf jede Stelle
       und hängte das ganze Glossar an jede Frage. */
    const suche = begriffssucheBauen(glossar(eintrag('Ohne Formen', [])));

    expect(suche.imText('Beliebiger Text.')).toHaveLength(0);
    expect(suche.eintraege).toHaveLength(1);
  });

  it('nimmt Sonderzeichen in einer Wortform wörtlich', () => {
    // „F im Fünfeck" enthält keine Regex-Zeichen, aber § und Punkte kommen vor.
    const suche = begriffssucheBauen(glossar(eintrag('Paragraf', ['§ 1', 'Abs.'])));

    expect(suche.imText('Siehe § 1 des Gesetzes.')).toHaveLength(1);
    expect(suche.imText('Siehe Abs. 2.')).toHaveLength(1);
    expect(suche.imText('Siehe § 12.')).toHaveLength(0);
  });

  it('findet in einem leeren Text nichts', () => {
    const suche = begriffssucheBauen(glossar(eintrag('Führen', ['führen'])));

    expect(suche.imText('')).toHaveLength(0);
  });
});
