text: import isNumeric from '../utils/isNumeric';
  import getClientRect from '../utils/getClientRect';
  import find from '../utils/find';
  
  /**
   * Converts a string containing value + unit into a px value number
   * @function
   * @memberof {modifiers~offset}
   * @private
   * @argument {String} str - Value + unit string
   * @argument {String} measurement - `height` or `width`
   * @argument {Object} popperOffsets
   * @argument {Object} referenceOffsets
   * @returns {Number|String}
   * Value in pixels, or original string if no values were extracted
   */
  export function toValue(str, measurement, popperOffsets, referenceOffsets) {
      // separate value from unit
      const split = str.match(/((?:\-|\+)?\d*\.?\d*)(.*)/);
      const value = +split[1];
      const unit = split[2];
  
      // If it's not a number it's an operator, I guess
      if (!value) {
          return str;
      }
  
      if (unit.indexOf('%') === 0) {
          let element;
          switch (unit) {
              case '%p':
                  element = popperOffsets;
                  break;
              case '%':
              case '%r':
              default:
                  element = referenceOffsets;
          }
  
          const rect = getClientRect(element);
          return rect[measurement] / 100 * value;
      } else if (unit === 'vh' || unit === 'vw') {
          // if is a vh or vw, we calculate the size based on the viewport
          let size;
          if (unit === 'vh') {
              size = Math.max(
                  document.documentElement.clientHeight,
                  window.innerHeight || 0
              );
          } else {
              size = Math.max(
                  document.documentElement.clientWidth,
                  window.innerWidth || 0
              );
          }
          return size / 100 * value;
      } else {
          // if is an explicit pixel unit, we get rid of the unit and keep the value
          // if is an implicit unit, it's px, and we return just the value
          return value;
      }
  }
  
  /**
   * Parse an `offset` string to extrapolate `x` and `y` numeric offsets.
   * @function
   * @memberof {modifiers~offset}
   * @private
   * @argument {String} offset
   * @argument {Object} popperOffsets
   * @argument {Object} referenceOffsets
   * @argument {String} basePlacement
   * @returns {Array} a two cells array with x and y offsets in numbers
   */
  export function parseOffset(
      offset,
      popperOffsets,
      referenceOffsets,
      basePlacement
  ) {
      const offsets = [0, 0];
  
      // Use height if placement is left or right and index is 0 otherwise use width
      // in this way the first offset will use an axis and the second one
      // will use the other one
      const useHeight = ['right', 'left'].indexOf(basePlacement) !== -1;
  
      // Split the offset string to obtain a list of values and operands
      // The regex addresses values with the plus or minus sign in front (+10, -20, etc)
      const fragments = offset.split(/(\+|\-)/).map(frag => frag.trim());
  
      // Detect if the offset string contains a pair of values or a single one
      // they could be separated by comma or space
      const divider = fragments.indexOf(
          find(fragments, frag => frag.search(/,|\s/) !== -1)
      );
  
      if (fragments[divider] && fragments[divider].indexOf(',') === -1) {
          console.warn(
              'Offsets separated by white space(s) are deprecated, use a comma (,) instead.'
          );
      }
  
      // If divider is found, we divide the list of values and operands to divide
      // them by ofset X and Y.
      const splitRegex = /\s*,\s*|\s+/;
      let ops = divider !== -1
          ? [
              fragments
                  .slice(0, divider)
                  .concat([fragments[divider].split(splitRegex)[0]]),
              [fragments[divider].split(splitRegex)[1]].concat(
                  fragments.slice(divider + 1)
              ),
          ]
          : [fragments];
  
      // Convert the values with units to absolute pixels to allow our computations
      ops = ops.map((op, index) => {
          // Most of the units rely on the orientation of the popper
          const measurement = (index === 1 ? !useHeight : useHeight)
              ? 'height'
              : 'width';
          let mergeWithPrevious = false;
          return (
              op
                  // This aggregates any `+` or `-` sign that aren't considered operators
                  // e.g.: 10 + +5 => [10, +, +5]
                  .reduce((a, b) => {
                      if (a[a.length - 1] === '' && ['+', '-'].indexOf(b) !== -1) {
                          a[a.length - 1] = b;
                          mergeWithPrevious = true;
                          return a;
                      } else if (mergeWithPrevious) {
                          a[a.length - 1] += b;
                          mergeWithPrevious = false;
                          return a;
                      } else {
                          return a.concat(b);
                      }
                  }, [])
                  // Here we convert the string values into number values (in px)
                  .map(str => toValue(str, measurement, popperOffsets, referenceOffsets))
          );
      });
  
      // Loop trough the offsets arrays and execute the operations
      ops.forEach((op, index) => {
          op.forEach((frag, index2) => {
              if (isNumeric(frag)) {
                  offsets[index] += frag * (op[index2 - 1] === '-' ? -1 : 1);
              }
          });
      });
      return offsets;
  }
  
  /**
   * @function
   * @memberof Modifiers
   * @argument {Object} data - The data object generated by update method
   * @argument {Object} options - Modifiers configuration and options
   * @argument {Number|String} options.offset=0
   * The offset value as described in the modifier description
   * @returns {Object} The data object, properly modified
   */
  export default function offset(data, { offset }) {
      const { placement, offsets: { popper, reference } } = data;
      const basePlacement = placement.split('-')[0];
  
      let offsets;
      if (isNumeric(+offset)) {
          offsets = [+offset, 0];
      } else {
          offsets = parseOffset(offset, popper, reference, basePlacement);
      }
  
      if (basePlacement === 'left') {
          popper.top += offsets[0];
          popper.left -= offsets[1];
      } else if (basePlacement === 'right') {
          popper.top += offsets[0];
          popper.left += offsets[1];
      } else if (basePlacement === 'top') {
          popper.left += offsets[0];
          popper.top -= offsets[1];
      } else if (basePlacement === 'bottom') {
          popper.left += offsets[0];
          popper.top += offsets[1];
      }
  
      data.popper = popper;
      return data;
  }