import _extends from "@babel/runtime/helpers/esm/extends";
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
import _isUndefined from "lodash/isUndefined";
import React, { useRef, useEffect, useImperativeHandle, useCallback } from 'react';
import get from 'lodash/get';
import isNil from 'lodash/isNil';
import { contains } from 'dom-lib';
import Overlay from './Overlay';
import { createChainedFunction, usePortal, useControlled } from '../utils';
import isOneOf from '../utils/isOneOf';

function mergeEvents(events, props) {
  if (events === void 0) {
    events = {};
  }

  if (props === void 0) {
    props = {};
  }

  var nextEvents = {};
  Object.keys(events).forEach(function (eventName) {
    if (events[eventName]) {
      var _props;

      nextEvents[eventName] = createChainedFunction(events[eventName], (_props = props) === null || _props === void 0 ? void 0 : _props[eventName]);
    }
  });
  return nextEvents;
}

/**
 * Useful for mouseover and mouseout.
 * In order to resolve the node entering the mouseover element, a mouseout event and a mouseover event will be triggered.
 * https://javascript.info/mousemove-mouseover-mouseout-mouseenter-mouseleave
 * @param handler
 * @param event
 */
function onMouseEventHandler(handler, event, delay) {
  var target = event.currentTarget;
  var related = event.relatedTarget || get(event, ['nativeEvent', 'toElement']);

  if ((!related || related !== target) && !contains(target, related)) {
    handler(event, delay);
  }
}

var defaultProps = {
  trigger: ['hover', 'focus'],
  placement: 'bottomStart',
  rootClose: true
};
var OverlayTrigger = /*#__PURE__*/React.forwardRef(function (props, ref) {
  var children = props.children,
      container = props.container,
      controlId = props.controlId,
      defaultOpen = props.defaultOpen,
      trigger = props.trigger,
      disabled = props.disabled,
      readOnly = props.readOnly,
      plaintext = props.plaintext,
      openProp = props.open,
      delay = props.delay,
      delayOpenProp = props.delayOpen,
      delayCloseProp = props.delayClose,
      enterable = props.enterable,
      placement = props.placement,
      speaker = props.speaker,
      onClick = props.onClick,
      onMouseOver = props.onMouseOver,
      onMouseOut = props.onMouseOut,
      onFocus = props.onFocus,
      onBlur = props.onBlur,
      onClose = props.onClose,
      rest = _objectWithoutPropertiesLoose(props, ["children", "container", "controlId", "defaultOpen", "trigger", "disabled", "readOnly", "plaintext", "open", "delay", "delayOpen", "delayClose", "enterable", "placement", "speaker", "onClick", "onMouseOver", "onMouseOut", "onFocus", "onBlur", "onClose"]);

  var _usePortal = usePortal({
    container: container
  }),
      Portal = _usePortal.Portal;

  var triggerRef = useRef();
  var overlayRef = useRef();

  var _useControlled = useControlled(openProp, defaultOpen),
      open = _useControlled[0],
      setOpen = _useControlled[1]; // Delay the timer to close/open the overlay
  // When the cursor moves from the trigger to the overlay, the overlay will be closed.
  // In order to keep the overlay open, a timer is used to delay the closing.


  var delayOpenTimer = useRef();
  var delayCloseTimer = useRef();
  var delayOpen = isNil(delayOpenProp) ? delay : delayOpenProp;
  var delayClose = isNil(delayCloseProp) ? delay : delayCloseProp; // Whether the cursor is on the overlay

  var isOnOverlay = useRef(false); // Whether the cursor is on the trigger

  var isOnTrigger = useRef(false);
  useEffect(function () {
    return function () {
      clearTimeout(delayOpenTimer.current);
      clearTimeout(delayCloseTimer.current);
    };
  }, []);
  var handleOpen = useCallback(function (delay) {
    var ms = _isUndefined(delay) ? delayOpen : delay;

    if (ms && typeof ms === 'number') {
      return delayOpenTimer.current = setTimeout(function () {
        delayOpenTimer.current = null;
        setOpen(true);
      }, ms);
    }

    setOpen(true);
  }, [delayOpen, setOpen]);
  var handleClose = useCallback(function (delay) {
    var ms = _isUndefined(delay) ? delayClose : delay;

    if (ms && typeof ms === 'number') {
      return delayCloseTimer.current = setTimeout(function () {
        delayCloseTimer.current = null;
        setOpen(false);
      }, ms);
    }

    setOpen(false);
  }, [delayClose, setOpen]);
  useImperativeHandle(ref, function () {
    return {
      get root() {
        return triggerRef.current;
      },

      get overlay() {
        var _overlayRef$current;

        return (_overlayRef$current = overlayRef.current) === null || _overlayRef$current === void 0 ? void 0 : _overlayRef$current.child;
      },

      open: handleOpen,
      close: handleClose,
      updatePosition: function updatePosition() {
        var _overlayRef$current2, _overlayRef$current2$;

        (_overlayRef$current2 = overlayRef.current) === null || _overlayRef$current2 === void 0 ? void 0 : (_overlayRef$current2$ = _overlayRef$current2.updatePosition) === null || _overlayRef$current2$ === void 0 ? void 0 : _overlayRef$current2$.call(_overlayRef$current2);
      }
    };
  });
  /**
   * Close after the cursor leaves.
   */

  var handleCloseWhenLeave = useCallback(function () {
    // When the cursor is not on the overlay and not on the trigger, it is closed.
    if (!isOnOverlay.current && !isOnTrigger.current) {
      handleClose();
    }
  }, [handleClose]);
  /**
   * Toggle open and closed state.
   */

  var handleOpenState = useCallback(function () {
    if (open) {
      handleCloseWhenLeave();
    } else {
      handleOpen();
    }
  }, [open, handleCloseWhenLeave, handleOpen]);
  var handleDelayedOpen = useCallback(function () {
    if (!enterable) {
      return handleOpen();
    }

    isOnTrigger.current = true;

    if (!isNil(delayCloseTimer.current)) {
      clearTimeout(delayCloseTimer.current);
      delayCloseTimer.current = null;
      return handleOpen();
    }

    if (open) {
      return;
    }

    handleOpen();
  }, [enterable, open, handleOpen]);
  var handleDelayedClose = useCallback(function () {
    if (!enterable) {
      handleClose();
    }

    isOnTrigger.current = false;

    if (!isNil(delayOpenTimer.current)) {
      clearTimeout(delayOpenTimer.current);
      delayOpenTimer.current = null;
      return;
    }

    if (!open || !isNil(delayCloseTimer.current)) {
      return;
    }

    delayCloseTimer.current = setTimeout(function () {
      clearTimeout(delayCloseTimer.current);
      delayCloseTimer.current = null;
      handleCloseWhenLeave();
    }, 200);
  }, [enterable, open, handleClose, handleCloseWhenLeave]);
  var handleSpeakerMouseEnter = useCallback(function () {
    isOnOverlay.current = true;
  }, []);
  var handleSpeakerMouseLeave = useCallback(function () {
    isOnOverlay.current = false;

    if (!isOneOf('click', trigger) && !isOneOf('active', trigger)) {
      handleCloseWhenLeave();
    }
  }, [handleCloseWhenLeave, trigger]);
  var triggerEvents = {
    onClick: onClick,
    onMouseOver: onMouseOver,
    onMouseOut: onMouseOut,
    onFocus: onFocus,
    onBlur: onBlur
  };

  if (!disabled && !readOnly && !plaintext) {
    if (isOneOf('click', trigger)) {
      triggerEvents.onClick = createChainedFunction(handleOpenState, triggerEvents.onClick);
    }

    if (isOneOf('active', trigger)) {
      triggerEvents.onClick = createChainedFunction(handleDelayedOpen, triggerEvents.onClick);
    }

    if (isOneOf('hover', trigger)) {
      var onMouseOverListener = null;
      var onMouseOutListener = null;

      if (trigger !== 'none') {
        onMouseOverListener = function onMouseOverListener(e) {
          return onMouseEventHandler(handleDelayedOpen, e);
        };

        onMouseOutListener = function onMouseOutListener(e) {
          return onMouseEventHandler(handleDelayedClose, e);
        };
      }

      triggerEvents.onMouseOver = createChainedFunction(onMouseOverListener, onMouseOver);
      triggerEvents.onMouseOut = createChainedFunction(onMouseOutListener, onMouseOut);
    }

    if (isOneOf('focus', trigger)) {
      triggerEvents.onFocus = createChainedFunction(handleDelayedOpen, onFocus);
      triggerEvents.onBlur = createChainedFunction(handleDelayedClose, onBlur);
    }
  }

  var renderOverlay = function renderOverlay() {
    var overlayProps = _extends({}, rest, {
      triggerTarget: triggerRef,
      onClose: trigger !== 'none' ? createChainedFunction(handleClose, onClose) : undefined,
      placement: placement,
      container: container,
      open: open
    });

    var speakerProps = {
      id: controlId
    }; // The purpose of adding mouse entry and exit events to the Overlay is to record whether the current cursor is on the Overlay.
    // When `trigger` is equal to `hover`, if the cursor leaves the `triggerTarget` and stays on the Overlay,
    // the Overlay will continue to remain open.

    if (trigger !== 'none' && enterable) {
      speakerProps.onMouseEnter = handleSpeakerMouseEnter;
      speakerProps.onMouseLeave = handleSpeakerMouseLeave;
    }

    return /*#__PURE__*/React.createElement(Overlay, _extends({}, overlayProps, {
      ref: overlayRef,
      childrenProps: speakerProps
    }), typeof speaker === 'function' ? function (props, ref) {
      return speaker(_extends({}, props, {
        onClose: handleClose
      }), ref);
    } : speaker);
  };

  return /*#__PURE__*/React.createElement(React.Fragment, null, typeof children === 'function' ? children(triggerEvents, triggerRef) : /*#__PURE__*/React.cloneElement(children, _extends({
    ref: triggerRef,
    'aria-describedby': controlId
  }, mergeEvents(triggerEvents, children.props))), /*#__PURE__*/React.createElement(Portal, null, renderOverlay()));
});
OverlayTrigger.displayName = 'OverlayTrigger';
OverlayTrigger.defaultProps = defaultProps;
export default OverlayTrigger;