import { isNonNullObject } from "./objects.js";
const { hasOwnProperty } = Object.prototype;
export function mergeDeep(...sources) {
    return mergeDeepArray(sources);
}
// In almost any situation where you could succeed in getting the
// TypeScript compiler to infer a tuple type for the sources array, you
// could just use mergeDeep instead of mergeDeepArray, so instead of
// trying to convert T[] to an intersection type we just infer the array
// element type, which works perfectly when the sources array has a
// consistent element type.
export function mergeDeepArray(sources) {
    let target = sources[0] || {};
    const count = sources.length;
    if (count > 1) {
        const merger = new DeepMerger();
        for (let i = 1; i < count; ++i) {
            target = merger.merge(target, sources[i]);
        }
    }
    return target;
}
const defaultReconciler = function (target, source, property) {
    return this.merge(target[property], source[property]);
};
export class DeepMerger {
    reconciler;
    constructor(reconciler = defaultReconciler) {
        this.reconciler = reconciler;
    }
    merge(target, source, ...context) {
        if (isNonNullObject(source) && isNonNullObject(target)) {
            Object.keys(source).forEach((sourceKey) => {
                if (hasOwnProperty.call(target, sourceKey)) {
                    const targetValue = target[sourceKey];
                    if (source[sourceKey] !== targetValue) {
                        const result = this.reconciler(target, source, sourceKey, ...context);
                        // A well-implemented reconciler may return targetValue to indicate
                        // the merge changed nothing about the structure of the target.
                        if (result !== targetValue) {
                            target = this.shallowCopyForMerge(target);
                            target[sourceKey] = result;
                        }
                    }
                }
                else {
                    // If there is no collision, the target can safely share memory with
                    // the source, and the recursion can terminate here.
                    target = this.shallowCopyForMerge(target);
                    target[sourceKey] = source[sourceKey];
                }
            });
            return target;
        }
        // If source (or target) is not an object, let source replace target.
        return source;
    }
    isObject = isNonNullObject;
    pastCopies = new Set();
    shallowCopyForMerge(value) {
        if (isNonNullObject(value)) {
            if (!this.pastCopies.has(value)) {
                if (Array.isArray(value)) {
                    value = value.slice(0);
                }
                else {
                    value = {
                        __proto__: Object.getPrototypeOf(value),
                        ...value,
                    };
                }
                this.pastCopies.add(value);
            }
        }
        return value;
    }
}
//# sourceMappingURL=mergeDeep.js.map
