Source: app/selectors/packageVoucherSelectors.js

import formValueSelector from 'redux-form/lib/formValueSelector';
import { createSelector } from 'reselect';
import FormNames from '../data/enum/FormNames';
import { shopFields } from '../data/enum/FieldNames/AccountFieldNames';
import { makeCollectionSelector } from './collectionSelector';
import { packageCollectionId } from '../data/collectionIds';
import { DISCOUNT_ITEM, VOUCHER } from '../data/entityTypes';

const EMPTY_OBJECT = {};
export const DEFAULT_LIMIT = 20;

/**
 * Select all of the packages available for the user
 * @returns {{}} Returns a array of packages
 */
export const packagesSelector = state => state.entities.subscriptionPackage;

/**
 * Selects the voucher based on the given VoucherCode
 * @returns {{}} Returns a voucher
 */
export const voucherSelector = (state, voucherCode) =>
  voucherCode ? state.entities[VOUCHER]?.[voucherCode.toLowerCase()] : EMPTY_OBJECT;

export const discountItemSelector = (state, voucherCode) =>
  voucherCode && state?.entities?.[DISCOUNT_ITEM]?.[voucherCode];

/**
 * Uses SubscriptionPackages and voucher entities. Looks-up the voucher and checks if the package
 * is eligible for a discount. Merges the package entity with the discount/voucher.
 * @returns {*[]} An array with packages
 */
export const makePackagesWithDiscountSelector = (formName = FormNames.CHECKOUT) => {
  const collectionSelector = makeCollectionSelector();
  const valueSelector = formValueSelector(formName);

  return createSelector(
    // Get package for current region
    (state, regionCode) =>
      collectionSelector(state, {
        collectionId: packageCollectionId({
          limit: DEFAULT_LIMIT,
          regionCode,
        }),
      }),

    // Get voucher code
    state => voucherSelector(state, valueSelector(state, shopFields.VOUCHER_CODE)),

    // Merge values
    ({ entities: packages }, voucher) => {
      const { discounts } = voucher;

      // We can return the packages if there aren't any discounts
      if (!discounts) {
        return packages;
      }

      // Returns subscriptionPackages with applied discount (if applicable)
      return packages.reduce((accumulator, packageData) => {
        // Find a discount for current packageId
        const discount = discounts.find(({ productIds }) =>
          productIds?.some(productId => productId === packageData.subscriptionProductId),
        );

        accumulator.push({
          ...packageData,
          discount,
        });

        return accumulator;
      }, []);
    },
  );
};

/**
 * Return the selected package.
 * @returns {*}
 */
export const makeSelectedPackageSelector = (formName = FormNames.CHECKOUT) => {
  const packagesWithDiscountSelector = makePackagesWithDiscountSelector(formName);
  const valueSelector = formValueSelector(formName);

  if (formName === FormNames.TRANSFER_TO_ONLINE_PACKAGE) {
    return createSelector(
      // Get all packages
      state => packagesSelector(state),

      // Get selected package
      state => valueSelector(state, shopFields.PACKAGE),

      // Find the package information
      (packages, packageId) => packages && packages[packageId],
    );
  }

  return createSelector(
    // Get packages with discounts applied to it
    packagesWithDiscountSelector,

    // Get selected package
    state => valueSelector(state, shopFields.PACKAGE),

    // Find the package
    (packages, packageId) => packages.find(a => a.id === packageId),
  );
};

/**
 * Return the next possible package to upgrade.
 * @returns {*}
 */
export const makeOffsetPackageSelector = (formName = FormNames.CHECKOUT, offset) => {
  const packagesWithDiscountSelector = makePackagesWithDiscountSelector(formName);
  const valueSelector = formValueSelector(formName);

  return createSelector(
    // Get packages with discounts applied to it
    packagesWithDiscountSelector,

    // Get selected package
    state => valueSelector(state, shopFields.PACKAGE),

    // Find the package
    (packages, packageId) => packages[packages.findIndex(a => a.id === packageId) + offset],
  );
};