"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
function _export(target, all) {
    for(var name in all)Object.defineProperty(target, name, {
        enumerable: true,
        get: Object.getOwnPropertyDescriptor(all, name).get
    });
}
_export(exports, {
    get assignObjectDefaults () {
        return assignObjectDefaults;
    },
    get clone () {
        return clone;
    },
    get createObjectPropListener () {
        return createObjectPropListener;
    },
    get deepClone () {
        return deepClone;
    },
    get deepExtend () {
        return deepExtend;
    },
    get deepMerge () {
        return deepMerge;
    },
    get deepObjectSize () {
        return deepObjectSize;
    },
    get defineGetter () {
        return defineGetter;
    },
    get duckSchema () {
        return duckSchema;
    },
    get extend () {
        return extend;
    },
    get getProperty () {
        return getProperty;
    },
    get hasOwnProperty () {
        return hasOwnProperty;
    },
    get inherit () {
        return inherit;
    },
    get isKeyValueObject () {
        return isKeyValueObject;
    },
    get isObject () {
        return isObject;
    },
    get isObjectEqual () {
        return isObjectEqual;
    },
    get mixin () {
        return mixin;
    },
    get objectEach () {
        return objectEach;
    },
    get setProperty () {
        return setProperty;
    }
});
const _array = require("./array");
const _mixed = require("./mixed");
const _errors = require("../helpers/errors");
function duckSchema(object) {
    let schema;
    if (Array.isArray(object)) {
        schema = object.length ? new Array(object.length).fill(null) : [];
    } else {
        schema = {};
        objectEach(object, (value, key)=>{
            if (key === '__children') {
                return;
            }
            if (value && typeof value === 'object' && !Array.isArray(value)) {
                schema[key] = duckSchema(value);
            } else if (Array.isArray(value)) {
                if (value.length && typeof value[0] === 'object' && !Array.isArray(value[0])) {
                    schema[key] = [
                        duckSchema(value[0])
                    ];
                } else {
                    schema[key] = [];
                }
            } else {
                schema[key] = null;
            }
        });
    }
    return schema;
}
function inherit(Child, Parent) {
    Parent.prototype.constructor = Parent;
    Child.prototype = new Parent();
    Child.prototype.constructor = Child;
    return Child;
}
function extend(target, extension, writableKeys) {
    const hasWritableKeys = Array.isArray(writableKeys);
    objectEach(extension, (value, key)=>{
        if (hasWritableKeys === false || writableKeys.includes(key)) {
            target[key] = value;
        }
    });
    return target;
}
function deepExtend(target, extension) {
    objectEach(extension, (value, key)=>{
        if (extension[key] && typeof extension[key] === 'object') {
            if (!target[key]) {
                if (Array.isArray(extension[key])) {
                    target[key] = [];
                } else if (Object.prototype.toString.call(extension[key]) === '[object Date]') {
                    target[key] = extension[key];
                } else {
                    target[key] = {};
                }
            }
            deepExtend(target[key], extension[key]);
        } else {
            target[key] = extension[key];
        }
    });
}
function deepClone(obj) {
    if (typeof obj === 'object') {
        return JSON.parse(JSON.stringify(obj));
    }
    return obj;
}
function clone(object) {
    const result = {};
    objectEach(object, (value, key)=>{
        result[key] = value;
    });
    return result;
}
function mixin(Base, ...mixins) {
    if (!Base.MIXINS) {
        Base.MIXINS = [];
    }
    (0, _array.arrayEach)(mixins, (mixinItem)=>{
        Base.MIXINS.push(mixinItem.MIXIN_NAME);
        objectEach(mixinItem, (value, key)=>{
            if (Base.prototype[key] !== undefined) {
                (0, _errors.throwWithCause)(`Mixin conflict. Property '${key}' already exist and cannot be overwritten.`);
            }
            if (typeof value === 'function') {
                Base.prototype[key] = value;
            } else {
                const getter = function _getter(property, initialValue) {
                    const propertyName = `_${property}`;
                    const initValue = (newValue)=>{
                        let result = newValue;
                        if (Array.isArray(result) || isObject(result)) {
                            result = deepClone(result);
                        }
                        return result;
                    };
                    return function() {
                        if (this[propertyName] === undefined) {
                            this[propertyName] = initValue(initialValue);
                        }
                        return this[propertyName];
                    };
                };
                const setter = function _setter(property) {
                    const propertyName = `_${property}`;
                    return function(newValue) {
                        this[propertyName] = newValue;
                    };
                };
                Object.defineProperty(Base.prototype, key, {
                    get: getter(key, value),
                    set: setter(key),
                    configurable: true
                });
            }
        });
    });
    return Base;
}
function isObjectEqual(object1, object2) {
    return JSON.stringify(object1) === JSON.stringify(object2);
}
function isObject(object) {
    return Object.prototype.toString.call(object) === '[object Object]';
}
function defineGetter(object, property, value, options) {
    options.value = value;
    options.writable = options.writable !== false;
    options.enumerable = options.enumerable !== false;
    options.configurable = options.configurable !== false;
    Object.defineProperty(object, property, options);
}
function objectEach(object, iteratee) {
    // eslint-disable-next-line no-restricted-syntax
    for(const key in object){
        if (!object.hasOwnProperty || object.hasOwnProperty && Object.prototype.hasOwnProperty.call(object, key)) {
            if (iteratee(object[key], key, object) === false) {
                break;
            }
        }
    }
    return object;
}
function getProperty(object, name) {
    const names = name.split('.');
    let result = object;
    objectEach(names, (nameItem)=>{
        result = result[nameItem];
        if (result === undefined) {
            result = undefined;
            return false;
        }
    });
    return result;
}
function setProperty(object, name, value) {
    if (typeof name !== 'string') {
        return;
    }
    const names = name.split('.');
    let workingObject = object;
    names.forEach((propName, index)=>{
        if (propName === '__proto__' || propName === 'constructor' || propName === 'prototype') {
            // Security: prototype-polluting is not allowed
            return;
        }
        if (index !== names.length - 1) {
            if (!hasOwnProperty(workingObject, propName)) {
                workingObject[propName] = {};
            }
            workingObject = workingObject[propName];
        } else {
            workingObject[propName] = value;
        }
    });
}
function deepObjectSize(object) {
    if (!isObject(object)) {
        return 0;
    }
    const recursObjLen = function(obj) {
        let result = 0;
        if (isObject(obj)) {
            objectEach(obj, (value, key)=>{
                if (key === '__children') {
                    return;
                }
                result += recursObjLen(value);
            });
        } else {
            result += 1;
        }
        return result;
    };
    return recursObjLen(object);
}
function createObjectPropListener(defaultValue, propertyToListen = 'value') {
    const privateProperty = `_${propertyToListen}`;
    const holder = {
        _touched: false,
        [privateProperty]: defaultValue,
        isTouched () {
            return this._touched;
        }
    };
    Object.defineProperty(holder, propertyToListen, {
        get () {
            return this[privateProperty];
        },
        set (value) {
            this._touched = true;
            this[privateProperty] = value;
        },
        enumerable: true,
        configurable: true
    });
    return holder;
}
function hasOwnProperty(object, key) {
    return Object.prototype.hasOwnProperty.call(object, key);
}
function assignObjectDefaults(target, defaults) {
    if (typeof target !== 'object' || target === null) {
        return defaults;
    }
    if (typeof defaults !== 'object' || defaults === null) {
        return target;
    }
    const result = {};
    // Assign defaults
    Object.keys(defaults).forEach((key)=>{
        if (typeof defaults[key] === 'object' && defaults[key] !== null && !Array.isArray(defaults[key])) {
            result[key] = assignObjectDefaults(target[key], defaults[key]);
        } else {
            result[key] = hasOwnProperty(target, key) && target[key] !== undefined ? target[key] : defaults[key];
        }
    });
    // Copy extra keys from target that aren't in defaults
    Object.keys(target).forEach((key)=>{
        if (!hasOwnProperty(result, key)) {
            result[key] = target[key];
        }
    });
    return result;
}
function deepMerge(target = {}, source = {}) {
    const result = {
        ...target
    };
    Object.keys(source).forEach((key)=>{
        // Prevent prototype pollution
        if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
            return;
        }
        const sourceValue = source[key];
        const targetValue = result[key];
        if (isObject(sourceValue) && isObject(targetValue)) {
            result[key] = deepMerge(targetValue, sourceValue);
        } else {
            result[key] = sourceValue;
        }
    });
    return result;
}
function isKeyValueObject(value) {
    return isObject(value) && (0, _mixed.isDefined)(value.key) && (0, _mixed.isDefined)(value.value);
}
