import { extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js';
import classnames from 'classnames';
import PropTypes from 'prop-types';
import { deprecateValuesWithin } from '../../prop-types/deprecateValuesWithin.js';
import React__default, { useRef, useMemo, useEffect } from 'react';
import useIsomorphicEffect from '../../internal/useIsomorphicEffect.js';
import { useMergedRefs } from '../../internal/useMergedRefs.js';
import { usePrefix } from '../../internal/usePrefix.js';
import { useEvent, useWindowEvent } from '../../internal/useEvent.js';
import { mapPopoverAlign } from '../../tools/mapPopoverAlign.js';
import { useFloating } from '../../../../../@floating-ui/react/dist/floating-ui.react.js';
import { useFeatureFlag } from '../FeatureFlags/index.js';
import { offset, flip, arrow, hide } from '../../../../../@floating-ui/react-dom/dist/floating-ui.react-dom.js';
import { autoUpdate } from '../../../../../@floating-ui/dom/dist/floating-ui.dom.js';

/**
 * 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.
 */


const PopoverContext = /*#__PURE__*/React__default.createContext({
  setFloating: {
    current: null
  },
  caretRef: {
    current: null
  },
  autoAlign: null
});

/**
 * Deprecated popover alignment values.
 * @deprecated Use NewPopoverAlignment instead.
 */

const Popover = /*#__PURE__*/React__default.forwardRef(function PopoverRenderFunction({
  isTabTip,
  align: initialAlign = isTabTip ? 'bottom-start' : 'bottom',
  as: BaseComponent = 'span',
  autoAlign = false,
  autoAlignBoundary,
  backgroundToken = 'layer',
  caret = isTabTip ? false : true,
  className: customClassName,
  children,
  border = false,
  dropShadow = true,
  highContrast = false,
  onRequestClose,
  open,
  alignmentAxisOffset,
  ...rest
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
},
//this is a workaround, have to come back and fix this.
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
forwardRef) {
  const prefix = usePrefix();
  const floating = useRef(null);
  const caretRef = useRef(null);
  const popover = useRef(null);
  const enableFloatingStyles = useFeatureFlag('enable-v12-dynamic-floating-styles') || autoAlign;
  const lastClickWasInsidePopoverContent = useRef(false);
  let align = mapPopoverAlign(initialAlign);

  // Tracks clicks inside PopoverContent to prevent it from closing when clicked, this handles an edge
  // case where the popover will close when clicking non-focusable elements (e.g. text)
  useEvent(popover, 'mousedown', event => {
    const target = event.target;
    lastClickWasInsidePopoverContent.current = refs.floating.current?.contains(target) || false;

    // reset flag
    if (lastClickWasInsidePopoverContent.current) {
      setTimeout(() => {
        lastClickWasInsidePopoverContent.current = false;
      }, 0);
    }
  });

  // The `Popover` should close whenever it and its children loses focus
  useEvent(popover, 'focusout', event => {
    const relatedTarget = event.relatedTarget;
    if (!relatedTarget) {
      // do not close if PopoverContent was clicked
      if (lastClickWasInsidePopoverContent.current) {
        lastClickWasInsidePopoverContent.current = false;
        return;
      }
      onRequestClose?.();
    } else if (relatedTarget && !popover.current?.contains(relatedTarget)) {
      const isOutsideFloating = enableFloatingStyles && refs.floating.current ? !refs.floating.current.contains(relatedTarget) : true;
      const isFocusableWrapper = relatedTarget && popover.current && relatedTarget.contains(popover.current);

      // Only close if focus moved outside both containers and not to an interactive parent wrapper
      if (isOutsideFloating && !isFocusableWrapper) {
        onRequestClose?.();
      }
    }
  });
  useWindowEvent('click', ({
    target
  }) => {
    if (open && target instanceof Node && !popover.current?.contains(target)) {
      onRequestClose?.();
    }
  });

  // Slug styling places a border around the popover content so the caret
  // needs to be placed 1px further outside the popover content. To do so,
  // we look to see if any of the children has a className containing "slug"
  const initialCaretHeight = React__default.Children.toArray(children).some(x => {
    return (
      // eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
      x?.props?.className?.includes('slug') ||
      // eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
      x?.props?.className?.includes('ai-label')
    );
  }) ? 7 : 6;
  // These defaults match the defaults defined in packages/styles/scss/components/popover/_popover.scss
  const popoverDimensions = useRef({
    offset: 10,
    caretHeight: initialCaretHeight
  });
  useIsomorphicEffect(() => {
    // The popover is only offset when a caret is present. Technically, the custom properties
    // accessed below can be set by a user even if caret=false, but doing so does not follow
    // the design specification for Popover.
    if (caret && popover.current) {
      // Gather the dimensions of the caret and prefer the values set via custom properties.
      // If a value is not set via a custom property, provide a default value that matches the
      // default values defined in the sass style file
      const getStyle = window.getComputedStyle(popover.current, null);
      const offsetProperty = getStyle.getPropertyValue(`--${prefix}-popover-offset`);
      const caretProperty = getStyle.getPropertyValue(`--${prefix}-popover-caret-height`);

      // Handle if the property values are in px or rem.
      // We want to store just the base number value without a unit suffix
      if (offsetProperty) {
        popoverDimensions.current.offset = offsetProperty.includes('px') ? Number(offsetProperty.split('px', 1)[0]) * 1 : Number(offsetProperty.split('rem', 1)[0]) * 16;
      }
      if (caretProperty) {
        popoverDimensions.current.caretHeight = caretProperty.includes('px') ? Number(caretProperty.split('px', 1)[0]) * 1 : Number(caretProperty.split('rem', 1)[0]) * 16;
      }
    }
  });
  const {
    refs,
    floatingStyles,
    placement,
    middlewareData
  } = useFloating(enableFloatingStyles ? {
    placement: align,
    // The floating element is positioned relative to its nearest
    // containing block (usually the viewport). It will in many cases also
    // “break” the floating element out of a clipping ancestor.
    // https://floating-ui.com/docs/misc#clipping
    strategy: 'fixed',
    // Middleware order matters, arrow should be last
    middleware: [offset(!isTabTip ? {
      alignmentAxis: alignmentAxisOffset,
      mainAxis: popoverDimensions?.current?.offset
    } : 0), autoAlign && flip({
      fallbackPlacements: isTabTip ? align.includes('bottom') ? ['bottom-start', 'bottom-end', 'top-start', 'top-end'] : ['top-start', 'top-end', 'bottom-start', 'bottom-end'] : align.includes('bottom') ? ['bottom', 'bottom-start', 'bottom-end', 'right', 'right-start', 'right-end', 'left', 'left-start', 'left-end', 'top', 'top-start', 'top-end'] : ['top', 'top-start', 'top-end', 'left', 'left-start', 'left-end', 'right', 'right-start', 'right-end', 'bottom', 'bottom-start', 'bottom-end'],
      fallbackStrategy: 'initialPlacement',
      fallbackAxisSideDirection: 'start',
      boundary: autoAlignBoundary
    }), arrow({
      element: caretRef,
      padding: 16
    }), autoAlign && hide()],
    whileElementsMounted: autoUpdate
  } : {}
  // When autoAlign is turned off & the `enable-v12-dynamic-floating-styles` feature flag is not
  // enabled, floating-ui will not be used
  );
  const value = useMemo(() => {
    return {
      floating,
      setFloating: refs.setFloating,
      caretRef,
      autoAlign: autoAlign
    };
  }, [refs.setFloating, autoAlign]);
  if (isTabTip) {
    const tabTipAlignments = ['bottom-start', 'bottom-end'];
    if (!tabTipAlignments.includes(align)) {
      align = 'bottom-start';
    }
  }
  useEffect(() => {
    if (enableFloatingStyles) {
      const updatedFloatingStyles = {
        ...floatingStyles,
        visibility: middlewareData.hide?.referenceHidden ? 'hidden' : 'visible'
      };
      Object.keys(updatedFloatingStyles).forEach(style => {
        if (refs.floating.current) {
          refs.floating.current.style[style] = updatedFloatingStyles[style];
        }
      });
      if (caret && middlewareData && middlewareData.arrow && caretRef?.current) {
        const {
          x,
          y
        } = middlewareData.arrow;
        const staticSide = {
          top: 'bottom',
          right: 'left',
          bottom: 'top',
          left: 'right'
        }[placement.split('-')[0]];
        caretRef.current.style.left = x != null ? `${x}px` : '';
        caretRef.current.style.top = y != null ? `${y}px` : '';

        // Ensure the static side gets unset when flipping to other placements' axes.
        caretRef.current.style.right = '';
        caretRef.current.style.bottom = '';
        if (staticSide) {
          caretRef.current.style[staticSide] = `${-popoverDimensions?.current?.caretHeight}px`;
        }
      }
    }
  }, [floatingStyles, refs.floating, enableFloatingStyles, middlewareData, placement, caret]);
  const ref = useMergedRefs([forwardRef, popover]);
  const currentAlignment = autoAlign && placement !== align ? placement : align;
  const className = classnames({
    [`${prefix}--popover-container`]: true,
    [`${prefix}--popover--caret`]: caret,
    [`${prefix}--popover--drop-shadow`]: dropShadow,
    [`${prefix}--popover--border`]: border,
    [`${prefix}--popover--high-contrast`]: highContrast,
    [`${prefix}--popover--open`]: open,
    [`${prefix}--popover--auto-align ${prefix}--autoalign`]: enableFloatingStyles,
    [`${prefix}--popover--${currentAlignment}`]: true,
    [`${prefix}--popover--tab-tip`]: isTabTip,
    [`${prefix}--popover--background-token__background`]: backgroundToken === 'background' && !highContrast
  }, customClassName);
  const mappedChildren = React__default.Children.map(children, child => {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
    const item = child;
    const displayName = item?.type?.displayName;

    /**
     * Only trigger elements (button) or trigger components (ToggletipButton) should be
     * cloned because these will be decorated with a trigger-specific className and ref.
     *
     * There are also some specific components that should not be cloned when autoAlign
     * is on, even if they are a trigger element.
     */
    const isTriggerElement = item?.type === 'button';
    const isTriggerComponent = enableFloatingStyles && displayName && ['ToggletipButton'].includes(displayName);
    const isAllowedTriggerComponent = enableFloatingStyles && !['ToggletipContent', 'PopoverContent'].includes(displayName);
    if (/*#__PURE__*/React__default.isValidElement(item) && (isTriggerElement || isTriggerComponent || isAllowedTriggerComponent)) {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
      const className = item?.props?.className;
      // eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
      const ref = (item?.props).ref;
      const tabTipClasses = classnames(`${prefix}--popover--tab-tip__button`, className);

      // eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
      return /*#__PURE__*/React__default.cloneElement(item, {
        className: isTabTip && item?.type === 'button' ? tabTipClasses : className || '',
        // With cloneElement, if you pass a `ref`, it overrides the original ref.
        // https://react.dev/reference/react/cloneElement#parameters
        // The block below works around this and ensures that the original ref is still
        // called while allowing the floating-ui reference element to be set as well.
        // `useMergedRefs` can't be used here because hooks can't be called from within a callback.
        // More here: https://github.com/facebook/react/issues/8873#issuecomment-489579878
        ref: node => {
          // For a popover, there isn't an explicit trigger component, it's just the first child that's
          // passed in which should *not* be PopoverContent.
          // For a toggletip there is a specific trigger component, ToggletipButton.
          // In either of these cases we want to set this as the reference node for floating-ui autoAlign
          // positioning.
          if (enableFloatingStyles && item?.type !== PopoverContent || enableFloatingStyles && item?.type['displayName'] === 'ToggletipButton') {
            // Set the reference element for floating-ui
            refs.setReference(node);
          }

          // Call the original ref, if any
          if (typeof ref === 'function') {
            ref(node);
          } else if (ref !== null && ref !== undefined) {
            ref.current = node;
          }
        }
      });
    } else {
      return item;
    }
  });
  const BaseComponentAsAny = BaseComponent;
  return /*#__PURE__*/React__default.createElement(PopoverContext.Provider, {
    value: value
  }, /*#__PURE__*/React__default.createElement(BaseComponentAsAny, _extends({}, rest, {
    className: className,
    ref: ref
  }), enableFloatingStyles || isTabTip ? mappedChildren : children));
});

// Note: this displayName is temporarily set so that Storybook ArgTable
// correctly displays the name of this component
{
  Popover.displayName = 'Popover';
}
Popover.propTypes = {
  /**
   * Specify how the popover should align with the trigger element
   */
  align: deprecateValuesWithin(PropTypes.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']), ['top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'left', 'left-start', 'left-end', 'right', 'right-start', 'right-end'], mapPopoverAlign),
  /**
   * **Experimental:** Provide an offset value for alignment axis. Only takes effect when `autoalign` is enabled.
   */
  alignmentAxisOffset: PropTypes.number,
  /**
   * Provide a custom element or component to render the top-level node for the
   * component.
   */
  as: PropTypes.oneOfType([PropTypes.string, PropTypes.elementType]),
  /**
   * Will auto-align the popover on first render if it is not visible. This prop
   * is currently experimental and is subject to future changes. Requires
   * React v17+
   * @see https://github.com/carbon-design-system/carbon/issues/18714
   */
  autoAlign: PropTypes.bool,
  /**
   * Specify the background token to use. Default is 'layer'.
   */
  backgroundToken: PropTypes.oneOf(['layer', 'background']),
  /**
   * Specify a bounding element to be used for autoAlign calculations. The viewport is used by default. This prop is currently experimental and is subject to future changes.
   */
  autoAlignBoundary: PropTypes.oneOfType([PropTypes.oneOf(['clippingAncestors']), PropTypes.elementType, PropTypes.arrayOf(PropTypes.elementType), PropTypes.exact({
    x: PropTypes.number.isRequired,
    y: PropTypes.number.isRequired,
    width: PropTypes.number.isRequired,
    height: PropTypes.number.isRequired
  })]),
  /**
   * Specify whether a caret should be rendered
   */
  caret: PropTypes.bool,
  /**
   * Specify whether a border should be rendered on the popover
   */
  border: PropTypes.bool,
  /**
   * Provide elements to be rendered inside of the component
   */
  children: PropTypes.node,
  /**
   * Provide a custom class name to be added to the outermost node in the
   * component
   */
  className: PropTypes.string,
  /**
   * Specify whether a drop shadow should be rendered on the popover
   */
  dropShadow: PropTypes.bool,
  /**
   * Render the component using the high-contrast variant
   */
  highContrast: PropTypes.bool,
  /**
   * Render the component using the tab tip variant
   */
  isTabTip: PropTypes.bool,
  /**
   * Specify a handler for closing popover.
   * The handler should take care of closing the popover, e.g. changing the `open` prop.
   */
  onRequestClose: PropTypes.func,
  /**
   * Specify whether the component is currently open or closed
   */
  open: PropTypes.bool.isRequired
};
function PopoverContentRenderFunction({
  className,
  children,
  ...rest
}, forwardRef) {
  const prefix = usePrefix();
  const {
    setFloating,
    caretRef,
    autoAlign
  } = React__default.useContext(PopoverContext);
  const ref = useMergedRefs([setFloating, forwardRef]);
  const enableFloatingStyles = useFeatureFlag('enable-v12-dynamic-floating-styles') || autoAlign;
  return /*#__PURE__*/React__default.createElement("span", _extends({}, rest, {
    className: `${prefix}--popover`
  }), /*#__PURE__*/React__default.createElement("span", {
    className: classnames(`${prefix}--popover-content`, className),
    ref: ref
  }, children, enableFloatingStyles && /*#__PURE__*/React__default.createElement("span", {
    className: classnames({
      [`${prefix}--popover-caret`]: true,
      [`${prefix}--popover--auto-align`]: true
    }),
    ref: caretRef
  })), !enableFloatingStyles && /*#__PURE__*/React__default.createElement("span", {
    className: classnames({
      [`${prefix}--popover-caret`]: true
    }),
    ref: caretRef
  }));
}
const PopoverContent = /*#__PURE__*/React__default.forwardRef(PopoverContentRenderFunction);
PopoverContent.displayName = 'PopoverContent';
PopoverContent.propTypes = {
  /**
   * Provide elements to be rendered inside of the component
   */
  children: PropTypes.node,
  /**
   * Provide a custom class name to be added to the outermost node in the
   * component
   */
  className: PropTypes.string
};

export { Popover, PopoverContent };
