Source: state/reducers/updateInventory.js

import { inventoryFilter } from "../../utils/conditionalFilters";
import {
  ACTIONS,
  cloneData,
  SPECIAL_DATA_KEYS,
} from "../../utils/reducerUtils";

/**
 * @typedef {Object} ConditionalInventoryItem
 * @property {String} condition
 * @property {String} name
 */

/**
 * Updates `data` in place to add/remove `items` based on the given `delta`.
 * This will create the inventory on the data object if it does not already exist.
 * This will also prevent any item's quantity from going below zero.
 *
 * If there is an object within `items` with a key `condition`, it will be considered before adding/removing the item.
 *
 * @param {Object} data
 * @param {(String[]|ConditionalInventoryItem[])} items
 * @param {Number} delta
 */
export const updateInventory = (data, items = null, delta) => {
  if (!data[SPECIAL_DATA_KEYS.INVENTORY]) {
    data[SPECIAL_DATA_KEYS.INVENTORY] = {};
  }
  if (items) {
    if (!Array.isArray(items)) {
      items = [items];
    }
    items.forEach((item) => {
      if (typeof item === "object" && item.condition) {
        if (inventoryFilter(item, data)) {
          data[SPECIAL_DATA_KEYS.INVENTORY][item.name] = Math.max(
            0,
            (data[SPECIAL_DATA_KEYS.INVENTORY][item] || 0) + delta
          );
        }
      } else if (typeof item === "string") {
        // Initialize if it doesn't exist
        // apply given delta
        data[SPECIAL_DATA_KEYS.INVENTORY][item] = Math.max(
          0,
          (data[SPECIAL_DATA_KEYS.INVENTORY][item] || 0) + delta
        );
      }
    });
  }
};

/**
 * If action.type === ACTIONS.UPDATE_INVENTORY.type, updates the inventory with an inventory list in `action.data`
 *
 * @param {Object} state
 * @param {Object} action
 * @return {Object}
 */
export function updateInventoryReducer(state, action) {
  if (action.type === ACTIONS.UPDATE_INVENTORY.type) {
    const data = cloneData(state.data);
    Object.entries(action.items).forEach(([key, value]) => {
      updateInventory(data, key, value);
    });
    return {
      ...state,
      data,
    };
  }

  return {
    ...state,
  };
}