import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
// (C) 2007-2026 GoodData Corporation
import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
import cx from "classnames";
import { usePropState } from "@gooddata/sdk-ui";
import { UiFocusManager } from "../@ui/UiFocusManager/UiFocusManager.js";
import { resolveRef } from "../@ui/UiFocusManager/utils.js";
import { FullScreenOverlay } from "../Overlay/FullScreenOverlay.js";
import { Overlay } from "../Overlay/Overlay.js";
import { useMediaQuery } from "../responsive/useMediaQuery.js";
import { isEscapeKey } from "../utils/events.js";
import { useId } from "../utils/useId.js";
import { DropdownButtonKeyboardWrapper } from "./DropdownButtonKeyboardWrapper.js";
const SCROLLBAR_SELECTOR = ".fixedDataTableLayout_main .ScrollbarLayout_main";
const MOBILE_DROPDOWN_ALIGN_POINTS = [
    {
        align: "tl tl",
    },
];
// this class is used in List component to allow scrolling inside dropdown body when list is present.
export const DROPDOWN_BODY_CLASS = "dropdown-body";
/**
 * Element.matches is only supported via prefix in IE11 and Edge.
 */
function matches(element, selector) {
    const matchesImpl = element.matches ?? element.msMatchesSelector;
    return matchesImpl.call(element, selector);
}
/**
 * Prevent the overlay from closing when scrolling and finishing
 * with a cursor position outside of the overlay.
 */
function shouldCloseOnClick(e) {
    const activeElement = document.activeElement ?? e.target;
    if (!activeElement) {
        return false;
    }
    const hasScrolled = matches(activeElement, SCROLLBAR_SELECTOR);
    return !hasScrolled;
}
/**
 * @internal
 */
export function Dropdown({ isOpen: isOpenProp, onToggle, className, openOnInit, closeOnParentScroll, closeOnMouseDrag, closeOnOutsideClick = true, overlayPositionType, alignPoints = [
    {
        align: "bl tl",
    },
], overlayZIndex, ignoreClicksOnByClass = [], renderBody, renderButton, onOpenStateChanged, onAlign, fullscreenOnMobile = true, enableEventPropagation = false, enableAutoToggle = true, closeOnEscape = false, autofocusOnOpen = false, initialFocus, returnFocusTo, accessibilityConfig, shouldTrapFocus = true, }) {
    const [isOpen, setIsOpen] = usePropState(isOpenProp ?? openOnInit ?? false);
    const id = useId();
    const dropdownId = `dropdown-${id}`;
    const dropdownButtonId = `dropdown-button-${id}`;
    const buttonWrapperRef = useRef(null);
    const _renderButton = (renderProps) => (_jsx(DropdownButtonKeyboardWrapper, { enabled: enableAutoToggle, onToggle: renderProps.toggleDropdown, closeOnEscape: closeOnEscape, isOpen: renderProps.isOpen, ref: buttonWrapperRef, id: dropdownButtonId, children: renderButton(renderProps) }));
    const toggleDropdown = useCallback((desiredState) => {
        if (typeof desiredState === "boolean") {
            if (onToggle) {
                onToggle(desiredState);
            }
            else {
                setIsOpen(desiredState);
            }
        }
        else {
            if (onToggle) {
                onToggle();
            }
            else {
                setIsOpen((state) => !state);
            }
        }
    }, [onToggle, setIsOpen]);
    const closeDropdown = useCallback(() => {
        toggleDropdown(false);
    }, [toggleDropdown]);
    const openDropdown = useCallback(() => {
        toggleDropdown(true);
    }, [toggleDropdown]);
    const mountRef = useRef(false);
    const buttonRef = useRef(null);
    useEffect(() => {
        if (mountRef.current && onOpenStateChanged) {
            onOpenStateChanged(isOpen);
        }
        return () => {
            mountRef.current = true;
        };
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [isOpen]);
    const renderButtonProps = useMemo(() => {
        const role = accessibilityConfig?.triggerRole ?? "button";
        return {
            isOpen,
            openDropdown,
            closeDropdown,
            toggleDropdown,
            dropdownId,
            ariaAttributes: {
                role,
                "aria-controls": isOpen ? dropdownId : undefined,
                "aria-expanded": isOpen,
                "aria-haspopup": accessibilityConfig?.popupRole ?? true,
            },
            accessibilityConfig: {
                role,
                popupId: dropdownId,
                isExpanded: isOpen,
                ariaExpanded: isOpen,
                ariaHaspopup: accessibilityConfig?.popupRole ?? true,
                ariaControls: isOpen ? dropdownId : undefined,
            },
        };
    }, [
        accessibilityConfig?.popupRole,
        accessibilityConfig?.triggerRole,
        closeDropdown,
        dropdownId,
        isOpen,
        openDropdown,
        toggleDropdown,
    ]);
    const renderBodyProps = {
        closeDropdown,
        ariaAttributes: {
            id: dropdownId,
            "aria-labelledby": dropdownButtonId,
            role: accessibilityConfig?.popupRole ?? "dialog",
        },
    };
    const handleTabOut = useCallback(() => {
        closeDropdown();
        resolveRef(returnFocusTo)?.focus();
    }, [closeDropdown, returnFocusTo]);
    // Escape must close one layer only. Overlay's own closeOnEscape listens on window,
    // where an ancestor dialog's Escape listener fires too — so handle Escape here in the
    // body and contain it before it leaves the dropdown.
    const handleBodyKeyDown = useCallback((event) => {
        if (closeOnEscape && isEscapeKey(event)) {
            event.stopPropagation();
            closeDropdown();
        }
    }, [closeOnEscape, closeDropdown]);
    // `.focus()` silently no-ops while the overlay is still `visibility: hidden` awaiting
    // alignment, so re-arm autofocus on the first align. A boolean, not a counter — the
    // overlay realigns after every update, so a per-align value would feed a render loop.
    const [overlayAligned, setOverlayAligned] = useState(false);
    if (!isOpen && overlayAligned) {
        setOverlayAligned(false);
    }
    const handleOverlayAlign = useCallback((alignment) => {
        setOverlayAligned(true);
        onAlign?.(alignment);
    }, [onAlign]);
    const isMobileDevice = useMediaQuery("mobileDevice");
    const renderDropdown = isOpen ? (fullscreenOnMobile && isMobileDevice ? (_jsx(FullScreenOverlay, { alignTo: "body", alignPoints: MOBILE_DROPDOWN_ALIGN_POINTS, onAlign: handleOverlayAlign, children: _jsx(UiFocusManager, { tabOutHandler: shouldTrapFocus ? undefined : handleTabOut, enableFocusTrap: shouldTrapFocus, enableAutofocus: autofocusOnOpen ? { initialFocus, refocusKey: overlayAligned } : false, enableReturnFocusOnUnmount: { returnFocusTo }, children: _jsxs("div", { className: "gd-mobile-dropdown-overlay overlay gd-flex-row-container", onKeyDown: handleBodyKeyDown, children: [
                    _jsx("div", { className: "gd-mobile-dropdown-header gd-flex-item", children: _renderButton({
                            ...renderButtonProps,
                            isMobile: true,
                            buttonRef: { current: null },
                        }) }), _jsx("div", { className: "gd-mobile-dropdown-content gd-flex-item-stretch gd-flex-row-container", children: renderBody({
                            ...renderBodyProps,
                            isMobile: true,
                        }) })
                ] }) }) })) : (_jsx(Overlay, { alignTo: `#${dropdownButtonId}`, positionType: overlayPositionType, alignPoints: alignPoints, closeOnOutsideClick: closeOnOutsideClick, closeOnMouseDrag: closeOnMouseDrag, closeOnParentScroll: closeOnParentScroll, closeOnEscape: closeOnEscape, shouldCloseOnClick: shouldCloseOnClick, ignoreClicksOnByClass: ignoreClicksOnByClass, onAlign: handleOverlayAlign, onClose: closeDropdown, 
        // Overlay prevents event propagation by default using defaultProps for these
        onClick: enableEventPropagation ? () => { } : undefined, onMouseOver: enableEventPropagation ? () => { } : undefined, onMouseUp: enableEventPropagation ? () => { } : undefined, zIndex: overlayZIndex, children: _jsx(UiFocusManager, { tabOutHandler: shouldTrapFocus ? undefined : handleTabOut, enableFocusTrap: shouldTrapFocus, enableAutofocus: autofocusOnOpen ? { initialFocus, refocusKey: overlayAligned } : false, enableReturnFocusOnUnmount: { returnFocusTo }, children: _jsx("div", { className: cx("overlay", DROPDOWN_BODY_CLASS), onKeyDown: handleBodyKeyDown, children: renderBody({
                    ...renderBodyProps,
                    isMobile: false,
                }) }) }) }))) : null;
    return (_jsxs("div", { className: className, children: [_renderButton({
                ...renderButtonProps,
                isMobile: false,
                buttonRef,
            }), renderDropdown] }));
}
