import isPlainObject from 'lodash/isPlainObject';

var ObjProto = Object.prototype;
var toString = ObjProto.toString;
export var hasOwn = ObjProto.hasOwnProperty;

var FN_MATCH_REGEXP = /^\s*function (\w+)/;

/**
 * 注：可能被用于从一个函数对象中提取函数名称
 *  FN_MATCH_REGEXP，正则表达式用于匹配函数的名称。
 * match：如果 type 存在且不为假值，则尝试将 type 转换为字符串并与正则表达式匹配，返回匹配结果或 null。
 */

// https://github.com/vuejs/vue/blob/dev/src/core/util/props.js#L159
export var getType = function getType(fn) {
  var type = fn !== null && fn !== undefined ? fn.type ? fn.type : fn : null;
  var match = type && type.toString().match(FN_MATCH_REGEXP);
  return match && match[1];
};

export var getNativeType = function getNativeType(value) {
  if (value === null || value === undefined) return null;
  var match = value.constructor.toString().match(FN_MATCH_REGEXP);
  return match && match[1];
};

/**
 * No-op function
 */
export var noop = function noop() {};

/**
 * Checks for a own property in an object
 *
 * @param {object} obj - Object
 * @param {string} prop - Property to check
 */
export var has = function has(obj, prop) {
  return hasOwn.call(obj, prop);
};

/**
 * Determines whether the passed value is an integer. Uses `Number.isInteger` if available
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger
 * @param {*} value - The value to be tested for being an integer.
 * @returns {boolean}
 */

/**
 * 注：用于检查一个值是否为整数
 * Number.isInteger:使用内置的方法来检查，如果该方法存在（ECMAScript 6 及以上版本中存在），则直接使用该方法进行判断。返回true/false
 * 如果方法不存在，则使用一个自定义的函数来进行判断。这个自定义函数接受一个参数 value，然后执行以下操作：
 * 首先检查 value 的类型是否为 number，这是因为整数必须是数字类型。然后使用 isFinite() 函数检查 value 是否为有限数值，排除了无穷大和 NaN。最后，使用 Math.floor() 函数将 value 向下取整，然后将结果与原始值进行比较。如果两者相等，则说明 value 是一个整数。
 */
export var isInteger = Number.isInteger || function (value) {
  return typeof value === 'number' && isFinite(value) && Math.floor(value) === value;
};

/**
 * Determines whether the passed value is an Array.
 *
 * @param {*} value - The value to be tested for being an array.
 * @returns {boolean}
 */

/**
 * 注:检查一个值是否为数组
 * 如果浏览器支持 Array.isArray 方法，则直接使用该方法进行判断
 * toString.call(value)：返回的结果是一个字符串格式为"[object 类型]",call方法用于改变this的指向，确保toString作用在传入的值上。
 */
export var isArray = Array.isArray || function (value) {
  return toString.call(value) === '[object Array]';
};

/**
 * Checks if a value is a function
 *
 * @param {any} value - Value to check
 * @returns {boolean}
 */
export var isFunction = function isFunction(value) {
  return toString.call(value) === '[object Function]';
};

/**
 * Adds a `def` method to the object returning a new object with passed in argument as `default` property
 *
 * @param {object} type - Object to enhance
 */
/**
 * 注：
 * 在 type 对象上定义一个名为 def 的属性，在 type 对象上定义一个名为 def 的属性,是一个函数
 * 如果传入的默认值 def 为 undefined，且当前对象的 default 属性也为 undefined，则将 default 属性设置为 undefined。
 * 如果 def 不是函数且不符合类型验证，则会发出警告并返回当前对象。
 * 将 def 赋值给 default 属性，如果 def 是数组或纯对象，则将其包装在一个函数中返回，否则直接赋值给 default。
 *
 * 判断是不是数组和纯对象包装在一个函数中返回原因：
 * 可能是为了确保在每次访问默认值时都能执行这个函数并返回一个新的数组或对象实例，而不是共享同一个引用
 */

export var withDefault = function withDefault(type) {
  Object.defineProperty(type, 'def', {
    value: function value(def) {
      if (def === undefined && this['default'] === undefined) {
        this['default'] = undefined;
        return this;
      }
      if (!isFunction(def) && !validateType(this, def)) {
        warn(this._vueTypes_name + ' - invalid default value: "' + def + '"', def);
        return this;
      }
      this['default'] = isArray(def) || isPlainObject(def) ? function () {
        return def;
      } : def;

      return this;
    },

    enumerable: false,
    writable: false
  });
};

/**
 * Adds a `isRequired` getter returning a new object with `required: true` key-value
 *
 * @param {object} type - Object to enhance
 */
/**
 * 注：
 * 通过 Object.defineProperty 方法在 type 对象上定义一个名为 isRequired 的属性，这个属性是一个 getter 方法，当访问 isRequired 属性时，会将 required 属性设置为 true，表示这个类型是必需的
 */
export var withRequired = function withRequired(type) {
  Object.defineProperty(type, 'isRequired', {
    get: function get() {
      this.required = true;
      return this;
    },

    enumerable: false
  });
};

/**
 * Adds `isRequired` and `def` modifiers to an object
 *
 * @param {string} name - Type internal name
 * @param {object} obj - Object to enhance
 * @returns {object}
 */
/**
 *  注：主要是用来对传入的对象 obj 进行一些属性定义和处理，包括添加一个特定的属性 _vueTypes_name
 *  Object.defineProperty：在obj对象上定义一个名为_vueTypes_name的属性name
 */
export var toType = function toType(name, obj) {
  Object.defineProperty(obj, '_vueTypes_name', {
    enumerable: false,
    writable: false,
    value: name
  });
  withRequired(obj);
  withDefault(obj);

  // 这段代码的作用是将对象中的验证函数绑定到对象本身，以确保在调用验证函数时，函数内部可以访问到对象的属性和方法
  if (isFunction(obj.validator)) {
    obj.validator = obj.validator.bind(obj);
  }
  return obj;
};

/**
 * Validates a given value against a prop type object
 *
 * @param {Object|*} type - Type to use for validation. Either a type object or a constructor
 * @param {*} value - Value to check
 * @param {boolean} silent - Silence warnings
 * @returns {boolean}
 */
export var validateType = function validateType(type, value) {
  var silent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;

  var typeToCheck = type;
  var valid = true;
  var expectedType = void 0;
  if (!isPlainObject(type)) {
    typeToCheck = { type: type };
  }
  var namePrefix = typeToCheck._vueTypes_name ? typeToCheck._vueTypes_name + ' - ' : '';

  if (hasOwn.call(typeToCheck, 'type') && typeToCheck.type !== null) {
    if (isArray(typeToCheck.type)) {
      valid = typeToCheck.type.some(function (type) {
        return validateType(type, value, true);
      });
      expectedType = typeToCheck.type.map(function (type) {
        return getType(type);
      }).join(' or ');
    } else {
      expectedType = getType(typeToCheck);

      if (expectedType === 'Array') {
        valid = isArray(value);
      } else if (expectedType === 'Object') {
        valid = isPlainObject(value);
      } else if (expectedType === 'String' || expectedType === 'Number' || expectedType === 'Boolean' || expectedType === 'Function') {
        valid = getNativeType(value) === expectedType;
      } else {
        valid = value instanceof typeToCheck.type;
      }
    }
  }

  if (!valid) {
    silent === false && warn(namePrefix + 'value "' + value + '" should be of type "' + expectedType + '"');
    return false;
  }

  if (hasOwn.call(typeToCheck, 'validator') && isFunction(typeToCheck.validator)) {
    valid = typeToCheck.validator(value);
    if (!valid && silent === false) warn(namePrefix + 'custom validation failed');
    return valid;
  }
  return valid;
};

var warn = noop;

if (process.env.NODE_ENV !== 'production') {
  var hasConsole = typeof console !== 'undefined';
  warn = function warn(msg) {
    if (hasConsole) {
      console.warn('[VueTypes warn]: ' + msg);
    }
  };
}

export { warn };