"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.cloneDeep = cloneDeep;
exports.uniqBy = uniqBy;
exports.sortBy = sortBy;
exports.setEquals = setEquals;
exports.arrayEquals = arrayEquals;
exports.anyEquals = anyEquals;
exports.objectEquals = objectEquals;
exports.asyncGeneratorToArray = asyncGeneratorToArray;
/**
 * Recursively deep clone a value. Handles primitives, arrays, plain objects, Date, RegExp, Set, Map, and typed arrays.
 * Non-cloneable values (functions, WeakMap, WeakSet, etc.) are copied by reference.
 */
function cloneDeep(obj) {
    if (obj === null || typeof obj !== 'object')
        return obj;
    if (obj instanceof Date)
        return new Date(obj.getTime());
    if (obj instanceof RegExp)
        return new RegExp(obj.source, obj.flags);
    if (Array.isArray(obj))
        return obj.map((item) => cloneDeep(item));
    if (obj instanceof Set) {
        const result = new Set();
        obj.forEach((v) => result.add(cloneDeep(v)));
        return result;
    }
    if (obj instanceof Map) {
        const result = new Map();
        obj.forEach((v, k) => result.set(cloneDeep(k), cloneDeep(v)));
        return result;
    }
    if (ArrayBuffer.isView(obj)) {
        if (obj instanceof DataView) {
            const clonedBuffer = obj.buffer.slice(obj.byteOffset, obj.byteOffset + obj.byteLength);
            return new DataView(clonedBuffer, 0, obj.byteLength);
        }
        const view = obj;
        if (typeof view.slice === 'function') {
            return view.slice();
        }
        const clonedBuffer = view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength);
        const TypedArrayCtor = view.constructor;
        const length = typeof view.BYTES_PER_ELEMENT === 'number' && typeof view.length === 'number' ? view.length : undefined;
        return new TypedArrayCtor(clonedBuffer, 0, length);
    }
    const result = Object.create(Object.getPrototypeOf(obj));
    for (const key of Object.keys(obj)) {
        result[key] = cloneDeep(obj[key]);
    }
    return result;
}
/**
 * Remove duplicate elements from an array based on a key function or property name.
 */
function uniqBy(arr, key) {
    const seen = new Set();
    return arr.filter((item) => {
        const k = typeof key === 'string' ? item[key] : key(item);
        if (seen.has(k))
            return false;
        seen.add(k);
        return true;
    });
}
/**
 * Sort an array by a key function. Returns a new sorted array (does not mutate the original).
 */
function sortBy(arr, keyFn) {
    return [...arr].sort((a, b) => {
        const ka = keyFn(a);
        const kb = keyFn(b);
        if (ka == null && kb == null)
            return 0;
        if (ka == null)
            return -1;
        if (kb == null)
            return 1;
        if (ka < kb)
            return -1;
        if (ka > kb)
            return 1;
        return 0;
    });
}
/**
 * Check if two sets are equals (they contain the same elements). Will not work if the content can't be compared by `==`.
 */
function setEquals(setA, setB) {
    if (setA.size != setB.size)
        return false;
    return Array.from(setA).every((x) => setB.has(x));
}
/**
 * Check if two arrays are equals (they contain the same elements).
 */
function arrayEquals(arrayA, arrayB) {
    if (arrayA.length != arrayB.length)
        return false;
    for (let i = 0; i < arrayA.length; ++i) {
        if (!anyEquals(arrayA[i], arrayB[i]))
            return false;
    }
    return true;
}
/**
 * Check if two elements are equals (they contain the same elements).
 */
function anyEquals(anyA, anyB) {
    //Test if object is array
    if (Array.isArray(anyA) && Array.isArray(anyB)) {
        return arrayEquals(anyA, anyB);
    } /*Test if objects are set */
    else if (anyA instanceof Set && anyB instanceof Set) {
        return setEquals(anyA, anyB);
    }
    else if (typeof anyA === 'object' && typeof anyB === 'object') {
        return objectEquals(anyA, anyB);
    }
    return anyA === anyB;
}
/**
 * Check if two objects are equals (by comparing the properties in a deep way).
 * @param objectA
 * @param objectB
 * @param ignoredProperties
 */
function objectEquals(objectA, objectB, ignoredProperties) {
    const missingInA = Object.keys(objectB)
        .filter((key) => !(ignoredProperties === null || ignoredProperties === void 0 ? void 0 : ignoredProperties.includes(key)))
        .filter((key) => !Object.keys(objectA).includes(key))
        .filter((key) => objectB[key] !== undefined && objectB[key] !== null);
    if (missingInA.length > 0)
        return false;
    const missingInB = Object.keys(objectA)
        .filter((key) => !(ignoredProperties === null || ignoredProperties === void 0 ? void 0 : ignoredProperties.includes(key)))
        .filter((key) => !Object.keys(objectB).includes(key))
        .filter((key) => objectA[key] !== undefined && objectA[key] !== null);
    if (missingInB.length > 0)
        return false;
    for (let i = 0, keys = Object.keys(objectA); i < keys.length; i++) {
        const key = keys[i];
        if (ignoredProperties === null || ignoredProperties === void 0 ? void 0 : ignoredProperties.includes(key))
            continue;
        if (!anyEquals(objectA[key], objectB[key]))
            return false;
    }
    return true;
}
/**
 * Fully consumes an async generator and provides all results as an array.
 * @param generator an async generator.
 */
function asyncGeneratorToArray(generator) {
    return __awaiter(this, void 0, void 0, function* () {
        const res = [];
        let latest = yield generator.next();
        while (!latest.done) {
            res.push(latest.value);
            latest = yield generator.next();
        }
        return res;
    });
}
//# sourceMappingURL=collection-utils.js.map