/**
 * Was ohne Scrollen erreichbar sein muss.
 *
 * Anlass ist eine Rückmeldung aus der Anwendung heraus: „Was mir nicht
 * gefällt, dass ich zum Lernen erstmal scrollen muss." Nachgemessen bei
 * 1063 Pixeln sichtbarer Höhe:
 *
 *   - Der Weg ins Lernen begann bei 2659 px – zweieinhalb Bildschirme
 *     Statusbericht vor der ersten Frage.
 *   - Nach dem Bestätigen begann die Rückmeldung bei rund 950 px und war
 *     damit praktisch unsichtbar; „Nächste Frage" stand bei 2115 px.
 *
 * Solche Regressionen fallen in keinem anderen Test auf: Alles ist da,
 * alles ist bedienbar, alles ist zugänglich – es steht nur zu weit unten.
 * Deshalb misst dieser Lauf Positionen statt Vorhandensein.
 *
 * Gemessen wird bei 1999 × 1125 Fenstergröße. Kleinere Fenster brauchen
 * zwangsläufig mehr Scrollen; die Zusage gilt der üblichen Arbeitsgröße.
 */

import { type ElectronApplication, type Page } from '@playwright/test';

import { amStart, appStarten, bauPruefen } from './electron-hilfe';
import { expect, test } from './konsolenwache';

let app: ElectronApplication | undefined;
let fenster: Page;

test.beforeAll(async () => {
  // Fehlender oder veralteter Bau ist ein Fehler, kein Grund zum Schweigen.
  bauPruefen();
  ({ app, fenster } = await appStarten());
  await app.evaluate(({ BrowserWindow }) => {
    BrowserWindow.getAllWindows()[0]?.setSize(1999, 1125);
  });
  await fenster.waitForTimeout(300);
});

test.afterAll(async () => {
  await app?.close();
});

async function sichtbareHoehe(): Promise<number> {
  return fenster.evaluate(() => window.innerHeight);
}

/**
 * Startet eine frische Sitzung und blättert bis zu einer Auswahlfrage.
 *
 * Jeder Test holt sich seine eigene Sitzung: Sonst hängt das Ergebnis davon
 * ab, wo der vorige aufgehört hat – und ein Durchlauf, der zufällig auf der
 * letzten Frage endet, landet in der Auswertung statt in der Sitzung.
 */
async function sitzungMitAuswahlfrage(): Promise<void> {
  /* Zurück zum Start – in Schritten: „Sitzung beenden“ führt zur Auswertung,
     erst von dort geht es „Zum Start“. */
  for (let i = 0; i < 4; i++) {
    if (await amStart(fenster)) {
      break;
    }
    const zurueck = fenster.getByRole('button', { name: /^(Zum Start|Sitzung beenden)$/u });
    if ((await zurueck.count()) === 0) {
      break;
    }
    await zurueck.first().click();
    await fenster.waitForTimeout(250);
  }

  await fenster.getByRole('button', { name: /Weiterlernen/u }).click();
  await fenster.getByRole('heading', { level: 1, name: /, Frage \d/u }).waitFor();

  for (let i = 0; i < 15; i++) {
    if ((await fenster.getByRole('radio').count()) > 0) {
      return;
    }
    await fenster
      .getByRole('button', { name: /Musterantwort anzeigen|^Antwort bestätigen$/u })
      .first()
      .click();
    const bewertung = fenster.getByRole('button', { name: /^(Gewusst|Sicher gewusst)$/u });
    if ((await bewertung.count()) > 0) {
      await bewertung.first().click();
    }
    const weiter = fenster.getByRole('button', { name: /^Nächste Frage$/u });
    if ((await weiter.count()) === 0) {
      /* Ende der Sitzung erreicht, ohne eine Auswahlfrage zu treffen – neu
         beginnen statt in der Auswertung stehen zu bleiben. */
      await sitzungMitAuswahlfrage();
      return;
    }
    await weiter.first().click();
    await fenster.waitForTimeout(150);
  }
}

test.describe('Ohne Scrollen erreichbar', () => {
  test('der Weg ins Lernen steht im ersten Bildschirm', async () => {
    const hoehe = await sichtbareHoehe();
    const knopf = fenster.getByRole('button', { name: /Weiterlernen/u });
    await expect(knopf).toBeVisible();

    const unten = await fenster.evaluate(() => {
      const b = [...document.querySelectorAll('button')].find((e) =>
        e.textContent.includes('Weiterlernen'),
      );
      return b === undefined
        ? Number.POSITIVE_INFINITY
        : Math.round(b.getBoundingClientRect().bottom);
    });

    expect(
      unten,
      `„Weiterlernen" endet bei ${String(unten)} px, sichtbar sind ${String(hoehe)} px.`,
    ).toBeLessThanOrEqual(hoehe);
  });

  test('alle fünf Einstiege stehen im ersten Bildschirm', async () => {
    const hoehe = await sichtbareHoehe();

    /* Der fünfte Knopf („Offene Fragen", seit 0.17.0) kostet keine Zeile:
       Das Raster stellt bei dieser Breite drei Spalten, vier Knöpfe standen
       also längst in zwei Reihen. Geprüft wird das hier und nicht gerechnet. */
    for (const name of ['Kapitel wählen', 'Nur Fehler', 'Gemerkte Fragen', 'Offene Fragen']) {
      const unten = await fenster.evaluate((gesucht) => {
        const b = [...document.querySelectorAll('button')].find((e) =>
          e.textContent.includes(gesucht),
        );
        return b === undefined
          ? Number.POSITIVE_INFINITY
          : Math.round(b.getBoundingClientRect().bottom);
      }, name);

      expect(unten, `„${name}" endet bei ${String(unten)} px`).toBeLessThanOrEqual(hoehe);
    }
  });

  test('die Rückmeldung erscheint ohne Scrollen', async () => {
    await sitzungMitAuswahlfrage();
    await fenster.getByRole('radio').first().click();
    await fenster.getByRole('button', { name: 'Antwort bestätigen' }).click();
    await fenster.waitForTimeout(250);
    await fenster.evaluate(() => {
      window.scrollTo(0, 0);
    });

    const hoehe = await sichtbareHoehe();

    /*
      Die Zusage ist genau: Wer bestätigt hat, liest Urteil UND Lösungssatz,
      ohne die Hand zur Maus zu nehmen. Gemessen wird deshalb die Unterkante
      des Lösungssatzes, nicht die Oberkante des Kastens – eine Überschrift
      am unteren Rand wäre keine Antwort.
    */
    const unten = await fenster.evaluate(() => {
      const satz = document.querySelector('.ergebnis p');
      return satz === null
        ? Number.POSITIVE_INFINITY
        : Math.round(satz.getBoundingClientRect().bottom);
    });

    expect(
      unten,
      `Der Lösungssatz endet bei ${String(unten)} px, sichtbar sind ${String(hoehe)} px.`,
    ).toBeLessThanOrEqual(hoehe);
  });

  test('die Frage ist die oberste Überschrift der Sitzung', async () => {
    /*
      Der Name der Anwendung steht in der Titelleiste des Fensters – ihn im
      Inhalt zu wiederholen kostete 167 Pixel und lenkte vom Stoff ab. Die
      oberste Überschrift benennt jetzt, worum es in dieser Ansicht geht:
      die Frage. Wer per Überschrift navigiert, landet damit an der Sache.
    */
    await sitzungMitAuswahlfrage();

    await expect(fenster.locator('.kopfbereich')).toHaveCount(0);

    const ueberschriften = fenster.getByRole('heading', { level: 1 });
    await expect(ueberschriften).toHaveCount(1);
    await expect(ueberschriften).toHaveClass(/frage__text/u);
  });

  test('die Frage steht weit oben', async () => {
    /*
      Vorher begann der Fragetext bei 299 Pixeln, in der ursprünglichen
      Fassung sogar bei 495.

      Gemessen wird der Moment des Ankommens. Jede neue Frage holt den Fokus
      auf ihre Überschrift (`Lernsitzung.tsx`); erst dann steht die Seite so,
      wie ein Lernender sie vorfindet. Diese Prüfung holte sich früher weder
      eine eigene Sitzung noch wartete sie darauf – sie maß, wo die vorige
      Prüfung den Rollbalken hatte stehen lassen. Nachgemessen über vier
      Fenstergrößen: Lag der Fokus auf der Überschrift, stand sie bei 166 px
      und die Seite war ungerollt; hing er noch auf „Nächste Frage", lag sie
      bei 4 px, bei kleinem Fenster bei −363 px. Das war eine Aussage über
      den Testablauf, nicht über die Anwendung.

      Die untere Schranke ist deshalb 0 und nicht 1: Eine Überschrift genau
      an der Oberkante ist der beste Fall, kein Fehler. Unter 0 wäre sie
      angeschnitten.
    */
    await sitzungMitAuswahlfrage();
    await expect
      .poll(() => fenster.evaluate(() => document.activeElement?.className ?? ''))
      .toContain('frage__text');

    const oben = await fenster.evaluate(() => {
      const ueberschrift = document.querySelector('.frage__text');
      return ueberschrift === null ? null : Math.round(ueberschrift.getBoundingClientRect().top);
    });

    if (oben === null) {
      throw new Error('Die Sitzung zeigt keine Frageüberschrift.');
    }
    expect(oben, `Der Fragetext beginnt bei ${String(oben)} px.`).toBeLessThan(220);
    expect(
      oben,
      `Der Fragetext steht über dem Sichtfeld (${String(oben)} px).`,
    ).toBeGreaterThanOrEqual(0);
  });
});
