/**
 * Ein kleines Fehlerprotokoll auf der Platte.
 *
 * **Wozu.** Der Rückmeldeweg dieser Anwendung ist eine E-Mail mit einem
 * kopierbaren Text (`shared/meldetext.ts`). Für einen Fehler an einer Frage
 * genügt das. Für „es stürzt manchmal ab“ genügt es nicht: Bis 0.22.0 landeten
 * sämtliche Fehler des Hauptprozesses ausschließlich in `console.error` – und
 * die sieht im gepackten Programm niemand, weil es ohne Konsole startet. Wer
 * melden wollte, hatte nichts in der Hand, und wer die Meldung bearbeiten
 * sollte, bekam „irgendwann geht es einfach zu“.
 *
 * **Was das hier nicht ist.** Keine Telemetrie. Die Datei geht nirgendwohin;
 * die Anwendung hat keinen Weg nach außen, über den sie das könnte. Sie liegt
 * im `userData`-Verzeichnis, und wer sie mitschicken will, schickt sie von
 * Hand mit – so wie den Meldetext auch.
 *
 * **Was hineingeschrieben werden darf.** Dieselbe Strenge wie im Meldetext:
 * kein Profilname, kein Lernstand, keine gegebene Antwort, kein Dateipfad mit
 * Benutzernamen. Ein Absturzgrund und eine Fehlermeldung haben einen Zweck für
 * die Bearbeitung – ein Heimatverzeichnis hat ihn nicht. {@link entpersoniert}
 * nimmt Pfade heraus, bevor irgendetwas geschrieben wird, und zwar an genau
 * einer Stelle: Zwei Reinigungen an zwei Stellen laufen auseinander.
 *
 * **Warum keine Rotation über mehrere Dateien.** Eine Datei mit Obergrenze ist
 * hier das Richtige: Wer sie mitschicken soll, soll eine mitschicken, nicht
 * vier. Läuft sie über, wird die ältere Hälfte weggeschnitten – der Anfang
 * eines Fehlers ist selten so wichtig wie sein Ende.
 */

import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, statSync } from 'node:fs';
import { homedir } from 'node:os';
import { dirname, join } from 'node:path';

/** Dateiname im `userData`-Verzeichnis. */
export const PROTOKOLL_DATEI = 'fehlerprotokoll.txt';

/**
 * Obergrenze der Datei.
 *
 * Groß genug für die Vorgeschichte mehrerer Abstürze, klein genug, um sie
 * anzusehen und an eine E-Mail zu hängen.
 */
export const MAX_BYTES = 256 * 1024;

/** Eine einzelne Zeile wird nie länger – sonst füllte ein Fehler die Datei. */
const MAX_ZEILE = 2000;

/** Wohin geschrieben wird; wird beim Einrichten gesetzt. */
let ziel: string | null = null;

/**
 * Nimmt Personenbezogenes aus einem Text.
 *
 * Betrifft in der Praxis genau eine Sorte Angabe, und die steckt in fast jeder
 * Fehlermeldung des Dateisystems: den Pfad zum Heimatverzeichnis, der unter
 * Windows wie unter macOS den Anmeldenamen enthält. Er wird durch `<Benutzer>`
 * ersetzt – die Meldung bleibt lesbar, der Name bleibt hier.
 *
 * Zusätzlich fliegen Zeilenumbrüche heraus: Eine Zeile ist ein Eintrag.
 * Andernfalls ließe sich eine Fehlermeldung so wählen, dass sie im Protokoll
 * wie mehrere Einträge aussieht.
 */
export function entpersoniert(text: string, heim: string = homedir()): string {
  const flach = text.replace(/[\r\n]+/gu, ' ').trim();
  if (heim === '') {
    return flach.slice(0, MAX_ZEILE);
  }

  /* Beide Trennzeichen: Node liefert Windows-Pfade mit Rückstrich, viele
     Meldungen aus Chromium und SQLite mit Schrägstrich. */
  const varianten = [heim, heim.replace(/\\/gu, '/')];
  let sauber = flach;
  for (const variante of varianten) {
    sauber = sauber.split(variante).join('<Benutzer>');
  }
  return sauber.slice(0, MAX_ZEILE);
}

/** Legt den Ablageort fest. Ohne diesen Aufruf wird nichts geschrieben. */
export function protokollEinrichten(userData: string): void {
  ziel = join(userData, PROTOKOLL_DATEI);
}

/** Der Ablageort, oder `null` – für die Anzeige im Systemzustand. */
export function protokollPfad(): string | null {
  return ziel;
}

/** Gegenstück für Tests. */
export function protokollVergessen(): void {
  ziel = null;
}

/** Schneidet die ältere Hälfte weg, wenn die Datei zu groß geworden ist. */
function kuerzenWennNoetig(pfad: string): void {
  if (!existsSync(pfad) || statSync(pfad).size <= MAX_BYTES) {
    return;
  }

  const inhalt = readFileSync(pfad, 'utf8');
  const rest = inhalt.slice(Math.floor(inhalt.length / 2));
  /* Am nächsten Zeilenanfang ansetzen, damit oben keine halbe Zeile steht. */
  const ab = rest.indexOf('\n');
  const gekuerzt = ab === -1 ? '' : rest.slice(ab + 1);

  const neben = `${pfad}.neu`;
  appendFileSync(neben, `[gekürzt] Ältere Einträge wurden entfernt.\n${gekuerzt}`, {
    encoding: 'utf8',
    flag: 'w',
  });
  renameSync(neben, pfad);
}

/**
 * Schreibt eine Zeile.
 *
 * Wirft nie: Ein Protokoll, das die Anwendung zu Fall bringt, wäre schlimmer
 * als keines. Misslingt das Schreiben – volle Platte, fehlendes Recht –,
 * bleibt es bei der Konsolenausgabe des Aufrufers.
 */
export function protokollieren(bereich: string, meldung: string, jetzt: Date = new Date()): void {
  const pfad = ziel;
  if (pfad === null) {
    return;
  }

  try {
    mkdirSync(dirname(pfad), { recursive: true });
    kuerzenWennNoetig(pfad);
    const zeile = `${jetzt.toISOString()} [${bereich}] ${entpersoniert(meldung)}\n`;
    appendFileSync(pfad, zeile, 'utf8');
  } catch {
    /* Bewusst still: siehe oben. */
  }
}

/** Fehlerobjekt oder beliebiger Wert als eine Zeile. */
export function fehlerZeile(fehler: unknown): string {
  if (fehler instanceof Error) {
    const erste = (fehler.stack ?? '').split('\n')[1]?.trim() ?? '';
    return erste === '' ? fehler.message : `${fehler.message} — ${erste}`;
  }
  return String(fehler);
}
