(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
    typeof define === 'function' && define.amd ? define(['exports'], factory) :
    (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["fast-copy"] = {}));
})(this, (function (exports) { 'use strict';

    var toStringFunction = Function.prototype.toString;
    var create = Object.create;
    var toStringObject = Object.prototype.toString;
    /**
     * @classdesc Fallback cache for when WeakMap is not natively supported
     */
    var LegacyCache = /** @class */ (function () {
        function LegacyCache() {
            this._keys = [];
            this._values = [];
        }
        LegacyCache.prototype.has = function (key) {
            return !!~this._keys.indexOf(key);
        };
        LegacyCache.prototype.get = function (key) {
            return this._values[this._keys.indexOf(key)];
        };
        LegacyCache.prototype.set = function (key, value) {
            this._keys.push(key);
            this._values.push(value);
        };
        return LegacyCache;
    }());
    function createCacheLegacy() {
        return new LegacyCache();
    }
    function createCacheModern() {
        return new WeakMap();
    }
    /**
     * Get a new cache object to prevent circular references.
     */
    var createCache = typeof WeakMap !== 'undefined' ? createCacheModern : createCacheLegacy;
    /**
     * Get an empty version of the object with the same prototype it has.
     */
    function getCleanClone(prototype) {
        if (!prototype) {
            return create(null);
        }
        var Constructor = prototype.constructor;
        if (Constructor === Object) {
            return prototype === Object.prototype ? {} : create(prototype);
        }
        if (~toStringFunction.call(Constructor).indexOf('[native code]')) {
            try {
                return new Constructor();
            }
            catch (_a) { }
        }
        return create(prototype);
    }
    function getRegExpFlagsLegacy(regExp) {
        var flags = '';
        if (regExp.global) {
            flags += 'g';
        }
        if (regExp.ignoreCase) {
            flags += 'i';
        }
        if (regExp.multiline) {
            flags += 'm';
        }
        if (regExp.unicode) {
            flags += 'u';
        }
        if (regExp.sticky) {
            flags += 'y';
        }
        return flags;
    }
    function getRegExpFlagsModern(regExp) {
        return regExp.flags;
    }
    /**
     * Get the flags to apply to the copied regexp.
     */
    var getRegExpFlags = /test/g.flags === 'g' ? getRegExpFlagsModern : getRegExpFlagsLegacy;
    function getTagLegacy(value) {
        var type = toStringObject.call(value);
        return type.substring(8, type.length - 1);
    }
    function getTagModern(value) {
        return value[Symbol.toStringTag] || getTagLegacy(value);
    }
    /**
     * Get the tag of the value passed, so that the correct copier can be used.
     */
    var getTag = typeof Symbol !== 'undefined' ? getTagModern : getTagLegacy;

    var defineProperty = Object.defineProperty, getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor, getOwnPropertyNames = Object.getOwnPropertyNames, getOwnPropertySymbols = Object.getOwnPropertySymbols;
    var _a = Object.prototype, hasOwnProperty = _a.hasOwnProperty, propertyIsEnumerable = _a.propertyIsEnumerable;
    var SUPPORTS_SYMBOL = typeof getOwnPropertySymbols === 'function';
    function getStrictPropertiesModern(object) {
        return getOwnPropertyNames(object).concat(getOwnPropertySymbols(object));
    }
    /**
     * Get the properites used when copying objects strictly. This includes both keys and symbols.
     */
    var getStrictProperties = SUPPORTS_SYMBOL
        ? getStrictPropertiesModern
        : getOwnPropertyNames;
    /**
     * Striclty copy all properties contained on the object.
     */
    function copyOwnPropertiesStrict(value, clone, state) {
        var properties = getStrictProperties(value);
        for (var index = 0, length_1 = properties.length, property = void 0, descriptor = void 0; index < length_1; ++index) {
            property = properties[index];
            if (property === 'callee' || property === 'caller') {
                continue;
            }
            descriptor = getOwnPropertyDescriptor(value, property);
            if (!descriptor) {
                // In extra edge cases where the property descriptor cannot be retrived, fall back to
                // the loose assignment.
                clone[property] = state.copier(value[property], state);
                continue;
            }
            // Only clone the value if actually a value, not a getter / setter.
            if (!descriptor.get && !descriptor.set) {
                descriptor.value = state.copier(descriptor.value, state);
            }
            try {
                defineProperty(clone, property, descriptor);
            }
            catch (error) {
                // Tee above can fail on node in edge cases, so fall back to the loose assignment.
                clone[property] = descriptor.value;
            }
        }
        return clone;
    }
    /**
     * Deeply copy the indexed values in the array.
     */
    function copyArrayLoose(array, state) {
        var clone = new state.Constructor();
        // set in the cache immediately to be able to reuse the object recursively
        state.cache.set(array, clone);
        for (var index = 0, length_2 = array.length; index < length_2; ++index) {
            clone[index] = state.copier(array[index], state);
        }
        return clone;
    }
    /**
     * Deeply copy the indexed values in the array, as well as any custom properties.
     */
    function copyArrayStrict(array, state) {
        var clone = new state.Constructor();
        // set in the cache immediately to be able to reuse the object recursively
        state.cache.set(array, clone);
        return copyOwnPropertiesStrict(array, clone, state);
    }
    /**
     * Copy the contents of the ArrayBuffer.
     */
    function copyArrayBuffer(arrayBuffer, _state) {
        return arrayBuffer.slice(0);
    }
    /**
     * Create a new Blob with the contents of the original.
     */
    function copyBlob(blob, _state) {
        return blob.slice(0, blob.size, blob.type);
    }
    /**
     * Create a new DataView with the contents of the original.
     */
    function copyDataView(dataView, state) {
        return new state.Constructor(copyArrayBuffer(dataView.buffer));
    }
    /**
     * Create a new Date based on the time of the original.
     */
    function copyDate(date, state) {
        return new state.Constructor(date.getTime());
    }
    /**
     * Deeply copy the keys and values of the original.
     */
    function copyMapLoose(map, state) {
        var clone = new state.Constructor();
        // set in the cache immediately to be able to reuse the object recursively
        state.cache.set(map, clone);
        map.forEach(function (value, key) {
            clone.set(key, state.copier(value, state));
        });
        return clone;
    }
    /**
     * Deeply copy the keys and values of the original, as well as any custom properties.
     */
    function copyMapStrict(map, state) {
        return copyOwnPropertiesStrict(map, copyMapLoose(map, state), state);
    }
    function copyObjectLooseLegacy(object, state) {
        var clone = getCleanClone(state.prototype);
        // set in the cache immediately to be able to reuse the object recursively
        state.cache.set(object, clone);
        for (var key in object) {
            if (hasOwnProperty.call(object, key)) {
                clone[key] = state.copier(object[key], state);
            }
        }
        return clone;
    }
    function copyObjectLooseModern(object, state) {
        var clone = getCleanClone(state.prototype);
        // set in the cache immediately to be able to reuse the object recursively
        state.cache.set(object, clone);
        for (var key in object) {
            if (hasOwnProperty.call(object, key)) {
                clone[key] = state.copier(object[key], state);
            }
        }
        var symbols = getOwnPropertySymbols(object);
        for (var index = 0, length_3 = symbols.length, symbol = void 0; index < length_3; ++index) {
            symbol = symbols[index];
            if (propertyIsEnumerable.call(object, symbol)) {
                clone[symbol] = state.copier(object[symbol], state);
            }
        }
        return clone;
    }
    /**
     * Deeply copy the properties (keys and symbols) and values of the original.
     */
    var copyObjectLoose = SUPPORTS_SYMBOL
        ? copyObjectLooseModern
        : copyObjectLooseLegacy;
    /**
     * Deeply copy the properties (keys and symbols) and values of the original, as well
     * as any hidden or non-enumerable properties.
     */
    function copyObjectStrict(object, state) {
        var clone = getCleanClone(state.prototype);
        // set in the cache immediately to be able to reuse the object recursively
        state.cache.set(object, clone);
        return copyOwnPropertiesStrict(object, clone, state);
    }
    /**
     * Create a new primitive wrapper from the value of the original.
     */
    function copyPrimitiveWrapper(primitiveObject, state) {
        return new state.Constructor(primitiveObject.valueOf());
    }
    /**
     * Create a new RegExp based on the value and flags of the original.
     */
    function copyRegExp(regExp, state) {
        var clone = new state.Constructor(regExp.source, getRegExpFlags(regExp));
        clone.lastIndex = regExp.lastIndex;
        return clone;
    }
    /**
     * Return the original value (an identity function).
     *
     * @note
     * THis is used for objects that cannot be copied, such as WeakMap.
     */
    function copySelf(value, _state) {
        return value;
    }
    /**
     * Deeply copy the values of the original.
     */
    function copySetLoose(set, state) {
        var clone = new state.Constructor();
        // set in the cache immediately to be able to reuse the object recursively
        state.cache.set(set, clone);
        set.forEach(function (value) {
            clone.add(state.copier(value, state));
        });
        return clone;
    }
    /**
     * Deeply copy the values of the original, as well as any custom properties.
     */
    function copySetStrict(set, state) {
        return copyOwnPropertiesStrict(set, copySetLoose(set, state), state);
    }

    var isArray = Array.isArray;
    var assign = Object.assign;
    var getPrototypeOf = Object.getPrototypeOf || (function (obj) { return obj.__proto__; });
    var DEFAULT_LOOSE_OPTIONS = {
        array: copyArrayLoose,
        arrayBuffer: copyArrayBuffer,
        blob: copyBlob,
        dataView: copyDataView,
        date: copyDate,
        error: copySelf,
        map: copyMapLoose,
        object: copyObjectLoose,
        regExp: copyRegExp,
        set: copySetLoose,
    };
    var DEFAULT_STRICT_OPTIONS = assign({}, DEFAULT_LOOSE_OPTIONS, {
        array: copyArrayStrict,
        map: copyMapStrict,
        object: copyObjectStrict,
        set: copySetStrict,
    });
    /**
     * Get the copiers used for each specific object tag.
     */
    function getTagSpecificCopiers(options) {
        return {
            Arguments: options.object,
            Array: options.array,
            ArrayBuffer: options.arrayBuffer,
            Blob: options.blob,
            Boolean: copyPrimitiveWrapper,
            DataView: options.dataView,
            Date: options.date,
            Error: options.error,
            Float32Array: options.arrayBuffer,
            Float64Array: options.arrayBuffer,
            Int8Array: options.arrayBuffer,
            Int16Array: options.arrayBuffer,
            Int32Array: options.arrayBuffer,
            Map: options.map,
            Number: copyPrimitiveWrapper,
            Object: options.object,
            Promise: copySelf,
            RegExp: options.regExp,
            Set: options.set,
            String: copyPrimitiveWrapper,
            WeakMap: copySelf,
            WeakSet: copySelf,
            Uint8Array: options.arrayBuffer,
            Uint8ClampedArray: options.arrayBuffer,
            Uint16Array: options.arrayBuffer,
            Uint32Array: options.arrayBuffer,
            Uint64Array: options.arrayBuffer,
        };
    }
    /**
     * Create a custom copier based on the object-specific copy methods passed.
     */
    function createCopier(options) {
        var normalizedOptions = assign({}, DEFAULT_LOOSE_OPTIONS, options);
        var tagSpecificCopiers = getTagSpecificCopiers(normalizedOptions);
        var array = tagSpecificCopiers.Array, object = tagSpecificCopiers.Object;
        function copier(value, state) {
            state.prototype = state.Constructor = undefined;
            if (!value || typeof value !== 'object') {
                return value;
            }
            if (state.cache.has(value)) {
                return state.cache.get(value);
            }
            state.prototype = getPrototypeOf(value);
            state.Constructor = state.prototype && state.prototype.constructor;
            // plain objects
            if (!state.Constructor || state.Constructor === Object) {
                return object(value, state);
            }
            // arrays
            if (isArray(value)) {
                return array(value, state);
            }
            var tagSpecificCopier = tagSpecificCopiers[getTag(value)];
            if (tagSpecificCopier) {
                return tagSpecificCopier(value, state);
            }
            return typeof value.then === 'function' ? value : object(value, state);
        }
        return function copy(value) {
            return copier(value, {
                Constructor: undefined,
                cache: createCache(),
                copier: copier,
                prototype: undefined,
            });
        };
    }
    /**
     * Create a custom copier based on the object-specific copy methods passed, defaulting to the
     * same internals as `copyStrict`.
     */
    function createStrictCopier(options) {
        return createCopier(assign({}, DEFAULT_STRICT_OPTIONS, options));
    }
    /**
     * Copy an value deeply as much as possible, where strict recreation of object properties
     * are maintained. All properties (including non-enumerable ones) are copied with their
     * original property descriptors on both objects and arrays.
     */
    var copyStrict = createStrictCopier({});
    /**
     * Copy an value deeply as much as possible.
     */
    var index = createCopier({});

    exports.copyStrict = copyStrict;
    exports.createCopier = createCopier;
    exports.createStrictCopier = createStrictCopier;
    exports.default = index;

    Object.defineProperty(exports, '__esModule', { value: true });

}));
//# sourceMappingURL=index.js.map
