'use strict';

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

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

exports.diffProps = diffProps;
exports.compareProps = compareProps;

var _assert = require('../utils/assert');

var _assert2 = _interopRequireDefault(_assert);

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

// Returns an object with "change flags", either false or strings indicating reason for change
function diffProps(props, oldProps) {
  // First check if any props have changed (ignore props that will be examined separately)
  var propsChangedReason = compareProps({
    newProps: props,
    oldProps: oldProps,
    ignoreProps: { data: null, updateTriggers: null }
  });

  // Now check if any data related props have changed
  var dataChangedReason = diffDataProps(props, oldProps);

  // Check update triggers to determine if any attributes need regeneration
  // Note - if data has changed, all attributes will need regeneration, so skip this step
  var updateTriggersChangedReason = false;
  if (!dataChangedReason) {
    updateTriggersChangedReason = diffUpdateTriggers(props, oldProps);
  }

  return {
    dataChanged: dataChangedReason,
    propsChanged: propsChangedReason,
    updateTriggersChanged: updateTriggersChangedReason
  };
}

/**
 * Performs equality by iterating through keys on an object and returning false
 * when any key has values which are not strictly equal between the arguments.
 * @param {Object} opt.oldProps - object with old key/value pairs
 * @param {Object} opt.newProps - object with new key/value pairs
 * @param {Object} opt.ignoreProps={} - object, keys that should not be compared
 * @returns {null|String} - null when values of all keys are strictly equal.
 *   if unequal, returns a string explaining what changed.
 */
/* eslint-disable max-statements, max-depth, complexity */
function compareProps() {
  var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
      newProps = _ref.newProps,
      oldProps = _ref.oldProps,
      _ref$ignoreProps = _ref.ignoreProps,
      ignoreProps = _ref$ignoreProps === undefined ? {} : _ref$ignoreProps,
      _ref$shallowComparePr = _ref.shallowCompareProps,
      shallowCompareProps = _ref$shallowComparePr === undefined ? {} : _ref$shallowComparePr,
      _ref$triggerName = _ref.triggerName,
      triggerName = _ref$triggerName === undefined ? 'props' : _ref$triggerName;

  (0, _assert2.default)(oldProps !== undefined && newProps !== undefined, 'compareProps args');

  // shallow equality => deep equality
  if (oldProps === newProps) {
    return null;
  }

  // TODO - do we need these checks? Should never happen...
  if ((typeof newProps === 'undefined' ? 'undefined' : _typeof(newProps)) !== 'object' || newProps === null) {
    return triggerName + ' changed shallowly';
  }

  if ((typeof oldProps === 'undefined' ? 'undefined' : _typeof(oldProps)) !== 'object' || oldProps === null) {
    return triggerName + ' changed shallowly';
  }

  // Test if new props different from old props
  for (var key in oldProps) {
    if (!(key in ignoreProps)) {
      if (!(key in newProps)) {
        return triggerName + '.' + key + ' dropped: ' + oldProps[key] + ' -> undefined';
      }

      // If object has an equals function, invoke it
      var equals = newProps[key] && oldProps[key] && newProps[key].equals;
      if (equals && !equals.call(newProps[key], oldProps[key])) {
        return triggerName + '.' + key + ' changed deeply: ' + oldProps[key] + ' -> ' + newProps[key];
      }

      // If both new and old value are functions, ignore differences
      if (key in shallowCompareProps) {
        var type = _typeof(newProps[key]);
        if (type === 'function' && typeof oldProps[key] === 'function') {
          equals = true;
        }
      }

      if (!equals && oldProps[key] !== newProps[key]) {
        return triggerName + '.' + key + ' changed shallowly: ' + oldProps[key] + ' -> ' + newProps[key];
      }
    }
  }

  // Test if any new props have been added
  for (var _key in newProps) {
    if (!(_key in ignoreProps)) {
      if (!(_key in oldProps)) {
        return triggerName + '.' + _key + ' added: undefined -> ' + newProps[_key];
      }
    }
  }

  return null;
}
/* eslint-enable max-statements, max-depth, complexity */

// HELPERS

// The comparison of the data prop requires special handling
// the dataComparator should be used if supplied
function diffDataProps(props, oldProps) {
  if (oldProps === null) {
    return 'oldProps is null, initial diff';
  }

  // Support optional app defined comparison of data
  var dataComparator = props.dataComparator;

  if (dataComparator) {
    if (!dataComparator(props.data, oldProps.data)) {
      return 'Data comparator detected a change';
    }
    // Otherwise, do a shallow equal on props
  } else if (props.data !== oldProps.data) {
    return 'A new data container was supplied';
  }

  return null;
}

// Checks if any update triggers have changed
// also calls callback to invalidate attributes accordingly.
function diffUpdateTriggers(props, oldProps) {
  if (oldProps === null) {
    return 'oldProps is null, initial diff';
  }

  // If the 'all' updateTrigger fires, ignore testing others
  if ('all' in props.updateTriggers) {
    var diffReason = diffUpdateTrigger(oldProps, props, 'all');
    if (diffReason) {
      return { all: true };
    }
  }

  var triggerChanged = {};
  var reason = false;
  // If the 'all' updateTrigger didn't fire, need to check all others
  for (var triggerName in props.updateTriggers) {
    if (triggerName !== 'all') {
      var _diffReason = diffUpdateTrigger(oldProps, props, triggerName);
      if (_diffReason) {
        triggerChanged[triggerName] = true;
        reason = triggerChanged;
      }
    }
  }

  return reason;
}

function diffUpdateTrigger(props, oldProps, triggerName) {
  var newTriggers = props.updateTriggers[triggerName] || {};
  var oldTriggers = oldProps.updateTriggers[triggerName] || {};
  var diffReason = compareProps({
    oldProps: oldTriggers,
    newProps: newTriggers,
    triggerName: triggerName
  });
  return diffReason;
}
//# sourceMappingURL=props.js.map