import get from './get';
import getSortValue from './getSortValue';
import isEmpty from './isEmpty';

/**
 * Utility function to sort a list of items according a list of comparisons.
 * Each comparison can specify:
 *
 * - a comparison accessor, which is either a string object path or an accessor
 *   function;
 * - an optional direction to sort the comparison;
 * - an optional type for the comparison if the native value types are
 *   incorrect for sorting purposes; and
 * - [additional options to pass to
 *   `getSortValue`](./?path=/docs/utils-sorting-getSortValue).
 *
 * For convenience, comparisons can be specified as strings, in which case the
 * native type will be used and the result will be an ascending sort.
 *
 * Note that `sortBy` always copies the input array before sorting it, so it's
 * safe to call without explicitly copying the array first.
 *
 * ```js
 *
 * import { sortBy } from '@politico/interactive-style';
 * ```
 *
 * @param {any[]} items An array of items to sort
 * @param {string|Object[]} comparisons
 *   An array of comparisons to use, in order, to sort the items. Each
 *   comparison can be a string or an object. If you provide a comparison as
 *   a string, it is interpreted as the object `{ comparison: <string> }`.
 *   A comparison object can specify:
 *   - `comparison`: an accessor function that returns the appropriate value
 *     from an item or a string path to an attribute of an object (required).
 *   - `type`: an optional explicit override for the type to use in the
 *     comparison (forwarded to `getSortValue`). If not provided, the values
 *     are introspected with the `typeof` operator to determine the correct
 *     type to use.
 *   - `direction`: the direction of the sort (forwarded to `getSortValue`).
 *   - All other options are forwarded to `getSortValue` in its configuration
 *     object.
 *
 * @returns {any[]}
 */
const sortBy = function sortBy(items, comparisons = []) {
  const comps = comparisons.length > 0 ? comparisons : [null];
  const normalizeComparison = rawComparison => {
    if (!rawComparison) {
      return {
        comparison: d => d
      };
    }
    if (typeof rawComparison === 'object') {
      return rawComparison;
    }
    return {
      comparison: rawComparison
    };
  };
  const getDefaultType = (a, b, accessor) => {
    const aVal = get(a, accessor);
    const bVal = get(b, accessor);
    return isEmpty(aVal) ? typeof bVal : typeof aVal;
  };
  const sorter = comps.reduceRight((fallback, rawComparison) => {
    const {
      comparison: accessor,
      type,
      direction = 'asc',
      ...restOpts
    } = normalizeComparison(rawComparison);
    return (a, b) => getSortValue(a, b, direction, type || getDefaultType(a, b, accessor), {
      ...restOpts,
      getValue: value => get(value, accessor),
      fallback: fallback || undefined
    });
  }, null);
  return [...items].sort(sorter);
};
export default sortBy;