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

import type { Lernplan } from '../../../shared/lernplan';
import { fehlerMeldung, kanalFehltMeldung, lernBruecke } from '../lernen/bruecke';

export interface LernplanZustand {
  readonly laedt: boolean;
  readonly plan: Lernplan | null;
  /** Erklärt in einfachen Worten, warum der Plan fehlt. */
  readonly meldung: string | null;
  /**
   * Setzt oder löscht den Prüfungstermin und lädt den Plan neu.
   *
   * Wirft nicht: Ein Fehlschlag landet in {@link meldung}, damit die
   * Oberfläche ihn anzeigen kann, statt in einer abgewiesenen Zusage zu
   * verschwinden.
   */
  readonly terminSetzen: (termin: string | null) => Promise<void>;
  /** Kapitel dauerhaft ab- oder wieder anwählen. */
  readonly kapitelAusschlussSetzen: (kapitelAusschluss: readonly string[]) => Promise<void>;
  readonly neuLaden: () => void;
}

/**
 * Ergebnis eines Ladelaufs, zusammen mit dem Profil, zu dem es gehört.
 *
 * Das Profil steht bewusst mit im Zustand: Nur so lässt sich beim Rendern
 * erkennen, dass ein vorhandener Plan noch zu einem *anderen* Profil gehört –
 * ohne dafür im Effekt synchron den Zustand zurückzusetzen, was eine Kaskade
 * zusätzlicher Renderdurchläufe auslösen würde.
 */
type Ergebnis =
  | { readonly art: 'leer' }
  | { readonly art: 'fertig'; readonly profilId: number; readonly plan: Lernplan }
  | { readonly art: 'fehler'; readonly profilId: number; readonly meldung: string };

const OHNE_KANAL = kanalFehltMeldung(
  'lernen:plan',
  'Ihr Lernplan lässt sich zurzeit nicht anzeigen.',
);

/**
 * Lädt den Lernplan zu einem Profil.
 *
 * Bewusst getrennt von `useLernstand`: Der Plan rechnet über den gesamten
 * Katalog und darf ausfallen, ohne die Prüfungsreife-Anzeige mitzureißen.
 */
export function useLernplan(profilId: number | null): LernplanZustand {
  const [ergebnis, setzeErgebnis] = useState<Ergebnis>({ art: 'leer' });
  const [runde, setzeRunde] = useState(0);

  useEffect(() => {
    if (profilId === null) {
      return;
    }

    let abgebrochen = false;

    /* Benannte Funktion statt sofort aufgerufenem Ausdruck: nur so bleibt
       `abgebrochen` für den Typprüfer eine Veränderliche, die die
       Aufräumfunktion später umsetzen kann. */
    async function laden(id: number): Promise<void> {
      const planLesen = lernBruecke()?.lernPlan;
      if (!planLesen) {
        if (!abgebrochen) {
          setzeErgebnis({ art: 'fehler', profilId: id, meldung: OHNE_KANAL });
        }
        return;
      }

      try {
        const plan = await planLesen(id);
        if (!abgebrochen) {
          setzeErgebnis({ art: 'fertig', profilId: id, plan });
        }
      } catch (fehler: unknown) {
        if (!abgebrochen) {
          setzeErgebnis({
            art: 'fehler',
            profilId: id,
            meldung: fehlerMeldung(fehler, 'Ihr Lernplan konnte nicht geladen werden.'),
          });
        }
      }
    }

    void laden(profilId);

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

  /**
   * Kapitel dauerhaft ab- oder wieder anwählen.
   *
   * Denselben Weg wie der Prüfungstermin: dieselbe Profilzeile, derselbe
   * Kanal, dieselbe Karte. Beide gehören zusammen – der Termin sagt *bis
   * wann*, die Kapitelwahl sagt *wie viel*.
   */
  const kapitelAusschlussSetzen = useCallback(
    async (kapitelAusschluss: readonly string[]): Promise<void> => {
      if (profilId === null) {
        return;
      }
      const aktualisieren = lernBruecke()?.profilAktualisieren;
      if (!aktualisieren) {
        setzeErgebnis({
          art: 'fehler',
          profilId,
          meldung: kanalFehltMeldung(
            'profil:aktualisieren',
            'Die Kapitelauswahl lässt sich nicht sichern.',
          ),
        });
        return;
      }

      try {
        await aktualisieren(profilId, { kapitelAusschluss });
        setzeRunde((bisher) => bisher + 1);
      } catch (fehler: unknown) {
        setzeErgebnis({
          art: 'fehler',
          profilId,
          meldung: fehlerMeldung(fehler, 'Die Kapitelauswahl konnte nicht gesichert werden.'),
        });
      }
    },
    [profilId],
  );

  const neuLaden = useCallback(() => {
    setzeRunde((bisher) => bisher + 1);
  }, []);

  const terminSetzen = useCallback(
    async (termin: string | null): Promise<void> => {
      if (profilId === null) {
        return;
      }
      const aktualisieren = lernBruecke()?.profilAktualisieren;
      if (!aktualisieren) {
        setzeErgebnis({
          art: 'fehler',
          profilId,
          meldung: kanalFehltMeldung(
            'profil:aktualisieren',
            'Ihr Prüfungstermin lässt sich nicht sichern.',
          ),
        });
        return;
      }

      try {
        await aktualisieren(profilId, { pruefungstermin: termin });
        setzeRunde((bisher) => bisher + 1);
      } catch (fehler: unknown) {
        setzeErgebnis({
          art: 'fehler',
          profilId,
          meldung: fehlerMeldung(fehler, 'Ihr Prüfungstermin konnte nicht gesichert werden.'),
        });
      }
    },
    [profilId],
  );

  if (profilId === null) {
    return {
      laedt: false,
      plan: null,
      meldung: null,
      terminSetzen,
      kapitelAusschlussSetzen,
      neuLaden,
    };
  }

  /* Ein Ergebnis zu einem anderen Profil ist überholt: Dann läuft der Ladelauf
     für das neue Profil bereits, und bis er fertig ist, gilt „lädt“. */
  if (ergebnis.art === 'leer' || ergebnis.profilId !== profilId) {
    return {
      laedt: true,
      plan: null,
      meldung: null,
      terminSetzen,
      kapitelAusschlussSetzen,
      neuLaden,
    };
  }

  return ergebnis.art === 'fertig'
    ? {
        laedt: false,
        plan: ergebnis.plan,
        meldung: null,
        terminSetzen,
        kapitelAusschlussSetzen,
        neuLaden,
      }
    : {
        laedt: false,
        plan: null,
        meldung: ergebnis.meldung,
        terminSetzen,
        kapitelAusschlussSetzen,
        neuLaden,
      };
}
