Source: utils/handlebarsHelpers.js

import { updateData } from "../state/reducers/followLink";
import { updateInventory } from "../state/reducers/updateInventory";
import { ACTIONS, SPECIAL_DATA_KEYS } from "./reducerUtils";

const ARG_SPLIT_REGEX = /\s+/;

/**
 * Returns `context.fn(options.genmo.getData())` if we have all the items in `context.hash.items`
 *
 * @param {Object} handlebarsOptions Handlebars context object
 * @param {Object} options Options to process inventoryHas
 * @param {Genmo} options.genmo The genmo object to check
 * @param {Boolean} options.inverse Set to true if we should check if we don't have the items
 * @returns {String}
 */
const inventoryHasHelper = (handlebarsOptions, options) => {
  if (!options.genmo) {
    return "";
  }

  const items = handlebarsOptions.hash["items"].split(ARG_SPLIT_REGEX);

  if (options.inverse) {
    let hasNoItems = true;
    items.forEach((item) => {
      if (options.genmo.hasItem(item)) {
        hasNoItems = false;
      }
    });
    if (hasNoItems) {
      return handlebarsOptions.fn(options.genmo.getData());
    }
  } else {
    let hasAllItems = true;
    items.forEach((item) => {
      if (!options.genmo.hasItem(item)) {
        hasAllItems = false;
      }
    });
    if (hasAllItems) {
      return handlebarsOptions.fn(options.genmo.getData());
    }
  }

  return "";
};

const changedHelper = (handlebarsOptions, options) => {
  if (!options.genmo) {
    return "";
  }

  let keys = handlebarsOptions.hash["keys"];
  if (!keys) {
    keys = handlebarsOptions.hash["inventory"];
    options.inventory = true;
  }
  if (!keys) {
    return "";
  }
  keys = keys.split(ARG_SPLIT_REGEX);
  const snapshot = options.genmo.getMostRecentSnapshotWithActionType(
    ACTIONS.FOLLOW_LINK.type
  );

  if (snapshot) {
    let success = true;
    keys.forEach((key) => {
      let beforeData = snapshot.beforeState.data;
      let afterData = snapshot.afterState.data;
      if (options.inventory) {
        beforeData = snapshot.beforeState.data[SPECIAL_DATA_KEYS.INVENTORY];
        afterData = snapshot.afterState.data[SPECIAL_DATA_KEYS.INVENTORY];
      }
      if (options.inverse) {
        if (beforeData[key] !== afterData[key]) {
          success = false;
        }
      } else {
        if (beforeData[key] === afterData[key]) {
          success = false;
        }
      }
    });

    if (success) {
      return handlebarsOptions.fn(options.genmo.getData());
    }
  }

  return "";
};

/**
 * Will go through the Handlebars context, taking all key/value attributes
 * and updating `options.data` with their values (overwriting)
 *
 * @param {Object} handlebarsOptions Handlebars Context Object
 * @param {Object} options additional parameters
 * @param {Object} options.data The data object to modify
 * @param {Passage} options.currentPassage The current passage we are modifying, passed in to the helper creator
 * @returns {Object}
 */
const setDataHelper = (handlebarsOptions, options) => {
  if (!options.data) {
    return "";
  }

  const dataObj = {};
  Object.keys(handlebarsOptions.hash).forEach((key) => {
    let value;
    try {
      value = JSON.parse(handlebarsOptions.hash[key]);
    } catch (e) {
      value = handlebarsOptions.hash[key];
    }

    if (options.isPassageData) {
      if (!dataObj.passage_data) {
        dataObj.passage_data = {};
      }
      dataObj.passage_data[key] = value;
    } else {
      dataObj[key] = value;
    }
  });

  updateData(dataObj, {
    data: options.data,
    currentPassage: options.currentPassage,
  });

  return options.isPassageData
    ? `passage data set! ${Object.entries(handlebarsOptions.hash)}`
    : "";
};

const updateInventoryHelper = (handlebarsOptions, options) => {
  if (!options.data) {
    return "";
  }

  const delta = options.operation === "remove" ? -1 : 1;

  const itemsList = handlebarsOptions.hash["items"].split(ARG_SPLIT_REGEX);
  const condition = handlebarsOptions.hash["condition"];
  const items = [];
  itemsList.forEach((item) => {
    if (!condition) {
      items.push(item);
    } else {
      items.push({
        name: item,
        condition,
      });
    }
  });

  updateInventory(options.data, items, delta);

  return "";
};

/**
 * Returns a list of helpers to use in parsing passage text.
 *
 * @param {Genmo} genmo The genmo object
 * @returns {Object}
 */
export const getPassageHelpers = (genmo, otherHelpers = {}) => {
  return {
    inventory_has: (handlebarsOptions) =>
      inventoryHasHelper(handlebarsOptions, { genmo }),
    inventory_not_has: (handlebarsOptions) =>
      inventoryHasHelper(handlebarsOptions, { genmo, inverse: true }),
    changed: (handlebarsOptions) => changedHelper(handlebarsOptions, { genmo }),
    ...otherHelpers,
  };
};

/**
 * Returns a list of helpers to use when updating data upon following a link.
 *
 * @param {Object} data The state's data object. Will be updated in place.
 * @param {Passage} currentPassage The currentPassage object (i.e. the passage we are navigating to)
 * @returns {Object}
 */
export const getDataHelpers = (data, currentPassage) => {
  return {
    data_set: (handlebarsOptions) =>
      setDataHelper(handlebarsOptions, { data, currentPassage }),
    passage_data_set: (handlebarsOptions) =>
      setDataHelper(handlebarsOptions, {
        data,
        currentPassage,
        isPassageData: true,
      }),
    inventory_add: (handlebarsOptions) =>
      updateInventoryHelper(handlebarsOptions, {
        data,
        currentPassage,
        operation: "add",
      }),
    inventory_remove: (handlebarsOptions) =>
      updateInventoryHelper(handlebarsOptions, {
        data,
        currentPassage,
        operation: "remove",
      }),
  };
};