import { Component, createRef, type ErrorInfo, type ReactNode } from 'react';

interface FehlerauffangProps {
  readonly children: ReactNode;
}

interface FehlerauffangZustand {
  readonly grund: string | null;
}

/**
 * Kurzer, verständlicher Grund aus einem beliebigen Fehlerwert.
 *
 * Kein Stapelabbild in der Oberfläche: Es hilft niemandem beim Weiterkommen
 * und liest sich am Screenreader wie Rauschen. Die vollständige Meldung geht
 * in die Konsole.
 */
function grundText(fehler: unknown): string {
  if (fehler instanceof Error && fehler.message.trim().length > 0) {
    return fehler.message.trim();
  }
  const text = String(fehler).trim();
  return text.length > 0 ? text : 'Unbekannter Fehler';
}

/**
 * Fängt Fehler der Oberfläche ab, statt ein leeres Fenster zu hinterlassen.
 *
 * **Warum das nötig ist.** Wirft eine Komponente beim Rendern, baut React den
 * ganzen Baum ab. Ohne Auffangbereich bleibt ein weißes Fenster stehen – für
 * jemanden am Screenreader eine Sackgasse: kein Text, keine Überschrift,
 * keine Schaltfläche, nicht einmal die Auskunft, dass etwas schiefging.
 *
 * **Warum eine Klasse.** React bietet für diesen Zweck bis heute keinen Hook;
 * `componentDidCatch` und `getDerivedStateFromError` gibt es nur an
 * Klassenkomponenten. Das ist der einzige Grund.
 *
 * **Warum der Hinweis auf den Lernstand.** Wer eine Fehlerseite sieht, fragt
 * sich zuerst, ob die Arbeit weg ist. Sie ist es nicht: Antworten stehen in
 * der Datenbank, sobald sie bestätigt wurden, und ein laufender Prüfungsbogen
 * wird fortlaufend gesichert. Das gehört auf die Seite, nicht in eine
 * Anleitung.
 */
export class Fehlerauffang extends Component<FehlerauffangProps, FehlerauffangZustand> {
  override state: FehlerauffangZustand = { grund: null };

  /**
   * Die Überschrift der Absturzseite – das Ziel des Fokus.
   *
   * Über eine Referenz und nicht über `autoFocus`. Nachgemessen: React wendet
   * `autoFocus` nur auf `button`, `input`, `select` und `textarea` an; an
   * einer `h1` verschwindet es ersatzlos, das Element trägt danach nicht
   * einmal das Attribut. Bis 0.24.1 stand der Fokus nach einem Absturz
   * deshalb auf `<body>` – genau die Sackgasse, die dieser Auffangbereich
   * verhindern soll.
   */
  private readonly titel = createRef<HTMLHeadingElement>();

  static getDerivedStateFromError(fehler: unknown): FehlerauffangZustand {
    return { grund: grundText(fehler) };
  }

  override componentDidCatch(fehler: Error, info: ErrorInfo): void {
    console.error('[oberflaeche] Unbehandelter Fehler:', fehler, info.componentStack);
  }

  /*
    Beide Wege, und das ist kein Gürtel-und-Hosenträger.

    Wirft ein Kind schon beim **ersten** Aufbau, ist der Auffangbereich selbst
    noch am Einhängen: React zeigt die Fehlerseite, und danach läuft
    `componentDidMount`, nicht `componentDidUpdate`. Wirft es **später**, ist
    es umgekehrt. Nachgemessen fielen ohne den ersten Weg genau die Abstürze
    beim Aufbau durch – und das ist der häufigere Fall.
  */
  override componentDidMount(): void {
    this.fokusHolen();
  }

  override componentDidUpdate(_props: FehlerauffangProps, vorher: FehlerauffangZustand): void {
    if (vorher.grund === null) {
      this.fokusHolen();
    }
  }

  private fokusHolen(): void {
    if (this.state.grund !== null) {
      this.titel.current?.focus();
    }
  }

  override render(): ReactNode {
    const { grund } = this.state;
    if (grund === null) {
      return this.props.children;
    }

    return (
      <div className="seite">
        <main id="inhalt" className="abschnitt">
          {/*
            `role="alert"` und `tabIndex={-1}` mit gesetztem Fokus: Die Seite
            erscheint ohne Zutun, also muss sie sich melden UND den Fokus
            holen – sonst steht der Fokus auf einem Element, das es nicht
            mehr gibt, und die Tastatur hat keinen Anker. Den Fokus setzt
            `componentDidUpdate`; siehe dort, warum nicht `autoFocus`.
          */}
          <section className="karte abschnitt" role="alert">
            <h1 tabIndex={-1} ref={this.titel}>
              Die Anzeige ist abgestürzt
            </h1>

            <p>
              In der Oberfläche ist etwas schiefgegangen.{' '}
              <strong>Ihr Lernstand ist nicht betroffen:</strong> Beantwortete Fragen stehen in der
              Datenbank auf Ihrer Festplatte, und ein laufender Prüfungsbogen wird fortlaufend
              mitgeschrieben – Sie können ihn nach dem Neuladen fortsetzen.
            </p>

            <p>
              Der Fehler lautet: <span className="fehlerauffang__grund">{grund}</span>
            </p>

            <div>
              <button
                type="button"
                className="knopf knopf--haupt"
                onClick={() => {
                  window.location.reload();
                }}
              >
                Anwendung neu laden
              </button>
            </div>

            <p className="fehlerauffang__hinweis">
              Hilft das nicht, schließen Sie das Fenster und starten Sie das Programm neu. Über
              „Ansicht → Neu laden“ geht es ebenfalls.
            </p>
          </section>
        </main>
      </div>
    );
  }
}
