import { expect, test } from "@playwright/test";

import { HTML_LANG } from "../lib/locales";
import { PAGES } from "./routes";

/**
 * Every page in every locale: it loads, it says what it is, it declares its
 * canonical URL and both hreflang alternates, and it carries no horizontal
 * overflow at the viewport under test (Blueprint 17 rules 2, 11 and the SEO
 * requirements in section 12).
 */

test.describe("every page", () => {
  for (const page of PAGES) {
    test(`${page.name} — loads, is described, and fits the viewport`, async ({
      page: browserPage,
    }) => {
      const response = await browserPage.goto(page.path);
      expect(response?.status(), `${page.path} should return 200`).toBe(200);

      // The locale tree sets <html lang> correctly.
      await expect(browserPage.locator("html")).toHaveAttribute("lang", HTML_LANG[page.locale]);

      // Exactly one H1, and it is not empty.
      const h1 = browserPage.locator("h1");
      await expect(h1).toHaveCount(1);
      expect((await h1.textContent())?.trim().length ?? 0).toBeGreaterThan(2);

      // A real title and description, not a placeholder.
      const title = await browserPage.title();
      expect(title.length).toBeGreaterThan(10);
      const description = await browserPage
        .locator('meta[name="description"]')
        .getAttribute("content");
      expect(description?.length ?? 0).toBeGreaterThan(40);

      // Canonical plus both alternates and x-default.
      await expect(browserPage.locator('link[rel="canonical"]')).toHaveCount(1);
      for (const lang of ["sq", "mk", "x-default"]) {
        await expect(
          browserPage.locator(`link[rel="alternate"][hreflang="${lang}"]`),
          `${page.path} should declare hreflang ${lang}`,
        ).toHaveCount(1);
      }

      /*
       * Blueprint 17 rule 11: no horizontal scrolling at any size.
       *
       * The page must never scroll sideways. Content that is genuinely wider
       * than a phone — the chain diagram's rail — is allowed, but only inside
       * its own scroll container, where it is the reader's choice to move it
       * and the document stays still. So an element past the right edge is an
       * offender unless an ancestor declares `overflow-x: auto | scroll`.
       *
       * Geometry inside an SVG is exempt, and only geometry. A path's
       * bounding box is measured in the drawing's own coordinates, which the
       * outermost `<svg>` clips by definition — the video placeholder's
       * blades are drawn deliberately wider than their plate and swept off
       * its edge. Such a path cannot widen the document or scroll it, and the
       * `scrollWidth` assertion below is what actually proves that. Before
       * this exemption the check reported those blades on every module page
       * in every language, which is the kind of noise that stops a suite
       * from being read at all.
       */
      const overflow = await browserPage.evaluate(() => {
        const inScroller = (el: Element) => {
          for (let node = el.parentElement; node; node = node.parentElement) {
            const overflowX = getComputedStyle(node).overflowX;
            if (overflowX === "auto" || overflowX === "scroll") return true;
          }
          return false;
        };

        const isDrawing = (el: Element) => el instanceof SVGElement && el.ownerSVGElement !== null;

        return {
          doc: document.documentElement.scrollWidth,
          view: window.innerWidth,
          offenders: [...document.querySelectorAll("body *")]
            .filter((el) => {
              const rect = el.getBoundingClientRect();
              return (
                rect.width > 0 &&
                rect.right > window.innerWidth + 1 &&
                !inScroller(el) &&
                !isDrawing(el)
              );
            })
            .slice(0, 5)
            .map((el) => `${el.tagName}.${String(el.className).slice(0, 60)}`),
        };
      });
      expect(overflow.offenders, `${page.path} has elements past the right edge`).toEqual([]);
      expect(overflow.doc, `${page.path} scrolls horizontally`).toBeLessThanOrEqual(
        overflow.view + 1,
      );
    });
  }
});

test.describe("honesty", () => {
  /**
   * Blueprint 17 rule 1 and the project's factual discipline, asserted against
   * the rendered HTML rather than the content files — this is the check that
   * would catch a claim re-entering through a component.
   */
  const FORBIDDEN = [
    "TBD",
    "Coming soon",
    "COMING SOON",
    "Së shpejti",
    "Наскоро",
    "Lorem ipsum",
    "20 vjet",
    "20 години",
    "undefined",
    "NaN",
  ];

  for (const page of PAGES) {
    test(`${page.name} — renders no unconfirmed claim or placeholder`, async ({
      page: browserPage,
    }) => {
      await browserPage.goto(page.path);
      const body = (await browserPage.locator("body").innerText()).normalize();

      for (const phrase of FORBIDDEN) {
        expect(body, `${page.path} must not contain "${phrase}"`).not.toContain(phrase);
      }
    });
  }

  test("no client is named without written permission", async ({ page }) => {
    // Every reference in content is permission: pending or declined today, so
    // none of these names may appear anywhere on the site.
    const names = [
      "Impeksel",
      "New Relax",
      "Hotel Ignatia",
      "Rivedil",
      "Longe Bar",
      "Euroconsult",
      "BekVideo",
      "Shkëndija",
      "Foto Dilo",
      "Relax Pizza",
      "Top Fresh",
      "ecoconsulting",
      "Feder Bau",
      "Avtolaker",
      "Tree Park",
      "Biana",
      "OAZA",
    ];

    for (const route of ["/sq/klientet", "/mk/klienti", "/sq", "/sq/sektoret/hoteleri"]) {
      await page.goto(route);
      const body = await page.locator("body").innerText();
      for (const name of names) {
        expect(body, `${route} must not name "${name}"`).not.toContain(name);
      }
    }
  });
});
