/**
 * @license MIT
 * author: Cube Dev Team
 * @cube-dev/ui-kit v0.0.0-canary-127afd4
 * Released under the MIT license.
 */

import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { Pressable } from '@react-aria/interactions';
import { useEffect, useMemo, useRef, useState, } from 'react';
import { VisuallyHidden } from 'react-aria';
import { useEvent } from '../../_internal/index.js';
import { generateRandomId } from '../../utils/random.js';
import { mergeProps } from '../../utils/react/index.js';
import { useEventBus } from '../../utils/react/useEventBus.js';
import { MenuTrigger } from './Menu/index.js';
/**
 * Generic hook to manage a context menu component that opens at pointer coordinates.
 *
 * @param Component - A React component that represents the menu content (Menu or CommandMenu).
 * @param defaultTriggerProps - Default props to pass to the MenuTrigger.
 * @param defaultMenuProps - Default props to pass to the Menu component.
 * @returns An object with `targetRef` to attach to the container element, `open` function to open the menu at event coordinates, `close` function to close the menu, and `rendered` JSX element to include in your component tree.
 */
export function useContextMenu(Component, defaultTriggerProps, defaultMenuProps) {
    const [isOpen, setIsOpen] = useState(false);
    const [componentProps, setComponentProps] = useState(null);
    const [triggerProps, setTriggerProps] = useState(null);
    const [anchorPosition, setAnchorPosition] = useState(null);
    const targetRef = useRef(null);
    const invisibleAnchorRef = useRef(null);
    const setupRef = useRef(false);
    // Generate a unique ID for this menu instance
    const menuId = useMemo(() => generateRandomId(), []);
    // Get event bus for menu synchronization
    const { emit, on } = useEventBus();
    // Listen for other menus opening and close this one if needed
    useEffect(() => {
        const unsubscribe = on('popover:open', (data) => {
            // If another menu is opening and this menu is open, close this one
            if (data.menuId !== menuId && isOpen) {
                setIsOpen(false);
                setAnchorPosition(null);
            }
        });
        return unsubscribe;
    }, [on, menuId, isOpen]);
    // Emit event when this menu opens
    useEffect(() => {
        if (isOpen) {
            emit('popover:open', { menuId });
        }
    }, [isOpen, emit, menuId]);
    function setupCheck() {
        if (!setupRef.current) {
            throw new Error('useContextMenu: MenuTrigger must be rendered. Use `rendered` property to include it in your component tree.');
        }
    }
    // Helper function to calculate position relative to targetRef, taking the
    // element's scroll offset into account. Without the scroll offset the menu
    // would be rendered at the wrong place inside scrollable containers.
    const calculatePosition = (event) => {
        const container = targetRef.current;
        // If no event is provided, position at the center of the element
        if (!event) {
            if (!container) {
                return { x: 0, y: 0 };
            }
            const containerRect = container.getBoundingClientRect();
            const scrollLeft = container.scrollLeft;
            const scrollTop = container.scrollTop;
            const computed = window.getComputedStyle(container);
            const borderLeft = parseFloat(computed.borderLeftWidth) || 0;
            const borderTop = parseFloat(computed.borderTopWidth) || 0;
            // Position at the center of the element's content area
            const x = container.clientWidth / 2 + scrollLeft;
            const y = container.clientHeight / 2 + scrollTop;
            // Clamp to the full scroll size
            const clampedX = Math.max(0, Math.min(x, container.scrollWidth));
            const clampedY = Math.max(0, Math.min(y, container.scrollHeight));
            return { x: clampedX, y: clampedY };
        }
        // If the target reference is missing, fall back to viewport coordinates.
        if (!container) {
            const { clientX = 0, clientY = 0 } = event;
            return { x: clientX, y: clientY };
        }
        const containerRect = container.getBoundingClientRect();
        // Get coordinates from the event (viewport-relative)
        const { clientX, clientY } = event;
        // Take the element's scroll offset into account so that the coordinates are
        // relative to the **content** box, not the visible viewport of the
        // element.
        const scrollLeft = container.scrollLeft;
        const scrollTop = container.scrollTop;
        const computed = window.getComputedStyle(container);
        const borderLeft = parseFloat(computed.borderLeftWidth) || 0;
        const borderTop = parseFloat(computed.borderTopWidth) || 0;
        const x = clientX - containerRect.left - borderLeft + scrollLeft;
        const y = clientY - containerRect.top - borderTop + scrollTop;
        // Clamp to the full scroll size so that the invisible anchor always stays
        // inside the element regardless of the scroll position.
        const clampedX = Math.max(0, Math.min(x, container.scrollWidth));
        const clampedY = Math.max(0, Math.min(y, container.scrollHeight));
        return { x: clampedX, y: clampedY };
    };
    // 'open' accepts props, trigger props, and optional event for positioning, then opens the menu
    const open = useEvent((props = {}, triggerProps, event) => {
        setupCheck();
        // Ensure the target element can serve as a positioning context for the
        // invisible target element. If the consumer hasn't explicitly set
        // `position: relative | absolute | fixed | sticky` we switch it to
        // `relative` so that absolutely-positioned children are laid out correctly.
        if (targetRef.current) {
            const computedStyle = window.getComputedStyle(targetRef.current);
            if (computedStyle.position === 'static') {
                targetRef.current.style.position = 'relative';
            }
        }
        // Prevent default context menu if it's a context menu event
        if (event &&
            'preventDefault' in event &&
            typeof event.preventDefault === 'function') {
            event.preventDefault();
        }
        const { x, y } = calculatePosition(event);
        setAnchorPosition({ x, y });
        // Merge defaultMenuProps with provided props
        const finalProps = defaultMenuProps
            ? { ...defaultMenuProps, ...props }
            : props;
        setComponentProps(finalProps);
        setTriggerProps(triggerProps ?? null);
        setIsOpen(true);
    });
    const update = useEvent((props, triggerProps) => {
        setupCheck();
        // Merge defaultMenuProps with provided props
        const finalProps = defaultMenuProps
            ? { ...defaultMenuProps, ...props }
            : props;
        setComponentProps(finalProps);
        setTriggerProps(triggerProps ?? null);
    });
    const close = useEvent(() => {
        setIsOpen(false);
        setAnchorPosition(null);
    });
    // Context menu event handler
    const onContextMenu = useEvent((event) => {
        event.preventDefault();
        if (isOpen) {
            const pos = calculatePosition(event);
            setAnchorPosition(pos);
        }
        else {
            open(defaultMenuProps, undefined, event);
        }
    });
    // Bind the onContextMenu event to targetRef
    useEffect(() => {
        const element = targetRef.current;
        if (!element)
            return;
        element.addEventListener('contextmenu', onContextMenu);
        return () => {
            element.removeEventListener('contextmenu', onContextMenu);
        };
    }, [onContextMenu]);
    // Render the menu only when componentProps is set
    const renderedMenu = useMemo(() => {
        if (!componentProps || !anchorPosition)
            return null;
        return (_jsxs(_Fragment, { children: [_jsx("span", { ref: invisibleAnchorRef, style: {
                        position: 'absolute',
                        left: `${anchorPosition.x}px`,
                        top: `${anchorPosition.y}px`,
                        width: '0px',
                        height: '0px',
                        lineHeight: '0',
                        pointerEvents: 'none',
                        visibility: 'hidden',
                    } }), _jsxs(MenuTrigger, { isDummy: true, isOpen: isOpen, targetRef: invisibleAnchorRef, offset: 0, crossOffset: 0, placement: triggerProps?.placement ||
                        defaultTriggerProps?.placement ||
                        'bottom start', onOpenChange: setIsOpen, ...mergeProps(defaultTriggerProps, triggerProps || undefined), children: [_jsx(VisuallyHidden, { children: _jsx(Pressable, { children: _jsx("button", { "aria-label": "Open context menu" }) }) }), _jsx(Component, { ...componentProps })] })] }));
    }, [
        componentProps,
        triggerProps,
        isOpen,
        defaultTriggerProps,
        anchorPosition,
    ]);
    return {
        targetRef,
        open,
        update,
        close,
        isOpen,
        get rendered() {
            setupRef.current = true;
            return renderedMenu;
        },
    };
}

        