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

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

/**
 * Navigation, the language switch and the two interactive components
 * (Blueprint 03 and motion #2 / #4 in section 09).
 */

test.describe("language switch", () => {
  for (const page of PAGES) {
    test(`${page.name} — every language reaches this exact page`, async ({
      page: browserPage,
      viewport,
    }) => {
      await browserPage.goto(page.path);

      // Below 1024 the switch sits inside the menu sheet, by design.
      if ((viewport?.width ?? 0) < 1024) {
        await browserPage
          .getByRole("button", { name: /Menu|Мени|Menü/ })
          .first()
          .click();
        await expect(browserPage.locator("dialog[open]")).toBeVisible();
      }

      /*
       * All four, not just "the other one". A four-language switch is where
       * cross-locale routing breaks: one missing slug and a reader lands on a
       * home page instead of the page they were reading.
       */
      for (const other of LOCALES) {
        const link = browserPage.locator(`a[hreflang="${other}"]:visible`).first();
        await expect(link, `${other} is offered`).toBeVisible();
        await expect(link, `${other} points at the same page`).toHaveAttribute(
          "href",
          page.counterparts[other],
        );
      }
    });
  }

  test("switching sets the lang cookie and the root follows it", async ({
    page,
    context,
    viewport,
  }) => {
    await page.goto("/sq/mbeshtetje");

    if ((viewport?.width ?? 0) < 1024) {
      await page
        .getByRole("button", { name: /Menu|Мени/ })
        .first()
        .click();
      await expect(page.locator("dialog[open]")).toBeVisible();
    }

    await page.locator('a[hreflang="mk"]:visible').first().click();
    await page.waitForURL("**/mk/poddrshka");

    const cookie = (await context.cookies()).find((c) => c.name === "lang");
    expect(cookie?.value).toBe("mk");

    // The proxy reads the cookie when deciding where / goes.
    await page.goto("/");
    await page.waitForURL("**/mk");
    expect(new URL(page.url()).pathname).toBe("/mk");
  });
});

test.describe("desktop header", () => {
  test.skip(({ viewport }) => (viewport?.width ?? 0) < 1024, "desktop only");

  test("the row is five words: three links, the languages, the phone", async ({ page }) => {
    await page.goto("/sq");

    const nav = page.locator("header nav");
    await expect(nav.getByRole("link", { name: "Nexys", exact: true })).toBeVisible();
    await expect(nav.getByRole("link", { name: "Sulsys", exact: true })).toBeVisible();
    await expect(nav.getByRole("link", { name: "Kontakt", exact: true })).toBeVisible();

    // The phone is the header's action; the demo button has left the row.
    await expect(nav.locator('a[href^="tel:"]')).toBeVisible();
    await expect(nav.getByRole("link", { name: "Kërko prezantim" })).toHaveCount(0);

    // No disclosure: the modules live inside Nexys, not in a dropdown.
    await expect(nav.getByRole("button")).toHaveCount(0);
  });

  test("the current page is marked in the navigation", async ({ page }) => {
    await page.goto("/sq/sulsys");
    await expect(page.locator('nav a[aria-current="page"]')).toHaveText("Sulsys");
  });
});

test.describe("mobile header", () => {
  test.skip(({ viewport }) => (viewport?.width ?? 0) >= 1024, "mobile and tablet only");

  test("the menu sheet opens, traps focus and closes", async ({ page }) => {
    await page.goto("/sq");

    const open = page.getByRole("button", { name: "Menu", exact: true });
    await open.click();

    const dialog = page.locator("dialog[open]");
    await expect(dialog).toBeVisible();
    await expect(dialog.getByRole("link", { name: "Nexys", exact: true })).toBeVisible();
    await expect(dialog.getByRole("link", { name: /Nexys Finance/ })).toBeVisible();

    // Focus is inside the dialog, which is what makes it modal.
    const focusInside = await page.evaluate(() => {
      const el = document.activeElement;
      return el ? Boolean(el.closest("dialog")) : false;
    });
    expect(focusInside).toBe(true);

    await page.keyboard.press("Escape");
    await expect(page.locator("dialog[open]")).toHaveCount(0);
  });

  test("the slim bar appears only after one viewport of scrolling", async ({ page }) => {
    await page.goto("/sq");

    const bar = page.locator('[class*="slimBar"]');
    await expect(bar).toBeHidden();

    await page.evaluate(() => window.scrollTo(0, window.innerHeight * 1.5));
    await expect(bar).toBeVisible();
    await expect(bar.getByText("Kërko prezantim")).toBeVisible();
  });
});

test.describe("tutorial library", () => {
  test("no YouTube request is made until a video is opened", async ({ page }) => {
    const youtubeRequests: string[] = [];
    page.on("request", (request) => {
      const url = request.url();
      if (url.includes("youtube") || url.includes("ytimg") || url.includes("googlevideo")) {
        youtubeRequests.push(url);
      }
    });

    await page.goto("/sq/mbeshtetje");
    await page.waitForLoadState("networkidle");

    // This is the promise the privacy notice makes.
    expect(youtubeRequests, "the page must not contact YouTube on load").toEqual([]);

    await page.getByRole("button", { name: /Luaj:/ }).first().click();
    await expect(page.locator('iframe[src*="youtube-nocookie.com"]')).toBeVisible();
  });

  test("the module filter narrows the library", async ({ page }) => {
    await page.goto("/sq/mbeshtetje");

    const rows = page.locator('li[class*="item"]');
    const all = await rows.count();
    expect(all).toBeGreaterThan(10);

    await page.getByRole("button", { name: /04 Nexys Caffe/ }).click();
    await expect(rows).toHaveCount(3);
  });
});

test.describe("reduced motion", () => {
  test.use({ reducedMotion: "reduce" });

  test("the hero panel is complete with no animation", async ({ page }) => {
    await page.goto("/sq");

    // Every row, including the posted total, is visible immediately.
    const rows = page.locator('li[class*="RecordPanel"]');
    await expect(rows).toHaveCount(5);
    for (let i = 0; i < 5; i += 1) {
      await expect(rows.nth(i)).toBeVisible();
    }
    await expect(page.getByText("266.100,00")).toBeVisible();

    const animated = await page.evaluate(
      () =>
        [...document.querySelectorAll('[class*="RecordPanel"]')].filter(
          (el) => getComputedStyle(el).animationName !== "none",
        ).length,
    );
    expect(animated, "nothing should animate under reduced motion").toBe(0);
  });
});
