/* eslint func-style: 0 */
/* eslint no-var: 0 */
/* eslint object-shorthand: 0 */
'use strict';

var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };

var _isGetSetProp = require('is-get-set-prop');

var _isGetSetProp2 = _interopRequireDefault(_isGetSetProp);

var _isJsType = require('is-js-type');

var _isJsType2 = _interopRequireDefault(_isJsType);

var _isProtoProp = require('is-proto-prop');

var _isProtoProp2 = _interopRequireDefault(_isProtoProp);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

/**
 * Return type of value of left or right
 * @param {Object} o - left or right of node.object
 * @return {String} - type of o
 */
var getType = function getType(o) {
  var type = _typeof(o.value);

  if (o.regex) {
    return 'RegExp';
  }

  return type.charAt(0).toUpperCase() + type.slice(1);
};

/**
  * Returns type of binary expression result
  * @param {Object} o - node's object with a BinaryExpression type
  * @return {String} - type of value produced
  */
var binaryExpressionProduces = function binaryExpressionProduces(o) {
  var leftType = o.left.type === 'BinaryExpression' ? binaryExpressionProduces(o.left) : getType(o.left),
      rightType = o.right.type === 'BinaryExpression' ? binaryExpressionProduces(o.right) : getType(o.right);

  if (leftType === 'String' || rightType === 'String' || leftType === rightType && leftType === 'RegExp') {
    return 'String';
  }

  if (leftType === rightType) {
    return leftType;
  }

  return 'Unknown';
};

module.exports = function (context) {
  return {
    MemberExpression: function MemberExpression(node) {
      /* eslint complexity: [2, 14] */
      var methodName = void 0,
          proto = void 0;

      if (node.object.type === 'NewExpression') {
        proto = node.object.callee.name;
      } else if (node.object.type === 'Literal') {
        proto = getType(node.object);
      } else if (node.object.type === 'BinaryExpression') {
        proto = binaryExpressionProduces(node.object);
      } else if (node.object.type === 'Identifier' && node.property.name === 'prototype' && node.parent.property) {
        proto = node.object.name;
        methodName = node.parent.property.name;
      } else {
        proto = node.object.type.replace('Expression', '');
      }

      methodName = methodName || node.property.name || node.property.value;
      var type = node.parent.type;

      if (typeof methodName !== 'string' || typeof proto !== 'string' || !(0, _isJsType2.default)(proto)) {
        return;
      }

      var isExpression = type === 'ExpressionStatement' || type === 'MemberExpression';
      var unknownGetterSetterOrProtoExpressed = isExpression && !(0, _isGetSetProp2.default)(proto, methodName) && !(0, _isProtoProp2.default)(proto, methodName);

      var isFunctionCall = type === 'CallExpression';
      var getterSetterCalledAsFunction = isFunctionCall && (0, _isGetSetProp2.default)(proto, methodName);
      var unknownProtoCalledAsFunction = isFunctionCall && !(0, _isProtoProp2.default)(proto, methodName);

      if (unknownGetterSetterOrProtoExpressed || getterSetterCalledAsFunction || unknownProtoCalledAsFunction) {
        context.report(node, 'Avoid using extended native objects');
      }
    }
  };
};