src/components/templates/ItemDetails/ModifierItem/withModifierItem.jsx

import React from "react";
import {config} from "utils";
import {useMenuContext} from "components/providers/Menu/MenuContext";

/**
 * @typedef   {object}    props
 * @property  {string}    [className] - additional classes for parent container
 * @property  {boolean}   [isOptionalSingle] - if this modifier item is maximum of 1 and is not required
 * @property  {object}    item - itemsHash data from menu context
 * @property  {boolean}   [multiSelect] - if this modifier item has a maximum greater than 1
 * @property  {function}  removeModifier - remove a modifier payload from parent state
 * @property  {function}  addModifier - add a modifier payload to parent state
 * @property  {object}    parent - parent payload (option and item with selected nested modifiers)
 * @property  {number}    [quantity] - current modifier quantity in parent state
 * @property  {string}    type - style cell type from theme
 */

/**
 * withModifierItem - HOC for modifier items
 * @param     {*}         Component
 * @param     {props}     Component.props - The {@link props} from the option
 * @return    {*}
 */
const withModifierItem = (Component) => (props) => {
  const {
    className = "",
    isOptionalSingle = true,
    isSingleSelect,
    item = {},
    multiSelect = false,
    removeModifier = () => {},
    addModifier = () => {},
    parent = {},
    quantity = 0,
    type = "optionItem",
  } = props;

  const {isNested, options, price} = item;

  const {optionsHash} = useMenuContext();

  // payload is the current modifier item data for adding and removing
  const payload = {
    // Add current modifier price to payload
    price,
    // Extend parent payload
    ...parent,
    // Populate modifiers with default options when none are present (adding new nested mod)
    ...(isNested &&
      !Object.keys(parent?.modifiers).length && {
        modifiers: options.reduce((acc, cur) => {
          return {...acc, [cur.id]: []};
        }, {}),
      }),
  };

  // handleClick toggles the parent container click action, ignored when parent is nested
  const handleClick = Boolean(isOptionalSingle || !quantity);
  const handleRemove =
    (isNested || isOptionalSingle || isSingleSelect) && quantity;

  // nestedItems is an array of objects with selected nested modifier item data
  const nestedItems =
    (isNested &&
      parent?.quantity &&
      Object.entries(parent?.modifiers).reduce((accu, [id, items]) => {
        if (!items?.length) {
          return accu;
        }

        let counter = 0;
        const currItems = items.filter(({item}, index) => {
          return items.indexOf({item}) === index;
        });

        const {name} = optionsHash[id];
        return [
          ...accu,
          {
            id,
            name,
          },
        ];
      }, [])) ||
    [];

  /**
   * @function increment - increase the current modifier quantity by 1, onClick of addition button
   */
  const increment = () => addModifier(payload);

  /**
   * @function decrement - decrease the current modifier quantity by 1, onClick of reduction button
   */
  const decrement = () => removeModifier(payload);

  /**
   * @function onClick - parent container click action (adds initial or render first nested drawer)
   * @param {*} e - event
   * @returns {function} - add or remove current modifier from parent state
   */
  const onClick = (e) => {
    if (!isNested && !handleClick) {
      return null;
    }

    e.stopPropagation();
    e.nativeEvent.stopImmediatePropagation();

    if (isNested || !quantity || multiSelect) {
      return addModifier(payload, quantity || 1);
    }
    return removeModifier(payload);
  };

  /**
   * additionalProps - render inputs for modifier items
   * @constant {object}
   */
  const additionalProps = {
    className,
    configType: config.theme.item_details.modifier_items,
    decrement,
    handleRemove,
    increment,
    item,
    multiSelect,
    nestedItems,
    onClick,
    quantity,
    type,
  };

  return (
    <Component
      {...props} // TODO: Safely remove unknown prop spreads...
      {...additionalProps}
    />
  );
};

export default withModifierItem;