// (C) 2025-2026 GoodData Corporation
import { useCallback, useMemo, useRef } from "react";
import { arrow, autoUpdate, flip, offset, shift, size, useFloating, } from "@floating-ui/react";
import { useOverlayZIndexWithRegister } from "../../Overlay/OverlayContext.js";
import { alignPointsToFallbackPlacements, alignPointsToPlacement, getOffsetFromAlignPoint } from "./utils.js";
// Stable empty array to avoid re-creating on every render
const EMPTY_MIDDLEWARE = [];
/**
 * Hook for floating element positioning using floating-ui.
 *
 * @internal
 */
export function useFloatingPosition({ isOpen, onOpenChange, placement: placementProp = "bottom-start", alignPoints, strategy = "absolute", offset: offsetProp, autoFlip = true, fallbackPlacements: fallbackPlacementsProp, arrowRef, customMiddleware = EMPTY_MIDDLEWARE, maxWidth, maxHeight, zIndex: zIndexProp, shiftPadding = 8, }) {
    const floatingRef = useRef(null);
    const registeredZIndex = useOverlayZIndexWithRegister();
    const placement = useMemo(() => {
        if (alignPoints && alignPoints.length > 0) {
            return alignPointsToPlacement(alignPoints);
        }
        return placementProp;
    }, [alignPoints, placementProp]);
    const computedOffset = useMemo(() => {
        if (offsetProp !== undefined) {
            return offsetProp;
        }
        if (alignPoints && alignPoints.length > 0) {
            return getOffsetFromAlignPoint(alignPoints[0], placement);
        }
        return 0;
    }, [alignPoints, offsetProp, placement]);
    const fallbackPlacements = useMemo(() => {
        if (fallbackPlacementsProp) {
            return fallbackPlacementsProp;
        }
        if (alignPoints && alignPoints.length > 1) {
            return alignPointsToFallbackPlacements(alignPoints);
        }
        return undefined;
    }, [alignPoints, fallbackPlacementsProp]);
    const middleware = useMemo(() => {
        const mw = [offset(computedOffset), ...customMiddleware];
        if (autoFlip) {
            mw.push(flip({
                fallbackPlacements,
                fallbackStrategy: "initialPlacement",
            }));
        }
        mw.push(shift({ padding: shiftPadding }));
        if (maxWidth !== undefined || maxHeight !== undefined) {
            mw.push(size({
                apply({ availableWidth, availableHeight, elements }) {
                    const maxW = typeof maxWidth === "number" ? `${maxWidth}px` : maxWidth;
                    const maxH = typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight;
                    Object.assign(elements.floating.style, {
                        maxWidth: maxW ?? `${availableWidth}px`,
                        maxHeight: maxH ?? `${availableHeight}px`,
                    });
                },
            }));
        }
        if (arrowRef) {
            mw.push(arrow({ element: arrowRef }));
        }
        return mw;
    }, [
        computedOffset,
        customMiddleware,
        autoFlip,
        shiftPadding,
        maxWidth,
        maxHeight,
        arrowRef,
        fallbackPlacements,
    ]);
    const { refs, floatingStyles, context, middlewareData, placement: actualPlacement, } = useFloating({
        open: isOpen,
        onOpenChange,
        placement,
        strategy,
        whileElementsMounted: autoUpdate,
        middleware,
    });
    const zIndex = zIndexProp ?? registeredZIndex;
    // Memoize setFloating to maintain referential stability
    const setFloating = useCallback((node) => {
        refs.setFloating(node);
        floatingRef.current = node;
    }, [refs]);
    // Memoize the refs object to prevent unnecessary effect runs in consumers
    const stableRefs = useMemo(() => ({
        setReference: refs.setReference,
        setFloating,
        reference: refs.reference,
        floating: floatingRef,
    }), [refs, setFloating]);
    return {
        refs: stableRefs,
        floatingStyles,
        placement: actualPlacement,
        zIndex,
        context,
        middlewareData,
    };
}
