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

// Default rich fixture: all fields populated (hero_image, hero_vector, intro).
const pageUrl =
    process.env.TEST_PAGE_URL ||
    "http://community-works-collaborative.test/sample-page/";

// No-intro fixture: `intro` ACF field intentionally empty.
const noIntroUrl =
    process.env.TEST_PAGE_URL_NO_INTRO ||
    "http://community-works-collaborative.test/inner-page-no-intro/";

// No-asset fixture: `hero_image` and `hero_vector` ACF fields intentionally empty.
const noAssetUrl =
    process.env.TEST_PAGE_URL_NO_ASSET ||
    "http://community-works-collaborative.test/inner-page-no-asset/";

const getBox = async (locator) => {
    const box = await locator.boundingBox();
    expect(box).not.toBeNull();
    return box;
};

const expectNear = (actual, expected, tolerance = 3) => {
    expect(Math.abs(actual - expected)).toBeLessThanOrEqual(tolerance);
};

test.describe("inner page layout", () => {
    test("desktop: hero, intro, body render with intro inside hero", async ({
        page,
    }) => {
        const pageErrors = [];
        page.on("pageerror", (err) => pageErrors.push(err.message));

        await page.setViewportSize({ width: 1280, height: 900 });
        await page.goto(pageUrl);

        const hero = page.locator(".page-hero").first();
        const intro = page.locator(".page-hero__intro").first();
        const content = page.locator(".entry-content").first();

        await expect(hero).toBeVisible();
        await expect(intro).toBeVisible();
        await expect(content).toBeVisible();

        // Intro lives INSIDE the hero.
        const heroBox = await getBox(hero);
        const introBox = await getBox(intro);
        const contentBox = await getBox(content);

        // Intro is a descendant of the hero (same x, y is between hero top and bottom).
        expect(introBox.x).toBeGreaterThanOrEqual(heroBox.x - 1);
        expect(introBox.y).toBeGreaterThanOrEqual(heroBox.y - 1);
        expect(introBox.y + introBox.height).toBeLessThanOrEqual(
            heroBox.y + heroBox.height + 1,
        );

        // Body renders below the hero.
        expect(contentBox.y).toBeGreaterThanOrEqual(heroBox.y + heroBox.height - 1);

        // Heading is rendered inside the hero.
        await expect(hero.locator("h1").first()).toBeVisible();

        // Page renders without JS errors.
        expect(pageErrors).toEqual([]);
    });

    test("mobile 402: hero, intro, body order and intro width", async ({
        page,
    }) => {
        const pageErrors = [];
        page.on("pageerror", (err) => pageErrors.push(err.message));

        await page.setViewportSize({ width: 402, height: 874 });
        await page.goto(pageUrl);

        const hero = page.locator(".page-hero").first();
        const intro = page.locator(".page-hero__intro").first();
        const content = page.locator(".entry-content").first();

        await expect(hero).toBeVisible();
        await expect(intro).toBeVisible();
        await expect(content).toBeVisible();

        const heroBox = await getBox(hero);
        const introBox = await getBox(intro);
        const contentBox = await getBox(content);

        // Intro inside the hero.
        expect(introBox.x).toBeGreaterThanOrEqual(heroBox.x - 1);
        expect(introBox.y).toBeGreaterThanOrEqual(heroBox.y - 1);
        expect(introBox.y + introBox.height).toBeLessThanOrEqual(
            heroBox.y + heroBox.height + 1,
        );

        // Body below the hero.
        expect(contentBox.y).toBeGreaterThanOrEqual(heroBox.y + heroBox.height - 1);

        // Intro fits within the mobile viewport.
        expect(introBox.x + introBox.width).toBeLessThanOrEqual(402 + 3);

        // No JS errors.
        expect(pageErrors).toEqual([]);
    });

    test("mobile 320: nothing clips horizontally", async ({ page }) => {
        await page.setViewportSize({ width: 320, height: 800 });
        await page.goto(pageUrl);

        const hero = page.locator(".page-hero").first();
        const intro = page.locator(".page-hero__intro").first();

        await expect(hero).toBeVisible();
        await expect(intro).toBeVisible();

        const heroBox = await getBox(hero);
        const introBox = await getBox(intro);

        expect(heroBox.x + heroBox.width).toBeLessThanOrEqual(320 + 3);
        expect(introBox.x + introBox.width).toBeLessThanOrEqual(320 + 3);
    });

    test("mobile 767: layout still holds before the desktop breakpoint", async ({
        page,
    }) => {
        await page.setViewportSize({ width: 767, height: 900 });
        await page.goto(pageUrl);

        const hero = page.locator(".page-hero").first();
        const intro = page.locator(".page-hero__intro").first();

        await expect(hero).toBeVisible();
        await expect(intro).toBeVisible();

        const heroBox = await getBox(hero);
        const introBox = await getBox(intro);

        expect(heroBox.x + heroBox.width).toBeLessThanOrEqual(767 + 3);
        expect(introBox.x + introBox.width).toBeLessThanOrEqual(767 + 3);
    });

    // Spec line 113: "A page with no `intro` field renders hero + body with no empty intro wrapper."
    test("no-intro: hero and body render with no .page-hero__intro wrapper", async ({
        page,
    }) => {
        await page.setViewportSize({ width: 1280, height: 900 });
        await page.goto(noIntroUrl);

        const hero = page.locator(".page-hero").first();
        const content = page.locator(".entry-content").first();

        await expect(hero).toBeVisible();
        await expect(content).toBeVisible();

        // No intro wrapper rendered when the intro field is empty.
        expect(await page.locator(".page-hero__intro").count()).toBe(0);

        // Hero and body still render in the expected order.
        const heroBox = await getBox(hero);
        const contentBox = await getBox(content);
        expect(contentBox.y).toBeGreaterThanOrEqual(heroBox.y + heroBox.height - 1);

        // Heading is still inside the hero.
        await expect(hero.locator("h1").first()).toBeVisible();
    });

    // Spec line 114: "A page with no `hero_image` and no `hero_vector` renders the existing dark text-only hero unchanged."
    test("no-asset: dark text-only hero renders without image or vector", async ({
        page,
    }) => {
        await page.setViewportSize({ width: 1280, height: 900 });
        await page.goto(noAssetUrl);

        const hero = page.locator(".page-hero").first();

        await expect(hero).toBeVisible();

        // No media layers when both assets are empty.
        expect(await page.locator(".page-hero__media").count()).toBe(0);
        expect(await page.locator(".page-hero__vector").count()).toBe(0);

        // Heading still renders inside the hero.
        await expect(hero.locator("h1").first()).toBeVisible();

        // Hero still exposes the dark text-only class hook.
        await expect(hero).toHaveClass(/page-hero/);
    });
});
