"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

exports.__esModule = true;
exports.default = exports.Setup = exports.createSetup = void 0;

var _debounce = _interopRequireDefault(require("lodash/debounce"));

var _mergeWith = _interopRequireDefault(require("lodash/mergeWith"));

var _redux = require("redux");

var _reselect = require("reselect");

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

var defaultConfig = {
  prefix: '',
  filterFn: null,
  // (filter, params) => (item) => item.name.indexOf(filter) > -1
  sortFn: null,
  // (by, params) => (a, b) => by ? a[by].localeCompare(b[by])
  loadFn: null,
  pagination: {
    page: 1,
    perPage: _utils.AVAILABLE_PER_PAGE[0],
    availablePerPage: _utils.AVAILABLE_PER_PAGE
  },
  sort: {
    by: '',
    reverse: false
  },
  filter: '',
  params: {},
  items: [],
  debounce: 250,
  server: false
};

var createSetup = function createSetup(config) {
  return new Setup(config);
};

exports.createSetup = createSetup;

var Setup =
/*#__PURE__*/
function () {
  function Setup(config) {
    if (config === void 0) {
      config = {};
    }

    /** @type {defaultConfig} */
    this.config = (0, _mergeWith.default)({}, defaultConfig, config, function (objValue, srcValue) {
      if (Array.isArray(objValue)) {
        return srcValue;
      }
    });

    if (!this.config.prefix) {
      throw new Error('Missing mandatory option: prefix');
    }

    if (!this.config.server && !this.config.items && !this.config.loadFn) {
      throw new Error('Missing mandatory option: items or loadFn');
    }

    if (this.config.server && !this.config.loadFn) {
      throw new Error('Missing mandatory option: loadFn');
    } //TODO Use createSelector
    //TODO Handle concurrency


    this._load = this.config.debounce ? (0, _debounce.default)(this._load, this.config.debounce) : this._load; // Safety

    this.load = this.load.bind(this);
    this.setFilter = this.setFilter.bind(this);
    this.setPage = this.setPage.bind(this);
    this.setPerPage = this.setPerPage.bind(this);
    this.setSort = this.setSort.bind(this);
    this.setParams = this.setParams.bind(this);
  } // Utils


  var _proto = Setup.prototype;

  _proto.prefix = function prefix() {
    return this.config.prefix;
  };

  _proto._hasState = function _hasState(state) {
    return undefined !== state;
  };

  _proto._prefixAction = function _prefixAction(action) {
    return "SETUP/" + this.config.prefix + "/" + action;
  };

  _proto._createAction = function _createAction(action, payload, error) {
    return {
      type: this._prefixAction(action),
      payload: payload,
      error: error
    };
  } // Actions
  ;

  _proto._createLoadedDispatch = function _createLoadedDispatch(action) {
    var _this = this;

    return function (dispatch) {
      dispatch(action);

      if (_this.config.server) {
        dispatch(_this.load());
      }
    };
  }
  /**
   * Debounced load function
   * @param {function} dispatch
   * @param {function} getState
   * @private
   */
  ;

  _proto._load = function _load(dispatch, getState) {
    var _this2 = this;

    if (!this.config.loadFn) {
      return;
    }

    var state = getState();
    var options = {
      params: this.getParams(state),
      filter: this.getFilter(state),
      sort: this.getSort(state),
      pagination: this.getPagination(state)
    };
    dispatch(this._createAction(_utils.LOAD));
    new Promise(function (res) {
      return res(_this2.config.loadFn(options));
    }) // safety for sync loadFn's
    .then(function (payload) {
      dispatch(_this2._createAction(_utils.LOAD_SUCCESS, payload));
      return payload;
    }).catch(function (e) {
      dispatch(_this2._createAction(_utils.LOAD_ERROR, e, true));
      throw e;
    });
  };

  _proto.load = function load() {
    var _this3 = this;

    return function (dispatch, getState) {
      _this3._load(dispatch, getState);
    };
  };

  _proto.setFilter = function setFilter(filter) {
    return this._createLoadedDispatch(this._createAction(_utils.FILTER, filter));
  };

  _proto.setSort = function setSort(by, reverse) {
    return this._createLoadedDispatch(this._createAction(_utils.SORT, {
      by: by,
      reverse: reverse
    }));
  };

  _proto.setPage = function setPage(page) {
    return this._createLoadedDispatch(this._createAction(_utils.PAGINATION, {
      page: page
    }));
  };

  _proto.setPerPage = function setPerPage(perPage) {
    return this._createLoadedDispatch(this._createAction(_utils.PAGINATION, {
      perPage: perPage
    }));
  };

  _proto.setParams = function setParams(params) {
    return this._createLoadedDispatch(this._createAction(_utils.PARAMS, params));
  } // Selectors
  ;

  _proto._filterItems = function _filterItems(items, filter, params) {
    var processed = [].concat(items);

    if (this.config.filterFn) {
      processed = processed.filter(this.config.filterFn(filter, params));
    }

    return processed;
  };

  _proto._processItems = function _processItems(filteredItems, pagination, sort, params) {
    var processed = [].concat(filteredItems);
    var page = pagination.page,
        perPage = pagination.perPage;
    var by = sort.by,
        reverse = sort.reverse;

    if (!this.config.server) {
      if (this.config.sortFn && by) {
        processed = processed.sort(this.config.sortFn(by, params));
      }

      if (reverse) {
        processed = processed.reverse();
      }

      if (perPage !== _utils.ALL_PAGES) {
        var start = (page - 1) * perPage;
        processed = processed.slice(start, start + perPage);
      }
    }

    return processed;
  };

  _proto.getState = function getState(state) {
    return state[this.config.prefix];
  };

  _proto.getAllItems = function getAllItems(state) {
    return this.getState(state).items;
  };

  _proto.getFilteredTotal = function getFilteredTotal(state) {
    return this.config.server && this.getState(state).filteredTotal || this.getFilteredItems(state).length;
  };

  _proto.getTotal = function getTotal(state) {
    return this.config.server && this.getState(state).total || this.getAllItems(state).length;
  };

  _proto.getSort = function getSort(state) {
    return this.getState(state).sort;
  };

  _proto.getFilter = function getFilter(state) {
    return this.getState(state).filter;
  };

  _proto.getError = function getError(state) {
    return this.getState(state).error;
  };

  _proto.getLoading = function getLoading(state) {
    return this.getState(state).loading;
  };

  _proto.getFetched = function getFetched(state) {
    return this.getState(state).fetched;
  };

  _proto.getPagination = function getPagination(state) {
    return this.getState(state).pagination;
  };

  _proto.getParams = function getParams(state) {
    return this.getState(state).params;
  };

  _proto.getFilteredItems = function getFilteredItems(state) {
    if (!this._getFilteredItems) {
      // lazily create selector and remember it
      this._getFilteredItems = (0, _reselect.createSelector)([this.getAllItems.bind(this), this.getFilter.bind(this), this.getParams.bind(this)], this._filterItems.bind(this));
    }

    return this._getFilteredItems(state);
  };

  _proto.getItems = function getItems(state) {
    if (!this._getItems) {
      // lazily create selector and remember it
      this._getItems = (0, _reselect.createSelector)([this.getFilteredItems.bind(this), this.getPagination.bind(this), this.getSort.bind(this), this.getParams.bind(this)], this._processItems.bind(this));
    }

    return this._getItems(state);
  } // Reducer
  ;

  _proto._errorReducer = function _errorReducer(state, action) {
    if (state === void 0) {
      state = null;
    }

    switch (action.type) {
      case this._prefixAction(_utils.LOAD):
      case this._prefixAction(_utils.LOAD_SUCCESS):
        return null;

      case this._prefixAction(_utils.LOAD_ERROR):
        return action.payload;

      default:
        return state;
    }
  };

  _proto._loadingReducer = function _loadingReducer(state, action) {
    if (state === void 0) {
      state = false;
    }

    switch (action.type) {
      case this._prefixAction(_utils.LOAD):
        return true;

      case this._prefixAction(_utils.LOAD_ERROR):
      case this._prefixAction(_utils.LOAD_SUCCESS):
        return false;

      default:
        return state;
    }
  };

  _proto._fetchedReducer = function _fetchedReducer(state, action) {
    if (state === void 0) {
      state = false;
    }

    switch (action.type) {
      case this._prefixAction(_utils.LOAD):
        return true;

      default:
        return state;
    }
  };

  _proto._paramsReducer = function _paramsReducer(state, action) {
    if (!this._hasState(state)) {
      state = Object.assign({}, this.config.params);
    }

    switch (action.type) {
      case this._prefixAction(_utils.PARAMS):
        return Object.assign({}, state, action.payload);

      default:
        return state;
    }
  }
  /**
   * TODO Combine Reducers
   * @param state
   * @param action
   * @return {*}
   * @private
   */
  ;

  _proto._paginationReducer = function _paginationReducer(state, action) {
    if (!this._hasState(state)) {
      state = Object.assign({}, this.config.pagination);
    }

    switch (action.type) {
      case this._prefixAction(_utils.PARAMS):
      case this._prefixAction(_utils.SORT):
      case this._prefixAction(_utils.FILTER):
        return Object.assign({}, state, {
          page: 1
        });

      case this._prefixAction(_utils.PAGINATION):
        var page = action.payload.perPage && action.payload.perPage !== state.perPage ? 1 // TODO Recalculate
        : action.payload.page || state.page;
        return Object.assign({}, state, {
          page: page,
          perPage: action.payload.perPage || state.perPage
        });

      default:
        return state;
    }
  };

  _proto._filterReducer = function _filterReducer(state, action) {
    if (!this._hasState(state)) {
      state = this.config.filter;
    }

    switch (action.type) {
      case this._prefixAction(_utils.FILTER):
        return action.payload;

      default:
        return state;
    }
  };

  _proto._sortReducer = function _sortReducer(state, action) {
    if (!this._hasState(state)) {
      state = Object.assign({}, this.config.sort);
    }

    switch (action.type) {
      case this._prefixAction(_utils.SORT):
        return Object.assign({}, state, {
          by: action.payload.by,
          reverse: action.payload.reverse
        });

      default:
        return state;
    }
  };

  _proto._itemsReducer = function _itemsReducer(state, action) {
    if (!this._hasState(state) && this.config.items) {
      state = [].concat(this.config.items);
    }

    switch (action.type) {
      case this._prefixAction(_utils.LOAD_SUCCESS):
        if (!Array.isArray(action.payload)) {
          return [].concat(action.payload.items);
        }

        return [].concat(action.payload);

      default:
        return state;
    }
  };

  _proto._totalReducer = function _totalReducer(state, action) {
    if (!this._hasState(state) && this.config.items) {
      state = this.config.items.length;
    }

    switch (action.type) {
      case this._prefixAction(_utils.LOAD_SUCCESS):
        if (!Array.isArray(action.payload)) {
          return action.payload.total;
        }

        return action.payload.length;

      default:
        return state;
    }
  };

  _proto._filteredTotalReducer = function _filteredTotalReducer(state, action) {
    if (!this._hasState(state) && this.config.items) {
      state = this.config.items.length;
    }

    switch (action.type) {
      case this._prefixAction(_utils.LOAD_SUCCESS):
        if (!Array.isArray(action.payload)) {
          return action.payload.filteredTotal;
        }

        return action.payload.length;

      default:
        return state;
    }
  };

  _proto.createReducer = function createReducer() {
    return (0, _redux.combineReducers)({
      fetched: this._fetchedReducer.bind(this),
      params: this._paramsReducer.bind(this),
      error: this._errorReducer.bind(this),
      loading: this._loadingReducer.bind(this),
      pagination: this._paginationReducer.bind(this),
      filter: this._filterReducer.bind(this),
      sort: this._sortReducer.bind(this),
      items: this._itemsReducer.bind(this),
      total: this._totalReducer.bind(this),
      filteredTotal: this._filteredTotalReducer.bind(this)
    });
  };

  return Setup;
}();

exports.Setup = Setup;
var _default = createSetup;
exports.default = _default;