import { assertObject, throwInvalidType } from "./asserts.js";
import { skipAssertJSONValue } from "./config.js";
import { hasOwn } from "./has-own.js";
/**
 * Checks deep equality of two JSON value with (almost) same semantics as
 * `JSON.stringify`. The only difference is that with `JSON.stringify` the
 * ordering of the properties in an object/map/dictionary matters. In
 * {@link deepEqual} the following two values are consider equal, even though the
 * strings JSON.stringify would produce is different:
 *
 * ```js
 * assert(deepEqual(t({a: 1, b: 2}, {b: 2, a: 1}))
 * ```
 */
export function deepEqual(a, b) {
    if (a === b) {
        return true;
    }
    if (typeof a !== typeof b) {
        return false;
    }
    switch (typeof a) {
        case 'boolean':
        case 'number':
        case 'string':
            return false;
    }
    // a cannot be undefined here because either a and b are undefined or their
    // types are different.
    // oxlint-disable-next-line  @typescript-eslint/no-non-null-assertion
    a = a;
    // 'object'
    if (Array.isArray(a)) {
        if (!Array.isArray(b)) {
            return false;
        }
        if (a.length !== b.length) {
            return false;
        }
        for (let i = 0; i < a.length; i++) {
            if (!deepEqual(a[i], b[i])) {
                return false;
            }
        }
        return true;
    }
    if (a === null || b === null) {
        return false;
    }
    if (Array.isArray(b)) {
        return false;
    }
    // We know a and b are objects here but type inference is not smart enough.
    a = a;
    b = b;
    // We use for-in loops instead of for of Object.keys() to make sure deepEquals
    // does not allocate any objects.
    let aSize = 0;
    for (const key in a) {
        if (hasOwn(a, key)) {
            if (!deepEqual(a[key], b[key])) {
                return false;
            }
            aSize++;
        }
    }
    let bSize = 0;
    for (const key in b) {
        if (hasOwn(b, key)) {
            bSize++;
        }
    }
    return aSize === bSize;
}
export function assertJSONValue(v) {
    if (skipAssertJSONValue) {
        return;
    }
    switch (typeof v) {
        case 'boolean':
        case 'number':
        case 'string':
            return;
        case 'object':
            if (v === null) {
                return;
            }
            if (Array.isArray(v)) {
                return assertJSONArray(v);
            }
            return assertObjectIsJSONObject(v);
    }
    throwInvalidType(v, 'JSON value');
}
export function assertJSONObject(v) {
    assertObject(v);
    assertObjectIsJSONObject(v);
}
function assertObjectIsJSONObject(v) {
    for (const k in v) {
        if (hasOwn(v, k)) {
            const value = v[k];
            if (value !== undefined) {
                assertJSONValue(value);
            }
        }
    }
}
function assertJSONArray(v) {
    for (const item of v) {
        assertJSONValue(item);
    }
}
/**
 * Checks if a value is a JSON value. If there is a value that is not a JSON
 * value, the path parameter is updated to the path of the invalid value.
 */
export function isJSONValue(v, path) {
    switch (typeof v) {
        case 'boolean':
        case 'number':
        case 'string':
            return true;
        case 'object':
            if (v === null) {
                return true;
            }
            if (Array.isArray(v)) {
                return isJSONArray(v, path);
            }
            return objectIsJSONObject(v, path);
    }
    return false;
}
export function isJSONObject(v, path) {
    if (typeof v !== 'object' || v === null) {
        return false;
    }
    return objectIsJSONObject(v, path);
}
function objectIsJSONObject(v, path) {
    for (const k in v) {
        if (hasOwn(v, k)) {
            path.push(k);
            const value = v[k];
            if (value !== undefined && !isJSONValue(value, path)) {
                return false;
            }
            path.pop();
        }
    }
    return true;
}
function isJSONArray(v, path) {
    for (let i = 0; i < v.length; i++) {
        path.push(i);
        if (!isJSONValue(v[i], path)) {
            return false;
        }
        path.pop();
    }
    return true;
}
//# sourceMappingURL=json.js.map