'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

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; };

var _react = require('react');

var _react2 = _interopRequireDefault(_react);

var _propTypes = require('prop-types');

var _propTypes2 = _interopRequireDefault(_propTypes);

var _theming = require('theming');

var _theming2 = _interopRequireDefault(_theming);

var _jss = require('./jss');

var _jss2 = _interopRequireDefault(_jss);

var _compose = require('./compose');

var _compose2 = _interopRequireDefault(_compose);

var _getDisplayName = require('./getDisplayName');

var _getDisplayName2 = _interopRequireDefault(_getDisplayName);

var _ns = require('./ns');

var ns = _interopRequireWildcard(_ns);

var _contextTypes = require('./contextTypes');

var _contextTypes2 = _interopRequireDefault(_contextTypes);

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 _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }

var env = process.env.NODE_ENV;

// Like a Symbol
var dynamicStylesNs = Math.random();

/*
 * # Use cases
 *
 * - Unthemed component accepts styles object
 * - Themed component accepts styles creator function which takes theme as a single argument
 * - Multiple instances will re-use the same static sheet via sheets manager
 * - Sheet manager identifies static sheets by theme as a key
 * - For unthemed components theme is an empty object
 * - The very first instance will add static sheet to sheets manager
 * - Every further instances will get that static sheet from sheet manager
 * - Every mount of every instance will call method `sheetsManager.manage`,
 * thus incrementing reference counter.
 * - Every unmount of every instance will call method `sheetsManager.unmanage`,
 * thus decrementing reference counter.
 * - `sheetsManager.unmanage` under the hood will detach static sheet once reference
 * counter is zero.
 * - Dynamic styles are not shared between instances
 *
 */

var getStyles = function getStyles(stylesOrCreator, theme) {
  if (typeof stylesOrCreator !== 'function') {
    return stylesOrCreator;
  }
  return stylesOrCreator(theme);
};

// Returns an object with array property as a key and true as a value.
var toMap = function toMap(arr) {
  return arr.reduce(function (map, prop) {
    map[prop] = true;
    return map;
  }, {});
};

var defaultInjectProps = {
  sheet: false,
  classes: true,
  theme: true
};

var managersCounter = 0;

/**
 * Wrap a Component into a JSS Container Component.
 *
 * @param {Object|Function} stylesOrCreator
 * @param {Component} InnerComponent
 * @param {Object} [options]
 * @return {Component}
 */

exports['default'] = function (stylesOrCreator, InnerComponent) {
  var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};

  var isThemingEnabled = typeof stylesOrCreator === 'function';

  var _options$theming = options.theming,
      theming = _options$theming === undefined ? _theming2['default'] : _options$theming,
      inject = options.inject,
      optionsJss = options.jss,
      sheetOptions = _objectWithoutProperties(options, ['theming', 'inject', 'jss']);

  var injectMap = inject ? toMap(inject) : defaultInjectProps;
  var themeListener = theming.themeListener;

  var displayName = (0, _getDisplayName2['default'])(InnerComponent);
  var defaultClassNamePrefix = env === 'production' ? undefined : displayName + '-';
  var noTheme = {};
  var managerId = managersCounter++;
  var manager = new _jss.SheetsManager();
  var defaultProps = _extends({}, InnerComponent.defaultProps);
  delete defaultProps.classes;

  var Jss = function (_Component) {
    _inherits(Jss, _Component);

    function Jss(props, context) {
      _classCallCheck(this, Jss);

      var _this = _possibleConstructorReturn(this, (Jss.__proto__ || Object.getPrototypeOf(Jss)).call(this, props, context));

      _initialiseProps.call(_this);

      var theme = isThemingEnabled ? themeListener.initial(context) : noTheme;

      _this.state = _this.createState({ theme: theme }, props);
      return _this;
    }

    _createClass(Jss, [{
      key: 'componentWillMount',
      value: function componentWillMount() {
        this.manage(this.state);
      }
    }, {
      key: 'componentDidMount',
      value: function componentDidMount() {
        if (isThemingEnabled) {
          this.unsubscribeId = themeListener.subscribe(this.context, this.setTheme);
        }
      }
    }, {
      key: 'componentWillReceiveProps',
      value: function componentWillReceiveProps(nextProps, nextContext) {
        this.context = nextContext;
        var dynamicSheet = this.state.dynamicSheet;

        if (dynamicSheet) dynamicSheet.update(nextProps);
      }
    }, {
      key: 'componentWillUpdate',
      value: function componentWillUpdate(nextProps, nextState) {
        if (isThemingEnabled && this.state.theme !== nextState.theme) {
          var newState = this.createState(nextState, nextProps);
          this.manage(newState);
          this.manager.unmanage(this.state.theme);
          this.setState(newState);
        }
      }
    }, {
      key: 'componentDidUpdate',
      value: function componentDidUpdate(prevProps, prevState) {
        // We remove previous dynamicSheet only after new one was created to avoid FOUC.
        if (prevState.dynamicSheet !== this.state.dynamicSheet) {
          this.jss.removeStyleSheet(prevState.dynamicSheet);
        }
      }
    }, {
      key: 'componentWillUnmount',
      value: function componentWillUnmount() {
        if (this.unsubscribeId) {
          themeListener.unsubscribe(this.context, this.unsubscribeId);
        }

        this.manager.unmanage(this.state.theme);
        if (this.state.dynamicSheet) {
          this.state.dynamicSheet.detach();
        }
      }
    }, {
      key: 'createState',
      value: function createState(_ref, _ref2) {
        var theme = _ref.theme,
            dynamicSheet = _ref.dynamicSheet;
        var userClasses = _ref2.classes;

        var contextSheetOptions = this.context[ns.sheetOptions];
        if (contextSheetOptions && contextSheetOptions.disableStylesGeneration) {
          return { theme: theme, dynamicSheet: dynamicSheet, classes: {} };
        }

        var classNamePrefix = defaultClassNamePrefix;
        var staticSheet = this.manager.get(theme);

        if (contextSheetOptions && contextSheetOptions.classNamePrefix) {
          classNamePrefix = contextSheetOptions.classNamePrefix + classNamePrefix;
        }

        if (!staticSheet) {
          var styles = getStyles(stylesOrCreator, theme);
          staticSheet = this.jss.createStyleSheet(styles, _extends({}, sheetOptions, contextSheetOptions, {
            meta: displayName + ', ' + (isThemingEnabled ? 'Themed' : 'Unthemed') + ', Static',
            classNamePrefix: classNamePrefix
          }));
          this.manager.add(theme, staticSheet);
          staticSheet[dynamicStylesNs] = (0, _jss.getDynamicStyles)(styles);
        }

        var dynamicStyles = staticSheet[dynamicStylesNs];

        if (dynamicStyles) {
          dynamicSheet = this.jss.createStyleSheet(dynamicStyles, _extends({}, sheetOptions, contextSheetOptions, {
            meta: displayName + ', ' + (isThemingEnabled ? 'Themed' : 'Unthemed') + ', Dynamic',
            classNamePrefix: classNamePrefix,
            link: true
          }));
        }

        var defaultClasses = InnerComponent.defaultProps ? InnerComponent.defaultProps.classes : {};
        var jssClasses = dynamicSheet ? (0, _compose2['default'])(staticSheet.classes, dynamicSheet.classes) : staticSheet.classes;
        var classes = _extends({}, defaultClasses, jssClasses, userClasses);

        return { theme: theme, dynamicSheet: dynamicSheet, classes: classes };
      }
    }, {
      key: 'manage',
      value: function manage(_ref3) {
        var theme = _ref3.theme,
            dynamicSheet = _ref3.dynamicSheet;

        var contextSheetOptions = this.context[ns.sheetOptions];
        if (contextSheetOptions && contextSheetOptions.disableStylesGeneration) {
          return;
        }
        var registry = this.context[ns.sheetsRegistry];

        var staticSheet = this.manager.manage(theme);
        if (registry) registry.add(staticSheet);

        if (dynamicSheet) {
          dynamicSheet.update(this.props).attach();
          if (registry) registry.add(dynamicSheet);
        }
      }
    }, {
      key: 'render',
      value: function render() {
        var _state = this.state,
            theme = _state.theme,
            dynamicSheet = _state.dynamicSheet,
            classes = _state.classes;

        var _props = this.props,
            innerRef = _props.innerRef,
            props = _objectWithoutProperties(_props, ['innerRef']);

        var sheet = dynamicSheet || this.manager.get(theme);

        if (injectMap.sheet && !props.sheet) props.sheet = sheet;
        if (isThemingEnabled && injectMap.theme && !props.theme) props.theme = theme;

        // We have merged classes already.
        if (injectMap.classes) props.classes = classes;

        return _react2['default'].createElement(InnerComponent, _extends({ ref: innerRef }, props));
      }
    }, {
      key: 'jss',
      get: function get() {
        return this.context[ns.jss] || optionsJss || _jss2['default'];
      }
    }, {
      key: 'manager',
      get: function get() {
        var managers = this.context[ns.managers];

        // If `managers` map is present in the context, we use it in order to
        // let JssProvider reset them when new response has to render server-side.
        if (managers) {
          if (!managers[managerId]) {
            managers[managerId] = new _jss.SheetsManager();
          }
          return managers[managerId];
        }

        return manager;
      }
    }]);

    return Jss;
  }(_react.Component);

  Jss.displayName = 'Jss(' + displayName + ')';
  Jss.InnerComponent = InnerComponent;
  Jss.contextTypes = _extends({}, _contextTypes2['default'], isThemingEnabled && themeListener.contextTypes);
  Jss.propTypes = {
    innerRef: _propTypes2['default'].func
  };
  Jss.defaultProps = defaultProps;

  var _initialiseProps = function _initialiseProps() {
    var _this2 = this;

    this.setTheme = function (theme) {
      return _this2.setState({ theme: theme });
    };
  };

  return Jss;
};