/* @oracle/oraclejet-preact: undefined */
import { useRef, useEffect, useState, useCallback } from 'preact/hooks';
import { jsx } from 'preact/jsx-runtime';
import { u as useId } from './useId-03dbfdf0.js';
import { u as useHover } from './useHover-19340c22.js';
import { u as useFocus } from './useFocus-38c95977.js';
import { u as useTouch } from './useTouch-4828df25.js';
import { m as mergeProps } from './mergeProps-88ea8306.js';
import { u as useAnimation } from './useAnimation-a5eb7604.js';
import { u as useComponentTheme } from './useComponentTheme-d2f9e47f.js';
import { u as useTestId } from './useTestId-9b504481.js';
import { u as useThemeInterpolations } from './useThemeInterpolations-8dd8029f.js';
import { c as classNames } from './classNames-4e12b00d.js';
import { TooltipContentRedwoodTheme } from './hooks/UNSAFE_useTooltip/themes/redwood/TooltipContentTheme.js';
import { u as useColorScheme } from './useColorScheme-c4e237ad.js';
import { g as getWrappedTooltipContent, a as getGlobalTooltipManager, S as SHOW_TOOLTIP_DELAY, H as HIDE_TOOLTIP_DELAY } from './tooltipUtils-768450a7.js';

const TooltipContent = ({ children, id, isOpen, isDatatip, onTransitionEnd, testId, ...props }) => {
    const wrapperRef = useRef(null);
    const popoverRef = useRef(null);
    const testIdProps = useTestId(testId);
    const { baseTheme, variantClasses, styles } = useComponentTheme(TooltipContentRedwoodTheme, {
        variant: isDatatip ? 'datatip' : 'tooltip'
    });
    const schemeClass = useThemeInterpolations();
    const { nodeRef } = useAnimation(isOpen ? 'mounted' : 'unmounted', {
        animationStates: {
            mounted: (_node) => {
                const childNode = _node.firstChild;
                return {
                    from: {
                        maxHeight: '0',
                        opacity: '0%'
                    },
                    to: {
                        maxHeight: `${childNode?.offsetHeight}px`,
                        opacity: isDatatip ? '100%' : '95%'
                    },
                    options: {
                        duration: isDatatip ? 1 : 150,
                        easing: [0.0, 0.0, 0.2, 1]
                    }
                };
            },
            unmounted: (_node) => {
                return {
                    from: {
                        opacity: isDatatip ? '100%' : '95%'
                    },
                    to: {
                        maxHeight: '0',
                        opacity: '0%'
                    },
                    options: {
                        duration: isDatatip ? 1 : 150,
                        easing: [0.0, 0.0, 0.2, 1]
                    }
                };
            }
        },
        isAnimatedOnMount: true,
        onAnimationEnd: () => onTransitionEnd?.()
    });
    // Set up initial wrapper height so Tooltip can animate from the bottom to the top
    useEffect(() => {
        if (wrapperRef.current && popoverRef.current?.offsetHeight) {
            wrapperRef.current.style.height = `${popoverRef.current?.offsetHeight}px`;
        }
    }, []);
    return (jsx("div", { ref: wrapperRef, id: id, role: "tooltip", class: classNames([styles.wrapper, schemeClass, baseTheme]), ...testIdProps, children: jsx("div", { ref: nodeRef, class: styles.inner, ...props, children: jsx("div", { ref: popoverRef, class: classNames([variantClasses, styles.content]), children: children }) }) }));
};

const useTooltipControlled = ({ text, isOpen = false, variant = 'tooltip', position = 'bottom', focusStrategy = 'browser', isDisabled = false, anchor = { x: 'element', y: 'element' }, offset, onToggle, testId }) => {
    // Global TooltipManager singleton instance
    const globalTooltipManager = getGlobalTooltipManager();
    const disabled = isDisabled || !text;
    const isInitialRenderState = useRef(true);
    const [state, _setState] = useState(!disabled && isOpen ? 'mounting' : 'unmounted');
    const stateRef = useRef(state);
    const setState = (newState) => {
        stateRef.current = newState;
        _setState(newState);
    };
    // const tooltipId = useRef<string>(useId());
    const [needsUpdate, setNeedsUpdate] = useState(false);
    setNeedsUpdate(false);
    const isDatatip = variant === 'datatip';
    const [isLogicalFocus, setLogicalFocus] = useState(false);
    const isLogicalFocusStrategy = focusStrategy === 'logical';
    const isInitialRenderTrigger = useRef(true);
    const { hoverProps, isHover } = useHover({});
    const { touchProps, isTouch } = useTouch({ isDisabled }); // TODO replace by useLongPress actionhook
    // we only care about keyboard focus, don't enable useFocus hook if isHover or isTouch are active
    const { focusProps, isFocus } = useFocus({
        isDisabled: isLogicalFocusStrategy
    });
    // only enable the popoverHover hook if the tooltip is actually open
    const { hoverProps: popoverHoverProps, isHover: popoverIsHover } = useHover({
        isDisabled: isDisabled || !isOpen
    });
    const isPointer = anchor.x === 'pointer' || anchor.y === 'pointer';
    const uniqueIdRef = useRef(useId());
    const targetRef = useRef(null);
    const coordsRef = useRef({ x: -9999, y: -9999 });
    // if anchor is set to pointer and target does not have keyboard focus, use coords
    const usedRef = isPointer && !isFocus ? coordsRef : targetRef;
    const showDelay = isDatatip ? 0 : SHOW_TOOLTIP_DELAY;
    const hideDelay = isDatatip ? 0 : HIDE_TOOLTIP_DELAY;
    const toggleTimeoutRef = useRef(null);
    const currentColorScheme = useColorScheme();
    const focusActive = useRef(false);
    const hoverActive = useRef(false);
    const toggleTimeoutClear = () => {
        if (toggleTimeoutRef.current) {
            clearTimeout(toggleTimeoutRef.current);
            toggleTimeoutRef.current = null;
        }
    };
    /*
     * This effect executes when isFocus/isLogicalFocus changes
     * - focusActive ref is set if the focus change follows a Tab key
     * - hoverActive ref is reset, if focusActive becomes true
     */
    useEffect(() => {
        if (isLogicalFocusStrategy) {
            focusActive.current = isLogicalFocus;
        }
        else {
            focusActive.current = isFocus && globalTooltipManager.isTabKeyActive();
        }
        if (focusActive.current) {
            hoverActive.current = false;
        }
    }, [isFocus, isLogicalFocus, isLogicalFocusStrategy, globalTooltipManager]);
    /*
     * This effect executes when isHover state changes
     * - focusActive ref is reset, if hoverActive becomes true
     */
    useEffect(() => {
        hoverActive.current = isHover;
    }, [isHover]);
    useEffect(() => {
        // inner function for state transitions on various trigger changes
        const updateState = (open) => {
            if (open) {
                // At least one toggle event is set to true
                switch (state) {
                    case 'unmounted':
                        setState('mountPending'); // Set mount pending state if tooltip is unmounted
                        break;
                    case 'unmounting': // Switch back to mounting when unmounting was in progress and the tooltip should stay on
                        setState('mounting');
                        onToggle?.({ value: true });
                        break;
                    case 'unmountPending':
                        toggleTimeoutClear();
                        setState('mounted'); // Clear timers immediately and keep the tooltip mounted
                        break;
                }
            }
            else {
                // No toggle events are set to true
                switch (state) {
                    case 'mounting':
                    case 'mounted':
                        setState('unmountPending'); // Set unmount pending state if tooltip is mounted or in mounting transition
                        break;
                    case 'mountPending':
                        toggleTimeoutClear();
                        __resetCoords();
                        setState('unmounted'); // Unmount tooltip immediately if mounting is pending
                        break;
                }
            }
        };
        if (isInitialRenderTrigger.current) {
            // On onitial render do not trigger rerender
            isInitialRenderTrigger.current = false;
            return;
        }
        // When pointer is used, handleMouseMove method is responsible for state updates
        // until the tooltip is mounted
        if (isPointer && isHover && state === 'unmounted') {
            return;
        }
        updateState((isHover && hoverActive.current) ||
            ((isFocus || isLogicalFocus) && focusActive.current) ||
            popoverIsHover ||
            isTouch);
    }, [isHover, isFocus, isLogicalFocus, popoverIsHover, isTouch, isPointer, state, onToggle]);
    const destroyCallback = useCallback(() => {
        focusActive.current = false;
        hoverActive.current = false;
        onToggle?.({ value: false });
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []); // don't want to depend on the 'onToggle' prop as some clients tend to pass a new callback on each render
    /*
     * Global mousedown handler to dismiss the tooltip on anchor click. This is especially needed
     * for Safari which does not fire mouse events during animations when the anchor moves (JET-72605).
     */
    const mouseDownHandler = useCallback((event) => {
        if (usedRef.current instanceof Element && usedRef.current.contains(event.target)) {
            toggleTimeoutClear();
            document.removeEventListener('mousedown', mouseDownHandler, true);
            destroyCallback();
        }
    }, [usedRef, destroyCallback]);
    useEffect(() => {
        if (isInitialRenderState.current) {
            // On onitial render do not trigger rerender
            isInitialRenderState.current = false;
            return;
        }
        setState(isOpen ? 'mounting' : 'unmounting');
    }, [isOpen]);
    useEffect(() => {
        return () => {
            // make sure to clear the toggle timeout on unmount
            toggleTimeoutClear();
            // reset focusActive and hoverActive refs
            focusActive.current = false;
            hoverActive.current = false;
        };
    }, []);
    /*
     * This effect executes when the component state changes.
     * On 'mounted' state, the tooltip registers itself with the global tooltip manager.
     * On 'unmounted', the tooltip unregisters from the global tooltip manager.
     */
    useEffect(() => {
        if (state === 'mounted') {
            globalTooltipManager.register(destroyCallback);
        }
        else if (state === 'unmounted') {
            globalTooltipManager.unregister(destroyCallback);
            // clean up the mousedown handler just in case
            document.removeEventListener('mousedown', mouseDownHandler, true);
        }
    }, [state, destroyCallback, globalTooltipManager, mouseDownHandler]);
    /*
     * Set a timer to toggle the state afer the 'pending' period expires
     */
    useEffect(() => {
        if (!toggleTimeoutRef.current && (state === 'mountPending' || state === 'unmountPending')) {
            if (state === 'mountPending') {
                document.addEventListener('mousedown', mouseDownHandler, true);
            }
            toggleTimeoutRef.current = setTimeout(() => {
                toggleTimeoutClear();
                // make sure the state has not changed in the mean time before onToggle
                if ((state === 'mountPending' || state === 'unmountPending') &&
                    state === stateRef.current) {
                    onToggle?.({ value: state === 'mountPending' });
                }
            }, state === 'mountPending' ? showDelay : hideDelay);
        }
        return () => {
            toggleTimeoutClear();
        };
    }, [state, showDelay, hideDelay, onToggle, mouseDownHandler]);
    const handleMouseMove = (event) => {
        // we only want track mouse move while the state is 'unmounted' or when moving a 'mounted' datatip
        if (state !== 'unmounted' && !(variant === 'datatip' && state === 'mounted')) {
            return;
        }
        const targetRect = targetRef?.current?.getBoundingClientRect();
        // Tooltip maintains static offset from left side of window
        const distanceX = document.body.scrollLeft + (targetRect?.left || 0) + (targetRect?.width || 0);
        // Tooltip maintains static offset from top side of the window
        const distanceY = document.body.scrollTop + (targetRect?.top || 0) + (targetRect?.height || 0);
        // Display tooltip at the mouse position if both anchors are set to pointer
        const x = anchor.x === 'pointer' ? event.clientX : distanceX;
        const y = anchor.y === 'pointer' ? event.clientY : distanceY;
        if (state === 'mounted') {
            coordsRef.current = { x, y };
            if (needsUpdate !== true)
                setNeedsUpdate(true);
        }
        else if (state === 'unmounted') {
            toggleTimeoutClear();
            toggleTimeoutRef.current = setTimeout(() => {
                if (state === 'unmounted') {
                    coordsRef.current = { x, y };
                    onToggle?.({ value: true });
                }
            }, hideDelay);
        }
    };
    const onLogicalFocus = (target) => {
        setLogicalFocus(true);
        targetRef.current = target;
        usedRef.current = target;
    };
    const onLogicalBlur = () => {
        setLogicalFocus(false);
    };
    const onFocusCallback = useCallback((event) => {
        targetRef.current = event.currentTarget;
    }, []);
    const onBlurCallback = useCallback((event) => {
        if (event.eventPhase === Event.AT_TARGET) {
            // reset focusActive flag
            focusActive.current = false;
        }
    }, []);
    const focusInitProps = isLogicalFocusStrategy
        ? {}
        : {
            onFocus: onFocusCallback,
            onBlur: onBlurCallback
        };
    const hoverInitProps = {
        onMouseEnter: useCallback((event) => {
            targetRef.current = event.target;
        }, [])
    };
    const keyInitProps = {
        onKeyDown: useCallback((event) => {
            if (event.code === 'Escape' && focusActive.current) {
                event.preventDefault();
                // focus no longer active after Escape
                focusActive.current = false;
                onToggle?.({ value: false });
            }
        }, [onToggle])
    };
    let actionableProps;
    const __resetCoords = () => {
        coordsRef.current = { x: -9999, y: -9999 };
    };
    if (disabled) {
        actionableProps = mergeProps(hoverProps, focusProps, hoverInitProps, focusInitProps);
    }
    else {
        const anchorInitProps = {
            'aria-describedby': uniqueIdRef.current,
            ...(isPointer && {
                onMouseLeave: () => {
                    if (state === 'mounting') {
                        return;
                    }
                    toggleTimeoutClear();
                },
                onMouseMove: (event) => {
                    handleMouseMove(event);
                }
            })
        };
        actionableProps = mergeProps(hoverProps, focusProps, touchProps, hoverInitProps, focusInitProps, anchorInitProps, keyInitProps, isLogicalFocusStrategy ? { onLogicalFocus, onLogicalBlur } : {});
    }
    // No need to process tooltip if disabled or no text is provided
    // Include actionableProps to detect mouseenter in components
    // hovered state won't update unless actionableProps are already present in the component when mouse enters
    if (disabled) {
        toggleTimeoutClear();
        __resetCoords();
        return {
            tooltipContent: null,
            tooltipProps: actionableProps
        };
    }
    const placement = position;
    const handleTransitionEnd = () => {
        if (state === 'mounting') {
            setState('mounted');
        }
        if (state === 'unmounting') {
            __resetCoords();
            setState('unmounted');
        }
    };
    const renderRawContent = (jsx(TooltipContent, { id: uniqueIdRef.current, testId: testId, isOpen: ['mounting', 'mounted', 'unmountPending'].includes(state), ...popoverHoverProps, isDatatip: isDatatip, onTransitionEnd: handleTransitionEnd, children: text }));
    const renderContent = getWrappedTooltipContent(renderRawContent, variant, currentColorScheme, usedRef, placement, offset);
    return {
        tooltipContent: !['unmounted', 'mountPending'].includes(state) && renderContent,
        tooltipProps: actionableProps
    };
};

/**
 * @license
 * Copyright (c) %FIRST_YEAR% %CURRENT_YEAR%, Oracle and/or its affiliates.
 * The Universal Permissive License (UPL), Version 1.0
 * as shown at https://oss.oracle.com/licenses/upl/
 * @ignore
 */
/**
 * A hook with tooltip implementation.
 * Returns event handlers that can enhance the target element with a tooltip displayed
 * on hover and focus.
 *
 * @param text tooltip text
 * @param position tooltip placement relative to the target element
 * @param isDisabled determines if the tooltip is disabled
 * @param anchor determines if the target is an element or the pointer
 * @param offset offset from the placement
 * @param variant specifies if the rendered popup should have 'tooltip' or 'datatip' look and behavior
 * @returns an object with the 'tooltipContent' and 'tooltipProps' properties.
 */
const useTooltip = ({ text, position = 'bottom', isDisabled = false, anchor = { x: 'element', y: 'element' }, offset, variant = 'tooltip', testId }) => {
    const [isOpen, setIsOpen] = useState(false);
    const disabled = isDisabled || !text;
    const onToggleHandler = useCallback((details) => {
        setIsOpen(details.value);
    }, []);
    const { tooltipContent, tooltipProps } = useTooltipControlled({
        text,
        isOpen,
        position,
        isDisabled: disabled,
        offset,
        anchor,
        variant,
        onToggle: onToggleHandler,
        testId
    });
    return {
        tooltipContent,
        tooltipProps
    };
};

export { TooltipContent as T, useTooltipControlled as a, useTooltip as u };
//# sourceMappingURL=useTooltip-89ca3f3e.js.map
