import { isArrayEqualStrict } from "../../collection/array/isArrayEqualStrict.js";
import { isArraysEqualWithComparator } from "../../collection/array/isArraysEqualWithComparator.js";

/**
 * @template A,B
 * @param {A} a
 * @param {B} b
 * @param {function(a:A, b:B):boolean} [value_equality_function] allows you to insert custom element comparison function
 * @param {*} [value_equality_function_context]
 * @returns {boolean}
 */
export function objectDeepEquals(
    a, b,
    value_equality_function = objectDeepEquals,
    value_equality_function_context = null
) {

    if (a === b) {
        // identity shortcut
        return true;
    }

    const tA = typeof a;
    const tB = typeof b;

    if (tA !== tB) {
        return false;
    }

    if (tA !== "object") {
        // primitive types, identity equality already checked
        return false;
    }

    if (a === null || b === null) {
        // we know that A and B are not the same, so if one of them is a null - there is no possible equality
        return false;
    }

    if (Array.isArray(a)) {
        // special fast path for arrays
        if (!Array.isArray(b)) {
            // one is an array, the other is not
            return false;
        }
        return isArraysEqualWithComparator(a, b, value_equality_function, value_equality_function_context);
    }

    // try equals function
    if (
        typeof a.equals === "function"
        && typeof b.equals === "function"
    ) {
        return a.equals(b);
    }

    const keys_a = Object.keys(a);
    const keys_b = Object.keys(b);

    // sort keys to eliminate ordering concerns
    keys_a.sort();
    keys_b.sort();

    if (!isArrayEqualStrict(keys_a, keys_b)) {
        // different fields
        return false;
    }

    const key_count = keys_a.length;

    for (let i = 0; i < key_count; i++) {
        const key = keys_a[i];

        const a_value = a[key];
        const b_value = b[key];

        const values_are_equal = value_equality_function.call(
            value_equality_function_context,
            a_value,
            b_value
        );

        if (!values_are_equal) {
            return false;
        }
    }

    // looks like we have equality
    return true;

}
