/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { Assertion, util } from "chai";
import { Geometry } from "@itwin/core-geometry";
export const defaultOpts = {
    tolerance: 1e-10,
    considerNonExistingAndUndefinedEqual: false,
};
/** get whether two numbers are almost equal within a tolerance  */
const isAlmostEqualNumber = (a, b, tol) => Geometry.isSameCoordinate(a, b, tol);
/** normalize a classname for comparisons */
const normalizeClassName = (name) => name.toLowerCase().replace(/:/, ".");
/**
 * The diff shown on failure will show undefined fields as part of the diff even if
 * consideringNonExistingAndUndefinedEqual is true. You can ignore that.
 * We would have to mutate the object to clean the diff, although it is worth considering.
 */
export function advancedDeepEqual(e, a, options = {}) {
    const normalizedClassNameProps = options.normalizeClassNameProps === true
        ? ["classFullName", "relClassName"]
        : options.normalizeClassNameProps || [];
    if (options.tolerance === undefined)
        options.tolerance = defaultOpts.tolerance;
    if (e === a)
        return true;
    if (typeof e !== typeof a)
        return false;
    switch (typeof e) {
        case "number":
            return isAlmostEqualNumber(e, a, options.tolerance);
        case "string":
        case "boolean":
        case "function":
        case "symbol":
        case "undefined":
            return false; // these objects can only be strict equal which was already tested
        case "object":
            if ((e === null) !== (a === null))
                return false;
            const eSize = Object.keys(e).filter((k) => options.considerNonExistingAndUndefinedEqual && e[k] !== undefined).length;
            const aSize = Object.keys(a).filter((k) => options.considerNonExistingAndUndefinedEqual && a[k] !== undefined).length;
            return (eSize === aSize || !!options.useSubsetEquality) && Object.keys(e).every((keyOfE) => (keyOfE in a || options.considerNonExistingAndUndefinedEqual) &&
                normalizedClassNameProps.includes(keyOfE)
                ? advancedDeepEqual(normalizeClassName(e[keyOfE]), normalizeClassName(a[keyOfE]))
                : advancedDeepEqual(e[keyOfE], a[keyOfE], options));
        default: // bigint unhandled
            throw Error(`unhandled deep compare type code returned from typeof, "${typeof e}"`);
    }
}
Assertion.addMethod("advancedEqual", function advancedEqual(expected, options = {}) {
    if (options.tolerance === undefined)
        options.tolerance = 1e-10;
    const actual = this._obj;
    const isDeep = util.flag(this, "deep");
    this.assert(isDeep
        ? advancedDeepEqual(expected, actual, options)
        : isAlmostEqualNumber(expected, actual, options.tolerance), `expected ${isDeep ? "deep equality of " : " "}#{exp} and #{act} with a tolerance of ${options.tolerance}`, `expected ${isDeep ? "deep inequality of " : " "}#{exp} and #{act} with a tolerance of ${options.tolerance}`, expected, actual);
});
Assertion.addMethod("subsetEqual", function subsetEqual(expected, options = {}) {
    if (options.tolerance === undefined)
        options.tolerance = 1e-10;
    const actual = this._obj;
    this.assert(advancedDeepEqual(expected, actual, { ...options, useSubsetEquality: true }), `expected #{act} to contain as a subset #{exp}`, `expected #{act} not to contain as a subset #{exp}`, expected, actual);
});
//# sourceMappingURL=AdvancedEqual.js.map