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

import type { Erklaerungstiefe } from '../../../../shared/druck/fehlerprotokoll';
import type { Schriftgroesse } from '../../../../shared/druck/stil';
import {
  seitenFehlerprotokoll,
  seitenFragenliste,
  umfangssatz,
} from '../../../../shared/druck/umfang';
import type { Kapitel } from '../../../../shared/katalog';
import { bridge } from '../../bridge';
import { fehlerMeldung, kanalFehltMeldung } from '../../lernen/bruecke';
import { Dateiknoepfe } from './Dateiknoepfe';
import { Hinweis } from './Hinweis';

type Zustand =
  | { readonly art: 'bereit' }
  | { readonly art: 'laeuft' }
  | { readonly art: 'fertig'; readonly meldung: string; readonly kennung: string | undefined }
  | { readonly art: 'abgebrochen' }
  | { readonly art: 'fehler'; readonly meldung: string };

/**
 * Die Schriftgrößenwahl – als eigener Baustein, weil sie dreimal vorkommt.
 *
 * Der Gruppenname kommt aus {@link useId} und **nicht** aus einer Konstante.
 * Bis Fassung 0.18.0 stand in `Lernberichtausgabe` fest `name="berichtSchrift"`;
 * mit drei Ausgabeblöcken auf demselben Bildschirm hätten alle drei
 * dieselbe Radiogruppe gebildet – Großdruck im Fehlerprotokoll stellte den
 * Lernbericht mit um.
 *
 * Seit Fassung 0.20.0 steht auf dem Startbildschirm immer nur einer der drei
 * Blöcke (`Druckkarte.tsx`), die Kollision kann dort also nicht mehr
 * auftreten. Der eigene Gruppenname bleibt trotzdem: Er ist die Eigenschaft
 * des Bausteins und nicht die seiner heutigen Umgebung.
 */
export function Schriftwahl({
  wert,
  onWert,
}: {
  readonly wert: Schriftgroesse;
  readonly onWert: (wert: Schriftgroesse) => void;
}): React.JSX.Element {
  const gruppe = useId();

  return (
    <fieldset className="bericht__groesse">
      <legend>Schriftgröße des Dokuments</legend>
      {(
        [
          ['normal', 'Normal (12 Punkt)'],
          ['gross', 'Großdruck (17 Punkt)'],
        ] as const
      ).map(([stufe, beschriftung]) => (
        <label key={stufe} className="bericht__wahl">
          <input
            type="radio"
            name={gruppe}
            value={stufe}
            checked={wert === stufe}
            onChange={() => {
              onWert(stufe);
            }}
          />
          {beschriftung}
        </label>
      ))}
    </fieldset>
  );
}

/** Meldungsbereich – sichtbar für alle, angesagt für Screenreader. */
function Meldung({
  zustand,
  was,
}: {
  readonly zustand: Zustand;
  readonly was: string;
}): React.JSX.Element {
  return (
    <>
      <div role="status" aria-live="polite" className="bericht__meldung">
        {zustand.art === 'fertig' ? <Hinweis art="hinweis">{zustand.meldung}</Hinweis> : null}
        {zustand.art === 'abgebrochen' ? (
          <Hinweis art="hinweis">Es wurde nichts gespeichert.</Hinweis>
        ) : null}
        {zustand.art === 'fehler' ? <Hinweis art="fehler">{zustand.meldung}</Hinweis> : null}
        <span className="nur-screenreader">
          {zustand.art === 'laeuft' ? `${was} wird erzeugt.` : ''}
        </span>
      </div>

      {/* Außerhalb der Live-Region: Zwei Schaltflächen sind nichts, was
          vorgelesen werden müsste, sobald sie erscheinen. */}
      {zustand.art === 'fertig' ? <Dateiknoepfe kennung={zustand.kennung} /> : null}
    </>
  );
}

// ── Fehlerprotokoll ─────────────────────────────────────────────────────

export interface FehlerprotokollausgabeProps {
  readonly profilId: number | null;
  /** Wie viele Fragen zuletzt falsch beantwortet wurden; `null` ohne Lernstand. */
  readonly fehleranzahl: number | null;
}

/**
 * Das Fehlerprotokoll als PDF.
 *
 * Die Fragenzahl kommt aus derselben Größe, die der Einstieg „Nur Fehler“
 * nennt. Sie steht hier, damit die Umfangsansage nicht raten muss – und
 * damit niemand ein leeres Dokument erzeugt, ohne es vorher zu wissen.
 */
export function Fehlerprotokollausgabe({
  profilId,
  fehleranzahl,
}: FehlerprotokollausgabeProps): React.JSX.Element {
  const [zustand, setzeZustand] = useState<Zustand>({ art: 'bereit' });
  const [groesse, setzeGroesse] = useState<Schriftgroesse>('normal');
  const [tiefe, setzeTiefe] = useState<Erklaerungstiefe>('kurz');
  const tiefeGruppe = useId();
  const laeuftRef = useRef(false);

  const seiten = useMemo(
    () => (fehleranzahl === null ? null : seitenFehlerprotokoll(fehleranzahl, tiefe, groesse)),
    [fehleranzahl, groesse, tiefe],
  );

  const speichern = useCallback(() => {
    if (laeuftRef.current) {
      return;
    }
    const drucken = bridge()?.fehlerprotokollDrucken;
    if (!drucken || profilId === null) {
      setzeZustand({
        art: 'fehler',
        meldung: !drucken
          ? kanalFehltMeldung('druck:fehlerprotokoll', 'Das Protokoll kann nicht erzeugt werden.')
          : 'Ohne Lernprofil gibt es kein Fehlerprotokoll – legen Sie zuerst ein Profil an.',
      });
      return;
    }

    laeuftRef.current = true;
    setzeZustand({ art: 'laeuft' });

    void drucken(profilId, groesse, tiefe)
      .then((ergebnis) => {
        setzeZustand(
          ergebnis.gespeichert && ergebnis.pfad !== null
            ? {
                art: 'fertig',
                meldung: `Das Fehlerprotokoll wurde gespeichert: ${ergebnis.pfad}`,
                kennung: ergebnis.dateiKennung,
              }
            : { art: 'abgebrochen' },
        );
      })
      .catch((fehler: unknown) => {
        setzeZustand({
          art: 'fehler',
          meldung: fehlerMeldung(fehler, 'Das Fehlerprotokoll konnte nicht gespeichert werden.'),
        });
      })
      .finally(() => {
        laeuftRef.current = false;
      });
  }, [groesse, profilId, tiefe]);

  const laeuft = zustand.art === 'laeuft';
  const leer = fehleranzahl === 0;

  return (
    <div className="bericht">
      <p>
        Die Fragen, die Sie zuletzt falsch beantwortet haben – mit allen Antwortmöglichkeiten, der
        richtigen Antwort und einer Begründung. Dasselbe, was der Einstieg „Nur Fehler“ vorlegt, nur
        auf Papier.
      </p>

      {leer ? (
        <Hinweis art="hinweis">
          Zurzeit ist keine Frage als zuletzt falsch beantwortet vermerkt. Das Dokument bliebe leer.
        </Hinweis>
      ) : null}

      <fieldset className="bericht__groesse">
        <legend>Umfang der Begründungen</legend>
        {(
          [
            ['kurz', 'Kurzerklärung'],
            ['voll', 'Vollständige Begründung mit Fundstellen'],
          ] as const
        ).map(([stufe, beschriftung]) => (
          <label key={stufe} className="bericht__wahl">
            <input
              type="radio"
              name={tiefeGruppe}
              value={stufe}
              checked={tiefe === stufe}
              onChange={() => {
                setzeTiefe(stufe);
              }}
            />
            {beschriftung}
          </label>
        ))}
      </fieldset>

      <Schriftwahl wert={groesse} onWert={setzeGroesse} />

      {seiten !== null && !leer ? (
        <p className="bericht__umfang">
          {fehleranzahl === 1 ? '1 Frage' : `${String(fehleranzahl ?? 0)} Fragen`}.{' '}
          {umfangssatz(seiten)}
        </p>
      ) : null}

      <div>
        <button type="button" className="knopf" aria-disabled={laeuft} onClick={speichern}>
          {laeuft ? 'Das Protokoll wird erzeugt …' : 'Fehlerprotokoll als PDF speichern'}
        </button>
      </div>

      <Meldung zustand={zustand} was="Das Fehlerprotokoll" />
    </div>
  );
}

// ── Fragenliste ─────────────────────────────────────────────────────────

export interface FragenlistenausgabeProps {
  readonly kapitel: readonly Kapitel[];
  /** Fragenzahl je Bereichskennung, für die Umfangsansage. */
  readonly fragenJeBereich: ReadonlyMap<string, number>;
  readonly fragenGesamt: number;
}

/**
 * Die Fragenliste als PDF – ein Bogen zum Bearbeiten auf Papier.
 *
 * Der Lösungsanhang ist **vorgegeben**. Der Fall „ganzer Katalog ohne
 * Lösungen“ ist der einzige, in dem gar keine Auszugserwägung mehr trägt –
 * 575 von 575 Fragen und keine einzige Lösungskennzeichnung. Solange die
 * Anfrage beim Bundesverwaltungsamt aussteht, ist die vorsichtige Vorgabe die
 * richtige; abwählen lässt sie sich jederzeit.
 */
export function Fragenlistenausgabe({
  kapitel,
  fragenJeBereich,
  fragenGesamt,
}: FragenlistenausgabeProps): React.JSX.Element {
  const [zustand, setzeZustand] = useState<Zustand>({ art: 'bereit' });
  const [groesse, setzeGroesse] = useState<Schriftgroesse>('normal');
  const [mitLoesungen, setzeMitLoesungen] = useState(true);
  const [bereich, setzeBereich] = useState('');
  const auswahlId = useId();
  const anhangGruppe = useId();
  const laeuftRef = useRef(false);

  const anzahl = bereich === '' ? fragenGesamt : (fragenJeBereich.get(bereich) ?? 0);
  const seiten = useMemo(
    () => seitenFragenliste(anzahl, mitLoesungen, groesse),
    [anzahl, groesse, mitLoesungen],
  );

  const speichern = useCallback(() => {
    if (laeuftRef.current) {
      return;
    }
    const drucken = bridge()?.fragenlisteDrucken;
    if (!drucken) {
      setzeZustand({
        art: 'fehler',
        meldung: kanalFehltMeldung('druck:fragenliste', 'Die Liste kann nicht erzeugt werden.'),
      });
      return;
    }

    laeuftRef.current = true;
    setzeZustand({ art: 'laeuft' });

    void drucken(bereich === '' ? [] : [bereich], groesse, mitLoesungen)
      .then((ergebnis) => {
        setzeZustand(
          ergebnis.gespeichert && ergebnis.pfad !== null
            ? {
                art: 'fertig',
                meldung: `Die Fragenliste wurde gespeichert: ${ergebnis.pfad}`,
                kennung: ergebnis.dateiKennung,
              }
            : { art: 'abgebrochen' },
        );
      })
      .catch((fehler: unknown) => {
        setzeZustand({
          art: 'fehler',
          meldung: fehlerMeldung(fehler, 'Die Fragenliste konnte nicht gespeichert werden.'),
        });
      })
      .finally(() => {
        laeuftRef.current = false;
      });
  }, [bereich, groesse, mitLoesungen]);

  const laeuft = zustand.art === 'laeuft';

  return (
    <div className="bericht">
      <p>
        Fragen zum Bearbeiten auf Papier: mit Kästchen zum Ankreuzen und Linien für die offenen
        Fragen. Welche Antwort richtig ist, steht im Hauptteil nicht.
      </p>

      <div className="bericht__auswahl">
        <label htmlFor={auswahlId}>Welche Fragen?</label>
        <select
          id={auswahlId}
          value={bereich}
          onChange={(ereignis) => {
            setzeBereich(ereignis.target.value);
          }}
        >
          <option value="">Gesamter Fragenkatalog ({fragenGesamt} Fragen)</option>
          {kapitel.map((k) => (
            <optgroup key={k.id} label={`Kapitel ${k.id} – ${k.titel}`}>
              <option value={k.id}>
                Kapitel {k.id} vollständig ({fragenJeBereich.get(k.id) ?? 0} Fragen)
              </option>
              {k.abschnitte.map((a) => (
                <option key={a.id} value={a.id}>
                  {a.id} – {a.titel} ({fragenJeBereich.get(a.id) ?? 0} Fragen)
                </option>
              ))}
            </optgroup>
          ))}
        </select>
      </div>

      <fieldset className="bericht__groesse">
        <legend>Lösungsanhang</legend>
        {(
          [
            [true, 'Mit Lösungsanhang am Ende'],
            [false, 'Ohne Lösungen'],
          ] as const
        ).map(([wert, beschriftung]) => (
          <label key={String(wert)} className="bericht__wahl">
            <input
              type="radio"
              name={anhangGruppe}
              value={String(wert)}
              checked={mitLoesungen === wert}
              onChange={() => {
                setzeMitLoesungen(wert);
              }}
            />
            {beschriftung}
          </label>
        ))}
      </fieldset>

      <Schriftwahl wert={groesse} onWert={setzeGroesse} />

      <p className="bericht__umfang">
        {anzahl === 1 ? '1 Frage' : `${String(anzahl)} Fragen`}. {umfangssatz(seiten)}
      </p>

      <div>
        <button type="button" className="knopf" aria-disabled={laeuft} onClick={speichern}>
          {laeuft ? 'Die Liste wird erzeugt …' : 'Fragenliste als PDF speichern'}
        </button>
      </div>

      <Meldung zustand={zustand} was="Die Fragenliste" />
    </div>
  );
}
