// @vitest-environment node
/**
 * Einstellbare Textabstände (WCAG 1.4.12).
 *
 * **Der Anlass.** Das Kriterium verlangt, dass sich Text mit weiterem Zeilen-,
 * Wort- und Zeichenabstand darstellen lässt, ohne dass Inhalt verlorengeht. Im
 * Browser erfüllt man das, indem nutzereigene Stilblätter toleriert werden;
 * der eigene Prüfplan (Z. 177) stellt den Fall mit einem
 * Entwicklerwerkzeug-Stilblatt nach.
 *
 * In der ausgelieferten Anwendung gibt es beides nicht – weder Erweiterungen
 * noch Entwicklerwerkzeuge, die sollen laut `docs/stand.md` sogar aus dem
 * Auslieferungsmenü verschwinden. Der Einzige, der die Abstände ändern kann,
 * ist die Anwendung selbst, und sie bot es bis 0.22.0 nicht an. Formal ist das
 * kein Verstoß (in einer geschlossenen Umgebung misst 1.4.12 die Toleranz
 * gegenüber Overrides, die es hier nicht geben kann) – praktisch ist es eine
 * Lücke bei genau denen, für die das Kriterium geschrieben wurde.
 */

import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';

import { describe, expect, it } from 'vitest';

import {
  TEXTABSTAENDE,
  TEXTABSTAND_BEZEICHNUNG,
  TEXTABSTAND_ERKLAERUNG,
  TEXTABSTAND_STANDARD,
  istTextabstand,
  textabstandAnsage,
  textabstandBereinigen,
} from '../src/shared/textabstand';

const wurzel = fileURLToPath(new URL('..', import.meta.url));

function stil(datei: string): string {
  return readFileSync(join(wurzel, 'src', 'renderer', 'src', 'styles', datei), 'utf8');
}

const TOKENS_CSS = stil('tokens.css');
const GLOBAL_CSS = stil('global.css');

describe('Die Stufen', () => {
  it('beginnt bei der gewohnten Darstellung', () => {
    /* Wer nichts einstellt, soll nichts merken. Eine Anwendung, die von sich
       aus weiter setzt, hätte eine Meinung, die ihr nicht zusteht. */
    expect(TEXTABSTAND_STANDARD).toBe('normal');
    expect(TEXTABSTAENDE[0]).toBe('normal');
  });

  it('benennt und erklärt jede Stufe', () => {
    for (const stufe of TEXTABSTAENDE) {
      expect(TEXTABSTAND_BEZEICHNUNG[stufe].trim().length).toBeGreaterThan(0);
      expect(TEXTABSTAND_ERKLAERUNG[stufe].trim().length).toBeGreaterThan(0);
    }
  });

  it('macht aus Unbrauchbarem die Vorgabe', () => {
    /* Ein Wert aus einer früheren Fassung oder von Hand in die Datei
       geschrieben darf die Darstellung nicht in einen Zustand bringen, den
       es nicht gibt. */
    expect(textabstandBereinigen('gibt-es-nicht')).toBe(TEXTABSTAND_STANDARD);
    expect(textabstandBereinigen(undefined)).toBe(TEXTABSTAND_STANDARD);
    expect(textabstandBereinigen(42)).toBe(TEXTABSTAND_STANDARD);
    expect(textabstandBereinigen('weit')).toBe('weit');
  });

  it('erkennt gültige Stufen', () => {
    expect(istTextabstand('sehr-weit')).toBe(true);
    expect(istTextabstand('mittel')).toBe(false);
  });

  it('sagt die gewählte Stufe an', () => {
    expect(textabstandAnsage('weit')).toContain('Weit');
  });
});

describe('Das Stilblatt setzt die Stufen wirklich um', () => {
  it('führt für jede Stufe außer der Vorgabe einen eigenen Block', () => {
    for (const stufe of TEXTABSTAENDE.filter((s) => s !== TEXTABSTAND_STANDARD)) {
      expect(TOKENS_CSS, `Stufe ohne Regelblock: ${stufe}`).toContain(
        `:root[data-textabstand='${stufe}']`,
      );
    }
  });

  it('vergrößert Zeilen-, Wort- und Zeichenabstand mit jeder Stufe', () => {
    /* Drei Stufen, die sich nur im Namen unterscheiden, wären eine Attrappe
       einer Einstellung. */
    const werte = (stufe: string): Record<string, string> => {
      const anfang = TOKENS_CSS.indexOf(`:root[data-textabstand='${stufe}']`);
      const block = TOKENS_CSS.slice(anfang, TOKENS_CSS.indexOf('}', anfang));
      return Object.fromEntries(
        [...block.matchAll(/--([a-z-]+):\s*([\d.]+)(em|rem)?/gu)].map((t) => [
          t[1] ?? '',
          t[2] ?? '',
        ]),
      );
    };

    const weit = werte('weit');
    const sehrWeit = werte('sehr-weit');

    for (const eigenschaft of ['zeilenhoehe-text', 'wortabstand', 'zeichenabstand']) {
      const a = Number(weit[eigenschaft]);
      const b = Number(sehrWeit[eigenschaft]);
      expect(a, `${eigenschaft} fehlt in „weit“`).toBeGreaterThan(0);
      expect(b, `${eigenschaft}: „sehr weit“ ist nicht weiter als „weit“`).toBeGreaterThan(a);
    }
  });

  it('erreicht die Messwerte des Kriteriums', () => {
    /* 1.4.12 nennt Wortabstand 0,16 em und Zeichenabstand 0,12 em als
       Mindestmaß. Die weiteste Stufe muss dort ankommen – sonst wäre die
       Einstellung eine Geste. */
    const block = TOKENS_CSS.slice(TOKENS_CSS.indexOf(":root[data-textabstand='sehr-weit']"));
    const wort = /--wortabstand:\s*([\d.]+)em/u.exec(block);
    const zeichen = /--zeichenabstand:\s*([\d.]+)em/u.exec(block);

    expect(Number(wort?.[1])).toBeGreaterThanOrEqual(0.16);
    expect(Number(zeichen?.[1])).toBeGreaterThanOrEqual(0.12);
  });

  it('reicht Wort- und Zeichenabstand an den Textkörper durch', () => {
    /* Ohne diese beiden Zeilen wären die Variablen gesetzt und wirkungslos. */
    const body = GLOBAL_CSS.slice(
      GLOBAL_CSS.indexOf('body {'),
      GLOBAL_CSS.indexOf('}', GLOBAL_CSS.indexOf('body {')),
    );
    expect(body).toMatch(/word-spacing:\s*var\(--wortabstand\)/u);
    expect(body).toMatch(/letter-spacing:\s*var\(--zeichenabstand\)/u);
  });

  it('lässt den Feinsatz der Überschriften nicht gegen die Einstellung stehen', () => {
    /* Der negative Zeichenabstand von h1 und h2 steht hart im Stilblatt.
       Bliebe er unangetastet, zöge die Einstellung überall – nur nicht in
       den Überschriften, wo mehr Luft am meisten hilft. */
    for (const ueberschrift of ['h1 {', 'h2 {']) {
      const anfang = GLOBAL_CSS.indexOf(ueberschrift);
      const block = GLOBAL_CSS.slice(anfang, GLOBAL_CSS.indexOf('}', anfang));
      expect(block, `${ueberschrift} übersteuert den Zeichenabstand nicht`).toMatch(
        /letter-spacing:\s*max\([^)]*var\(--zeichenabstand\)\)/u,
      );
    }
  });

  it('gibt der Vorgabe eine rechenbare Länge statt „normal“', () => {
    /* `max(-0.015em, normal)` wäre ungültig – die ganze Angabe fiele aus,
       und der Feinsatz der Überschriften verschwände stillschweigend. */
    const wurzelblock = TOKENS_CSS.slice(TOKENS_CSS.indexOf(':root {'));
    expect(wurzelblock).toMatch(/--zeichenabstand:\s*0em/u);
    expect(wurzelblock).toMatch(/--wortabstand:\s*0em/u);
  });
});
