'use strict';

/*!
 * node.extend
 * Copyright 2011, John Resig
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * http://jquery.org/license
 *
 * @fileoverview
 * Port of jQuery.extend that actually works on node.js
 */
var is = require('is');
var hasOwn = require('hasown');

var defineProperty = Object.defineProperty;
var gOPD = Object.getOwnPropertyDescriptor;

// If name is '__proto__', and Object.defineProperty is available, define __proto__ as an own property on target
var setProperty = function setP(target, name, value) {
  if (defineProperty && name === '__proto__') {
    defineProperty(target, name, {
      enumerable: true,
      configurable: true,
      value: value,
      writable: true
    });
  } else {
    target[name] = value; // eslint-disable-line no-param-reassign
  }
};

// Return undefined instead of __proto__ if '__proto__' is not an own property
var getProperty = function getP(obj, name) {
  if (name === '__proto__') {
    if (!hasOwn(obj, name)) {
      return void 0;
    } else if (gOPD) {
      // In early versions of node, obj['__proto__'] is buggy when obj has
      // __proto__ as an own property. Object.getOwnPropertyDescriptor() works.
      return gOPD(obj, name).value;
    }
  }

  return obj[name];
};

module.exports = function extend() {
  var target = arguments[0] || {};
  var i = 1;
  var length = arguments.length;
  var deep = false;
  var options, name, src, copy, copyIsArray, clone;

  // Handle a deep copy situation
  if (typeof target === 'boolean') {
    deep = target;
    target = arguments[1] || {};
    // skip the boolean and the target
    i = 2;
  }

  // Handle case when target is a string or something (possible in deep copy)
  if (typeof target !== 'object' && !is.fn(target)) {
    target = {};
  }

  for (; i < length; i++) {
    // Only deal with non-null/undefined values
    options = arguments[i];
    if (options != null) {
      if (typeof options === 'string') {
        options = options.split('');
      }
      // Extend the base object
      for (name in options) {
        src = getProperty(target, name);
        copy = getProperty(options, name);

        // Prevent never-ending loop
        if (target === copy) {
          continue;
        }

        // Recurse if we're merging plain objects or arrays
        if (deep && copy && (is.hash(copy) || (copyIsArray = is.array(copy)))) {
          if (copyIsArray) {
            copyIsArray = false;
            clone = src && is.array(src) ? src : [];
          } else {
            clone = src && is.hash(src) ? src : {};
          }

          // Never move original objects, clone them
          setProperty(target, name, extend(deep, clone, copy));

        // Don't bring in undefined values
        } else if (typeof copy !== 'undefined') {
          setProperty(target, name, copy);
        }
      }
    }
  }

  // Return the modified object
  return target;
};
