import _typeof from "@babel/runtime/helpers/esm/typeof";
import clone from 'lodash/clone';
import toPath from 'lodash/toPath';
import * as React from 'react';

// Assertions

/** @private is the value an empty array? */
export var isEmptyArray = function isEmptyArray(value) {
  return Array.isArray(value) && value.length === 0;
};

/** @private is the given object a Function? */
export var isFunction = function isFunction(obj) {
  return typeof obj === 'function';
};

/** @private is the given object an Object? */
export var isObject = function isObject(obj) {
  return obj !== null && _typeof(obj) === 'object';
};

/** @private is the given object an integer? */
export var isInteger = function isInteger(obj) {
  return String(Math.floor(Number(obj))) === obj;
};

/** @private is the given object a string? */
export var isString = function isString(obj) {
  return Object.prototype.toString.call(obj) === '[object String]';
};

/** @private is the given object a NaN? */
// eslint-disable-next-line no-self-compare
export var isNaN = function isNaN(obj) {
  return obj !== obj;
};

/** @private Does a React component have exactly 0 children? */
export var isEmptyChildren = function isEmptyChildren(children) {
  return React.Children.count(children) === 0;
};

/** @private is the given object/value a promise? */
export var isPromise = function isPromise(value) {
  return isObject(value) && isFunction(value.then);
};

/** @private is the given object/value a type of synthetic event? */
export var isInputEvent = function isInputEvent(value) {
  return value && isObject(value) && isObject(value.target);
};

/**
 * Same as document.activeElement but wraps in a try-catch block. In IE it is
 * not safe to call document.activeElement if there is nothing focused.
 *
 * The activeElement will be null only if the document or document body is not
 * yet defined.
 *
 * @param {?Document} doc Defaults to current document.
 * @return {Element | null}
 * @see https://github.com/facebook/fbjs/blob/main/packages/fbjs/src/core/dom/getActiveElement.js
 */
export function getActiveElement(doc) {
  doc = doc || (typeof document !== 'undefined' ? document : undefined);
  if (typeof doc === 'undefined') {
    return null;
  }
  try {
    return doc.activeElement || doc.body;
  } catch (e) {
    return doc.body;
  }
}

/**
 * Deeply get a value from an object via its path.
 */
export function getIn(obj, key, def) {
  var p = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
  var path = toPath(key);
  while (obj && p < path.length) {
    obj = obj[path[p++]];
  }

  // check if path is not in the end
  if (p !== path.length && !obj) {
    return def;
  }
  return obj === undefined ? def : obj;
}

/**
 * Deeply set a value from in object via it's path. If the value at `path`
 * has changed, return a shallow copy of obj with `value` set at `path`.
 * If `value` has not changed, return the original `obj`.
 *
 * Existing objects / arrays along `path` are also shallow copied. Sibling
 * objects along path retain the same internal js reference. Since new
 * objects / arrays are only created along `path`, we can test if anything
 * changed in a nested structure by comparing the object's reference in
 * the old and new object, similar to how russian doll cache invalidation
 * works.
 *
 * In earlier versions of this function, which used cloneDeep, there were
 * issues whereby settings a nested value would mutate the parent
 * instead of creating a new object. `clone` avoids that bug making a
 * shallow copy of the objects along the update path
 * so no object is mutated in place.
 *
 * Before changing this function, please read through the following
 * discussions.
 *
 * @see https://github.com/developit/linkstate
 * @see https://github.com/jaredpalmer/formik/pull/123
 */
export function setIn(obj, path, value) {
  var res = clone(obj); // this keeps inheritance when obj is a class
  var resVal = res;
  var i = 0;
  var pathArray = toPath(path);
  for (; i < pathArray.length - 1; i++) {
    var currentPath = pathArray[i];
    var currentObj = getIn(obj, pathArray.slice(0, i + 1));
    if (currentObj && (isObject(currentObj) || Array.isArray(currentObj))) {
      resVal = resVal[currentPath] = clone(currentObj);
    } else {
      var nextPath = pathArray[i + 1];
      resVal = resVal[currentPath] = isInteger(nextPath) && Number(nextPath) >= 0 ? [] : {};
    }
  }

  // Return original object if new value is the same as current
  if ((i === 0 ? obj : resVal)[pathArray[i]] === value) {
    return obj;
  }
  if (value === undefined) {
    delete resVal[pathArray[i]];
  } else {
    resVal[pathArray[i]] = value;
  }

  // If the path array has a single element, the loop did not run.
  // Deleting on `resVal` had no effect in this scenario, so we delete on the result instead.
  if (i === 0 && value === undefined) {
    delete res[pathArray[i]];
  }
  return res;
}

/**
 * Recursively a set the same value for all keys and arrays nested object, cloning
 * @param object
 * @param value
 * @param visited
 * @param response
 */
export function setNestedObjectValues(object, value) {
  var visited = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : new WeakMap();
  var response = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
  for (var _i = 0, _Object$keys = Object.keys(object); _i < _Object$keys.length; _i++) {
    var k = _Object$keys[_i];
    var val = object[k];
    if (isObject(val)) {
      if (!visited.get(val)) {
        visited.set(val, true);
        // In order to keep array values consistent for both dot path  and
        // bracket syntax, we need to check if this is an array so that
        // this will output  { friends: [true] } and not { friends: { "0": true } }
        response[k] = Array.isArray(val) ? [] : {};
        setNestedObjectValues(val, value, visited, response[k]);
      }
    } else {
      response[k] = value;
    }
  }
  return response;
}