"use strict";
/**
 * Copyright: (c) Myia SAS 2026.
 * This file and its contents are licensed under the AGPLv3 License.
 * Please see the LICENSE file at the root of this repository
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.wrapExpect = wrapExpect;
const locator_patch_1 = require("./locator-patch");
const logger_1 = require("../../util/logger");
// Duck-type: a Playwright Locator always has both `.boundingBox` and
// `.page` as functions. FrameLocator / ElementHandle / Page won't match.
// We don't use `instanceof` because the Locator class is created lazily
// inside Playwright and isn't reliably exported for runtime checks.
function isLocator(target) {
    if (!target || typeof target !== 'object')
        return false;
    const obj = target;
    return typeof obj.boundingBox === 'function' && typeof obj.page === 'function';
}
// Assertion methods in Playwright all start with `to` (toBeVisible,
// toHaveText, …). Every other property we see on the assertion
// instance — `not`, symbol keys, internal fields — we leave alone.
function isAssertionMethodName(prop) {
    return typeof prop === 'string' && prop.startsWith('to');
}
function wrapAssertion(assertion, locator) {
    return new Proxy(assertion, {
        get(target, prop, _receiver) {
            // Read against `target` (not the proxy) so getters like `.not`
            // see the original `this` and don't recurse through the proxy.
            const value = Reflect.get(target, prop, target);
            if (prop === 'not' && value && typeof value === 'object') {
                return wrapAssertion(value, locator);
            }
            if (isAssertionMethodName(prop) && typeof value === 'function') {
                return async function patchedAssertion(...args) {
                    const page = typeof locator.page === 'function'
                        ? locator.page()
                        : null;
                    let cleanup = null;
                    const session = (0, locator_patch_1.getActiveCaptureSession)();
                    if (page && session) {
                        try {
                            // Scroll the target into view before the screenshot —
                            // mirrors what locator actions already do — except for
                            // `toBeInViewport()` / `not.toBeInViewport()`, which
                            // are the only assertions whose outcome depends on
                            // viewport position. For those we leave scroll alone so
                            // we don't change what the user is asserting.
                            const scrollBeforeCapture = prop !== 'toBeInViewport';
                            cleanup = await session.captureWithHighlight(page, locator, `assert-${prop}`, {
                                scrollBeforeCapture,
                            });
                        }
                        catch (err) {
                            // Capture is best-effort: never block the assertion.
                            logger_1.log.warn(`captureWithHighlight rejected for assert-${String(prop)}`, err);
                        }
                    }
                    try {
                        return await value.apply(target, args);
                    }
                    finally {
                        if (cleanup) {
                            try {
                                await cleanup();
                            }
                            catch (err) {
                                // Cleanup is already capped via withTimeout inside
                                // the session.
                                logger_1.log.warn(`overlay cleanup rejected after assert-${String(prop)}`, err);
                            }
                        }
                    }
                };
            }
            return value;
        },
    });
}
// Build a callable wrapping `origFn(target, ...)`-shaped functions —
// used for both `expect` and `expect.soft`.
function wrapExpectCallable(origFn) {
    const wrapped = function wrappedExpect(target, ...rest) {
        const assertion = origFn.call(this, target, ...rest);
        if (isLocator(target) && assertion && typeof assertion === 'object') {
            return wrapAssertion(assertion, target);
        }
        return assertion;
    };
    return wrapped;
}
function wrapExpect(origExpect) {
    const wrapped = wrapExpectCallable(origExpect);
    // Copy static properties across so `wrapped.soft`, `wrapped.poll`,
    // `wrapped.configure`, `wrapped.extend`, etc. remain callable. `soft`
    // shares the `(target, ...)` contract so we wrap it the same way the
    // top-level expect is wrapped; everything else is forwarded as-is.
    for (const key of Reflect.ownKeys(origExpect)) {
        if (key === 'length' || key === 'name' || key === 'prototype')
            continue;
        const descriptor = Object.getOwnPropertyDescriptor(origExpect, key);
        if (!descriptor)
            continue;
        if (key === 'soft' &&
            'value' in descriptor &&
            typeof descriptor.value === 'function') {
            const softFn = descriptor.value;
            descriptor.value = wrapExpectCallable(softFn);
        }
        try {
            Object.defineProperty(wrapped, key, descriptor);
        }
        catch (err) {
            // Some descriptors may be non-configurable on the target; skip
            // them — the caller can still reach them via the original expect.
            logger_1.log.warn(`could not copy expect.${String(key)} descriptor onto the wrapper`, err);
        }
    }
    return wrapped;
}
