import { useEffect, useId, useRef, useState } from 'react';

import { SITZUNGSUMFANG } from '../../../../shared/lernplan';
import type { KatalogIndex } from '../../hooks/useKatalog';
import type { Suchanfrage } from '../../lernen/typen';
import { useFragensuche } from '../../hooks/useFragensuche';
import { Hinweis } from '../lernen/Hinweis';
import { Trefferkarte } from './Trefferkarte';

export interface SuchansichtProps {
  readonly einstiegRef: React.RefObject<HTMLHeadingElement | null>;
  readonly katalogIndex: KatalogIndex;
  /**
   * Die Anfrage, mit der die Ansicht aufgeht – etwa nach einer Sitzung.
   *
   * Text UND Bereich: Bis Fassung 0.26.0 kam nur der Text zurück, und die
   * Liste stand danach über dem ganzen Katalog. „Dieselbe Trefferliste“ war
   * sie damit nicht.
   */
  readonly anfangsanfrage?: Suchanfrage | undefined;
  readonly onUeben: (frageIds: readonly string[], titel: string, anfrage: Suchanfrage) => void;
  readonly onZurueck: () => void;
}

/** Wie viele Treffer je Schritt erscheinen. */
const SCHRITT = 25;

/**
 * Beschriftung des Sammelknopfs.
 *
 * Drei Fälle statt einem Satzbaukasten: „Alle 1 Fragen üben“ war ungrammatisch
 * und stand ausgerechnet neben einem gleichwertigen „Frage X üben“ derselben
 * einen Frage. Und beim Stöbern hat niemand gesucht – dort sind es Fragen,
 * keine Treffer.
 */
function sammelknopftext(anzahl: number, stoebern: boolean): string {
  if (anzahl === 1) {
    return 'Diese eine Frage üben';
  }
  if (anzahl <= SITZUNGSUMFANG) {
    return `Alle ${String(anzahl)} Fragen üben`;
  }
  const woher = stoebern ? 'Fragen' : 'Treffern';
  return `Die ersten ${String(SITZUNGSUMFANG)} von ${String(anzahl)} ${woher} üben`;
}

/**
 * Fragen durchsuchen – und, bei leerem Feld, der Fragen-Browser.
 *
 * ## Zwei Aufgaben, eine Ansicht
 *
 * Ein leeres Suchfeld ist kein Fehlerzustand, sondern die vollständige Liste
 * des gewählten Bereichs in Katalogreihenfolge. „Alle Fragen zu Notwehr“ ist
 * damit ein Griff: Bereich „I.5“, Feld leer, 43 Fragen. Der seit `PLAN.md`
 * Phase 2 offene Fragen-Browser ist damit eingelöst, ohne eine zweite Ansicht
 * zu bauen, zu prüfen, zu bebildern und barrierefrei zu halten.
 *
 * ## Warum nur zwei Bedienelemente
 *
 * Suchfeld und Bereichswahl, mehr nicht. Naheliegend wären Kästchen für „wo
 * gesucht wird“ und „nur ganze Wörter“ – das sind Tokenizer-Fragen im
 * Nutzerkostüm. Ein Prüfling kann sie nicht beantworten, weil er nicht weiß,
 * wo das Wort steht. Die Software entscheidet und sagt an jedem Treffer, was
 * sie getan hat.
 *
 * ## Warum eine Radiogruppe und kein Aufklappmenü
 *
 * Unter `components/` gibt es kein einziges `<select>`. Das von Chromium
 * gezeichnete Menü folgt der anwendungseigenen Anzeigegröße nicht und
 * verhält sich unter den Windows-Kontrastdesigns anders als die
 * Hausbausteine. Die `<fieldset>`-Radiogruppe ist die Form aus
 * `Pruefungswahl.tsx`, ist genau **ein** Tabulatorhalt und hat Kontrast,
 * Zielgröße und `forced-colors` bereits belegt.
 */
export function Suchansicht({
  einstiegRef,
  katalogIndex,
  anfangsanfrage,
  onUeben,
  onZurueck,
}: SuchansichtProps): React.JSX.Element {
  const titelId = useId();
  const feldId = useId();
  const bereichsgruppe = useId();

  const suche = useFragensuche(katalogIndex.katalog, anfangsanfrage);
  const { ergebnis } = suche;

  const feldRef = useRef<HTMLInputElement | null>(null);
  const [gezeigt, gezeigtSetzen] = useState(SCHRITT);
  const neuRef = useRef<HTMLHeadingElement | null>(null);
  const nachgeladen = useRef(false);

  /*
    Jede Änderung der Anfrage beginnt die Liste von vorn – sonst stünde nach
    einer neuen Suche unvermittelt „50 von 3 angezeigt“ da.

    Angepasst wird beim Rendern, nicht in einem Effekt: Dasselbe Muster wie
    das Profiltor in `App.tsx`. Ein Effekt zeichnete die Liste erst in ihrer
    alten Länge und gleich darauf noch einmal.
  */
  const [letzterText, letzterTextSetzen] = useState(suche.text);
  const [letzterBereich, letzterBereichSetzen] = useState(suche.bereich);
  if (suche.text !== letzterText || suche.bereich !== letzterBereich) {
    letzterTextSetzen(suche.text);
    letzterBereichSetzen(suche.bereich);
    gezeigtSetzen(SCHRITT);
  }

  /* Der Fokus wandert auf den ersten neu erschienenen Treffer – nur nach
     einem Klick auf „Weitere anzeigen“, nicht beim ersten Aufbau. */
  useEffect(() => {
    if (nachgeladen.current) {
      nachgeladen.current = false;
      neuRef.current?.focus();
    }
  }, [gezeigt]);

  const sichtbar = ergebnis.treffer.slice(0, gezeigt);
  /* Text und Bereich als Paar – eine Trefferliste entsteht erst aus
     beidem, und einzeln zurückgereicht ergäben sie eine Liste, die der
     Nutzende nie gesehen hat. */
  const anfrage: Suchanfrage = { text: suche.text, bereich: suche.bereich };

  const bereichsname = suche.bereiche.find((b) => b.id === suche.bereich)?.titel ?? 'Alle Bereiche';

  /*
    Die drei Angebotsknöpfe im Leerzustand räumen den Kasten weg, in dem sie
    stehen – der fokussierte Knopf verschwindet und der Fokus fällt auf
    <body>. Wer mit Tastatur oder Bildschirmleser arbeitet, stünde danach am
    Dokumentanfang. Der Fokus geht deshalb ins Suchfeld: Dort steht jetzt die
    neue Anfrage, und von dort ist die Liste einen Tabulatorsprung entfernt.
  */
  const neuSuchen = (aenderung: () => void): void => {
    aenderung();
    feldRef.current?.focus();
  };

  const uebenAlle = (): void => {
    const ids = ergebnis.treffer.slice(0, SITZUNGSUMFANG).map((t) => t.frageId);
    onUeben(ids, ergebnis.stoebern ? bereichsname : `Suche: ${suche.text.trim()}`, anfrage);
  };

  return (
    <section className="suche abschnitt" aria-labelledby={titelId}>
      {/* Immer gerendert, auch im Lade- und Fehlerfall: Sonst liefe der
          Fokuseffekt aus App.tsx ins Leere. */}
      <h1 id={titelId} ref={einstiegRef} tabIndex={-1}>
        Fragen durchsuchen
      </h1>

      <p>
        Durchsucht wird der Wortlaut aller {katalogIndex.katalog.fragen.length} amtlichen Fragen
        samt ihrer Antwortmöglichkeiten. Groß- und Kleinschreibung, Umlaute und ß spielen keine
        Rolle. Bei leerem Suchfeld steht der ganze gewählte Bereich da.
      </p>

      <div role="search" className="suche__eingabe">
        <div className="suche__feldzeile">
          <label htmlFor={feldId}>Suchbegriff</label>
          <input
            id={feldId}
            ref={feldRef}
            type="search"
            className="suche__feld"
            value={suche.text}
            onChange={(ereignis) => {
              suche.textSetzen(ereignis.target.value);
            }}
          />
        </div>

        <fieldset className="suche__bereiche">
          <legend>Bereich</legend>
          <div className="suche__bereichsliste">
            {suche.bereiche.map((bereich) => {
              const id = `${bereichsgruppe}-${bereich.id ?? 'alle'}`;
              return (
                <div className="suche__bereich" key={bereich.id ?? 'alle'}>
                  <input
                    className="suche__bereichsfeld"
                    id={id}
                    type="radio"
                    name={bereichsgruppe}
                    checked={bereich.id === suche.bereich}
                    onChange={() => {
                      suche.bereichSetzen(bereich.id);
                    }}
                  />
                  <label className="suche__bereichsbeschriftung" htmlFor={id}>
                    {bereich.titel} ({bereich.anzahl})
                  </label>
                </div>
              );
            })}
          </div>
        </fieldset>
      </div>

      {/*
        Genau eine Live-Region, von der ersten Darstellung an im Baum. Wird
        sie erst eingehängt, sobald es Treffer gibt, verschluckt der
        Bildschirmleser die erste Meldung. Ganze Sätze, keine Bruchstücke.
      */}
      <p className="suche__anzahl" role="status">
        {suche.ansage}
      </p>

      {suche.nummernIds.length === 0 ? null : (
        <div className="suche__nummern">
          <p className="suche__nummernkopf">
            {suche.nummernIds.length === 1
              ? 'Über die Fragennummer gefunden:'
              : `Diese Nummer gibt es in ${String(suche.nummernIds.length)} Kapiteln:`}
          </p>
          <ol className="suche__treffer">
            {suche.nummernIds.map((id) => {
              const frage = katalogIndex.frage(id);
              return frage === undefined ? null : (
                <Trefferkarte
                  key={id}
                  frage={frage}
                  kapitelTitel={katalogIndex.bereichTitel(frage.kapitel)}
                  abschnittTitel={
                    frage.abschnitt === null ? null : katalogIndex.bereichTitel(frage.abschnitt)
                  }
                  klasse="nummer"
                  optionenMitTreffer={[]}
                  auchImFragetext={false}
                  woerter={[]}
                  onUeben={() => {
                    onUeben([id], `Frage ${frage.amtliche_nummer}`, anfrage);
                  }}
                />
              );
            })}
          </ol>
        </div>
      )}

      {ergebnis.zuKurz && suche.nummernIds.length === 0 ? (
        <p className="suche__leer">Bitte noch ein Zeichen eingeben.</p>
      ) : null}

      {suche.bruecke === null ? null : (
        <Hinweis art="hinweis">
          Der Katalog benutzt dafür andere Wörter.{' '}
          {suche.bruecke.vorschlaege.map((vorschlag) => (
            <button
              key={vorschlag.wort}
              type="button"
              className="knopf knopf--leise"
              onClick={() => {
                neuSuchen(() => {
                  suche.textSetzen(vorschlag.wort);
                });
              }}
            >
              {vorschlag.wort} ({vorschlag.anzahl})
            </button>
          ))}
        </Hinweis>
      )}

      {suche.leer ? (
        <div className="suche__leer">
          <p>Keine Frage gefunden.</p>

          {suche.unbekannt.length > 0 ? (
            <p>
              {suche.unbekannt.map((w) => `„${w}“`).join(' und ')}{' '}
              {suche.unbekannt.length === 1 ? 'kommt' : 'kommen'} im Katalog nicht vor.
            </p>
          ) : null}

          {suche.bereich !== null && suche.ohneBereich > 0 ? (
            <p>
              Ohne die Einschränkung auf {bereichsname} gäbe es {suche.ohneBereich} Treffer.{' '}
              <button
                type="button"
                className="knopf knopf--leise"
                onClick={() => {
                  neuSuchen(() => {
                    suche.bereichSetzen(null);
                  });
                }}
              >
                Einschränkung aufheben
              </button>
            </p>
          ) : null}

          {suche.zerlegung === null ? null : (
            <p>
              Sinngemäß gefunden: „{suche.zerlegung.links}“ und „{suche.zerlegung.rechts}“.{' '}
              <button
                type="button"
                className="knopf knopf--leise"
                onClick={() => {
                  neuSuchen(() => {
                    suche.textSetzen(
                      `${suche.zerlegung?.links ?? ''} ${suche.zerlegung?.rechts ?? ''}`,
                    );
                  });
                }}
              >
                Danach suchen ({suche.zerlegung.anzahl} Fragen)
              </button>
            </p>
          )}
        </div>
      ) : null}

      {ergebnis.treffer.length === 0 ? null : (
        <>
          <div className="suche__sammelaktion">
            <button type="button" className="knopf knopf--haupt" onClick={uebenAlle}>
              {sammelknopftext(ergebnis.treffer.length, ergebnis.stoebern)}
            </button>
          </div>

          <ol className="suche__treffer">
            {sichtbar.map((treffer, stelle) => {
              const frage = katalogIndex.frage(treffer.frageId);
              return frage === undefined ? null : (
                <Trefferkarte
                  key={treffer.frageId}
                  frage={frage}
                  kapitelTitel={katalogIndex.bereichTitel(frage.kapitel)}
                  abschnittTitel={
                    frage.abschnitt === null ? null : katalogIndex.bereichTitel(frage.abschnitt)
                  }
                  klasse={ergebnis.stoebern ? null : treffer.klasse}
                  optionenMitTreffer={treffer.optionen}
                  auchImFragetext={treffer.auchImFragetext}
                  woerter={ergebnis.woerter}
                  titelRef={stelle === gezeigt - SCHRITT ? neuRef : undefined}
                  onUeben={() => {
                    onUeben([treffer.frageId], `Frage ${frage.amtliche_nummer}`, anfrage);
                  }}
                />
              );
            })}
          </ol>

          {gezeigt >= ergebnis.treffer.length ? null : (
            <div className="suche__mehr">
              {/* Kein Nachladen beim Scrollen: Ein wanderndes Dokumentende ist
                  für Tastatur und Bildschirmleser feindlich. */}
              <button
                type="button"
                className="knopf"
                onClick={() => {
                  nachgeladen.current = true;
                  gezeigtSetzen(gezeigt + SCHRITT);
                }}
              >
                Weitere {Math.min(SCHRITT, ergebnis.treffer.length - gezeigt)} anzeigen ({gezeigt}{' '}
                von {ergebnis.treffer.length})
              </button>
            </div>
          )}
        </>
      )}

      <div className="suche__aktionen">
        <button type="button" className="knopf" onClick={onZurueck}>
          Zum Start
        </button>
      </div>
    </section>
  );
}
