'use strict';

exports.__esModule = true;

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

exports.default = createUncontrollable;

var _react = require('react');

var _react2 = _interopRequireDefault(_react);

var _invariant = require('invariant');

var _invariant2 = _interopRequireDefault(_invariant);

var _utils = require('./utils');

var utils = _interopRequireWildcard(_utils);

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }

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

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

function createUncontrollable(mixin, set) {

  return uncontrollable;

  function uncontrollable(Component, controlledValues) {
    var _class, _temp;

    var methods = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];

    var displayName = Component.displayName || Component.name || 'Component',
        basePropTypes = utils.getType(Component).propTypes,
        isCompositeComponent = utils.isReactComponent(Component),
        controlledProps = Object.keys(controlledValues),
        propTypes;

    var OMIT_PROPS = ['valueLink', 'checkedLink'].concat(controlledProps.map(utils.defaultKey));

    propTypes = utils.uncontrolledPropTypes(controlledValues, basePropTypes, displayName);

    (0, _invariant2.default)(isCompositeComponent || !methods.length, '[uncontrollable] stateless function components cannot pass through methods ' + 'because they have no associated instances. Check component: ' + displayName + ', ' + 'attempting to pass through methods: ' + methods.join(', '));

    methods = utils.transform(methods, function (obj, method) {
      obj[method] = function () {
        var _refs$inner;

        return (_refs$inner = this.refs.inner)[method].apply(_refs$inner, arguments);
      };
    }, {});

    var component = (_temp = _class = function (_React$Component) {
      _inherits(component, _React$Component);

      function component() {
        _classCallCheck(this, component);

        return _possibleConstructorReturn(this, _React$Component.apply(this, arguments));
      }

      component.prototype.shouldComponentUpdate = function shouldComponentUpdate() {
        for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
          args[_key] = arguments[_key];
        }

        return !mixin.shouldComponentUpdate || mixin.shouldComponentUpdate.apply(this, args);
      };

      component.prototype.componentWillMount = function componentWillMount() {
        var _this2 = this;

        var props = this.props;

        this._values = {};

        controlledProps.forEach(function (key) {
          _this2._values[key] = props[utils.defaultKey(key)];
        });
      };

      /**
       * If a prop switches from controlled to Uncontrolled
       * reset its value to the defaultValue
       */


      component.prototype.componentWillReceiveProps = function componentWillReceiveProps(nextProps) {
        var _this3 = this;

        var props = this.props;

        if (mixin.componentWillReceiveProps) {
          mixin.componentWillReceiveProps.call(this, nextProps);
        }

        controlledProps.forEach(function (key) {
          if (utils.getValue(nextProps, key) === undefined && utils.getValue(props, key) !== undefined) {
            _this3._values[key] = nextProps[utils.defaultKey(key)];
          }
        });
      };

      component.prototype.componentWillUnmount = function componentWillUnmount() {
        this.unmounted = true;
      };

      component.prototype.getControlledInstance = function getControlledInstance() {
        return this.refs.inner;
      };

      component.prototype.render = function render() {
        var _this4 = this;

        var newProps = {},
            props = omitProps(this.props);

        utils.each(controlledValues, function (handle, propName) {
          var linkPropName = utils.getLinkName(propName),
              prop = _this4.props[propName];

          if (linkPropName && !isProp(_this4.props, propName) && isProp(_this4.props, linkPropName)) {
            prop = _this4.props[linkPropName].value;
          }

          newProps[propName] = prop !== undefined ? prop : _this4._values[propName];

          newProps[handle] = setAndNotify.bind(_this4, propName);
        });

        newProps = _extends({}, props, newProps, {
          ref: isCompositeComponent ? 'inner' : null
        });

        return _react2.default.createElement(Component, newProps);
      };

      return component;
    }(_react2.default.Component), _class.displayName = 'Uncontrolled(' + displayName + ')', _class.propTypes = propTypes, _temp);

    _extends(component.prototype, methods);

    component.ControlledComponent = Component;

    /**
     * useful when wrapping a Component and you want to control
     * everything
     */
    component.deferControlTo = function (newComponent) {
      var additions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      var nextMethods = arguments[2];

      return uncontrollable(newComponent, _extends({}, controlledValues, additions), nextMethods);
    };

    return component;

    function setAndNotify(propName, value) {
      var linkName = utils.getLinkName(propName),
          handler = this.props[controlledValues[propName]];

      if (linkName && isProp(this.props, linkName) && !handler) {
        handler = this.props[linkName].requestChange;
      }

      for (var _len2 = arguments.length, args = Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) {
        args[_key2 - 2] = arguments[_key2];
      }

      set(this, propName, handler, value, args);
    }

    function isProp(props, prop) {
      return props[prop] !== undefined;
    }

    function omitProps(props) {
      var result = {};

      utils.each(props, function (value, key) {
        if (OMIT_PROPS.indexOf(key) === -1) result[key] = value;
      });

      return result;
    }
  }
}
module.exports = exports['default'];