/**
 * Die aufklappbare Fundstelle.
 *
 * **Der Anlass.** Jede Erklärungstafel schloss mit „Im Gesetz nachlesen“ und
 * einer Liste von 1853 Fundstellen – reine Textzitate ohne Sprungziel. Eine
 * Software, die ausdrücklich vollständig offline arbeitet, forderte damit zu
 * etwas auf, das offline gerade nicht ging: Ein Normtext lag nicht bei.
 *
 * Geprüft wird hier vor allem das Verhalten an den Rändern: ohne
 * mitgelieferte Texte bleibt es beim Zitat, und eine Anlagenstelle, die sich
 * nicht eindeutig auflösen lässt, springt nirgendwohin, sondern sagt es.
 */

import { render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import { Normstelle } from '../src/renderer/src/components/lernen/Normstelle';
import { normtexteZwischenspeicherLeeren } from '../src/renderer/src/hooks/useNormtexte';
import type { Normtexte } from '../src/shared/normtexte';

const BESTAND: Normtexte = {
  meta: {
    version: 1,
    stand: '2026-08-29',
    hinweis: 'Auszug',
    gesetzesstand: { WaffG: 'Zuletzt geändert am 20.3.2026' },
  },
  gesetze: {
    WaffG: {
      bezeichnung: 'Waffengesetz',
      stand: 'Zuletzt geändert am 20.3.2026',
      quelle: 'https://www.gesetze-im-internet.de/waffg_2002/',
      normen: {
        '§ 1': {
          titel: 'Gegenstand und Zweck des Gesetzes',
          istAnlage: false,
          absaetze: { '1': 'Dieses Gesetz regelt den Umgang mit Waffen oder Munition.' },
        },
        'Anlage 1': {
          titel: '(zu § 1 Abs. 4)',
          istAnlage: true,
          bloecke: [
            { marke: 'Abschnitt 1', pfad: [], text: 'Abschnitt 1:' },
            {
              marke: '1.1',
              pfad: ['Abschnitt 1'],
              text: '1.1\nSchusswaffen sind Gegenstände, bei denen Geschosse getrieben werden.',
            },
          ],
        },
      },
    },
  },
};

/**
 * Wartet, bis die Normtexte geladen sind, und liefert den Aufklapper.
 *
 * Solange geladen wird, zeigt das Bauteil das reine Zitat – richtig so, aber
 * ohne diesen Umweg klickte der Test in genau diesen Zwischenzustand. Der
 * Zusatz „im Wortlaut anzeigen“ gibt es nur in der aufklappbaren Fassung.
 */
async function aufklapper(zitat: string): Promise<HTMLElement> {
  await screen.findAllByText(/im Wortlaut anzeigen/u);
  return screen.getByText(zitat);
}

function bruecke(normtexte: Normtexte | null): void {
  vi.stubGlobal(
    'lernApp',
    normtexte === null ? {} : { normtexteLaden: vi.fn(() => Promise.resolve(normtexte)) },
  );
}

beforeEach(() => {
  normtexteZwischenspeicherLeeren();
});

afterEach(() => {
  vi.unstubAllGlobals();
  normtexteZwischenspeicherLeeren();
});

describe('Normstelle', () => {
  it('bleibt beim Zitat, solange keine Normtexte vorliegen', async () => {
    /* Ein leerer Aufklapper verspräche etwas, das nicht da ist. Wer ohne die
       Datei arbeitet, sieht genau das, was er vor 0.22.0 sah. */
    bruecke(null);
    render(<Normstelle fundstelle={{ gesetz: 'WaffG', norm: '§ 1', absatz: '1' }} />);

    expect(await screen.findByText('§ 1 Abs. 1 WaffG')).toBeInTheDocument();
    expect(screen.queryByRole('group')).not.toBeInTheDocument();
  });

  it('zeigt den Absatz im Wortlaut, wenn man aufklappt', async () => {
    bruecke(BESTAND);
    const nutzer = userEvent.setup();
    render(<Normstelle fundstelle={{ gesetz: 'WaffG', norm: '§ 1', absatz: '1' }} />);

    const schalter = await aufklapper('§ 1 Abs. 1 WaffG');
    // Vor dem Aufklappen steht der Text nicht im Dokument: Eine Auswertung
    // führt bis zu 80 Fragen mit je mehreren Fundstellen auf.
    expect(screen.queryByText(/Dieses Gesetz regelt/u)).not.toBeInTheDocument();

    await nutzer.click(schalter);

    expect(screen.getByText(/Dieses Gesetz regelt den Umgang/u)).toBeInTheDocument();
    expect(screen.getByText('Gegenstand und Zweck des Gesetzes')).toBeInTheDocument();
  });

  it('nennt Gesetz und Änderungsstand beim Wortlaut', async () => {
    /* Ein Gesetzestext ohne Fassungsangabe ist eine Behauptung über heute,
       die morgen falsch sein kann. */
    bruecke(BESTAND);
    const nutzer = userEvent.setup();
    render(<Normstelle fundstelle={{ gesetz: 'WaffG', norm: '§ 1', absatz: '1' }} />);

    await nutzer.click(await aufklapper('§ 1 Abs. 1 WaffG'));

    expect(screen.getByText(/Waffengesetz – Zuletzt geändert am 20\.3\.2026/u)).toBeInTheDocument();
    expect(screen.getByText(/Amtlicher Wortlaut, unverändert übernommen/u)).toBeInTheDocument();
  });

  it('trifft bei einer Anlage den zitierten Block', async () => {
    bruecke(BESTAND);
    const nutzer = userEvent.setup();
    render(
      <Normstelle
        fundstelle={{
          gesetz: 'WaffG',
          norm: 'Anlage 1',
          stelle: 'Abschnitt 1 Nr. 1.1',
        }}
      />,
    );

    await nutzer.click(await aufklapper('Anlage 1 Abschnitt 1 Nr. 1.1 WaffG'));

    expect(screen.getByText(/Schusswaffen sind Gegenstände/u)).toBeInTheDocument();
    expect(screen.getByText('Abschnitt 1')).toBeInTheDocument();
  });

  it('sagt bei einer Abbildung, warum kein Text dasteht', async () => {
    /* Anlage II der BeschussV besteht aus den Beschusszeichen; das amtliche
       XML führt sie nur als Bildunterschrift. Das zu sagen ist ehrlicher, als
       den Leser suchen zu lassen – und besser, als an eine womöglich falsche
       Stelle zu springen. */
    bruecke(BESTAND);
    const nutzer = userEvent.setup();
    render(
      <Normstelle fundstelle={{ gesetz: 'WaffG', norm: 'Anlage 1', stelle: 'Abbildung 5b' }} />,
    );

    await nutzer.click(await aufklapper('Anlage 1 Abbildung 5b WaffG'));

    expect(screen.getByText(/ist eine Abbildung/u)).toBeInTheDocument();
    expect(screen.getByText(/liegen dieser Anwendung nicht bei/u)).toBeInTheDocument();
    // Die Anlage bleibt erreichbar – beschnitten wird nichts.
    expect(screen.getByText('Ganze Anlage anzeigen')).toBeInTheDocument();
  });

  it('zeigt die ganze Anlage, wenn die Stelle mehrdeutig ist', async () => {
    bruecke(BESTAND);
    const nutzer = userEvent.setup();
    render(
      <Normstelle
        fundstelle={{ gesetz: 'WaffG', norm: 'Anlage 1', stelle: 'Abschnitt 9 Nr. 4.2' }}
      />,
    );

    await nutzer.click(await aufklapper('Anlage 1 Abschnitt 9 Nr. 4.2 WaffG'));

    expect(screen.getByText(/nicht eindeutig auffinden/u)).toBeInTheDocument();
    const ganze = screen.getByText('Ganze Anlage anzeigen');
    await nutzer.click(ganze);
    expect(screen.getByText(/Schusswaffen sind Gegenstände/u)).toBeInTheDocument();
  });

  it('meldet ehrlich, wenn zu einer Fundstelle nichts vorliegt', async () => {
    bruecke(BESTAND);
    const nutzer = userEvent.setup();
    render(<Normstelle fundstelle={{ gesetz: 'StGB', norm: '§ 173' }} />);

    await nutzer.click(await aufklapper('§ 173 StGB'));

    expect(screen.getByText(/liegt kein Text bei/u)).toBeInTheDocument();
  });

  it('trägt für Bildschirmleser die ausgeschriebene Form', async () => {
    bruecke(BESTAND);
    render(<Normstelle fundstelle={{ gesetz: 'WaffG', norm: '§ 1', absatz: '1' }} />);

    const schalter = await aufklapper('§ 1 Abs. 1 WaffG');
    const summary = schalter.closest('summary');

    expect(summary).not.toBeNull();
    expect(
      within(summary!).getByText(', Paragraf 1 Absatz 1 Waffengesetz, im Wortlaut anzeigen'),
    ).toBeInTheDocument();
  });

  /*
    WCAG 2.5.3 (Beschriftung im Namen): Wer die Anwendung mit der Stimme
    bedient, sagt, was er sieht. Bis 0.24.1 war das Zitat `aria-hidden`, und
    der zugängliche Name enthielt keinen einzigen sichtbaren Bestandteil –
    genau die Fehlerfigur F96.
  */
  it('nennt im zugänglichen Namen auch den sichtbaren Wortlaut', async () => {
    bruecke(BESTAND);
    render(
      <Normstelle
        fundstelle={{ gesetz: 'WaffG', norm: '§ 1', absatz: '1', nummer: '3', buchstabe: 'b' }}
      />,
    );

    const summary = (await aufklapper('§ 1 Abs. 1 Nr. 3 Buchst. b WaffG')).closest('summary');

    expect(summary).not.toBeNull();
    const name = summary?.textContent ?? '';
    for (const teil of ['§ 1', 'Abs. 1', 'Nr. 3', 'Buchst. b', 'WaffG']) {
      expect(name).toContain(teil);
    }
    expect(summary?.querySelector('[aria-hidden="true"]')).toBeNull();
  });
});
