import { readFileSync } from 'node:fs';
import { createRequire } from 'node:module';
import { dirname, join } from 'node:path';

import type { Page } from '@playwright/test';
import type { AxeResults, run as axeRun, RunOptions } from 'axe-core';

/**
 * axe-core in einer Electron-Seite ausführen.
 *
 * WARUM NICHT `@axe-core/playwright`?
 * Dieses Paket ruft intern `browserContext.newPage()` auf, um die
 * axe-Quelle zu serialisieren. Electron unterstützt das nicht – der
 * Aufruf endet in `Protocol error (Target.createTarget): Not supported`.
 * Deshalb wird hier die Quelle direkt injiziert.
 *
 * Die Injektion läuft über `page.evaluate()` (CDP `Runtime.evaluate`) statt
 * über `addScriptTag`. Nur so bleibt die strenge Produktions-CSP
 * (`script-src 'self'`) unangetastet – wir prüfen die Seite also genau in
 * der Konfiguration, die auch ausgeliefert wird.
 */

const require_ = createRequire(__filename);
const axeQuellPfad = join(dirname(require_.resolve('axe-core')), 'axe.min.js');
const axeQuelle = readFileSync(axeQuellPfad, 'utf8');

/*
  Der Regelsatz steht in src/shared/wcag.ts – eine Quelle für Unit- und
  E2E-Ebene. Hier wird er nur weitergereicht, damit die Suiten unter e2e/
  ihn nicht quer durch den Baum importieren müssen.
*/
export { WCAG_TAGS } from '../src/shared/wcag';

/** Ein einzelner Befund in lesbarer Form (statt eines rohen Objekt-Dumps). */
export interface Befund {
  readonly regel: string;
  readonly wirkung: string;
  readonly hilfe: string;
  readonly betroffen: readonly string[];
}

async function axeSicherstellen(seite: Page): Promise<void> {
  const schonGeladen = await seite.evaluate(
    () => typeof (globalThis as Record<string, unknown>)['axe'] !== 'undefined',
  );
  if (!schonGeladen) {
    await seite.evaluate(axeQuelle);
  }
}

/** Führt axe-core auf der gesamten Seite aus. */
export async function axePruefen(seite: Page, optionen: RunOptions): Promise<AxeResults> {
  await axeSicherstellen(seite);

  return seite.evaluate(async (opts: RunOptions) => {
    const axe = (globalThis as unknown as { axe: { run: typeof axeRun } }).axe;
    return axe.run(document, opts);
  }, optionen);
}

function verdichten(ergebnisse: AxeResults['violations'], nurRegeln?: readonly string[]): Befund[] {
  return ergebnisse
    .filter((v) => !nurRegeln || nurRegeln.includes(v.id))
    .map((v) => ({
      regel: v.id,
      wirkung: v.impact ?? 'unbekannt',
      hilfe: v.help,
      betroffen: v.nodes.map((n) => n.target.join(' ')),
    }));
}

/** Verdichtet Verstöße zu einer Liste, die im Fehlerfall gut lesbar ist. */
export function befunde(ergebnis: AxeResults, nurRegeln?: readonly string[]): Befund[] {
  return verdichten(ergebnis.violations, nurRegeln);
}

/**
 * Die Fälle, die axe **nicht entscheiden konnte** (`incomplete`).
 *
 * Diese Liste ist bewusst getrennt und bricht keinen Lauf: axe legt hier ab,
 * was es nicht sicher beurteilen kann – etwa Farbkontrast über einem
 * Verlaufshintergrund. Sie zu einem Fehler zu machen hieße, Rauschen zum
 * Befund zu erklären.
 *
 * Sie aber wortlos zu verschweigen wäre schlimmer: Vorher las
 * {@link befunde} ausschließlich `violations`, und alles Unentschiedene
 * verschwand spurlos. Ein Lauf meldete „keine Befunde", während axe in
 * Wahrheit gesagt hatte: „kann ich nicht beurteilen." Wer den Anspruch
 * WCAG 2.2 AA erhebt, muss diese Fälle wenigstens sehen.
 */
export function unentschieden(ergebnis: AxeResults, nurRegeln?: readonly string[]): Befund[] {
  return verdichten(ergebnis.incomplete, nurRegeln);
}
