import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { forwardRef, useContext, useRef, useEffect, useMemo, useState, useLayoutEffect, useCallback } from 'react';
import { createPortal } from 'react-dom';
import { usePopper } from 'react-popper';
import { useAfterInitialEffect, useConfiguration, useConsolidatedRef, useElement, useEvent, usePopoverMap, useUID } from '../../hooks';
import { getRelativeOffset, windowIsAvailable } from '../../utils';
import { PopoverManagerContext } from './PopoverContext';
import { StyledPopover, StyledPopoverArrow } from './Popover.styles';
import { placeAndContain } from './modifiers';
const delays = {
    none: 0,
    short: 500,
    long: 1000
};
const Popover = forwardRef(function Popover({ show = true, portal = true, target, placement = 'bottom', strategy = 'fixed', style, modifiers = [], arrow = false, groupId, showDelay = 'none', hideDelay = 'none', hideOnTargetHidden = false, children, onHide, onShow, ...restProps }, ref) {
    const { portalTarget: configurationPortal } = useConfiguration();
    const { checkActive, setActive, popovers } = useContext(PopoverManagerContext);
    const [popperEl, setPopperEl] = useElement();
    const popperRef = useConsolidatedRef(ref, setPopperEl);
    const arrowRef = useRef(null);
    const forceUpdateRef = useRef();
    const initialOffset = useRef(null);
    const [showPopover, setShowPopover] = useState(show);
    const timeout = useRef();
    const uid = useUID();
    const resolvedTarget = target instanceof Element ? target : target?.contextElement;
    const portalTarget = typeof portal === 'boolean' ? configurationPortal : portal;
    const memoedModifiers = useMemo(() => {
        const flipEnabled = !!modifiers
            .slice()
            .reverse()
            .find(modifier => modifier.name === 'flip' && modifier.enabled !== undefined)?.enabled;
        return [
            {
                name: 'computeStyles',
                options: {
                    gpuAcceleration: false
                }
            },
            {
                name: 'offset',
                options: {
                    offset: [0, arrow ? 5.5 : 2]
                }
            },
            { name: 'hide', enabled: hideOnTargetHidden },
            { name: 'arrow', enabled: arrow, options: { element: arrowRef.current, padding: 7 } },
            {
                name: 'preventOverflow',
                options: {
                    tether: !modifiers.find(m => m.name === 'sameWidth' || 'fitToContent'),
                    padding: 16
                }
            },
            {
                ...placeAndContain,
                enabled: !flipEnabled
            },
            ...modifiers
        ];
    }, [arrow, hideOnTargetHidden, modifiers]);
    const offset = useMemo(() => {
        const offsetModifier = [...memoedModifiers]
            .reverse()
            .find(m => m.name === 'offset' && m.enabled !== false);
        return Array.isArray(offsetModifier?.options?.offset)
            ? offsetModifier?.options?.offset[1] ?? 0
            : 0;
    }, [memoedModifiers]);
    const { styles, attributes, forceUpdate } = usePopper(target, popperEl, {
        placement,
        strategy,
        modifiers: memoedModifiers
    });
    if (forceUpdate)
        forceUpdateRef.current = forceUpdate;
    // Setter-less useState to avoid re-running these constructors on every render with useRef.
    const [ac] = useState(() => new AbortController());
    const [observedSet] = useState(() => new WeakSet());
    const [ro] = useState(() => new ResizeObserver(entries => {
        let updateQueued = false;
        for (const { target: resizeTarget } of entries) {
            // Ignore the first ResizeObserver event for each element since ResizeObserver always fires once when calling .observe().
            if (!observedSet.has(resizeTarget)) {
                observedSet.add(resizeTarget);
            }
            else if (!updateQueued) {
                updateQueued = true;
                if (!ac.signal.aborted) {
                    forceUpdateRef.current?.();
                }
            }
        }
    }));
    useEffect(() => {
        return () => {
            ac.abort();
            ro.disconnect();
        };
    }, []);
    useEffect(() => {
        if (!popperEl)
            return;
        ro.observe(popperEl);
        return () => {
            ro.unobserve(popperEl);
        };
    }, [popperEl]);
    useEffect(() => {
        if (!resolvedTarget)
            return;
        ro.observe(resolvedTarget);
        return () => {
            ro.unobserve(resolvedTarget);
        };
    }, [resolvedTarget]);
    useLayoutEffect(() => {
        if (resolvedTarget) {
            initialOffset.current = getRelativeOffset(resolvedTarget, document.documentElement);
        }
        else {
            initialOffset.current = null;
        }
    }, [resolvedTarget]);
    useEffect(() => {
        if (!showPopover || !resolvedTarget)
            return;
        const rafAC = new AbortController();
        let rafID;
        rafAC.signal.addEventListener('aborted', () => {
            cancelAnimationFrame(rafID);
        });
        const checkCoords = () => {
            if (initialOffset.current) {
                const newOffset = getRelativeOffset(resolvedTarget, document.documentElement);
                if (initialOffset.current.x !== newOffset.x || initialOffset.current.y !== newOffset.y) {
                    initialOffset.current = newOffset;
                    forceUpdateRef.current?.();
                }
            }
            if (!rafAC.signal.aborted) {
                rafID = requestAnimationFrame(checkCoords);
            }
        };
        rafID = requestAnimationFrame(checkCoords);
        return () => {
            rafAC.abort();
        };
    }, [showPopover, resolvedTarget]);
    useEffect(() => {
        if (groupId && show)
            setActive(uid, groupId);
        /**
         *  TODO: Remove usages of forceUpdateRef.current?.() below in favor of permanent fix to address following issues:
         *  * Resize observer is not running immediately after initial mount
         *  * Menu scrollAt is not applied on initial render
         */
        if (windowIsAvailable) {
            if (timeout.current)
                clearTimeout(timeout.current);
            timeout.current = window.setTimeout(() => {
                setShowPopover(show);
                if (show)
                    forceUpdateRef.current?.();
            }, delays[show ? showDelay : hideDelay]);
        }
        else {
            setShowPopover(show);
            if (show)
                forceUpdateRef.current?.();
        }
    }, [show]);
    useEffect(() => {
        return () => clearTimeout(timeout.current);
    }, []);
    useAfterInitialEffect(() => {
        if (!showPopover) {
            onHide?.();
        }
        else {
            onShow?.();
        }
    }, [showPopover]);
    useEffect(() => {
        if (groupId && popovers[groupId] && popovers[groupId] !== uid) {
            setShowPopover(false);
        }
    }, [groupId ? popovers[groupId] : undefined]);
    const popoverId = usePopoverMap(resolvedTarget, !!portal);
    const triggerPopoverFocusEvent = useCallback((e) => {
        if (resolvedTarget && portal) {
            const event = new Event(e.type.startsWith('popover:') ? e.type : `popover:${e.type}`, {
                bubbles: true,
                cancelable: true
            });
            resolvedTarget.dispatchEvent(event);
        }
    }, [resolvedTarget, portal]);
    // need to retrigger focus events on the target element to correctly notify useFocusWithin hook if popover is portal-ed
    useEvent('focusout', triggerPopoverFocusEvent, { target: popperRef });
    useEvent('popover:focusout', triggerPopoverFocusEvent, { target: popperRef });
    useEvent('focusin', triggerPopoverFocusEvent, { target: popperRef });
    useEvent('popover:focusin', triggerPopoverFocusEvent, { target: popperRef });
    const content = (_jsxs(StyledPopover, { ...restProps, "data-popover-id": popoverId, portal: !!(portal && portalTarget), offset: offset, ref: popperRef, style: { ...style, ...styles.popper }, ...attributes.popper, children: [arrow && _jsx(StyledPopoverArrow, { ref: arrowRef, style: { ...styles.arrow } }), children] }));
    if (!target || !showPopover || (groupId && !checkActive(uid, groupId)))
        return null;
    return portal && portalTarget ? createPortal(content, portalTarget) : content;
});
export default Popover;
//# sourceMappingURL=Popover.js.map