'use strict';

const isWhat = require('is-what');

function concatArrays(originVal, newVal) {
  if (isWhat.isArray(originVal) && isWhat.isArray(newVal)) {
    return originVal.concat(newVal);
  }
  return newVal;
}

function assignProp(carry, key, newVal, originalObject) {
  const propType = {}.propertyIsEnumerable.call(originalObject, key) ? "enumerable" : "nonenumerable";
  if (propType === "enumerable")
    carry[key] = newVal;
  if (propType === "nonenumerable") {
    Object.defineProperty(carry, key, {
      value: newVal,
      enumerable: false,
      writable: true,
      configurable: true
    });
  }
}
function mergeRecursively(origin, newComer, compareFn) {
  if (!isWhat.isPlainObject(newComer))
    return newComer;
  let newObject = {};
  if (isWhat.isPlainObject(origin)) {
    const props2 = Object.getOwnPropertyNames(origin);
    const symbols2 = Object.getOwnPropertySymbols(origin);
    newObject = [...props2, ...symbols2].reduce((carry, key) => {
      const targetVal = origin[key];
      if (!isWhat.isSymbol(key) && !Object.getOwnPropertyNames(newComer).includes(key) || isWhat.isSymbol(key) && !Object.getOwnPropertySymbols(newComer).includes(key)) {
        assignProp(
          carry,
          key,
          targetVal,
          origin
        );
      }
      return carry;
    }, {});
  }
  const props = Object.getOwnPropertyNames(newComer);
  const symbols = Object.getOwnPropertySymbols(newComer);
  const result = [...props, ...symbols].reduce((carry, key) => {
    let newVal = newComer[key];
    const targetVal = isWhat.isPlainObject(origin) ? origin[key] : void 0;
    if (targetVal !== void 0 && isWhat.isPlainObject(newVal)) {
      newVal = mergeRecursively(targetVal, newVal, compareFn);
    }
    const propToAssign = compareFn ? compareFn(targetVal, newVal, key) : newVal;
    assignProp(
      carry,
      key,
      propToAssign,
      newComer
    );
    return carry;
  }, newObject);
  return result;
}
function merge(object, ...otherObjects) {
  return otherObjects.reduce((result, newComer) => {
    return mergeRecursively(result, newComer);
  }, object);
}
function mergeAndCompare(compareFn, object, ...otherObjects) {
  return otherObjects.reduce((result, newComer) => {
    return mergeRecursively(result, newComer, compareFn);
  }, object);
}
function mergeAndConcat(object, ...otherObjects) {
  return otherObjects.reduce((result, newComer) => {
    return mergeRecursively(result, newComer, concatArrays);
  }, object);
}

exports.concatArrays = concatArrays;
exports.merge = merge;
exports.mergeAndCompare = mergeAndCompare;
exports.mergeAndConcat = mergeAndConcat;
