/**
 * Adapted from https://github.com/mattphillips/jest-chain/blob/main/src/chain.js
 */
import { evaluatedBy } from "./evaluatedBy.js";
class JestlikeAssertionError extends Error {
    constructor(result, callsite) {
        super(typeof result.message === "function" ? result.message() : result.message);
        Object.defineProperty(this, "matcherResult", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        this.matcherResult = result;
        if (Error.captureStackTrace) {
            Error.captureStackTrace(this, callsite);
        }
    }
}
const _wrapMatchers = (matchers, evaluator, originalArgs, originalExpect, staticPath = []) => {
    return Object.keys(matchers)
        .filter((name) => typeof matchers[name] === "function")
        .map((name) => {
        const newMatcher = async (...args) => {
            try {
                const score = await evaluatedBy(originalArgs[0], evaluator);
                let result = originalExpect(score);
                for (const pathEntry of staticPath) {
                    result = result[pathEntry];
                }
                result = result[name](...args); // run matcher up to current state
                if (result && typeof result.then === "function") {
                    return Object.assign(Promise.resolve(result), matchers);
                }
                else {
                    return matchers;
                }
            }
            catch (error) {
                if (!error.matcherResult) {
                    throw error;
                }
                else {
                    throw new JestlikeAssertionError(error.matcherResult, newMatcher);
                }
            }
        };
        return { [name]: newMatcher };
    });
};
const addEvaluatedBy = (matchers, originalArgs, originalExpect, staticPath = []) => {
    let spreadMatchers = { ...matchers };
    // Handle Bun, which uses a class, and Vitest which uses something weird
    if (Object.keys(matchers).length === 0 ||
        !Object.keys(matchers).includes("toEqual")) {
        const prototypeProps = Object.getOwnPropertyNames(Object.getPrototypeOf(matchers));
        spreadMatchers = Object.fromEntries(prototypeProps.map((prop) => {
            try {
                return [prop, matchers[prop]];
            }
            catch (_e) {
                // Ignore bizarre Bun bug
                return [];
            }
        }));
    }
    return Object.assign({}, matchers, {
        evaluatedBy: function (evaluator) {
            const mappedMatchers = _wrapMatchers(spreadMatchers, evaluator, originalArgs, originalExpect, []);
            // .not etc.
            const staticMatchers = Object.keys(spreadMatchers)
                .filter((name) => typeof matchers[name] !== "function")
                .map((name) => {
                return {
                    [name]: Object.assign({}, ..._wrapMatchers(spreadMatchers, evaluator, originalArgs, originalExpect, staticPath.concat(name))),
                };
            });
            return Object.assign({}, ...mappedMatchers, ...staticMatchers);
        },
    });
};
export function wrapExpect(originalExpect) {
    // proxy the expect function
    const expectProxy = Object.assign((...args) => addEvaluatedBy(originalExpect(...args), args, originalExpect, []), // partially apply expect to get all matchers and chain them
    originalExpect);
    return expectProxy;
}
globalThis.expect = wrapExpect(globalThis.expect);
