"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
exports.__esModule = true;
exports.InMemoryBackend = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _chance = require("chance");
var _orderBy = _interopRequireDefault(require("lodash/orderBy"));
var _shuffle = _interopRequireDefault(require("lodash/shuffle"));
var _events = require("events");
var _core = require("react-query/core");
class InMemoryBackend {
  constructor(_params) {
    (0, _defineProperty2.default)(this, "createOne", void 0);
    (0, _defineProperty2.default)(this, "itemKey", void 0);
    (0, _defineProperty2.default)(this, "predicate", void 0);
    (0, _defineProperty2.default)(this, "orderBy", void 0);
    (0, _defineProperty2.default)(this, "projection", void 0);
    (0, _defineProperty2.default)(this, "initialTotal", void 0);
    (0, _defineProperty2.default)(this, "delay", void 0);
    (0, _defineProperty2.default)(this, "paginationMode", void 0);
    (0, _defineProperty2.default)(this, "enableTotal", void 0);
    (0, _defineProperty2.default)(this, "excludeOrdinalFilters", void 0);
    (0, _defineProperty2.default)(this, "events", new _events.EventEmitter());
    (0, _defineProperty2.default)(this, "map", void 0);
    (0, _defineProperty2.default)(this, "chance", new _chance.Chance());
    (0, _defineProperty2.default)(this, "fetchData", async query => {
      const fetchDataStartPromises = this.events.listeners('fetchDataStart').map(listener => listener(query));
      if (fetchDataStartPromises.length) {
        await Promise.all(fetchDataStartPromises);
      }
      await this.simulateAsyncDelay();
      return this.fetchDataSync(query);
    });
    (0, _defineProperty2.default)(this, "fetchTotal", async query => {
      const fetchTotalStartPromises = this.events.listeners('fetchTotalStart').map(listener => listener(query));
      if (fetchTotalStartPromises.length) {
        await Promise.all(fetchTotalStartPromises);
      }
      await this.simulateAsyncDelay();
      return this.fetchDataSync({
        ...query,
        limit: 1
      }).total ?? 0;
    });
    (0, _defineProperty2.default)(this, "deleteMany", async (items, params) => {
      await this.simulateAsyncDelay(params);
      items.forEach(item => this.map.delete(this.itemKey(item)));
    });
    (0, _defineProperty2.default)(this, "update", async item => {
      await this.simulateAsyncDelay();
      this.map.set(this.itemKey(item), item);
    });
    (0, _defineProperty2.default)(this, "categoriesOrdinal", new Map());
    (0, _defineProperty2.default)(this, "elementAt", index => this.values[index]);
    (0, _defineProperty2.default)(this, "randomItem", (predicate = _ => true) => {
      const index = Math.floor(Math.random() * this.available);
      return {
        index,
        item: (0, _shuffle.default)(this.values).find(predicate)
      };
    });
    this.enableTotal = _params.enableTotal ?? true;
    this.createOne = _params.createOne;
    this.itemKey = _params.itemKey;
    this.paginationMode = _params.paginationMode;
    this.predicate = _params.predicate ?? (() => () => true);
    this.orderBy = _params.orderBy;
    this.projection = _params.projection;
    this.initialTotal = _params.total;
    this.delay = _params.delay ?? {
      min: 0,
      max: 5
    };
    this.excludeOrdinalFilters = _params.excludeOrdinalFilters ?? [];
    this.map = new Map(this.newList(_params.total).map(item => [this.itemKey(item), item]));
  }
  async simulateAsyncDelay({
    delay,
    throwError
  } = {}) {
    const {
      chance
    } = this;
    await new Promise(resolve => setTimeout(resolve, chance.integer(delay ?? this.delay))); // simulate async

    await (throwError == null ? void 0 : throwError());
  }
  newList(length) {
    return new Array(length).fill(null).map((_, index) => this.createOne(index));
  }
  fetchDataSync(query) {
    const {
      itemKey,
      predicate,
      orderBy: getOrderBy,
      paginationMode
    } = this;
    const ordinal = this._getOrdinal(query.filters ?? {});
    const allItems = this.values;
    const orders = getOrderBy == null ? void 0 : getOrderBy(query);
    let itemsAfterOrdering;
    if (orders != null && orders.length) {
      itemsAfterOrdering = (0, _orderBy.default)(allItems, orders.map(({
        fieldName
      }) => fieldName), orders.map(({
        order
      }) => order));
    } else if (ordinal.size) {
      itemsAfterOrdering = (0, _orderBy.default)(allItems, item => ordinal.get(itemKey(item)), ['asc']);
    } else {
      itemsAfterOrdering = allItems;
    }
    const itemsAfterFiltering = itemsAfterOrdering.filter(predicate(query));
    let offset;
    if (paginationMode === 'cursor') {
      offset = query.cursor ? itemsAfterFiltering.findIndex(value => itemKey(value) === query.cursor) : 0;
    } else {
      offset = query.cursor ? itemsAfterFiltering.findIndex(value => itemKey(value) === query.cursor) : query.offset ?? 0;
    }
    const itemsAfterSlicing = itemsAfterFiltering.slice(offset, offset + query.limit);
    const cursorItem = itemsAfterFiltering[offset + query.limit];
    const itemsAfterFieldsProjection = this.projection ? itemsAfterSlicing.map(this.projection(query)) : itemsAfterSlicing;
    return {
      items: itemsAfterFieldsProjection,
      ...(this.enableTotal ? {
        total: itemsAfterFiltering.length,
        available: allItems.length
      } : {}),
      cursor: cursorItem && itemKey(cursorItem)
    };
  }
  async deleteAll(params = {}) {
    const {
      filters = {}
    } = params;
    await this.simulateAsyncDelay(params);
    const {
      predicate,
      itemKey
    } = this;
    const isItemDeleted = predicate({
      filters,
      limit: 1
    });
    for (const item of this.values) {
      if (isItemDeleted(item)) {
        this.map.delete(itemKey(item));
      }
    }
    this.events.emit('deleteAll');
  }
  clear() {
    this.map.clear();
  }
  async updateMany(items, params = {}) {
    await this.simulateAsyncDelay(params);
    for (const item of items) {
      this.map.set(this.itemKey(item), item);
    }
    this.events.emit('updatedMany');
  }
  _getOrdinal(filters) {
    const {
      categoriesOrdinal,
      excludeOrdinalFilters
    } = this;
    const category = (0, _core.hashQueryKey)([{
      filters: Object.fromEntries(Object.entries(filters).filter(([key]) => !excludeOrdinalFilters.includes(key)))
    }]);
    const ordinal = categoriesOrdinal.get(category) ?? new Map();
    if (!categoriesOrdinal.has(category)) {
      categoriesOrdinal.set(category, ordinal);
    }
    return ordinal;
  }
  async move(events, params = {}) {
    const {
      itemKey
    } = this;
    const moveStartPromises = this.events.listeners('moveStart').map(listener => listener(events));
    if (moveStartPromises.length) {
      await Promise.all(moveStartPromises);
    }
    await this.simulateAsyncDelay(params);
    for (const {
      from,
      after,
      filters
    } of events) {
      const ordinal = this._getOrdinal(filters);
      const array = ordinal.size ? (0, _orderBy.default)(this.values.slice(), item => ordinal.get(itemKey(item)), ['asc']) : this.values.slice();
      const fromKey = itemKey(from.item);
      const toKey = after != null ? itemKey(after.item) : null;
      const fromIndex = array.findIndex(item => fromKey === itemKey(item));
      if (fromIndex !== -1) {
        array.splice(fromIndex, 1);
      }
      if (toKey != null) {
        const toIndex = array.findIndex(item => toKey === itemKey(item));
        if (toIndex !== -1) {
          array.splice(toIndex + 1, 0, from.item);
        }
      } else {
        array.splice(0, 0, from.item);
      }
      ordinal.clear();
      for (let i = 0, len = array.length; i < len; i++) {
        ordinal.set(itemKey(array[i]), i);
      }
    }
  }
  async updateAll(patch, params = {}) {
    const {
      filters = {}
    } = params;
    await this.simulateAsyncDelay(params);
    const {
      predicate
    } = this;
    const itemsAfterFiltering = this.values.filter(predicate({
      filters,
      limit: 1
    }));
    const patchedItems = itemsAfterFiltering.map(value => ({
      ...value,
      ...patch(value)
    }));
    for (const item of patchedItems) {
      this.map.set(this.itemKey(item), item);
    }
    this.events.emit('updateAll');
  }
  async unshift(newItems) {
    await this.simulateAsyncDelay();
    const map = new Map();
    for (const item of newItems) {
      map.set(this.itemKey(item), item);
    }
    for (const [key, value] of Array.from(this.map.entries())) {
      map.set(key, value);
    }
    this.map = map;
  }
  async push(newItems) {
    await this.simulateAsyncDelay();
    for (const item of newItems) {
      this.map.set(this.itemKey(item), item);
    }
  }
  getRandomItems(count, {
    limit,
    page
  }) {
    const {
      chance,
      values
    } = this;
    const offset = (page - 1) * limit;
    return chance.pickset(values.slice(offset, offset + limit), count);
  }
  get values() {
    return Array.from(this.map.values());
  }
  get available() {
    return this.map.size;
  }
  get(key) {
    if (key == null) {
      return undefined;
    }
    return this.map.get(key);
  }
}
exports.InMemoryBackend = InMemoryBackend;
//# sourceMappingURL=InMemoryBackend.js.map