'use strict';

var _rollupPluginBabelHelpers = require('../../_virtual/_rollupPluginBabelHelpers.js');
var classnames = require('classnames');
var PropTypes = require('prop-types');
var React = require('react');
var index = require('../Popover/index.js');
var keys = require('../../internal/keyboard/keys.js');
var match = require('../../internal/keyboard/match.js');
var useDelayedState = require('../../internal/useDelayedState.js');
var useId = require('../../internal/useId.js');
var useNoInteractiveChildren = require('../../internal/useNoInteractiveChildren.js');
var usePrefix = require('../../internal/usePrefix.js');
var useIsomorphicEffect = require('../../internal/useIsomorphicEffect.js');

function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }

var classnames__default = /*#__PURE__*/_interopDefault(classnames);
var PropTypes__default = /*#__PURE__*/_interopDefault(PropTypes);
var React__default = /*#__PURE__*/_interopDefault(React);

/**
 * Copyright IBM Corp. 2016, 2023
 *
 * This source code is licensed under the Apache-2.0 license found in the
 * LICENSE file in the root directory of this source tree.
 */


/**
 * Event types that trigger a "drag" to stop.
 */
const DRAG_STOP_EVENT_TYPES = new Set(['mouseup', 'touchend', 'touchcancel']);
// eslint-disable-next-line react/display-name -- https://github.com/carbon-design-system/carbon/issues/20452
const Tooltip = /*#__PURE__*/React__default.default.forwardRef(({
  as,
  align = 'top',
  className: customClassName,
  children,
  label,
  description,
  enterDelayMs = 100,
  leaveDelayMs = 300,
  defaultOpen = false,
  closeOnActivation = false,
  dropShadow = false,
  highContrast = true,
  // TODO: remove in v12, highContrast should not be configurable
  ...rest
}, ref) => {
  const tooltipRef = React.useRef(null);
  const [open, setOpen] = useDelayedState.useDelayedState(defaultOpen);
  const [isDragging, setIsDragging] = React.useState(false);
  const [focusByMouse, setFocusByMouse] = React.useState(false);
  const [isPointerIntersecting, setIsPointerIntersecting] = React.useState(false);
  const id = useId.useId('tooltip');
  const prefix = usePrefix.usePrefix();
  const child = React__default.default.Children.only(children);
  const {
    'aria-labelledby': ariaLabelledBy,
    'aria-describedby': ariaDescribedBy
  } = child?.props ?? {};
  const hasLabel = !!label;
  const labelledBy = ariaLabelledBy ?? (hasLabel ? id : undefined);
  const describedBy = ariaDescribedBy ?? (!hasLabel ? id : undefined);
  const triggerProps = {
    onFocus: () => !focusByMouse && setOpen(true),
    onBlur: () => {
      setOpen(false);
      setFocusByMouse(false);
    },
    onClick: () => closeOnActivation && setOpen(false),
    // This should be placed on the trigger in case the element is disabled
    onMouseEnter,
    onMouseLeave,
    onMouseDown,
    onMouseMove: onMouseMove,
    onTouchStart: onDragStart,
    'aria-labelledby': labelledBy,
    'aria-describedby': describedBy
  };

  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
  function getChildEventHandlers(childProps) {
    const eventHandlerFunctions = Object.keys(triggerProps).filter(prop => prop.startsWith('on'));
    const eventHandlers = {};
    eventHandlerFunctions.forEach(functionName => {
      eventHandlers[functionName] = evt => {
        triggerProps[functionName](evt);
        if (childProps?.[functionName]) {
          childProps?.[functionName](evt);
        }
      };
    });
    return eventHandlers;
  }
  const onKeyDown = React.useCallback(event => {
    if (open && match.match(event, keys.Escape)) {
      event.stopPropagation();
      setOpen(false);
    }
    if (open && closeOnActivation && (match.match(event, keys.Enter) || match.match(event, keys.Space))) {
      setOpen(false);
    }
  }, [closeOnActivation, open, setOpen]);
  useIsomorphicEffect(() => {
    if (!open) {
      return undefined;
    }
    function handleKeyDown(event) {
      if (match.match(event, keys.Escape)) {
        onKeyDown(event);
      }
    }
    document.addEventListener('keydown', handleKeyDown);
    return () => {
      document.removeEventListener('keydown', handleKeyDown);
    };
  }, [open, onKeyDown]);
  function onMouseEnter() {
    // Interactive Tags should not support onMouseEnter
    if (!rest?.onMouseEnter) {
      setIsPointerIntersecting(true);
      setOpen(true, enterDelayMs);
    }
  }
  function onMouseDown() {
    setFocusByMouse(true);
    onDragStart();
  }
  function onMouseLeave() {
    setIsPointerIntersecting(false);
    if (isDragging) {
      return;
    }
    setOpen(false, leaveDelayMs);
  }
  function onMouseMove(evt) {
    if (evt.buttons === 1) {
      setIsDragging(true);
    } else {
      setIsDragging(false);
    }
  }
  function onDragStart() {
    setIsDragging(true);
  }
  const onDragStop = React.useCallback(() => {
    setIsDragging(false);
    // Close the tooltip, unless the mouse pointer is within the bounds of the
    // trigger.
    if (!isPointerIntersecting) {
      setOpen(false, leaveDelayMs);
    }
  }, [isPointerIntersecting, leaveDelayMs, setOpen]);
  useNoInteractiveChildren.useNoInteractiveChildren(tooltipRef, 'The Tooltip component must have no interactive content rendered by the' + '`label` or `description` prop');
  React.useEffect(() => {
    if (isDragging) {
      // Register drag stop handlers.
      DRAG_STOP_EVENT_TYPES.forEach(eventType => {
        document.addEventListener(eventType, onDragStop);
      });
    }
    return () => {
      DRAG_STOP_EVENT_TYPES.forEach(eventType => {
        document.removeEventListener(eventType, onDragStop);
      });
    };
  }, [isDragging, onDragStop]);
  return (
    /*#__PURE__*/
    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
    React__default.default.createElement(index.Popover, _rollupPluginBabelHelpers.extends({
      as: as,
      ref: ref
    }, rest, {
      align: align,
      className: classnames__default.default(`${prefix}--tooltip`, customClassName),
      dropShadow: dropShadow,
      highContrast: highContrast // TODO: v12 hard-set highContrast to true
      ,
      onKeyDown: onKeyDown,
      onMouseLeave: onMouseLeave,
      open: open
    }), /*#__PURE__*/React__default.default.createElement("div", {
      className: `${prefix}--tooltip-trigger__wrapper`
    }, typeof child !== 'undefined' ? /*#__PURE__*/React__default.default.cloneElement(child, {
      ...triggerProps,
      ...getChildEventHandlers(child.props)
    }) : null), /*#__PURE__*/React__default.default.createElement(index.PopoverContent, {
      "aria-hidden": open ? 'false' : 'true',
      className: `${prefix}--tooltip-content`,
      id: id,
      onMouseEnter: onMouseEnter,
      role: "tooltip"
    }, label || description))
  );
});
Tooltip.propTypes = {
  /**
   * Specify how the trigger should align with the tooltip
   */
  align: PropTypes__default.default.oneOf(['top', 'top-left',
  // deprecated use top-start instead
  'top-right',
  // deprecated use top-end instead

  'bottom', 'bottom-left',
  // deprecated use bottom-start instead
  'bottom-right',
  // deprecated use bottom-end instead

  'left', 'left-bottom',
  // deprecated use left-end instead
  'left-top',
  // deprecated use left-start instead

  'right', 'right-bottom',
  // deprecated use right-end instead
  'right-top',
  // deprecated use right-start instead

  // new values to match floating-ui
  'top-start', 'top-end', 'bottom-start', 'bottom-end', 'left-end', 'left-start', 'right-end', 'right-start']),
  /**
   * Pass in the child to which the tooltip will be applied
   */
  children: PropTypes__default.default.node,
  /**
   * Specify an optional className to be applied to the container node
   */
  className: PropTypes__default.default.string,
  /**
   * Determines wether the tooltip should close when inner content is activated (click, Enter or Space)
   */
  closeOnActivation: PropTypes__default.default.bool,
  /**
   * Specify whether the tooltip should be open when it first renders
   */
  defaultOpen: PropTypes__default.default.bool,
  /**
   * Provide the description to be rendered inside of the Tooltip. The
   * description will use `aria-describedby` and will describe the child node
   * in addition to the text rendered inside of the child. This means that if you
   * have text in the child node, that it will be announced alongside the
   * description to the screen reader.
   *
   * Note: if label and description are both provided, label will be used and
   * description will not be used
   */
  description: PropTypes__default.default.node,
  /**
   * Specify whether a drop shadow should be rendered
   */
  dropShadow: PropTypes__default.default.bool,
  /**
   * Specify the duration in milliseconds to delay before displaying the tooltip
   */
  enterDelayMs: PropTypes__default.default.number,
  /**
   * Render the component using the high-contrast theme
   */
  highContrast: PropTypes__default.default.bool,
  // TODO: remove in v12, highContrast should not be configurable

  /**
   * Provide the label to be rendered inside of the Tooltip. The label will use
   * `aria-labelledby` and will fully describe the child node that is provided.
   * This means that if you have text in the child node, that it will not be
   * announced to the screen reader.
   *
   * Note: if label and description are both provided, description will not be
   * used
   */
  label: PropTypes__default.default.node,
  /**
   * Specify the duration in milliseconds to delay before hiding the tooltip
   */
  leaveDelayMs: PropTypes__default.default.number
};

exports.Tooltip = Tooltip;
