// @vitest-environment jsdom
import { describe, it, expect, beforeAll } from "vitest";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import axe from "axe-core";

/**
 * Tests d'accessibilité statiques.
 *
 * On charge le HTML livré tel quel dans jsdom et on passe axe-core dessus.
 * Pas de navigateur, pas de conteneur : ~1s pour les deux pages.
 *
 * Limite assumée : les règles qui nécessitent un vrai moteur de rendu
 * (contraste de couleurs, cibles tactiles) ne peuvent pas s'évaluer sous jsdom
 * et sont désactivées. Elles ne sont donc PAS couvertes ici — voir le README
 * de la CI pour l'audit Lighthouse manuel qui les complète.
 */

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = path.resolve(__dirname, "../../..");

const PAGES = {
  "site public": path.join(REPO_ROOT, "apps/web/site/index.html"),
  "dashboard admin": path.join(REPO_ROOT, "apps/admin/site/index.html"),
};

// Règles inapplicables sous jsdom (pas de layout ni de couleurs calculées).
const DISABLED_RULES = {
  "color-contrast": { enabled: false },
  "target-size": { enabled: false },
};

async function analyse(htmlPath) {
  const html = fs.readFileSync(htmlPath, "utf8");
  // On remplace le document entier pour conserver <html lang> et <head>.
  document.documentElement.innerHTML = html
    .replace(/<!doctype html>/i, "")
    .replace(/<\/?html[^>]*>/gi, "");

  const langMatch = html.match(/<html[^>]*\blang="([^"]+)"/i);
  if (langMatch) document.documentElement.setAttribute("lang", langMatch[1]);

  return axe.run(document, {
    rules: DISABLED_RULES,
    resultTypes: ["violations"],
  });
}

describe.each(Object.entries(PAGES))("a11y — %s", (label, htmlPath) => {
  let results;

  beforeAll(async () => {
    results = await analyse(htmlPath);
  }, 30000);

  it("n'a aucune violation critique ou sérieuse", () => {
    const blocking = results.violations.filter((v) => ["critical", "serious"].includes(v.impact));
    const summary = blocking.map(
      (v) => `[${v.impact}] ${v.id}: ${v.help}\n    ${v.nodes.map((n) => n.target.join(" ")).join("\n    ")}`
    );
    expect(summary, `${label} — violations bloquantes :\n${summary.join("\n")}`).toEqual([]);
  });

  it("déclare une langue de document", () => {
    expect(document.documentElement.getAttribute("lang")).toBeTruthy();
  });

  it("n'utilise pas de tabindex positif (casse l'ordre de tabulation)", () => {
    const positive = [...document.querySelectorAll("[tabindex]")].filter(
      (el) => Number(el.getAttribute("tabindex")) > 0
    );
    expect(positive.map((el) => el.outerHTML.slice(0, 80))).toEqual([]);
  });

  it("donne un nom accessible à chaque contrôle de formulaire", () => {
    const unnamed = [...document.querySelectorAll("input, select, textarea")]
      .filter((el) => el.type !== "hidden")
      .filter((el) => {
        const id = el.getAttribute("id");
        return !(
          el.getAttribute("aria-label") ||
          el.getAttribute("aria-labelledby") ||
          el.getAttribute("title") ||
          el.closest("label") ||
          (id && document.querySelector(`label[for="${CSS.escape(id)}"]`))
        );
      });
    expect(unnamed.map((el) => el.outerHTML.slice(0, 100))).toEqual([]);
  });

  it("chaque image porte un attribut alt", () => {
    const noAlt = [...document.querySelectorAll("img")].filter((el) => !el.hasAttribute("alt"));
    expect(noAlt.map((el) => el.getAttribute("src"))).toEqual([]);
  });
});
