import React, { useState, useEffect, useRef, useMemo, useCallback, useImperativeHandle } from 'react';
import bindElementResize, { unbind as unbindElementResize } from 'element-resize-event';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import helper from '../DOMHelper';
import positionUtils from './positionUtils';
import { getDOMNode } from '../utils';
import { useUpdateEffect } from '../utils';
var defaultProps = {
  containerPadding: 0,
  placement: 'right'
};

var usePosition = function usePosition(props, ref) {
  var placement = props.placement,
      preventOverflow = props.preventOverflow,
      containerPadding = props.containerPadding,
      container = props.container,
      triggerTarget = props.triggerTarget;
  var containerRef = useRef();
  var lastTargetRef = useRef();
  var defaultPosition = {
    positionLeft: 0,
    positionTop: 0,
    arrowOffsetLeft: null,
    arrowOffsetTop: null
  };

  var _useState = useState(defaultPosition),
      position = _useState[0],
      setPosition = _useState[1];

  var utils = useMemo(function () {
    return positionUtils({
      placement: placement,
      preventOverflow: preventOverflow,
      padding: containerPadding
    });
  }, [placement, preventOverflow, containerPadding]);
  var updatePosition = useCallback(
  /**
   * @param placementChanged  Whether the placement has changed
   * @param forceUpdateDOM Whether to update the DOM directly
   * @returns void
   */
  function (placementChanged, forceUpdateDOM) {
    if (placementChanged === void 0) {
      placementChanged = true;
    }

    if (!(triggerTarget === null || triggerTarget === void 0 ? void 0 : triggerTarget.current)) {
      return;
    }

    var targetElement = getDOMNode(triggerTarget);

    if (!helper.isElement(targetElement)) {
      throw new Error('`target` should return an HTMLElement');
    } //  If the target and placement do not change, the position is not updated.


    if (targetElement === lastTargetRef.current && !placementChanged) {
      return;
    }

    var overlay = getDOMNode(ref.current);
    var containerElement = helper.getContainer(typeof container === 'function' ? container() : container, helper.ownerDocument(ref.current).body);
    var posi = utils.calcOverlayPosition(overlay, targetElement, containerElement);

    if (forceUpdateDOM && overlay) {
      var _overlay$className, _overlay$className$ma;

      var preClassName = overlay === null || overlay === void 0 ? void 0 : (_overlay$className = overlay.className) === null || _overlay$className === void 0 ? void 0 : (_overlay$className$ma = _overlay$className.match(/(placement-\S+)/)) === null || _overlay$className$ma === void 0 ? void 0 : _overlay$className$ma[0];
      helper.removeClass(overlay, preClassName);
      helper.addClass(overlay, posi.positionClassName);
      helper.addStyle(overlay, {
        left: posi.positionLeft + "px",
        top: posi.positionTop + "px"
      });
    } else {
      setPosition(posi);
    }

    containerRef.current = containerElement;
    lastTargetRef.current = targetElement;
  }, [container, ref, triggerTarget, utils]);
  useEffect(function () {
    updatePosition(false);
    var overlay = getDOMNode(ref.current);
    var containerScrollListener;

    if (containerRef.current && preventOverflow) {
      var _containerRef$current;

      // Update the overlay position when the container scroll bar is scrolling
      containerScrollListener = helper.on(((_containerRef$current = containerRef.current) === null || _containerRef$current === void 0 ? void 0 : _containerRef$current.tagName) === 'BODY' ? window : containerRef.current, 'scroll', function () {
        return updatePosition(true, true);
      });
    } // Update the position when the window size changes


    var resizeListener = helper.on(window, 'resize', function () {
      return updatePosition(true, true);
    });

    if (overlay) {
      // Update the position when the size of the overlay changes
      bindElementResize(overlay, function () {
        return updatePosition(true, true);
      });
    }

    return function () {
      var _containerScrollListe;

      lastTargetRef.current = null;
      (_containerScrollListe = containerScrollListener) === null || _containerScrollListe === void 0 ? void 0 : _containerScrollListe.off();
      resizeListener === null || resizeListener === void 0 ? void 0 : resizeListener.off();

      if (overlay) {
        unbindElementResize(overlay);
      }
    };
  }, [preventOverflow, ref, updatePosition]);
  useUpdateEffect(function () {
    return updatePosition();
  }, [updatePosition, placement]);
  return [position, updatePosition];
};

var Position = /*#__PURE__*/React.forwardRef(function (props, ref) {
  var children = props.children,
      className = props.className;
  var childRef = React.useRef();

  var _usePosition = usePosition(props, childRef),
      position = _usePosition[0],
      updatePosition = _usePosition[1];

  var positionClassName = position.positionClassName,
      arrowOffsetLeft = position.arrowOffsetLeft,
      arrowOffsetTop = position.arrowOffsetTop,
      positionLeft = position.positionLeft,
      positionTop = position.positionTop;
  useImperativeHandle(ref, function () {
    return {
      get child() {
        return childRef.current;
      },

      updatePosition: updatePosition
    };
  });

  if (typeof children === 'function') {
    var childProps = {
      className: classNames(className, positionClassName),
      arrowOffsetLeft: arrowOffsetLeft,
      arrowOffsetTop: arrowOffsetTop,
      left: positionLeft,
      top: positionTop
    };
    return children(childProps, childRef);
  }

  return children;
});
Position.displayName = 'Position';
Position.defaultProps = defaultProps;
Position.propTypes = {
  className: PropTypes.string,
  children: PropTypes.func,
  container: PropTypes.oneOfType([PropTypes.func, PropTypes.any]),
  containerPadding: PropTypes.number,
  placement: PropTypes.any,
  preventOverflow: PropTypes.bool,
  triggerTarget: PropTypes.any
};
export default Position;