import { test, expect } from "@playwright/test";
import { promises as fs } from "fs";
import * as os from "os";
import * as path from "path";
import { V3 } from "../../lib/v3/v3.js";
import { v3TestConfig } from "./v3.config.js";
import { Frame } from "../../lib/v3/understudy/frame.js";
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
test.describe("Page.screenshot options", () => {
    let v3;
    test.beforeEach(async () => {
        v3 = new V3(v3TestConfig);
        await v3.init();
    });
    test.afterEach(async () => {
        await v3?.close?.().catch(() => { });
    });
    test("rejects clip combined with fullPage", async () => {
        const page = v3.context.pages()[0];
        await page.goto("data:text/html,<html><body>test</body></html>");
        await expect(page.screenshot({
            fullPage: true,
            clip: { x: 0, y: 0, width: 100, height: 100 },
        })).rejects.toThrow(/clip and fullPage/);
    });
    test("rejects unsupported image type", async () => {
        const page = v3.context.pages()[0];
        await page.goto("data:text/html,<html><body>noop</body></html>");
        await expect(page.screenshot({
            // @ts-expect-error intentional invalid type for runtime validation
            type: "webp",
        })).rejects.toThrow(/unsupported image type/);
    });
    test("rejects jpeg quality for png screenshots", async () => {
        const page = v3.context.pages()[0];
        await page.goto("data:text/html,<html><body>noop</body></html>");
        await expect(page.screenshot({ type: "png", quality: 50 })).rejects.toThrow(/quality option is only valid/);
    });
    test("honours timeout option", async () => {
        const page = v3.context.pages()[0];
        await page.goto("data:text/html,<html><body>noop</body></html>");
        const mainFrame = page.mainFrame();
        const originalScreenshot = mainFrame.screenshot.bind(mainFrame);
        mainFrame.screenshot = async () => {
            await wait(50);
            return Buffer.from("late");
        };
        try {
            await expect(page.screenshot({ timeout: 10 })).rejects.toThrow(/timeout/);
        }
        finally {
            mainFrame.screenshot = originalScreenshot;
        }
    });
    test("applies advanced options and cleans up overlays", async () => {
        const page = v3.context.pages()[0];
        const screenshotTimeout = process.env.CI ? 15000 : 5000;
        const testStart = Date.now();
        console.log(`[screenshot-test] start ${new Date(testStart).toISOString()} timeout=${screenshotTimeout}`);
        const html = `
      <!doctype html>
      <html>
        <head>
          <meta charset="utf-8" />
          <style>
            body { background: #aaccee; margin: 0; height: 100vh; display: flex; flex-direction: column; align-items: flex-start; }
            .mask-target { width: 80px; height: 80px; margin: 40px; background: rgb(0, 180, 60); animation: pulse 1s infinite alternate; }
            @keyframes pulse { from { transform: scale(1); } to { transform: scale(1.2); } }
          </style>
        </head>
        <body>
          <div class="mask-target"></div>
          <div class="mask-target"></div>
          <input id="focus-me" value="focus" />
          <script>document.getElementById('focus-me').focus();</script>
        </body>
      </html>
    `;
        await page.goto("data:text/html," + encodeURIComponent(html));
        console.log(`[screenshot-test] page loaded in ${Date.now() - testStart}ms`);
        const maskLocator = page.locator(".mask-target");
        const tempPath = path.join(os.tmpdir(), `stagehand-screenshot-${Date.now()}-${Math.random().toString(36).slice(2)}.jpeg`);
        console.log(`[screenshot-test] tempPath=${tempPath}`);
        const targetId = page.targetId();
        const screenshotCalls = [];
        const evaluateCalls = [];
        const originalScreenshot = Frame.prototype.screenshot;
        const originalEvaluate = Frame.prototype.evaluate;
        // Hook Frame.screenshot so we can assert which options reach CDP without writing real data.
        Frame.prototype.screenshot = async function screenshotSpy(options) {
            const frame = this;
            if (frame.pageId === targetId) {
                screenshotCalls.push({ frameId: frame.frameId, options });
                return Buffer.from("stub-image");
            }
            return originalScreenshot.call(this, options);
        };
        // Spy on Frame.evaluate to capture the arguments used to inject CSS/masks.
        Frame.prototype.evaluate = async function evaluateSpy(expression, arg) {
            const frame = this;
            if (frame.pageId === targetId) {
                evaluateCalls.push({ frameId: frame.frameId, arg });
            }
            return originalEvaluate.call(this, expression, arg);
        };
        const internalPage = page;
        const sendCalls = [];
        const originalSend = internalPage.mainSession.send.bind(internalPage.mainSession);
        // Capture background overrides so we can confirm omitBackground toggles on/off.
        internalPage.mainSession.send = async (method, params) => {
            sendCalls.push({ method, params });
            return originalSend(method, params);
        };
        try {
            const maskCount = await maskLocator.count();
            console.log(`[screenshot-test] maskLocator.count=${maskCount}`);
            const buffer = await page.screenshot({
                animations: "disabled",
                caret: "hide",
                clip: { x: 0, y: 0, width: 200, height: 200 },
                mask: [maskLocator],
                maskColor: "rgba(255, 0, 0, 0.4)",
                omitBackground: true,
                path: tempPath,
                quality: 80,
                scale: "css",
                style: "body { border: 3px solid black; }",
                timeout: screenshotTimeout,
                type: "jpeg",
            });
            console.log(`[screenshot-test] screenshot returned bytes=${buffer.length} elapsed=${Date.now() - testStart}ms`);
            expect(Buffer.isBuffer(buffer)).toBeTruthy();
            expect(screenshotCalls.length).toBeGreaterThanOrEqual(1);
            console.log(`[screenshot-test] screenshotCalls=${screenshotCalls.length} evaluateCalls=${evaluateCalls.length} sendCalls=${sendCalls.length}`);
            const recorded = screenshotCalls[0]?.options ?? {};
            expect(recorded).toMatchObject({ type: "jpeg", quality: 80 });
            expect(recorded?.clip).toMatchObject({
                x: 0,
                y: 0,
                width: 200,
                height: 200,
            });
            if (typeof recorded?.scale === "number") {
                expect(recorded.scale).toBeGreaterThan(0);
                expect(recorded.scale).toBeLessThanOrEqual(2);
            }
            await fs.stat(tempPath);
            const maskNodes = await page.evaluate(() => document.querySelectorAll("[data-stagehand-mask]").length);
            expect(maskNodes).toBe(0);
            const styleNodes = await page.evaluate(() => document.querySelectorAll("[data-stagehand-style]").length);
            expect(styleNodes).toBe(0);
            const backgroundCalls = sendCalls.filter((call) => call.method === "Emulation.setDefaultBackgroundColorOverride");
            expect(backgroundCalls.length).toBeGreaterThan(1);
            expect(backgroundCalls.some((call) => call.params &&
                typeof call.params === "object" &&
                "color" in call.params)).toBeTruthy();
            expect(backgroundCalls.some((call) => !call.params ||
                Object.keys(call.params).length === 0)).toBeTruthy();
            const cssArgs = evaluateCalls
                .map((entry) => {
                const value = entry.arg;
                return value?.css ?? null;
            })
                .filter((css) => typeof css === "string");
            const tokens = evaluateCalls
                .map((entry) => {
                const arg = entry.arg;
                return arg?.token ?? null;
            })
                .filter((token) => typeof token === "string");
            // Tokens include which helper injected the style (animations/caret/custom).
            expect(tokens.some((token) => token.includes("animations"))).toBeTruthy();
            expect(tokens.some((token) => token.includes("caret"))).toBeTruthy();
            expect(tokens.some((token) => token.includes("custom"))).toBeTruthy();
            // Custom style should bubble through so we check the actual CSS text.
            expect(cssArgs.some((css) => css.includes("border: 3px solid black"))).toBeTruthy();
            const maskCalls = evaluateCalls.filter((entry) => {
                const arg = entry.arg;
                return (arg &&
                    typeof arg === "object" &&
                    "rects" in arg);
            });
            expect(maskCalls.length).toBeGreaterThan(0);
            const rects = maskCalls[0]?.arg
                ?.rects;
            expect(Array.isArray(rects)).toBeTruthy();
            expect(rects.length).toBe(2);
        }
        finally {
            Frame.prototype.screenshot = originalScreenshot;
            Frame.prototype.evaluate = originalEvaluate;
            internalPage.mainSession.send = originalSend;
            await fs.unlink(tempPath).catch(() => { });
        }
    });
    test("masks elements inside dialog top layer", async () => {
        const page = v3.context.pages()[0];
        const html = `
      <!doctype html>
      <html>
        <head>
          <meta charset="utf-8" />
          <style>
            dialog { padding: 16px; border: 2px solid #444; }
            #dialog-input { display: block; width: 160px; height: 32px; }
          </style>
        </head>
        <body>
          <dialog id="dialog">
            <label>Secret <input id="dialog-input" value="top-layer" /></label>
          </dialog>
          <script>
            const dialog = document.getElementById("dialog");
            if (dialog) {
              if (typeof dialog.showModal === "function") {
                try {
                  dialog.showModal();
                } catch {
                  dialog.setAttribute("open", "");
                }
              } else {
                dialog.setAttribute("open", "");
              }
            }
          </script>
        </body>
      </html>
    `;
        await page.goto("data:text/html," + encodeURIComponent(html));
        const targetId = page.targetId();
        const originalScreenshot = Frame.prototype.screenshot;
        let dialogMaskCount = 0;
        Frame.prototype.screenshot = async function screenshotSpy(options) {
            const frame = this;
            if (frame.pageId === targetId) {
                dialogMaskCount = await frame.evaluate(() => {
                    const dialog = document.querySelector("dialog[open]");
                    if (!dialog)
                        return 0;
                    return dialog.querySelectorAll("[data-stagehand-mask]").length;
                });
                return Buffer.from("stub-image");
            }
            return originalScreenshot.call(this, options);
        };
        try {
            await page.screenshot({
                mask: [page.locator("#dialog-input")],
            });
            expect(dialogMaskCount).toBeGreaterThan(0);
        }
        finally {
            Frame.prototype.screenshot = originalScreenshot;
        }
    });
});
//# sourceMappingURL=page-screenshot.spec.js.map