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

import type { Lernuebersicht, Profil } from '../../../shared/lernstand';
import { bridge } from '../bridge';
import { fehlerMeldung, kanalFehltMeldung, lernBruecke } from '../lernen/bruecke';

export interface Lernstand {
  readonly laedt: boolean;
  /** Profil, unter dem gespeichert wird; `null`, solange keines feststeht. */
  readonly profilId: number | null;
  /** Alle angelegten Profile – leer, solange sie nicht geladen sind. */
  readonly profile: readonly Profil[];
  readonly uebersicht: Lernuebersicht | null;
  /** Erklärt in einfachen Worten, warum der Lernstand fehlt. */
  readonly meldung: string | null;
  readonly neuLaden: () => void;
  /**
   * Wechselt das Profil.
   *
   * **Nur aus einem ruhigen Zustand heraus aufrufen** – vom Startbildschirm,
   * nicht aus einer laufenden Sitzung oder Prüfung. Die Lauf-Hooks laden bei
   * einem Profilwechsel zwar ihre Frageliste neu, behalten aber Position,
   * Protokolle, Eingaben und die Uhr. Eine danach bestätigte Antwort ginge
   * unter dem neuen Profil in die Datenbank, obwohl die Frage aus der
   * Zusammenstellung des alten stammt.
   */
  readonly wechselnZu: (profilId: number) => void;
}

interface Innen {
  readonly laedt: boolean;
  readonly profile: readonly Profil[];
  readonly profilId: number | null;
  readonly uebersicht: Lernuebersicht | null;
  readonly meldung: string | null;
}

const OHNE_KANAL = kanalFehltMeldung(
  'profil:liste',
  'Ihr Lernstand ist zurzeit nicht verfügbar. Sie können trotzdem üben; Ihre Antworten werden dabei nicht gespeichert.',
);

const OHNE_PROFIL =
  'Es ist noch kein Lernprofil angelegt. Sie können trotzdem üben; Ihre Antworten werden dabei nicht gespeichert.';

function anfangszustand(): Innen {
  const bereit = lernBruecke()?.profilListe !== undefined;
  return {
    laedt: bereit,
    profile: [],
    profilId: null,
    uebersicht: null,
    meldung: bereit ? null : OHNE_KANAL,
  };
}

/**
 * Ermittelt Profil, Profilliste und Prüfungsreife-Übersicht.
 *
 * Beide Kanäle dürfen ausfallen: die Oberfläche bleibt bedienbar und sagt
 * verständlich, was dadurch fehlt.
 *
 * **Warum die Profil-ID beim Neuladen stehen bleibt.** Früher setzte
 * `neuLaden` den ganzen Zustand auf „lädt" zurück, und „lädt" hieß
 * `profilId: null`. Jeder Verbraucher deutete das als „kein Profil": Die
 * Prüfungsauswertung meldete für einen Augenblick „Ohne Lernprofil wird kein
 * Verlauf geführt", der Lernplan verschwand, der Export meldete einen Fehler.
 * Sichtbar war das nach jeder Prüfungssimulation als kurzes Aufblitzen. Die
 * Profil-ID ändert sich beim Neuladen aber gar nicht – sie bleibt jetzt
 * stehen, und nur die Zahlen darunter werden erneuert.
 */
export function useLernstand(): Lernstand {
  const [innen, setzeInnen] = useState<Innen>(anfangszustand);
  const [runde, setzeRunde] = useState(0);
  /** Ausdrücklich gewähltes Profil; überstimmt das zuletzt gespeicherte. */
  const gewaehlt = useRef<number | null>(null);

  useEffect(() => {
    let abgebrochen = false;

    /* Bewusst eine benannte Funktion statt eines sofort aufgerufenen
       Ausdrucks: nur so bleibt `abgebrochen` für den Typprüfer eine
       Veränderliche, die die Aufräumfunktion später umsetzen kann. */
    async function ermitteln(): Promise<void> {
      const bruecke = lernBruecke();
      const profilListe = bruecke?.profilListe;
      if (!bruecke || !profilListe) {
        return;
      }

      let profile: readonly Profil[];
      try {
        profile = await profilListe();
      } catch (fehler: unknown) {
        if (!abgebrochen) {
          setzeInnen((bisher) => ({
            ...bisher,
            laedt: false,
            meldung: fehlerMeldung(
              fehler,
              'Ihr Lernprofil konnte nicht geladen werden. Sie können trotzdem üben; Ihre Antworten werden dabei nicht gespeichert.',
            ),
          }));
        }
        return;
      }

      const [erstes] = profile;
      if (!erstes) {
        if (!abgebrochen) {
          setzeInnen({
            laedt: false,
            profile: [],
            profilId: null,
            uebersicht: null,
            meldung: OHNE_PROFIL,
          });
        }
        return;
      }

      /* Reihenfolge der Wahl: ausdrücklich gewählt, sonst das zuletzt
         benutzte, sonst das erste. Ein gespeichertes Profil, das es nicht
         mehr gibt – gelöscht, oder die Datei von Hand bearbeitet –, wird
         übergangen statt zu einem leeren Lernstand zu führen. */
      const kennt = (id: number | null | undefined): boolean =>
        id !== null && id !== undefined && profile.some((p) => p.id === id);

      let profilId = erstes.id;
      if (kennt(gewaehlt.current)) {
        profilId = gewaehlt.current ?? profilId;
      } else {
        try {
          const gespeichert = await bridge()?.einstellungenLesen();
          if (kennt(gespeichert?.profilId)) {
            profilId = gespeichert?.profilId ?? profilId;
          }
        } catch {
          /* Ohne Einstellungen bleibt es beim ersten Profil. */
        }
      }
      gewaehlt.current = profilId;

      const uebersichtLesen = bruecke.lernUebersicht;
      if (!uebersichtLesen) {
        if (!abgebrochen) {
          setzeInnen({
            laedt: false,
            profile,
            profilId,
            uebersicht: null,
            meldung: kanalFehltMeldung(
              'lernen:uebersicht',
              'Ihre Prüfungsreife lässt sich zurzeit nicht anzeigen.',
            ),
          });
        }
        return;
      }

      try {
        const uebersicht = await uebersichtLesen(profilId);
        if (!abgebrochen) {
          setzeInnen({ laedt: false, profile, profilId, uebersicht, meldung: null });
        }
      } catch (fehler: unknown) {
        if (!abgebrochen) {
          setzeInnen({
            laedt: false,
            profile,
            profilId,
            uebersicht: null,
            meldung: fehlerMeldung(fehler, 'Ihre Prüfungsreife konnte nicht geladen werden.'),
          });
        }
      }
    }

    void ermitteln();

    return () => {
      abgebrochen = true;
    };
  }, [runde]);

  const neuLaden = useCallback(() => {
    /* Kein Zurücksetzen auf „lädt": Die Profil-ID ändert sich dabei nicht,
       und ein zwischenzeitliches `null` deuteten alle Verbraucher als „kein
       Profil". */
    setzeRunde((bisher) => bisher + 1);
  }, []);

  const wechselnZu = useCallback((profilId: number) => {
    const vorher = gewaehlt.current;
    gewaehlt.current = profilId;

    /* Das zuletzt benutzte Profil merken – auch dann, wenn es dasselbe
       bleibt: Beim ersten Start steht in den Einstellungen noch nichts, und
       die Bestätigung „ja, ich bin das" ist genau der Augenblick, in dem es
       hineingehört. Schlägt das Sichern fehl, ist die Wahl trotzdem
       vollzogen; beim nächsten Start beginnt die Anwendung dann wieder beim
       ersten Profil. */
    void bridge()
      ?.einstellungenSchreiben({ profilId })
      .catch((fehler: unknown) => {
        console.warn('[lernstand] Profilwahl konnte nicht gesichert werden:', fehler);
      });

    /* Wer sein eigenes Profil bestätigt, soll keine Ladeanzeige sehen. */
    if (vorher === profilId) {
      return;
    }

    /* Die ID springt sofort um, ohne den Umweg über `null`: Sonst schriebe
       ein Verbraucher in genau diesem Augenblick ins Leere. Die Zahlen
       darunter kommen mit der nächsten Runde nach. */
    setzeInnen((bisher) => ({ ...bisher, profilId, uebersicht: null, laedt: true }));
    setzeRunde((bisher) => bisher + 1);
  }, []);

  return { ...innen, neuLaden, wechselnZu };
}
