import type { Page, Locator } from "@playwright/test"
import { test as base, expect as pwExpect } from "@playwright/test"
import { attachConsoleMonitor } from "../fixtures/console-monitor"

export { expect, type Page, API_BASE, clickSpaLink } from "../e2e/fixtures"
import { expect } from "../e2e/fixtures"

type VisualFixtures = {}

export const test = base.extend<VisualFixtures>({
    /**
     * Override page fixture: BrowserSync domcontentloaded workaround.
     */
    page: async ({ page }, use) => {
        const monitor = attachConsoleMonitor(page)

        const origGoto = page.goto.bind(page)
        const origReload = page.reload.bind(page)

        page.goto = ((url: string, opts?: any) =>
            origGoto(url, { waitUntil: "domcontentloaded", ...opts })) as typeof page.goto
        page.reload = ((opts?: any) => origReload({ waitUntil: "domcontentloaded", ...opts })) as typeof page.reload

        await use(page)
        monitor.assertNoErrors()
    },
})

/**
 * Wait for the SPA to be fully rendered and stable for visual comparison.
 * Waits for skeleton loaders to disappear and CSS to settle.
 */
export async function waitForVisualReady(page: Page, opts?: { timeout?: number }): Promise<void> {
    const timeout = opts?.timeout ?? 15000
    await page.waitForLoadState("domcontentloaded")
    await expect(page.locator("#appContainer")).not.toBeEmpty({ timeout })
    try {
        await page.waitForFunction(() => document.querySelectorAll(".animate-pulse").length === 0, { timeout: 10000 })
    } catch {
        // Skeleton loaders may not exist on every page
    }
    await page.waitForTimeout(800)
}

/**
 * Navigate to a route with visual readiness wait.
 */
export async function navigateToRoute(page: Page, routePath: string): Promise<void> {
    const url = page.url()
    const match = url.match(/\/([a-z]{2})(\/|$)/)
    const lang = match?.[1] || "de"
    const fullPath = routePath === "/" ? `/${lang}` : `/${lang}${routePath}`
    await page.goto(fullPath, { waitUntil: "domcontentloaded" })
    await waitForVisualReady(page)
}

/**
 * Hide dynamic content that would cause screenshot flakiness:
 * - BrowserSync overlay
 * - All animations and transitions
 * - Blinking cursor
 */
export async function hideDynamicContent(page: Page): Promise<void> {
    await page.addStyleTag({
        content: `
            #__bs_notify__, #__bs_notify__:before, #__bs_notify__:after { display: none !important; }
            *, *::before, *::after {
                animation-duration: 0s !important;
                animation-delay: 0s !important;
                transition-duration: 0s !important;
                transition-delay: 0s !important;
            }
            * { caret-color: transparent !important; }
        `,
    })
}

/**
 * Returns locators for non-deterministic elements that should be masked
 * in screenshots (e.g. cart badges, timestamps).
 * Customize this for your project.
 */
export function getDynamicMasks(page: Page): Locator[] {
    return [
        // Add project-specific dynamic element selectors here:
        // page.locator('[data-testid="cart-badge"]'),
    ]
}

/**
 * Prepare the page for a screenshot: hide dynamic content and scroll to top.
 */
export async function prepareForScreenshot(page: Page): Promise<void> {
    await hideDynamicContent(page)
    await page.evaluate(() => window.scrollTo(0, 0))
    await page.waitForTimeout(300)
}

/**
 * Take a screenshot and compare against baseline.
 */
export async function expectScreenshot(
    page: Page,
    name: string,
    opts?: {
        fullPage?: boolean
        mask?: Locator[]
        maxDiffPixelRatio?: number
    }
): Promise<void> {
    const masks = [...getDynamicMasks(page), ...(opts?.mask ?? [])]
    await pwExpect(page).toHaveScreenshot(name, {
        fullPage: opts?.fullPage ?? false,
        mask: masks,
        maxDiffPixelRatio: opts?.maxDiffPixelRatio ?? 0.02,
    })
}
