/**
 * Ersatzweise Auswertung eines Simulationslaufs – reine Funktionen.
 *
 * Regelfall ist der Kanal `pruefung:auswerten`. Fällt er aus, rechnet die
 * Oberfläche selbst – und zwar mit {@link urteilBilden} aus dem Vertrag, damit
 * beide Wege dasselbe Urteil ergeben. Eine Multiple-Choice-Antwort gilt nur
 * dann als richtig, wenn genau die richtigen Optionen gewählt wurden
 * ({@link bewerteAuswahl}).
 */

import { bewerteAuswahl, type Frage } from '../../../shared/katalog';
import {
  urteilBilden,
  type BereichErgebnis,
  type Pruefungsantwort,
  type Pruefungsergebnis,
  type Pruefungsprofil,
} from '../../../shared/pruefung';
import { istAuswahlfrage } from '../lernen/bewertung';
import { gehoertZuBereich } from './bogen';

export interface Bewertungsdaten {
  /** Wirksames Profil, also mit den tatsächlich gewählten Werten. */
  readonly profil: Pruefungsprofil;
  readonly fragen: readonly Frage[];
  readonly antworten: readonly Pruefungsantwort[];
  readonly bereichTitel: (bereichId: string) => string;
  readonly dauerMs: number;
  readonly zeitAbgelaufen: boolean;
  /** Zeitpunkt als ISO-Zeichenkette – wird hereingereicht, nicht erzeugt. */
  readonly zeitpunkt: string;
}

interface Zaehler {
  gesamt: number;
  richtig: number;
}

/** Wurde die Frage überhaupt bearbeitet? */
function istLeer(antwort: Pruefungsantwort | undefined, auswahlfrage: boolean): boolean {
  if (antwort === undefined) {
    return true;
  }
  if (auswahlfrage) {
    return antwort.auswahl.length === 0;
  }
  return (antwort.freitext ?? '').trim().length === 0 && antwort.selbstAlsRichtig === undefined;
}

export function ergebnisBerechnen(daten: Bewertungsdaten): Pruefungsergebnis {
  const nachId = new Map(daten.antworten.map((antwort) => [antwort.frageId, antwort]));
  const bereiche = new Map<string, Zaehler>();
  const richtigJeFrage = new Map<string, boolean>();
  const fehlerIds: string[] = [];

  let richtig = 0;
  let falsch = 0;
  let unbeantwortet = 0;

  for (const frage of daten.fragen) {
    const antwort = nachId.get(frage.id);
    const auswahlfrage = istAuswahlfrage(frage);
    const leer = istLeer(antwort, auswahlfrage);
    const istRichtig =
      antwort !== undefined &&
      !leer &&
      (auswahlfrage
        ? bewerteAuswahl(frage, antwort.auswahl).richtig
        : antwort.selbstAlsRichtig === true);

    richtigJeFrage.set(frage.id, istRichtig);

    /* Zerlegung wie im Anwendungskern: richtig + falsch + unbeantwortet
       ergibt gesamt. In `fehlerIds` gehören dagegen auch die unbeantworteten –
       wer sie nie gesehen hat, soll sie üben können. */
    if (istRichtig) {
      richtig += 1;
    } else if (leer) {
      unbeantwortet += 1;
      fehlerIds.push(frage.id);
    } else {
      falsch += 1;
      fehlerIds.push(frage.id);
    }

    const bereichId = frage.abschnitt ?? frage.kapitel;
    const zaehler = bereiche.get(bereichId) ?? { gesamt: 0, richtig: 0 };
    zaehler.gesamt += 1;
    if (istRichtig) {
      zaehler.richtig += 1;
    }
    bereiche.set(bereichId, zaehler);
  }

  /* K.-o.-Kriterien: Auch nicht bearbeitete Fragen des Bereichs zählen als
     Fehler – in der Prüfung bringt eine leere Antwort keinen Punkt. */
  const verletzteKriterien = (daten.profil.koKriterien ?? []).flatMap((kriterium) => {
    const fehler = daten.fragen.filter(
      (frage) =>
        gehoertZuBereich(frage, kriterium.bereich) && richtigJeFrage.get(frage.id) !== true,
    ).length;
    return fehler > kriterium.maxFehler
      ? [
          `${kriterium.bezeichnung}: ${String(fehler)} Fehler bei höchstens ${String(kriterium.maxFehler)} zulässigen`,
        ]
      : [];
  });

  const gesamt = daten.fragen.length;
  const { urteil, begruendung } = urteilBilden(daten.profil, richtig, gesamt, verletzteKriterien);

  const bereichListe: BereichErgebnis[] = [...bereiche.entries()]
    .map(([bereich, zaehler]) => ({
      bereich,
      titel: daten.bereichTitel(bereich),
      gesamt: zaehler.gesamt,
      richtig: zaehler.richtig,
    }))
    .sort((a, b) => a.bereich.localeCompare(b.bereich, 'de'));

  return {
    profilId: daten.profil.id,
    gesamt,
    richtig,
    falsch,
    unbeantwortet,
    quote: gesamt > 0 ? richtig / gesamt : 0,
    urteil,
    begruendung,
    verletzteKriterien,
    bereiche: bereichListe,
    fehlerIds,
    dauerMs: daten.dauerMs,
    zeitAbgelaufen: daten.zeitAbgelaufen,
    zeitpunkt: daten.zeitpunkt,
  };
}
