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

import { profilFinden, type Pruefungsprofil } from '../../../../shared/pruefung';
import type { KatalogIndex } from '../../hooks/useKatalog';
import { useFokusBeimAufbau } from '../../hooks/useFokusBeimAufbau';
import { usePruefung } from '../../hooks/usePruefung';
import { wirksamesProfil } from '../../pruefung/auftrag';
import type { Pruefungsabschluss, Pruefungslauf as Laufauftrag } from '../../pruefung/typen';
import { useVorlesen } from '../../hooks/useVorlesen';
import { frageVorlesen } from '../../lernen/vorlesetexte';
import { AuswahlAntwort } from '../lernen/AuswahlAntwort';
import { Fortschritt } from '../lernen/Fortschritt';
import { FrageKopf } from '../lernen/FrageKopf';
import { FreitextAntwort } from '../lernen/FreitextAntwort';
import { Hinweis } from '../lernen/Hinweis';
import { Vorlesetaste } from '../lernen/Vorlesetaste';
import { Abgabebestaetigung, type Abgabeanlass } from './Abgabebestaetigung';
import { Fragenuebersicht } from './Fragenuebersicht';
import { Nachbewertung } from './Nachbewertung';
import { Restzeit } from './Restzeit';

export interface PruefungslaufProps {
  readonly lauf: Laufauftrag;
  readonly katalogIndex: KatalogIndex;
  readonly profilId: number | null;
  /**
   * Sprachausgabe eingeschaltet.
   *
   * Auch in der Simulation – und gerade hier. Wer den Text hören muss, weil
   * Lesen schwerfällt, bräuchte die Hilfe sonst überall außer in der
   * Prüfungsvorbereitung. Vorgelesen wird nur die Frage; eine Rückmeldung
   * gibt es in der Simulation von niemandem.
   */
  readonly vorlesenAktiv: boolean;
  readonly einstiegRef: React.RefObject<HTMLHeadingElement | null>;
  readonly onFertig: (abschluss: Pruefungsabschluss) => void;
  readonly onAbbrechen: () => void;
}

/**
 * Ein Simulationslauf.
 *
 * Das unbekannte Profil wird vor allen Hooks abgefangen, damit die
 * Hook-Reihenfolge über alle Renderdurchläufe gleich bleibt; die eigentliche
 * Ansicht steckt deshalb in {@link Lauf}.
 */
export function Pruefungslauf({
  lauf,
  katalogIndex,
  profilId,
  vorlesenAktiv,
  einstiegRef,
  onFertig,
  onAbbrechen,
}: PruefungslaufProps): React.JSX.Element {
  const basisProfil = profilFinden(lauf.auftrag.profilId);

  if (!basisProfil) {
    return (
      <section className="pruefung abschnitt" aria-labelledby="pruefung-fehler-titel">
        <h1 id="pruefung-fehler-titel" ref={einstiegRef} tabIndex={-1}>
          Prüfungssimulation
        </h1>
        <Hinweis art="fehler">
          Das Prüfungsprofil „{lauf.auftrag.profilId}“ ist unbekannt. Bitte stellen Sie die
          Simulation neu ein.
        </Hinweis>
        <button type="button" className="knopf knopf--haupt" onClick={onAbbrechen}>
          Zum Start
        </button>
      </section>
    );
  }

  return (
    <Lauf
      lauf={lauf}
      katalogIndex={katalogIndex}
      profilId={profilId}
      vorlesenAktiv={vorlesenAktiv}
      einstiegRef={einstiegRef}
      onFertig={onFertig}
      onAbbrechen={onAbbrechen}
      basisProfil={basisProfil}
    />
  );
}

interface LaufProps extends PruefungslaufProps {
  readonly basisProfil: Pruefungsprofil;
}

function Lauf({
  lauf,
  katalogIndex,
  profilId,
  vorlesenAktiv,
  einstiegRef,
  onFertig,
  onAbbrechen,
  basisProfil,
}: LaufProps): React.JSX.Element {
  const titelId = useId();
  /* Erklärt, warum „Vorherige“ bzw. „Nächste Frage“ am Rand des Bogens nicht
     weiterführt – die Knöpfe verweisen darauf, statt zu verstummen. */
  const randId = useId();
  const profil = useMemo(
    () => wirksamesProfil(basisProfil, lauf.auftrag),
    [basisProfil, lauf.auftrag],
  );

  const pruefung = usePruefung({
    auftrag: lauf.auftrag,
    profil,
    katalogIndex,
    profilId,
    fortsetzung: lauf.fortsetzung ?? null,
    onFertig,
  });

  /* Nach `usePruefung`, weil die Sprachausgabe die aktuelle Fragennummer
     braucht: Blättert man weiter, hat die Stimme zu schweigen, sonst liest
     sie die vorige Frage zu Ende, während die neue dasteht. */
  const stimme = useVorlesen(vorlesenAktiv, pruefung.nummer);

  const frageTitelRef = useRef<HTMLHeadingElement | null>(null);
  const ersterAufbau = useRef(true);
  /* Die Schaltflächen, die zur Rückfrage führen. Wird sie geschlossen,
     gehört der Fokus dorthin zurück – nicht an den Anfang der Frage. */
  const abgebenRef = useRef<HTMLButtonElement | null>(null);
  const abbrechenRef = useRef<HTMLButtonElement | null>(null);
  const vorigePhase = useRef<typeof pruefung.phase | null>(null);
  const vorigeNummer = useRef<number | null>(null);

  /*
    Beim Blättern und beim Sprung aus der Übersicht wandert der Fokus auf die
    Überschrift der neuen Frage. Beim ersten Aufbau nicht: dort hat gerade die
    Bereichsüberschrift den Fokus übernommen.
  */
  /* Beide Wege enden in derselben Rückfrage; der Anlass steuert nur deren
     Wortlaut. Er liegt hier statt im Hook, weil er reine Darstellung ist –
     und zwingend vor den frühen Rücksprüngen, damit die Hook-Reihenfolge
     bei jedem Aufbau dieselbe ist. */
  const [anlass, setzeAnlass] = useState<Abgabeanlass>('abgeben');

  const { ladezustand, nummer, phase } = pruefung;
  useEffect(() => {
    const vorher = vorigePhase.current;
    const vorherNummer = vorigeNummer.current;
    vorigePhase.current = phase;
    vorigeNummer.current = nummer;

    if (ladezustand !== 'bereit' || phase !== 'bearbeiten') {
      return;
    }
    if (ersterAufbau.current) {
      // Beim ersten Aufbau hat die Bereichsüberschrift den Fokus übernommen.
      ersterAufbau.current = false;
      return;
    }

    /* Rückweg aus der Rückfrage: Die Schaltflächen des Dialogs verschwinden,
       der Fokus muss aufgefangen werden. Er gehört auf den Knopf, der die
       Rückfrage geöffnet hat – ein Sprung an den Anfang der Frage zwänge
       dazu, sich durch alle Antwortoptionen zurückzuarbeiten. */
    if (vorher === 'abgabefrage') {
      const ziel = anlass === 'abgeben' ? abgebenRef.current : abbrechenRef.current;
      if (ziel !== null) {
        ziel.focus();
        return;
      }
      // Ohne Ziel lieber die Überschrift als `body`.
    }

    if (vorherNummer === null || nummer !== vorherNummer || vorher !== 'bearbeiten') {
      frageTitelRef.current?.focus();
    }
  }, [ladezustand, nummer, phase, anlass]);

  if (ladezustand === 'laedt') {
    return (
      <section className="pruefung abschnitt" aria-labelledby={titelId}>
        <h1 id={titelId} ref={einstiegRef} tabIndex={-1}>
          Prüfungssimulation: {basisProfil.name}
        </h1>
        <p>Der Prüfungsbogen wird zusammengestellt …</p>
      </section>
    );
  }

  const { aktuelle } = pruefung;

  if (aktuelle === null) {
    return (
      <section className="pruefung abschnitt" aria-labelledby={titelId}>
        <h1 id={titelId} ref={einstiegRef} tabIndex={-1}>
          Prüfungssimulation: {basisProfil.name}
        </h1>
        <Hinweis art={ladezustand === 'fehler' ? 'fehler' : 'hinweis'}>
          {pruefung.meldung ?? 'Dieser Prüfungsbogen enthält keine Fragen.'}
        </Hinweis>
        <button type="button" className="knopf knopf--haupt" onClick={onAbbrechen}>
          Zum Start
        </button>
      </section>
    );
  }

  function rueckfrageOeffnen(grund: Abgabeanlass): void {
    setzeAnlass(grund);
    pruefung.abgabeAnfragen();
  }

  const bearbeitbar = phase === 'bearbeiten' || phase === 'abgabefrage';
  const amAnfang = pruefung.nummer <= 1;
  const amEnde = pruefung.nummer >= pruefung.gesamt;

  return (
    <section className="pruefung abschnitt" aria-labelledby={titelId}>
      <h1 id={titelId} ref={einstiegRef} tabIndex={-1}>
        Prüfungssimulation: {basisProfil.name}
      </h1>

      {pruefung.hinweis !== null ? <Hinweis art="hinweis">{pruefung.hinweis}</Hinweis> : null}

      {/*
        Abweichungen beim Zusammenstellen – bis Fassung 0.20.0 standen sie nur
        im Protokoll des Hauptprozesses (Befund 7.10), und wer simulierte,
        hielt seinen Bogen für profilgetreu.

        `alsMeldung` und damit `role="alert"`: Der Bogen kommt erst nach dem
        Bildaufbau, der Kasten erscheint also nachträglich. Ohne Ansage bliebe
        die Auskunft Sehenden vorbehalten. Die Sätze kommen fertig aus dem
        Anwendungskern; hier wird nichts nachformuliert.
      */}
      {pruefung.warnungen.length > 0 ? (
        <Hinweis art="hinweis" alsMeldung blockinhalt={pruefung.warnungen.length > 1}>
          {pruefung.warnungen.length === 1 ? (
            `Dieser Bogen weicht von den Vorgaben ab. ${pruefung.warnungen[0] ?? ''}`
          ) : (
            <>
              <p>Dieser Bogen weicht in mehreren Punkten von den Vorgaben ab:</p>
              <ul className="pruefung__abweichungen">
                {pruefung.warnungen.map((satz) => (
                  <li key={satz}>{satz}</li>
                ))}
              </ul>
            </>
          )}
        </Hinweis>
      ) : null}

      <div className="pruefung__kopf">
        <Fortschritt
          beschriftung="Fortschritt der Simulation"
          text={`Frage ${String(pruefung.nummer)} von ${String(pruefung.gesamt)}`}
          wert={pruefung.nummer}
          minimum={0}
          maximum={pruefung.gesamt}
        />
        {bearbeitbar ? (
          <Restzeit
            verbleibendMs={pruefung.restzeitMs}
            mitZeitlimit={pruefung.gesamtMinuten !== null}
          />
        ) : null}
      </div>

      {phase === 'auswerten' ? <Auswertungshinweis /> : null}

      {phase === 'nachbewertung' ? (
        <Nachbewertung
          fragen={pruefung.offeneFragen}
          freitextVon={pruefung.freitextVon}
          bewertungVon={pruefung.selbstbewertungVon}
          onBewerten={pruefung.selbstbewerten}
          onAbschliessen={pruefung.nachbewertungAbschliessen}
        />
      ) : null}

      {bearbeitbar ? (
        <>
          <p className="pruefung__regel">
            In der Simulation gibt es – wie in der Prüfung – keine Rückmeldung zu einzelnen
            Antworten. Sie können jede Antwort bis zur Abgabe ändern.
          </p>

          <FrageKopf
            key={aktuelle.frage.id}
            frage={aktuelle.frage}
            ebene={2}
            kapitelTitel={katalogIndex.bereichTitel(aktuelle.frage.kapitel)}
            abschnittTitel={
              aktuelle.frage.abschnitt === null
                ? null
                : katalogIndex.bereichTitel(aktuelle.frage.abschnitt)
            }
            titelRef={frageTitelRef}
            vorlesezusatz={`Frage ${String(pruefung.nummer)} von ${String(pruefung.gesamt)}`}
            bildDaten={katalogIndex.bild}
          />

          {aktuelle.auswahlfrage ? (
            <AuswahlAntwort
              key={`antwort-${aktuelle.frage.id}`}
              optionen={aktuelle.optionen}
              auswahl={pruefung.auswahlVon(aktuelle.frage.id)}
              mehrfach={aktuelle.mehrfach}
              anzahlVerraten={false}
              gesperrt={false}
              zeigeLoesung={false}
              kuerzelAktiv={false}
              onUmschalten={pruefung.optionWaehlen}
              bildDaten={katalogIndex.bild}
            />
          ) : (
            <FreitextAntwort
              key={`antwort-${aktuelle.frage.id}`}
              text={pruefung.freitextVon(aktuelle.frage.id)}
              gesperrt={false}
              onText={pruefung.freitextSetzen}
            />
          )}

          <div className="pruefung__aktionen">
            {vorlesenAktiv && stimme.moeglich ? (
              <Vorlesetaste
                laeuft={stimme.laeuft}
                gegenstand="Frage"
                onSprich={() => {
                  stimme.sprich(frageVorlesen(aktuelle.frage, aktuelle.optionen));
                }}
                onAbbrechen={stimme.abbrechen}
              />
            ) : null}

            {/* Merken im Lauf. Es verrät nichts über die Lösung – es ist eine
                Notiz an sich selbst. Ohne den Knopf hinterließ eine geratene
                oder unsichere Frage keine Spur: Nach der Abgabe zählt nur
                richtig oder falsch, und wer zufällig getroffen hat, findet
                die Frage nie wieder. */}
            <button
              type="button"
              className="knopf knopf--merken"
              aria-pressed={pruefung.istGemerkt(aktuelle.frage.id)}
              onClick={pruefung.merkenUmschalten}
            >
              <span aria-hidden="true">{pruefung.istGemerkt(aktuelle.frage.id) ? '★' : '☆'}</span>{' '}
              Frage merken
            </button>

            {/* `aria-disabled` statt `disabled` (Projektregel, siehe
                docs/stand.md 7.13): An den Enden des Bogens verschwände der
                Knopf sonst im letzten Klick unter dem Finger aus der
                Tabulatorreihenfolge, und der Fokus fiele auf `<body>`. So
                bleibt er stehen, meldet sich als nicht verfügbar, und der
                Klickbehandler kehrt früh zurück. Warum er nicht weiterführt,
                steht als Satz unter der Knopfreihe. */}
            <button
              type="button"
              className="knopf"
              aria-disabled={amAnfang}
              {...(amAnfang ? { 'aria-describedby': randId } : {})}
              onClick={() => {
                if (!amAnfang) {
                  pruefung.zurueck();
                }
              }}
            >
              Vorherige Frage
            </button>
            <button
              type="button"
              className="knopf"
              aria-disabled={amEnde}
              {...(amEnde ? { 'aria-describedby': randId } : {})}
              onClick={() => {
                if (!amEnde) {
                  pruefung.weiter();
                }
              }}
            >
              Nächste Frage
            </button>
            <button
              type="button"
              className="knopf knopf--haupt"
              ref={abgebenRef}
              onClick={() => {
                rueckfrageOeffnen('abgeben');
              }}
            >
              Simulation abgeben
            </button>
            {/* Ein Ausstieg ohne Wertung muss unmittelbar erreichbar sein.
                Ihn hinter „abgeben“ zu verstecken, hielte Übende in einer
                Simulation fest, die sie nur ausprobieren wollten. */}
            <button
              type="button"
              className="knopf knopf--leise"
              ref={abbrechenRef}
              onClick={() => {
                rueckfrageOeffnen('abbrechen');
              }}
            >
              Simulation abbrechen
            </button>
          </div>

          {/* Steht unter der Knopfreihe, nicht darüber: So rückt beim Blättern
              nichts nach, was der Prüfling gerade liest. */}
          {amAnfang || amEnde ? (
            <p className="pruefung__blaetterhinweis" id={randId}>
              {amAnfang && amEnde
                ? 'Dieser Bogen hat nur eine Frage – es gibt nichts zu blättern.'
                : amAnfang
                  ? 'Sie sind bei der ersten Frage; zurück geht es von hier nicht.'
                  : 'Sie sind bei der letzten Frage; weiter geht es von hier nicht.'}
            </p>
          ) : null}

          {phase === 'abgabefrage' ? (
            <Abgabebestaetigung
              anlass={anlass}
              gesamt={pruefung.gesamt}
              beantwortet={pruefung.beantwortet}
              offen={pruefung.offen}
              onAbgeben={pruefung.abgabeBestaetigen}
              onWeiterbearbeiten={pruefung.abgabeAbbrechen}
              onVerwerfen={onAbbrechen}
            />
          ) : null}

          <Fragenuebersicht
            fragen={pruefung.fragen}
            position={pruefung.nummer - 1}
            istBeantwortet={pruefung.istBeantwortet}
            beantwortet={pruefung.beantwortet}
            offen={pruefung.offen}
            onWaehlen={pruefung.zuFrage}
          />
        </>
      ) : null}

      {/* Ansagen der Simulation – höflich und von Anfang an im DOM vorhanden. */}
      <div className="nur-screenreader" role="status" aria-live="polite" aria-atomic="true">
        {pruefung.ansage}
      </div>
    </section>
  );
}

/**
 * Wartehinweis während der Auswertung.
 *
 * Zwischen dem Abgeben und dem Erscheinen der Auswertung verschwindet die
 * auslösende Schaltfläche. Ohne einen Anker fiele der Fokus auf `body`, und
 * mit der Tastatur stünde man ohne Ort da – bei Zeitablauf, wo gar kein Klick
 * vorausging, ebenso.
 *
 * Der Fokus liegt auf dem Wartetext selbst. Eine zusätzliche Ansage über die
 * Live-Region unterbleibt bewusst: Beides zugleich spräche denselben Satz
 * zweimal.
 */
function Auswertungshinweis(): React.JSX.Element {
  const ref = useFokusBeimAufbau<HTMLParagraphElement>();

  return (
    <p className="pruefung__warten" ref={ref} tabIndex={-1}>
      Ihre Simulation wird ausgewertet …
    </p>
  );
}
