import { useEffect, useState } from 'react';

import type { KatalogMeta } from '../../../shared/katalog';
import type { AnwendungsInfo, Katalogwechsel } from '../../../shared/ipc';
import { bridge } from '../bridge';
import { Hinweis } from './lernen/Hinweis';

export interface SystemStatusProps {
  /** Kopfdaten des geladenen Fragenkatalogs, oder `null`, solange keiner da ist. */
  readonly katalog?: KatalogMeta | null;
}

/**
 * Der Rechnername der Quelle, ohne Pfad.
 *
 * Die volle Quell-URL ist rund hundert Zeichen lang und stünde in dieser
 * Liste als Zeilensalat. Sie steht ungekürzt unter „Über diese Software“;
 * hier genügt der Ort zum Nachsehen. Abgeleitet und nicht eingetippt: Ein
 * zweiter, von Hand gepflegter Eintrag liefe bei der nächsten BVA-Fassung
 * auseinander – genau der Fehler, den `docs/entscheidung-katalog-aktualitaet.md`
 * beschreibt.
 */
function quellort(url: string): string {
  try {
    return new URL(url).hostname;
  } catch {
    /* Steht dort keine gültige Adresse, ist die rohe Angabe immer noch
       besser als gar keine – es ist eine Angabe zur Herkunft. */
    return url;
  }
}

/**
 * ISO-Datum als deutsches Datum. Bleibt der Wert unlesbar, wird er
 * unverändert gezeigt – eine Angabe zur Fehlersuche darf nicht verschwinden,
 * nur weil sie ein unerwartetes Format hat.
 */
function deutschesDatum(iso: string): string {
  const zeit = Date.parse(iso);
  if (Number.isNaN(zeit)) {
    return iso;
  }
  return new Date(zeit).toLocaleDateString('de-DE', {
    day: '2-digit',
    month: '2-digit',
    year: 'numeric',
  });
}

/** Zahl mit deutschem Zählwort – „1 Eintrag“, „2 Einträge“. */
function anzahl(wert: number, einzahl: string, mehrzahl: string): string {
  return `${String(wert)} ${wert === 1 ? einzahl : mehrzahl}`;
}

/**
 * Der Meldetext zu einem Katalogwechsel: was geschah, was betroffen ist, dass
 * nichts gelöscht wurde.
 *
 * Als eine Zeichenkette gebaut und nicht als JSX-Stückwerk, damit der Satz am
 * Stück prüfbar bleibt und keine Leerzeichen zwischen Ausdrücken verloren
 * gehen. „Fortschritt und Wiedervorlage“ ist bewusst eng gefasst: Genau dort
 * zählen die verwaisten Zeilen nicht mehr mit; die Tagesbilanz dagegen ist
 * ein Protokoll des Getanen und behält sie (siehe `lernfragen` im Kern).
 */
function katalogwechselText(wechsel: Katalogwechsel): string {
  const kopf =
    'Der Fragenkatalog hat die Fassung gewechselt: ' +
    `bisher Stand ${deutschesDatum(wechsel.vorher)}, jetzt Stand ${deutschesDatum(wechsel.nachher)}.`;

  if (wechsel.verwaisteStaende === 0 && wechsel.verwaisteAntworten === 0) {
    return `${kopf} Alle gespeicherten Antworten und Fragenstände passen zu Fragen der jetzt geladenen Fassung.`;
  }

  return (
    `${kopf} ${anzahl(wechsel.verwaisteAntworten, 'Eintrag', 'Einträge')} im Antwortprotokoll und ` +
    `${anzahl(wechsel.verwaisteStaende, 'gespeicherter Fragenstand', 'gespeicherte Fragenstände')} ` +
    'gehören zu Fragennummern, die es in der jetzt geladenen Fassung nicht gibt – sie zählen in ' +
    'Fortschritt und Wiedervorlage nicht mehr mit. Gelöscht wurde nichts.'
  );
}

type Zustand =
  | { readonly art: 'laedt' }
  | { readonly art: 'fertig'; readonly info: AnwendungsInfo }
  | { readonly art: 'fehler'; readonly meldung: string };

/**
 * Zeigt Laufzeitinformationen aus dem Main-Prozess – gleichzeitig der
 * sichtbare Nachweis, dass der typisierte IPC-Kanal funktioniert.
 */
export function SystemStatus({ katalog = null }: SystemStatusProps = {}): React.JSX.Element {
  /* Der Fehlerfall „keine Bridge“ steht schon beim ersten Render fest.
     Er gehört deshalb in den Initialwert – ein setState im Effekt würde
     hier nur eine überflüssige zweite Renderrunde auslösen. */
  const [zustand, setzeZustand] = useState<Zustand>(() =>
    bridge()
      ? { art: 'laedt' }
      : {
          art: 'fehler',
          meldung: 'Keine Verbindung zum Anwendungskern (Preload-Bridge nicht verfügbar).',
        },
  );

  useEffect(() => {
    let abgebrochen = false;
    const api = bridge();

    if (!api) {
      return;
    }

    void api
      .anwendungsInfoLesen()
      .then((info) => {
        if (!abgebrochen) {
          setzeZustand({ art: 'fertig', info });
        }
      })
      .catch((fehler: unknown) => {
        if (!abgebrochen) {
          setzeZustand({
            art: 'fehler',
            meldung: fehler instanceof Error ? fehler.message : String(fehler),
          });
        }
      });

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

  if (zustand.art === 'laedt') {
    return <p>Systeminformationen werden geladen …</p>;
  }

  if (zustand.art === 'fehler') {
    return (
      <p>
        <span className="statusabzeichen statusabzeichen--fehler">Nicht verfügbar</span>{' '}
        {zustand.meldung}
      </p>
    );
  }

  const { info } = zustand;
  const dbOk = info.datenbank.verfuegbar;
  const wechsel = info.katalogwechsel ?? null;

  return (
    <>
      {/*
        Ein Katalogwechsel kommt vor der Liste: Er ist die einzige Angabe
        hier, die eine Handlungsfolge hat – wessen Lernstand unter einer
        anderen Fassung entstand, soll das nicht am Ende einer Aufzählung
        von Versionsnummern erfahren. Der Kern meldet ihn genau in der
        Sitzung, die den Wechsel zuerst sieht; danach schweigt er wieder.
      */}
      {wechsel === null ? null : <Hinweis art="hinweis">{katalogwechselText(wechsel)}</Hinweis>}
      <dl className="statusliste">
        <dt>Programmversion</dt>
        <dd>{info.anwendungsVersion}</dd>

        {/* Ohne Git zur Bauzeit gibt es keine Kennung. Dann steht hier nichts,
          statt eines nichtssagenden „unbekannt“. */}
        {info.baukennung === '' ? null : (
          <>
            <dt>Baustand</dt>
            <dd>
              <code className="statusliste__baukennung">{info.baukennung}</code>
              {info.baustand === '' ? null : ` vom ${deutschesDatum(info.baustand)}`}
            </dd>
          </>
        )}

        <dt>Electron</dt>
        <dd>{info.electronVersion}</dd>

        <dt>Chromium</dt>
        <dd>{info.chromeVersion}</dd>

        <dt>Node.js</dt>
        <dd>{info.nodeVersion}</dd>

        {/*
        Der Katalogstand gehört hierher und nicht nur unter „Über diese
        Software“: Diese Anwendung kann nicht wissen, ob das
        Bundesverwaltungsamt inzwischen eine neuere Fassung veröffentlicht hat
        – sie geht nicht ins Netz (siehe docs/entscheidung-katalog-aktualitaet.md).
        Was sie kann, ist sagen, wie alt ihre Inhalte sind, und wo man
        nachsieht. Das Datum kommt aus dem Katalog selbst, damit es mit ihm
        wandert.

        Als Text, nicht als Verweis: Die Anwendung öffnet von sich aus nichts
        nach außen. Dieselbe Begründung wie bei der Quelle unter „Über diese
        Software“ und beim Rückmeldeweg in `shared/kontakt.ts`.
      */}
        <dt>Fragenkatalog</dt>
        <dd>
          {katalog === null ? (
            'Nicht geladen.'
          ) : (
            <>
              Stand {deutschesDatum(katalog.stand)} · <code>{quellort(katalog.quelle_url)}</code>
            </>
          )}
        </dd>

        <dt>Datenbank</dt>
        <dd>
          <span
            className={`statusabzeichen ${dbOk ? 'statusabzeichen--ok' : 'statusabzeichen--fehler'}`}
          >
            {dbOk ? 'Bereit' : 'Fehler'}
          </span>{' '}
          {info.datenbank.meldung}
        </dd>

        {/*
          Der Ablageort des Fehlerprotokolls.

          Er steht hier, weil dieser Abschnitt ohnehin der ist, den jemand
          aufschlägt, der etwas melden will – das Handbuch verweist für
          Rückfragen ausdrücklich hierher. Ohne die Angabe wüsste niemand,
          dass es die Datei gibt, und sie wäre so nutzlos wie die Konsole,
          die sie ersetzt.

          Der zweite Satz ist keine Höflichkeit, sondern die Zusage, die
          diese Datei überhaupt vertretbar macht: Sie geht nirgendwohin.
        */}
        {info.protokollPfad === undefined || info.protokollPfad === null ? null : (
          <>
            <dt>Fehlerprotokoll</dt>
            <dd>
              <code className="statusliste__pfad">{info.protokollPfad}</code> Diese Datei bleibt auf
              Ihrem Rechner und wird nicht übertragen. Sie enthält technische Fehlermeldungen, keine
              Lerndaten. Wenn Sie einen Absturz melden, können Sie sie von Hand anhängen.
            </dd>
          </>
        )}
      </dl>
    </>
  );
}
