// tslint:disable
const isObject = (item) => item && typeof item === 'object' && !Array.isArray(item)

const mergeNew = (target, source) => {
    // Nothing to look at, go away
    if (target === undefined) return source
    if (source === undefined) return target

    // We know how to handle arrays! We don't merge those.
    if (Array.isArray(source)) return source

    // Nothing to merge here
    // @TODO: we might want to merge arrays here later on
    if (!isObject(source) || !isObject(target)) return source

    // Replace empty object with whatever we have insted of it
    if (!Object.entries(target || {}).length) return source

    const entries = [...Object.entries(target || {}), ...Object.entries(source || {})]

    const isHardToMerge = entries.reduce((acc, val) => (acc ? acc : isObject(val[1])), false)
    // Let's do it!
    if (!isHardToMerge) return { ...target, ...source }

    // So it's hard, okay then
    return entries
        .map(([key]) => key)
        .reduce((acc, key) => {
            // Check if we actually want to have undefined here
            return source.hasOwnProperty(key) && source[key] === undefined
                ? { ...acc, [key]: source[key] }
                : { ...acc, [key]: mergeNew(target[key], source[key]) }
        }, {})
}

export const mergeDeep = (target, ...rest) => rest.reduce((prev, next) => mergeNew(prev, next), target)