'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.getSelectedItems = exports.getError = exports.getResult = exports.getMostRecentDescriptor = exports.getIndexesByCsid = exports.getState = exports.isPending = exports.searchKey = undefined;

var _immutable = require('immutable');

var _immutable2 = _interopRequireDefault(_immutable);

var _objectHelpers = require('../helpers/objectHelpers');

var _recordDataHelpers = require('../helpers/recordDataHelpers');

var _login = require('../actions/login');

var _logout = require('../actions/logout');

var _record = require('../actions/record');

var _search = require('../actions/search');

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

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

/**
 * Generates a search key for a given search descriptor. All search descriptors that represent the
 * same search should have the same search key.
 */
var searchKey = exports.searchKey = function searchKey(searchDescriptor) {
  // First convert the search descriptor to an array of name/value pairs, sorted by name. This
  // ensures that the key is not sensitive to the order in which properties are iterated out of the
  // search descriptor object.

  var pairs = (0, _objectHelpers.asPairs)(_immutable2.default.Map.isMap(searchDescriptor) ? searchDescriptor.toJS() : searchDescriptor);

  // Serialize the name/value pairs to JSON.

  return JSON.stringify(pairs);
};

var handleSetMostRecentSearch = function handleSetMostRecentSearch(state, action) {
  var _action$meta = action.meta,
      searchName = _action$meta.searchName,
      searchDescriptor = _action$meta.searchDescriptor;


  var key = searchKey(searchDescriptor);
  var namedSearch = state.get(searchName);

  if (namedSearch && namedSearch.getIn(['byKey', key])) {
    return state.set(searchName, namedSearch.set('mostRecentKey', key));
  }

  return state;
};

var handleSearchStarted = function handleSearchStarted(state, action) {
  var _action$meta2 = action.meta,
      listTypeConfig = _action$meta2.listTypeConfig,
      searchName = _action$meta2.searchName,
      searchDescriptor = _action$meta2.searchDescriptor;
  var listNodeName = listTypeConfig.listNodeName,
      itemNodeName = listTypeConfig.itemNodeName;


  var namedSearch = state.get(searchName) || _immutable2.default.Map();

  var key = searchKey(searchDescriptor);
  var mostRecentKey = namedSearch.get('mostRecentKey');

  var updatedNamedSearch = namedSearch;
  var result = null;
  var itemsInPage = null;
  var totalItems = null;
  var items = null;

  if (mostRecentKey) {
    var mostRecentSearchState = namedSearch.getIn(['byKey', mostRecentKey]);
    var mostRecentSearchDescriptor = mostRecentSearchState.get('descriptor');

    var changes = (0, _objectHelpers.diff)(searchDescriptor.toJS(), mostRecentSearchDescriptor.toJS(), 3);
    var changeCount = Object.keys(changes).length;

    var pageChanged = 'searchQuery.p' in changes;
    var sortChanged = 'searchQuery.sort' in changes;
    var seqIDChanged = 'seqID' in changes;

    if (pageChanged && changeCount === 1) {
      // Only the page number changed from the last search. Seed these results with the total items
      // count from the previous results, since it probably will remain the same. This allows the
      // UI to render a smooth transition from this page to the next, without the count information
      // and pager blinking out.

      totalItems = mostRecentSearchState.getIn(['result', listNodeName, 'totalItems']);
    } else {
      // Something other than the page number changed from the last search. Clear out stored
      // search states. (If only the page number changed, keep the previous states around as a
      // cache so that flipping between pages will be fast.)

      updatedNamedSearch = updatedNamedSearch.set('byKey', _immutable2.default.Map());

      if (sortChanged && changeCount === 1) {
        // Only the sort changed from the last search. As above, seed these results with the total
        // items count. Also seed the itemsInPage count, since this is not expected to change.

        totalItems = mostRecentSearchState.getIn(['result', listNodeName, 'totalItems']);

        itemsInPage = mostRecentSearchState.getIn(['result', listNodeName, 'itemsInPage']);
      } else if (seqIDChanged && changeCount === 1) {
        // Only the seq id changed from the last search. This means that the search parameters
        // themselves haven't changed, but the search is being triggered because the data may have
        // changed. Seed the result with the previous total items, items in page, and items. This
        // makes for the smoothest possible transition to the new result set.

        totalItems = mostRecentSearchState.getIn(['result', listNodeName, 'totalItems']);

        itemsInPage = mostRecentSearchState.getIn(['result', listNodeName, 'itemsInPage']);

        items = mostRecentSearchState.getIn(['result', listNodeName, itemNodeName]);
      }

      delete changes['searchQuery.p'];
      delete changes['searchQuery.size'];
      delete changes['searchQuery.sort'];
      delete changes.seqID;

      if (Object.keys(changes).length > 0) {
        // Something other than page num, page size, sort, or seq id changed. Clear selected items.

        updatedNamedSearch = updatedNamedSearch.delete('selected');
      }
    }
  }

  // Seed the search state with a blank result that has the page num and size of this search, and
  // the total items and items in page counts from the last search, if they're not expected to
  // change. This allows a blank result table to be rendered while the search is pending,
  // preventing a flash of empty content.

  var searchQuery = searchDescriptor.get('searchQuery');
  var p = searchQuery.get('p');
  var size = searchQuery.get('size');

  var pageNum = typeof p === 'number' ? p.toString() : p;
  var pageSize = typeof size === 'number' ? size.toString() : size;

  result = _immutable2.default.fromJS(_defineProperty({}, listNodeName, _defineProperty({
    itemsInPage: itemsInPage,
    totalItems: totalItems,
    pageNum: pageNum,
    pageSize: pageSize
  }, itemNodeName, items)));

  updatedNamedSearch = updatedNamedSearch.set('mostRecentKey', key).setIn(['byKey', key], _immutable2.default.fromJS({
    result: result,
    descriptor: searchDescriptor,
    isPending: true
  }));

  return state.set(searchName, updatedNamedSearch);
};

var computeIndexesByCsid = function computeIndexesByCsid(listTypeConfig, result) {
  var listNodeName = listTypeConfig.listNodeName,
      itemNodeName = listTypeConfig.itemNodeName;


  var indexesByCsid = void 0;
  var items = result.getIn([listNodeName, itemNodeName]);

  if (items) {
    if (!_immutable2.default.List.isList(items)) {
      items = _immutable2.default.List.of(items);
    }

    indexesByCsid = _immutable2.default.Map(items.map(function (item, index) {
      return [item.get('csid') || item.get('docId'), index];
    }));
  }

  return indexesByCsid;
};

var setSearchResult = function setSearchResult(state, listTypeConfig, searchName, searchDescriptor, result) {
  var namedSearch = state.get(searchName);
  var key = searchKey(searchDescriptor);

  if (namedSearch && namedSearch.hasIn(['byKey', key])) {
    var searchState = namedSearch.getIn(['byKey', key]);

    var updatedSearchState = searchState.set('isPending', false).set('result', result).set('indexesByCsid', computeIndexesByCsid(listTypeConfig, result)).set('listNodeName', listTypeConfig.listNodeName).set('itemNodeName', listTypeConfig.itemNodeName).delete('error');

    var updatedNamedSearch = namedSearch.setIn(['byKey', key], updatedSearchState);

    return state.set(searchName, updatedNamedSearch);
  }

  return state;
};

var handleCreateEmptySearchResult = function handleCreateEmptySearchResult(state, action) {
  var _action$meta3 = action.meta,
      listTypeConfig = _action$meta3.listTypeConfig,
      searchName = _action$meta3.searchName,
      searchDescriptor = _action$meta3.searchDescriptor;


  var searchQuery = searchDescriptor.get('searchQuery');
  var p = searchQuery.get('p');
  var size = searchQuery.get('size');

  var pageNum = typeof p === 'number' ? p.toString() : p;
  var pageSize = typeof size === 'number' ? size.toString() : size;

  var result = _immutable2.default.fromJS(_defineProperty({}, listTypeConfig.listNodeName, {
    itemsInPage: '0',
    totalItems: '0',
    pageNum: pageNum,
    pageSize: pageSize
  }));

  return setSearchResult(state, listTypeConfig, searchName, searchDescriptor, result);
};

var handleSearchFulfilled = function handleSearchFulfilled(state, action) {
  var _action$meta4 = action.meta,
      listTypeConfig = _action$meta4.listTypeConfig,
      searchName = _action$meta4.searchName,
      searchDescriptor = _action$meta4.searchDescriptor;


  return setSearchResult(state, listTypeConfig, searchName, searchDescriptor, _immutable2.default.fromJS(action.payload.data));
};

var handleSearchRejected = function handleSearchRejected(state, action) {
  var _action$meta5 = action.meta,
      searchName = _action$meta5.searchName,
      searchDescriptor = _action$meta5.searchDescriptor;


  var namedSearch = state.get(searchName);
  var key = searchKey(searchDescriptor);

  if (namedSearch && namedSearch.hasIn(['byKey', key])) {
    var updatedNamedSearch = namedSearch.setIn(['byKey', key], namedSearch.getIn(['byKey', key]).set('isPending', false).set('error', _immutable2.default.fromJS(action.payload)).delete('result'));

    return state.set(searchName, updatedNamedSearch);
  }

  return state;
};

var handleSetAllResultItemsSelected = function handleSetAllResultItemsSelected(state, action) {
  var isSelected = action.payload;

  var _action$meta6 = action.meta,
      filter = _action$meta6.filter,
      listTypeConfig = _action$meta6.listTypeConfig,
      searchName = _action$meta6.searchName,
      searchDescriptor = _action$meta6.searchDescriptor;
  var listNodeName = listTypeConfig.listNodeName,
      itemNodeName = listTypeConfig.itemNodeName;


  var namedSearch = state.get(searchName);

  if (namedSearch) {
    var key = searchKey(searchDescriptor);
    var path = ['byKey', key, 'result', listNodeName, itemNodeName];

    var items = namedSearch.getIn(path);

    if (!_immutable2.default.List.isList(items)) {
      items = _immutable2.default.List.of(items);
    }

    if (filter) {
      items = items.filter(filter);
    }

    var selectedItems = namedSearch.get('selected') || _immutable2.default.Map();

    var updatedSelectedItems = void 0;

    if (isSelected) {
      updatedSelectedItems = selectedItems.withMutations(function (map) {
        return items.reduce(function (updatedMap, item) {
          return updatedMap.set(item.get('csid'), item);
        }, map);
      });
    } else {
      updatedSelectedItems = selectedItems.withMutations(function (map) {
        return items.reduce(function (updatedMap, item) {
          return updatedMap.delete(item.get('csid'));
        }, map);
      });
    }

    return state.set(searchName, namedSearch.set('selected', updatedSelectedItems));
  }

  return state;
};

var handleSetResultItemSelected = function handleSetResultItemSelected(state, action) {
  var isSelected = action.payload;

  var _action$meta7 = action.meta,
      listTypeConfig = _action$meta7.listTypeConfig,
      searchName = _action$meta7.searchName,
      searchDescriptor = _action$meta7.searchDescriptor,
      index = _action$meta7.index;
  var listNodeName = listTypeConfig.listNodeName,
      itemNodeName = listTypeConfig.itemNodeName;


  var namedSearch = state.get(searchName);

  if (namedSearch) {
    var key = searchKey(searchDescriptor);
    var path = ['byKey', key, 'result', listNodeName, itemNodeName, index];
    var item = (0, _recordDataHelpers.deepGet)(namedSearch, path);

    var csid = item.get('csid');

    var updatedNamedSearch = isSelected ? namedSearch.setIn(['selected', csid], item) : namedSearch.deleteIn(['selected', csid]);

    return state.set(searchName, updatedNamedSearch);
  }

  return state;
};

var clearNamedResults = function clearNamedResults(state, action) {
  return state.delete(action.meta.searchName);
};

var clearAllResults = function clearAllResults(state) {
  return state.clear();
};

var handleRecordDeleteFulfilled = function handleRecordDeleteFulfilled(state) {
  return clearAllResults(state);
};

var handleRecordTransitionFulfilled = function handleRecordTransitionFulfilled(state, action) {
  var transitionName = action.meta.transitionName;


  if (transitionName === 'delete') {
    return clearAllResults(state);
  }

  return state;
};

var handleLoginFulfilled = function handleLoginFulfilled(state, action) {
  var _action$meta8 = action.meta,
      prevUsername = _action$meta8.prevUsername,
      username = _action$meta8.username;


  if (prevUsername !== username) {
    // The logged in user has changed. Remove all search state, because the new user may not be
    // permitted to list some records that the previous user could.

    return clearAllResults(state);
  }

  return state;
};

exports.default = function () {
  var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _immutable2.default.Map();
  var action = arguments[1];

  switch (action.type) {
    case _search.CLEAR_SELECTED:
      return state.deleteIn([action.meta.searchName, 'selected']);
    case _search.CLEAR_SEARCH_RESULTS:
      return clearNamedResults(state, action);
    case _search.SET_MOST_RECENT_SEARCH:
      return handleSetMostRecentSearch(state, action);
    case _search.CREATE_EMPTY_SEARCH_RESULT:
      return handleCreateEmptySearchResult(state, action);
    case _search.SEARCH_STARTED:
      return handleSearchStarted(state, action);
    case _search.SEARCH_FULFILLED:
      return handleSearchFulfilled(state, action);
    case _search.SEARCH_REJECTED:
      return handleSearchRejected(state, action);
    case _search.SET_ALL_RESULT_ITEMS_SELECTED:
      return handleSetAllResultItemsSelected(state, action);
    case _search.SET_RESULT_ITEM_SELECTED:
      return handleSetResultItemSelected(state, action);
    case _search.DESELECT_RESULT_ITEM:
      return state.deleteIn([action.meta.searchName, 'selected', action.meta.csid]);
    case _record.SUBRECORD_CREATED:
      return clearNamedResults(state, action);
    case _record.RECORD_CREATED:
      return clearAllResults(state);
    case _record.RECORD_DELETE_FULFILLED:
      return handleRecordDeleteFulfilled(state, action);
    case _record.RECORD_TRANSITION_FULFILLED:
      return handleRecordTransitionFulfilled(state, action);
    case _login.LOGIN_FULFILLED:
      return handleLoginFulfilled(state, action);
    case _logout.LOGOUT_FULFILLED:
      return clearAllResults(state);
    default:
      return state;
  }
};

var isPending = exports.isPending = function isPending(state, searchName, searchDescriptor) {
  return state.getIn([searchName, 'byKey', searchKey(searchDescriptor), 'isPending']);
};

var getState = exports.getState = function getState(state, searchName, searchDescriptor) {
  return state.getIn([searchName, 'byKey', searchKey(searchDescriptor)]);
};

var getIndexesByCsid = exports.getIndexesByCsid = function getIndexesByCsid(state, searchName, searchDescriptor) {
  return state.getIn([searchName, 'byKey', searchKey(searchDescriptor), 'indexesByCsid']);
};

var getMostRecentDescriptor = exports.getMostRecentDescriptor = function getMostRecentDescriptor(state, searchName) {
  return state.getIn([searchName, 'byKey', state.getIn([searchName, 'mostRecentKey']), 'descriptor']);
};

var getResult = exports.getResult = function getResult(state, searchName, searchDescriptor) {
  return state.getIn([searchName, 'byKey', searchKey(searchDescriptor), 'result']);
};

var getError = exports.getError = function getError(state, searchName, searchDescriptor) {
  return state.getIn([searchName, 'byKey', searchKey(searchDescriptor), 'error']);
};

var getSelectedItems = exports.getSelectedItems = function getSelectedItems(state, searchName) {
  return state.getIn([searchName, 'selected']);
};