'use strict';

function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }

var React = _interopDefault(require('react'));
var PropTypes = _interopDefault(require('prop-types'));
var invariant = _interopDefault(require('invariant'));
var json2mq = _interopDefault(require('json2mq'));

function _defineProperty(obj, key, value) {
  if (key in obj) {
    Object.defineProperty(obj, key, {
      value: value,
      enumerable: true,
      configurable: true,
      writable: true
    });
  } else {
    obj[key] = value;
  }

  return obj;
}

function _extends() {
  _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;
  };

  return _extends.apply(this, arguments);
}

function _inheritsLoose(subClass, superClass) {
  subClass.prototype = Object.create(superClass.prototype);
  subClass.prototype.constructor = subClass;
  subClass.__proto__ = superClass;
}

function _assertThisInitialized(self) {
  if (self === void 0) {
    throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  }

  return self;
}

var MediaQueryListener =
/*#__PURE__*/
function () {
  function MediaQueryListener(targetWindow, query, listener) {
    var _this = this;

    this.nativeMediaQueryList = targetWindow.matchMedia(query);
    this.active = true; // Safari doesn't clear up listener with removeListener
    // when the listener is already waiting in the event queue.
    // Having an active flag to make sure the listener is not called
    // after we removeListener.

    this.cancellableListener = function () {
      _this.matches = _this.nativeMediaQueryList.matches;

      if (_this.active) {
        listener.apply(void 0, arguments);
      }
    };

    this.nativeMediaQueryList.addListener(this.cancellableListener);
    this.matches = this.nativeMediaQueryList.matches;
  }

  var _proto = MediaQueryListener.prototype;

  _proto.cancel = function cancel() {
    this.active = false;
    this.nativeMediaQueryList.removeListener(this.cancellableListener);
  };

  return MediaQueryListener;
}();

var queryType = PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.arrayOf(PropTypes.object.isRequired)]);
/**
 * Conditionally renders based on whether or not a media query matches.
 */

var Media =
/*#__PURE__*/
function (_React$Component) {
  _inheritsLoose(Media, _React$Component);

  function Media(props) {
    var _this;

    _this = _React$Component.call(this, props) || this;

    _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "queries", []);

    _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "getMatches", function () {
      var result = _this.queries.reduce(function (acc, _ref) {
        var _extends2;

        var name = _ref.name,
            mqListener = _ref.mqListener;
        return _extends({}, acc, (_extends2 = {}, _extends2[name] = mqListener.matches, _extends2));
      }, {}); // return result;


      return unwrapSingleQuery(result);
    });

    _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "updateMatches", function () {
      var newMatches = _this.getMatches();

      _this.setState(function () {
        return {
          matches: newMatches
        };
      }, _this.onChange);
    });

    !(!(!props.query && !props.queries) || props.query && props.queries) ? invariant(false, '<Media> must be supplied with either "query" or "queries"') : void 0;
    !(props.defaultMatches === undefined || !props.query || typeof props.defaultMatches === "boolean") ? invariant(false, "<Media> when query is set, defaultMatches must be a boolean, received " + typeof props.defaultMatches) : void 0;
    !(props.defaultMatches === undefined || !props.queries || typeof props.defaultMatches === "object") ? invariant(false, "<Media> when queries is set, defaultMatches must be a object of booleans, received " + typeof props.defaultMatches) : void 0;

    if (typeof window !== "object") {
      // In case we're rendering on the server, apply the default matches
      var matches;

      if (props.defaultMatches !== undefined) {
        matches = props.defaultMatches;
      } else if (props.query) {
        matches = true;
      }
      /* if (props.queries) */
      else {
          matches = Object.keys(_this.props.queries).reduce(function (acc, key) {
            var _extends3;

            return _extends({}, acc, (_extends3 = {}, _extends3[key] = true, _extends3));
          }, {});
        }

      _this.state = {
        matches: matches
      };
      return _assertThisInitialized(_this);
    }

    _this.initialize(); // Instead of calling this.updateMatches, we manually set the initial state to prevent
    // calling setState, which could trigger an unnecessary second render


    _this.state = {
      matches: _this.props.defaultMatches !== undefined ? _this.props.defaultMatches : _this.getMatches()
    };

    _this.onChange();

    return _this;
  }

  var _proto = Media.prototype;

  _proto.initialize = function initialize() {
    var _this2 = this;

    var targetWindow = this.props.targetWindow || window;
    !(typeof targetWindow.matchMedia === "function") ? invariant(false, "<Media targetWindow> does not support `matchMedia`.") : void 0;
    var queries = this.props.queries || wrapInQueryObject(this.props.query);
    this.queries = Object.keys(queries).map(function (name) {
      var query = queries[name];
      var qs = typeof query !== "string" ? json2mq(query) : query;
      var mqListener = new MediaQueryListener(targetWindow, qs, _this2.updateMatches);
      return {
        name: name,
        mqListener: mqListener
      };
    });
  };

  _proto.componentDidMount = function componentDidMount() {
    this.initialize(); // If props.defaultMatches has been set, ensure we trigger a two-pass render.
    // This is useful for SSR with mismatching defaultMatches vs actual matches from window.matchMedia
    // Details: https://github.com/ReactTraining/react-media/issues/81

    if (this.props.defaultMatches !== undefined) {
      this.updateMatches();
    }
  };

  _proto.onChange = function onChange() {
    var onChange = this.props.onChange;

    if (onChange) {
      onChange(this.state.matches);
    }
  };

  _proto.componentWillUnmount = function componentWillUnmount() {
    this.queries.forEach(function (_ref2) {
      var mqListener = _ref2.mqListener;
      return mqListener.cancel();
    });
  };

  _proto.render = function render() {
    var _this$props = this.props,
        children = _this$props.children,
        render = _this$props.render;
    var matches = this.state.matches;
    var isAnyMatches = typeof matches === "object" ? Object.keys(matches).some(function (key) {
      return matches[key];
    }) : matches;
    return render ? isAnyMatches ? render(matches) : null : children ? typeof children === "function" ? children(matches) : !Array.isArray(children) || children.length // Preact defaults to empty children array
    ? isAnyMatches ? // We have to check whether child is a composite component or not to decide should we
    // provide `matches` as a prop or not
    React.Children.only(children) && typeof React.Children.only(children).type === "string" ? React.Children.only(children) : React.cloneElement(React.Children.only(children), {
      matches: matches
    }) : null : null : null;
  };

  return Media;
}(React.Component);
/**
 * Wraps a single query in an object. This is used to provide backward compatibility with
 * the old `query` prop (as opposed to `queries`). If only a single query is passed, the object
 * will be unpacked down the line, but this allows our internals to assume an object of queries
 * at all times.
 */


_defineProperty(Media, "propTypes", {
  defaultMatches: PropTypes.oneOfType([PropTypes.bool, PropTypes.objectOf(PropTypes.bool)]),
  query: queryType,
  queries: PropTypes.objectOf(queryType),
  render: PropTypes.func,
  children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
  targetWindow: PropTypes.object,
  onChange: PropTypes.func
});

function wrapInQueryObject(query) {
  return {
    __DEFAULT__: query
  };
}
/**
 * Unwraps an object of queries, if it was originally passed as a single query.
 */


function unwrapSingleQuery(queryObject) {
  var queryNames = Object.keys(queryObject);

  if (queryNames.length === 1 && queryNames[0] === "__DEFAULT__") {
    return queryObject.__DEFAULT__;
  }

  return queryObject;
}

module.exports = Media;
