/* eslint-disable no-redeclare */

// XXX: This is highly optimized implementation, used in production
// see naive.js for the logic reference.
//
// See V8 Bailout Reasons for optimization reference:
// https://github.com/vhf/v8-bailout-reasons
var reactGuard = function (React, guardFn) {
  guardFn =
    guardFn ||
    function () {
      return null
    }

  function classComponentRender () {
    try {
      return this.__guardedRender__()
    } catch (err) {
      return guardFn(err, {
        props: this.props,
        state: this.state,
        displayName: this.constructor.displayName || this.constructor.name
      })
    }
  }

  function buildFunctionComponent (type) {
    var _type = function (props, publicContext, updateQueue) {
      try {
        return type(props, publicContext, updateQueue)
      } catch (err) {
        return guardFn(err, { props: props, displayName: type.displayName || type.name })
      }
    }
    Object.assign(_type, type, { displayName: type.displayName || type.name })
    return _type
  }

  React.__reactGuardOriginalCreateElement__ = React.createElement
  React.createElement = function (type, createElementProps) {
    // DOM component
    if (typeof type === 'string') {
      if (arguments.length === 2) {
        return React.__reactGuardOriginalCreateElement__(
          type,
          createElementProps
        )
      } else {
        var args = new Array(arguments.length)
        for (var i = 0; i < args.length; ++i) {
          args[i] = arguments[i]
        }
        return React.__reactGuardOriginalCreateElement__.apply(React, args)
      }
      // Class component
    } else if (
      typeof type === 'function' &&
      type.prototype &&
      'render' in type.prototype &&
      !('__guardedRender__' in type.prototype)
    ) {
      type.prototype.__guardedRender__ = type.prototype.render
      type.prototype.render = classComponentRender
      var args = new Array(arguments.length)
      for (var i = 0; i < args.length; ++i) {
        args[i] = arguments[i]
      }
      return React.__reactGuardOriginalCreateElement__.apply(React, args)
      // Function component
    } else if (
      typeof type === 'function' &&
      (!type.prototype || !('render' in type.prototype))
    ) {
      var _type
      if (type.__reactGuardGuardedFunction__) {
        _type = type.__reactGuardGuardedFunction__
      } else {
        _type = buildFunctionComponent(type)
        type.__reactGuardGuardedFunction__ = _type
      }
      var args = new Array(arguments.length)
      args[0] = _type
      for (var i = 1; i < args.length; ++i) {
        args[i] = arguments[i]
      }
      return React.__reactGuardOriginalCreateElement__.apply(React, args)
      // "Warm" class component
    } else {
      if (arguments.length === 2) {
        return React.__reactGuardOriginalCreateElement__(
          type,
          createElementProps
        )
      } else {
        var args = new Array(arguments.length)
        for (var i = 0; i < args.length; ++i) {
          args[i] = arguments[i]
        }
        return React.__reactGuardOriginalCreateElement__.apply(React, args)
      }
    }
  }
}

reactGuard.restore = function (React) {
  if ('__reactGuardOriginalCreateElement__' in React) {
    React.createElement = React.__reactGuardOriginalCreateElement__
    delete React.__reactGuardOriginalCreateElement__
  }
}

module.exports = reactGuard