import _toConsumableArray from '@babel/runtime/helpers/toConsumableArray';
import _slicedToArray from '@babel/runtime/helpers/slicedToArray';
import 'core-js/modules/es.array.concat.js';
import 'core-js/modules/es.array.filter.js';
import 'core-js/modules/es.array.includes.js';
import 'core-js/modules/es.array.join.js';
import 'core-js/modules/es.array.splice.js';
import 'core-js/modules/es.object.to-string.js';
import 'core-js/modules/es.string.includes.js';
import * as React from 'react';
import { useLangDirection } from '../utils/useLangDirection.js';

/**
 * constant override to fix the calculated result returned from getMenuOffset in carbon
 */
var RTL_OFFSET_FIX = 15;

/**
 * This is used as the callback for menuOffset on Tooltips, OverflowMenus, and FlyoutMenus.
 * That callback returns the underlying FloatingMenu element, direction, trigger element, and flipped
 * prop. It grabs the window dimensions and bounds for the menu element and determines if it is
 * overflowing the window. It returns a string indicating which directions are overflowing using the
 * same order as positioning in CSS. top, right, bottom, left. So an element can overflow on the
 * 'top-left', 'right-bottom', 'bottom-left', etc, etc.
 *
 * @param {HTMLElement} menuBody The underlying carbon FloatingMenu element
 * @param {string} menuDirection The direction prop
 * @param {HTMLElement} menuButton The button triggering the menu
 */
var isOffscreen = function isOffscreen(parentElementTop, parentElementBottom, menuBody, menuDirection, menuButton /* , flipped, offset */) {
  var buttonRect = menuButton.getBoundingClientRect();
  var tooltipRect = menuBody.getBoundingClientRect();
  var inputTopAndTriggerButtonTopGap = parentElementTop ? buttonRect.top - parentElementTop : 0;
  var inputBottomAndTriggerButtonBottomGap = parentElementBottom ? parentElementBottom - buttonRect.bottom : 0;
  var windowWidth = window.innerWidth || document.documentElement.clientWidth;
  var windowHeight = window.innerHeight || document.documentElement.clientHeight;
  var offTop = buttonRect.top - tooltipRect.height - inputTopAndTriggerButtonTopGap < 0;
  var offLeft = buttonRect.left - tooltipRect.width < 0;
  var offRight = buttonRect.right + tooltipRect.width > windowWidth;
  var offBottom = buttonRect.bottom + tooltipRect.height + inputBottomAndTriggerButtonBottomGap > windowHeight;
  var T = offTop ? 'top' : '';
  var R = offRight ? 'right' : '';
  var B = offBottom ? 'bottom' : '';
  var L = offLeft ? 'left' : '';
  return [T, R, B, L].filter(Boolean).join('-');
};

/**
 * This is used as the callback for menuOffset ONLY on Tooltips. It's needed due to
 * truncation that might happen on Tooltip trigger, therefore we need to consider
 * right position of the trigger as well. This function is used as an enhancement for
 * the isOffscreen method.
 *
 * @param {HTMLElement} menuBody The underlying carbon FloatingMenu element
 * @param {string} menuDirection The direction prop
 * @param {HTMLElement} menuButton The button triggering the menu
 * @returns {string} 'start', 'center' or 'end'
 */
var getTooltipAlignment = function getTooltipAlignment(menuBody, menuDirection, menuButton) {
  var buttonRect = menuButton.getBoundingClientRect();
  var tooltipRect = menuBody.getBoundingClientRect();
  var windowWidth = window.innerWidth || document.documentElement.clientWidth;
  var triggerCenter = (buttonRect.left + buttonRect.right) / 2;
  var tooltipCenter = tooltipRect.width / 2;
  if (triggerCenter - tooltipCenter < 0) {
    return 'start';
  }
  if (triggerCenter + tooltipCenter > windowWidth) {
    return 'end';
  }
  return 'center';
};

/**
 * Given a subset of props from the Tooltip, OverflowMenu, or FlyoutMenu. This hook returns a decorator
 * function around the original menuOffset function. It determines if the element overflows using that
 * internal callback from the carbon FloatingMenu and adjusts the direction, flipped, and menuOffset
 * properties accordingly.
 */
var usePopoverPositioning = function usePopoverPositioning(_ref) {
  var menuOffset = _ref.menuOffset,
    _ref$direction = _ref.direction,
    direction = _ref$direction === void 0 ? 'bottom' : _ref$direction,
    _ref$flipped = _ref.flipped,
    flipped = _ref$flipped === void 0 ? false : _ref$flipped,
    _ref$isOverflowMenu = _ref.isOverflowMenu,
    isOverflowMenu = _ref$isOverflowMenu === void 0 ? false : _ref$isOverflowMenu,
    _ref$useAutoPositioni = _ref.useAutoPositioning,
    useAutoPositioning = _ref$useAutoPositioni === void 0 ? true : _ref$useAutoPositioni,
    _ref$defaultAlignment = _ref.defaultAlignment,
    defaultAlignment = _ref$defaultAlignment === void 0 ? 'center' : _ref$defaultAlignment,
    parentElementTop = _ref.parentElementTop,
    parentElementBottom = _ref.parentElementBottom;
  var _React$useState = React.useState(),
    _React$useState2 = _slicedToArray(_React$useState, 2),
    adjustedDirection = _React$useState2[0],
    setAdjustedDirection = _React$useState2[1];
  var _React$useState3 = React.useState(defaultAlignment),
    _React$useState4 = _slicedToArray(_React$useState3, 2),
    adjustedAlignment = _React$useState4[0],
    setAdjustedAlignment = _React$useState4[1];
  var _React$useState5 = React.useState(),
    _React$useState6 = _slicedToArray(_React$useState5, 2),
    adjustedFlipped = _React$useState6[0],
    setAdjustedFlipped = _React$useState6[1];
  var _React$useState7 = React.useState({}),
    _React$useState8 = _slicedToArray(_React$useState7, 2),
    flyoutAlignment = _React$useState8[0].flyoutAlignment,
    setDirections = _React$useState8[1];
  var previousDirection = React.useRef();
  var langDir = useLangDirection();
  React.useEffect(function () {
    previousDirection.current = direction;
    setAdjustedDirection(direction);
    setDirections({
      tooltipDirection: direction.split('-')[0],
      flyoutAlignment: direction.split('-')[1]
    });
    setAdjustedFlipped(flipped);
  }, [direction, flipped]);

  /**
   * if the original menuOffset is a function, splice the updated direction and flipped
   * properties into the callback to get the appropriate values based on the new state, not
   * the original props. Otherwise, just return the offset object passed.
   */
  var getOffset = React.useCallback(function () {
    for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
      args[_key] = arguments[_key];
    }
    // splice in adjusted direction and flip to calculate new appropriate offset.
    args.splice(1, 1, adjustedDirection);
    args.splice(3, 1, adjustedFlipped);
    return typeof menuOffset === 'function' ? menuOffset.apply(void 0, args) : menuOffset;
  }, [adjustedDirection, adjustedFlipped, menuOffset]);
  var getAdjustedOffset = React.useCallback(function (tooltipElement, flyoutDirection, buttonElement) {
    var tooltipRect = tooltipElement.getBoundingClientRect();
    var buttonRect = flyoutAlignment ? buttonElement.parentNode.getBoundingClientRect() : buttonElement.getBoundingClientRect();
    var windowWidth = window.innerWidth || document.documentElement.clientWidth;
    var directionChange = "".concat(previousDirection.current, "->").concat(adjustedDirection);
    if (previousDirection.current !== adjustedDirection && flyoutAlignment) {
      switch (directionChange) {
        default:
          return {
            top: tooltipRect.y - buttonRect.y,
            left: tooltipRect.x - buttonRect.x
          };
      }
    }
    var left = 0;
    if (langDir === 'rtl' && isOverflowMenu) {
      left -= RTL_OFFSET_FIX;
    }
    if (previousDirection.current !== adjustedDirection) {
      switch (directionChange) {
        case 'bottom->left':
          return {
            top: tooltipRect.right >= windowWidth ? 9 : 0,
            left: tooltipRect.right >= windowWidth ? tooltipRect.right - buttonRect.left + 3 : 0
          };
        case 'top->left':
          {
            return {
              top: tooltipRect.right >= windowWidth ? 9 : 0,
              left: tooltipRect.right >= windowWidth ? tooltipRect.right - buttonRect.right + 3 : 0
            };
          }
        default:
          return {
            top: 0,
            left: left
          };
      }
    }

    // the flyout menus are off-by-one in either direction
    // depending on if it's left or right oriented.
    if (flyoutAlignment) {
      return {
        top: adjustedDirection.includes('right') ? -1 : adjustedDirection.includes('left') ? 1 : 0,
        left: left
      };
    }
    return {
      top: 0,
      left: left
    };
  }, [adjustedDirection, flyoutAlignment, isOverflowMenu, langDir]);

  /**
   * given the directions of overflow, adjust the direction of the tooltip
   * or flyout menu to allow it to stay within the bounds of the screen.
   *
   * For example, a tooltip with given direction of `left` that overflows to the left will adjust
   * the direction to `right`. A flyout menu given `left-end` that overflows to the `top-left` will
   * be adjusted to `right-start`.
   */
  var fixOverflow = React.useCallback(function (overflow, menuOffsetArgs, alignment) {
    var _menuOffsetArgs = _slicedToArray(menuOffsetArgs, 1),
      tooltipElement = _menuOffsetArgs[0];
    setAdjustedAlignment(alignment);
    switch (overflow) {
      case 'top':
        if (flyoutAlignment) {
          setAdjustedDirection("bottom-".concat(flyoutAlignment));
          tooltipElement.setAttribute('data-floating-menu-direction', 'bottom');
        } else {
          setAdjustedDirection('bottom');
        }
        break;
      case 'right':
      case 'top-right':
        if (direction === 'bottom-end') {
          break;
        }
        if (flyoutAlignment) {
          // fixes an edge case where if the flyout is right-end,
          // and causes an overflow to the top-right, then we need to
          // switch it to a left-start to fix both the overflow on the top
          // and bottom at the same time.
          if (overflow === 'top-right' && flyoutAlignment === 'end') {
            setAdjustedDirection("left-start");
          } else {
            setAdjustedDirection("left-".concat(flyoutAlignment));
          }
          tooltipElement.setAttribute('data-floating-menu-direction', 'left');
        } else if (isOverflowMenu) {
          setAdjustedFlipped(true);
          setAdjustedDirection(function (prevDirection) {
            return prevDirection === 'top' ? 'top' : 'bottom';
          });
          tooltipElement.setAttribute('data-floating-menu-direction', 'bottom');
        } else {
          setAdjustedDirection('left');
        }
        break;
      case 'right-bottom':
        if (flyoutAlignment) {
          setAdjustedDirection('top-end');
          tooltipElement.setAttribute('data-floating-menu-direction', 'top');
        } else if (isOverflowMenu) {
          setAdjustedDirection('top');
          setAdjustedFlipped(true);
          tooltipElement.setAttribute('data-floating-menu-direction', 'top');
        } else {
          setAdjustedDirection('left');
        }
        break;
      case 'bottom':
        if (flyoutAlignment) {
          setAdjustedDirection("top-".concat(flyoutAlignment));
          tooltipElement.setAttribute('data-floating-menu-direction', 'top');
        } else {
          setAdjustedDirection('top');
        }
        break;
      case 'left':
      case 'top-left':
        if (flyoutAlignment) {
          setAdjustedDirection('right-start');
          tooltipElement.setAttribute('data-floating-menu-direction', 'right');
        } else if (isOverflowMenu) {
          setAdjustedFlipped(false);
          setAdjustedDirection(function (prevDirection) {
            return prevDirection === 'top' ? 'top' : 'bottom';
          });
          tooltipElement.setAttribute('data-floating-menu-direction', 'bottom');
        } else {
          setAdjustedDirection('right');
        }
        break;
      case 'bottom-left':
        if (flyoutAlignment) {
          setAdjustedDirection('top-start');
          tooltipElement.setAttribute('data-floating-menu-direction', 'top');
        } else if (isOverflowMenu) {
          setAdjustedDirection('top');
          setAdjustedFlipped(false);
          tooltipElement.setAttribute('data-floating-menu-direction', 'top');
        } else {
          setAdjustedDirection('right');
        }
        break;
      case 'top-bottom':
        if (direction === 'bottom-end') {
          break;
        }
        if (flyoutAlignment) {
          setAdjustedDirection("bottom-".concat(flyoutAlignment));
          tooltipElement.setAttribute('data-floating-menu-direction', 'bottom');
        } else {
          setAdjustedDirection('bottom');
        }
        break;
    }

    // given the original offset calculate the difference between the original
    // position and the new position and return that value.
    var adjustedOffset = getAdjustedOffset.apply(void 0, _toConsumableArray(menuOffsetArgs));
    var newOffset = getOffset.apply(void 0, _toConsumableArray(menuOffsetArgs));
    previousDirection.current = adjustedDirection;
    return {
      top: newOffset.top + adjustedOffset.top,
      left: newOffset.left + adjustedOffset.left
    };
  }, [adjustedDirection, direction, flyoutAlignment, getAdjustedOffset, getOffset, isOverflowMenu]);

  /**
   * calculate menu offset is returned from the hook as a wrapper around the default
   * menuOffset functions for each component. This function, as a callback passed as menuOffset,
   * to the tooltips, flyout menus, etc, checks if the element overflows and adjusts the direction
   * and offset accordingly if it's fixable.
   */
  var calculateMenuOffset = React.useCallback(function () {
    for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
      args[_key2] = arguments[_key2];
    }
    var defaultOffset = getOffset.apply(void 0, args);
    if (langDir === 'rtl' && isOverflowMenu) {
      defaultOffset.left -= RTL_OFFSET_FIX;
    }
    if (!useAutoPositioning) {
      return defaultOffset;
    }

    // determine if the element is off-screen.
    var overflow = isOffscreen.apply(void 0, [parentElementTop, parentElementBottom].concat(args, [defaultOffset]));

    // determine if the element has new alignment
    var alignment = getTooltipAlignment.apply(void 0, args);

    // if it's offscreen in a direction we can fix, do so
    // otherwise leave it be. ie. 'top-left' is fixable, but
    // a large element with 'top-right-left' wouldn't be.
    switch (overflow) {
      case 'top-right':
      case 'right-bottom':
      case 'bottom-left':
      case 'top-left':
      case 'top':
      case 'right':
      case 'bottom':
      case 'top-bottom':
        return fixOverflow(overflow, [].concat(args), alignment);
      case 'left':
        return fixOverflow(overflow, [].concat(args), alignment);
      default:
        return defaultOffset;
    }
  }, [fixOverflow, getOffset, isOverflowMenu, langDir, parentElementBottom, parentElementTop, useAutoPositioning]);

  /**
   * These adjusted valued are updated when the calculateMenuOffset is called, and are passed
   * to the tooltip, overflow menu, or flyout menu instead of the original given direction if the
   * element overflows.
   */
  return [calculateMenuOffset, {
    adjustedDirection: adjustedDirection,
    adjustedFlipped: adjustedFlipped,
    adjustedAlignment: adjustedAlignment
  }];
};

export { usePopoverPositioning };
