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

/** Takt der Restzeitanzeige. */
const TAKT_MS = 1000;

/**
 * Ab dieser Lücke zwischen zwei Takten gilt der Ablauf als fraglich.
 *
 * Der Wert liegt bewusst weit über dem Takt: Ein belasteter Rechner
 * verschiebt einen Zeitgeber um einige hundert Millisekunden, ohne dass
 * irgendetwas vorgefallen wäre.
 */
const LUECKE_MS = 5000;

/** So viele Takte wird die Abgabe nach einer Lücke aufgeschoben. */
const KARENZ_TAKTE = 2;

/**
 * Verbleibende Zeit bis zu einem Zeitpunkt.
 *
 * `null` als Endzeitpunkt bedeutet: keine Zeitmessung. Dann läuft auch kein
 * Intervall – „Ohne Zeitbegrenzung“ ist nicht nur eine andere Anzeige,
 * sondern tatsächlich kein Zeitgeber (WCAG 2.2.1).
 *
 * `beiAblauf` wird genau einmal aus dem Intervall heraus gerufen, nicht aus
 * einem Effekt: Der Zeitgeber ist ein äußeres System, und von dort darf ein
 * Zustandswechsel angestoßen werden, ohne Folgerenderungen zu stapeln.
 *
 * **Die Karenz nach einer Taktlücke.** Schläft der Rechner mitten in einer
 * Simulation, steht beim Aufwachen unter Umständen schon eine abgelaufene
 * Wanduhr da. Der Hauptprozess misst die Schlafdauer und schickt eine
 * Gutschrift (`main/energie.ts`), die den Zeitursprung vorschreibt – aber sie
 * braucht einen Augenblick, und der erste nachgeholte Takt käme ihr sonst
 * zuvor und gäbe den Bogen ab. Deshalb wird die Abgabe aufgeschoben, wenn
 * zwischen zwei Takten eine auffällige Lücke lag.
 *
 * Aufgeschoben, **nicht gutgeschrieben**: Eine Taktlücke entsteht auch, wenn
 * Chromium den Zeitgeber eines verdeckten Fensters drosselt. Würde daraus
 * Zeit gutgeschrieben, ließe sich die Prüfungsuhr durch Minimieren anhalten.
 * Bleibt die Gutschrift aus, gibt der übernächste Takt ganz normal ab.
 */
export function useRestzeit(endeMs: number | null, beiAblauf?: () => void): number | null {
  const [jetzt, setzeJetzt] = useState(() => Date.now());
  const ablauf = useRef<(() => void) | undefined>(undefined);

  // Referenz statt Abhängigkeit: Ein neuer Rückruf soll das Intervall nicht
  // neu aufsetzen und damit die Zählung verschieben.
  useEffect(() => {
    ablauf.current = beiAblauf;
  }, [beiAblauf]);

  useEffect(() => {
    if (endeMs === null) {
      return undefined;
    }

    /* Der Bezugspunkt gehört in den Effekt: Eine Gutschrift setzt `endeMs`
       neu, der Effekt läuft wieder an, und die alte Lücke ist erledigt. */
    let letzterTakt = Date.now();
    let karenz = 0;

    const kennung = window.setInterval(() => {
      const nun = Date.now();
      const luecke = nun - letzterTakt;
      letzterTakt = nun;
      setzeJetzt(nun);

      if (nun < endeMs) {
        karenz = 0;
        return;
      }

      if (luecke > LUECKE_MS) {
        karenz = KARENZ_TAKTE;
        return;
      }
      if (karenz > 0) {
        karenz -= 1;
        return;
      }

      window.clearInterval(kennung);
      ablauf.current?.();
    }, TAKT_MS);

    return () => {
      window.clearInterval(kennung);
    };
  }, [endeMs]);

  return endeMs === null ? null : Math.max(0, endeMs - jetzt);
}
