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

// Scroll the whole page so lazy next/image elements enter the viewport and have
// time to load (dev-mode /_next/image optimization is slow), otherwise a
// fullPage screenshot captures blur placeholders mid-load.
async function settleImages(page: Page) {
  const height = await page.evaluate(() => document.body.scrollHeight);
  for (let y = 0; y < height; y += 800) {
    // The scroll steps are the point: parallelising them would jump straight to
    // the bottom and skip the dwell that lets each lazy image load. The directive
    // has to sit on the line right above the statement — a wrapped `//` block
    // would only target the next comment line.
    // oxlint-disable-next-line no-await-in-loop -- sequential on purpose
    await page.evaluate((to) => window.scrollTo(0, to), y);
    // oxlint-disable-next-line no-await-in-loop -- the delay IS the dwell
    await page.waitForTimeout(250);
  }
  await page.evaluate(() => window.scrollTo(0, 0));
  await page.waitForTimeout(1500);
}

test.describe("Home — Modern Editorial layout", () => {
  test.describe.configure({ timeout: 90_000 });

  test("/ renders hero, stats, and all editorial sections", async ({ page }) => {
    await page.goto("/");

    // Hero (split image/text)
    await expect(
      page.getByRole("heading", { level: 1, name: /Fine Jewellery Manufacturer/i }),
    ).toBeVisible();

    // Stats band
    await expect(page.getByText("Years of Excellence")).toBeVisible();
    await expect(page.getByText("Master Craftsmen")).toBeVisible();
    await expect(page.getByText("Annual Production")).toBeVisible();

    // Editorial sections
    await expect(page.getByRole("heading", { name: "Why Brands Choose Advance" })).toBeVisible();
    await expect(page.getByRole("heading", { name: "How We Work" })).toBeVisible();
    await expect(page.getByRole("heading", { name: "Worldwide Shipping" })).toBeVisible();
    await expect(page.getByRole("heading", { name: "Gallery", exact: true })).toBeVisible();

    // Footer (newsletter + copyright)
    await expect(page.getByRole("heading", { name: "Join Our Newsletter List" })).toBeVisible();

    await settleImages(page);
    await page.screenshot({
      path: "e2e/__screenshots__/home-en.png",
      fullPage: true,
    });
  });

  test("/fr is localized", async ({ page }) => {
    await page.goto("/fr");
    await expect(page.getByRole("heading", { name: "Comment nous travaillons" })).toBeVisible();
    await expect(page.getByText("Maîtres artisans", { exact: true })).toBeVisible();
    await settleImages(page);
    await page.screenshot({
      path: "e2e/__screenshots__/home-fr.png",
      fullPage: true,
    });
  });

  // `animation-timeline: view()` resolves against the nearest ancestor *scroll
  // container*, and `overflow: hidden` is one — a `.reveal` under such an
  // ancestor rides a timeline that never scrolls, so it silently never
  // animates. `overflow: clip` clips identically without the scroll container.
  test("no .reveal sits under an overflow:hidden ancestor", async ({ page }) => {
    await page.goto("/");
    await expect(page.getByRole("heading", { level: 1 })).toBeVisible();

    const offenders = await page.evaluate(() => {
      const found: string[] = [];
      for (const element of document.querySelectorAll(".reveal")) {
        for (let parent = element.parentElement; parent; parent = parent.parentElement) {
          const { overflowX, overflowY } = getComputedStyle(parent);
          if (overflowX === "hidden" || overflowY === "hidden") {
            found.push(
              `${parent.tagName.toLowerCase()}[class="${parent.getAttribute("class") ?? ""}"]`,
            );
            break;
          }
        }
      }
      return [...new Set(found)];
    });

    expect(offenders).toEqual([]);
  });
});

test.describe("Home — mobile menu", () => {
  test.use({ viewport: { width: 390, height: 844 } });

  test("the burger opens a panel whose height settles", async ({ page }) => {
    await page.goto("/");

    const panel = page.getByRole("navigation", { name: "Main menu" });
    await expect(panel).toBeHidden();

    await page.getByRole("button", { name: "Open main menu" }).click();
    await expect(panel).toBeVisible();

    // Regression guard: the panel is rendered *inside* the <header> that
    // HeaderShell measures with a ResizeObserver to publish `--header-h`. When
    // the panel bounded its own max-height on that variable, opening it fed the
    // observer its own size and the header oscillated between two heights,
    // clipping the last links every other frame. Three readings spaced by
    // 100 ms, so a two-frame flip-flop cannot pass for a settled layout.
    const header = page.locator("header");
    await page.waitForTimeout(300);
    const heights: number[] = [];
    for (let reading = 0; reading < 3; reading += 1) {
      // oxlint-disable-next-line no-await-in-loop -- readings must be sequential
      heights.push(await header.evaluate((el) => Math.round(el.getBoundingClientRect().height)));
      // oxlint-disable-next-line no-await-in-loop -- the delay IS the measurement
      await page.waitForTimeout(100);
    }
    expect(new Set(heights).size).toBe(1);

    // The 7 primary links plus the "Contact Us" CTA, all reachable.
    const links = panel.getByRole("link");
    await expect(links).toHaveCount(8);
    await Promise.all((await links.all()).map((link) => expect(link).toBeVisible()));
  });
});
