import { PROP_TYPE_ANY } from '../constants/props';
import { cloneDeep } from './clone-deep';
import { getComponentConfig } from './config';
import { identity } from './identity';
import { isUndefined, isObject, isArray, isFunction } from './inspect';
import { hasOwnProperty, clone, keys } from './object';
import { upperFirst } from './string';

// Suffix can be a falsey value so nothing is appended to string
// (helps when looping over props & some shouldn't change)
// Use data last parameters to allow for currying
const suffixPropName = (suffix, value) => value + (suffix ? upperFirst(suffix) : '');

// Generates a prop object
const makeProp = function () {
  let type = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : PROP_TYPE_ANY;
  let value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined;
  let requiredOrValidator = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : undefined;
  let validator = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : undefined;
  const required = requiredOrValidator === true;
  validator = required ? validator : requiredOrValidator;
  return {
    ...(type ? {
      type
    } : {}),
    ...(required ? {
      required
    } : isUndefined(value) ? {} : {
      default: isObject(value) ? () => value : value
    }),
    ...(isUndefined(validator) ? {} : {
      validator
    })
  };
};

// Copies props from one array/object to a new array/object
// Prop values are also cloned as new references to prevent possible
// mutation of original prop object values
// Optionally accepts a function to transform the prop name
const copyProps = function (props) {
  let transformFn = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : identity;
  if (isArray(props)) {
    return props.map(transformFn);
  }
  const copied = {};
  for (const prop in props) {
    /* istanbul ignore else */
    if (hasOwnProperty(props, prop)) {
      // If the prop value is an object, do a shallow clone
      // to prevent potential mutations to the original object
      copied[transformFn(prop)] = isObject(props[prop]) ? clone(props[prop]) : props[prop];
    }
  }
  return copied;
};

// Given an array of properties or an object of property keys,
// plucks all the values off the target object, returning a new object
// that has props that reference the original prop values
const pluckProps = function (keysToPluck, objToPluck) {
  let transformFn = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : identity;
  return (isArray(keysToPluck) ? keysToPluck.slice() : keys(keysToPluck)).reduce((memo, prop) => {
    memo[transformFn(prop)] = objToPluck[prop];
    return memo;
  }, {});
};

// Make a prop object configurable by global configuration
// Replaces the current `default` key of each prop with a `getComponentConfig()`
// call that falls back to the current default value of the prop
const makePropConfigurable = (prop, key, componentKey) => ({
  ...cloneDeep(prop),
  default: function bvConfigurablePropDefault() {
    const value = getComponentConfig(componentKey, key, prop.default);
    return isFunction(value) ? value() : value;
  }
});

// Make a props object configurable by global configuration
// Replaces the current `default` key of each prop with a `getComponentConfig()`
// call that falls back to the current default value of the prop
const makePropsConfigurable = (props, componentKey) => keys(props).reduce((result, key) => ({
  ...result,
  [key]: makePropConfigurable(props[key], key, componentKey)
}), {});

// Get function name we use in `makePropConfigurable()`
// for the prop default value override to compare
// against in `hasPropFunction()`
const configurablePropDefaultFnName = makePropConfigurable({}, '', '').default.name;

// Detect wether the given value is currently a function
// and isn't the props default function
const hasPropFunction = fn => isFunction(fn) && fn.name && fn.name !== configurablePropDefaultFnName;

export { copyProps, hasPropFunction, makeProp, makePropConfigurable, makePropsConfigurable, pluckProps, suffixPropName };
