"use client";
import { cnb } from "cnbuilder";
import { useCallback, useEffect, useRef, useState } from "react";
import { useUserInteractionMode } from "../interaction/UserInteractionModeProvider.js";
import { useScrollLock } from "../scroll/useScrollLock.js";
import { useDir } from "../typography/WritingDirectionProvider.js";
import { useEnsuredRef } from "../useEnsuredRef.js";
import { useEnsuredState } from "../useEnsuredState.js";
import { useHtmlClassName } from "../useHtmlClassName.js";
import { getPercentage } from "../utils/getPercentage.js";
import { getRangeDefaultValue } from "../utils/getRangeDefaultValue.js";
import { getRangeSteps } from "../utils/getRangeSteps.js";
import { nearest } from "../utils/nearest.js";
import { withinRange } from "../utils/withinRange.js";
import { isMouseDragStartEvent, isTouchDragStartEvent, updateDragPosition } from "./utils.js";
const noop = ()=>{
// do nothing
};
/**
 * This is most likely an internal only hook that provides the functionality for
 * dragging an element through mouse, touch, and keyboard events. The main use
 * cases so far for this hook are:
 * - window splitters
 * - sliders
 *
 * @remarks \@since 6.0.0
 */ export function useDraggable(options) {
    const { ref: propRef, min, max, rangeMin = min, rangeMax = max, step = 1, reversed = false, vertical = false, onKeyDown = noop, onMouseUp = noop, onMouseDown = noop, onMouseMove = noop, onTouchStart = noop, onTouchMove = noop, dragging: propDragging, setDragging: propSetDragging, value: propValue, setValue: propSetValue, defaultValue, withinOffsetParent = false, disabled = false, disableDraggingClassName = false, disableDraggingCursorClassName = disableDraggingClassName } = options;
    const [nodeRef, ref] = useEnsuredRef(propRef);
    const isTouch = useUserInteractionMode() === "touch";
    const draggingRef = useRef(false);
    const [dragPercentage, setDragPercentage] = useState(min);
    const [value, setValue] = useEnsuredState({
        value: propValue,
        setValue: propSetValue,
        defaultValue: getRangeDefaultValue({
            min,
            max,
            step: 1,
            defaultValue
        })
    });
    const [dragging, setDragging] = useEnsuredState({
        value: propDragging,
        setValue: propSetDragging,
        defaultValue: false
    });
    const isRTL = useDir().dir === "rtl";
    const percentage = dragging && withinOffsetParent ? dragPercentage : getPercentage({
        min,
        max,
        value
    });
    const maximum = useCallback(()=>{
        setValue(max);
    }, [
        max,
        setValue
    ]);
    const minimum = useCallback(()=>{
        setValue(min);
    }, [
        min,
        setValue
    ]);
    const increment = useCallback(()=>{
        setValue((prevValue)=>withinRange({
                min,
                max,
                value: prevValue + step
            }));
    }, [
        max,
        min,
        setValue,
        step
    ]);
    const decrement = useCallback(()=>{
        setValue((prevValue)=>withinRange({
                min,
                max,
                value: prevValue - step
            }));
    }, [
        max,
        min,
        setValue,
        step
    ]);
    const draggingClassName = dragging && !disableDraggingClassName;
    useHtmlClassName(cnb(draggingClassName && "rmd-dragging"));
    useHtmlClassName(cnb(!disableDraggingCursorClassName && draggingClassName && `rmd-dragging--${vertical ? "v" : "h"}`));
    const draggedOnce = useRef(false);
    useEffect(()=>{
        if (!dragging) {
            return;
        }
        draggedOnce.current = true;
        const updatePosition = (event)=>{
            if (!event.cancelable) {
                return;
            }
            event.preventDefault();
            event.stopPropagation();
            updateDragPosition({
                event,
                nodeRef,
                min,
                max,
                step,
                rangeMin,
                rangeMax,
                isRTL,
                isDragStart: false,
                reversed,
                vertical,
                setValue,
                setDragging,
                setDragPercentage,
                withinOffsetParent
            });
        };
        const stopDragging = (event)=>{
            updatePosition(event);
            draggingRef.current = false;
            setDragging(false);
        };
        const updateKey = isTouch ? "touchmove" : "mousemove";
        const stopKey = isTouch ? "touchend" : "mouseup";
        const passive = isTouch ? {
            passive: false
        } : undefined;
        window.addEventListener(updateKey, updatePosition, passive);
        window.addEventListener(stopKey, stopDragging);
        return ()=>{
            window.removeEventListener(updateKey, updatePosition);
            window.removeEventListener(stopKey, stopDragging);
        };
    }, [
        dragging,
        isRTL,
        isTouch,
        max,
        min,
        nodeRef,
        rangeMax,
        rangeMin,
        reversed,
        setDragging,
        setValue,
        step,
        vertical,
        withinOffsetParent
    ]);
    const prevRange = useRef({
        min,
        max,
        step
    });
    useEffect(()=>{
        if (prevRange.current.min === min && prevRange.current.max === max && prevRange.current.step === step) {
            return;
        }
        // ensure that if the `min`, `max`, or `step` value changes that the value
        // is updated as well. Without this, there will be a runtime error if the
        // value is not within the new range.
        prevRange.current = {
            min,
            max,
            step
        };
        setValue((prevValue)=>nearest({
                min,
                max,
                steps: getRangeSteps({
                    min,
                    max,
                    step
                }),
                value: prevValue
            }));
    }, [
        max,
        min,
        setValue,
        step
    ]);
    const mouseEventHandlers = {
        onMouseDown: useCallback((event)=>{
            onMouseDown(event);
            if (disabled || isTouch || !isMouseDragStartEvent(event)) {
                return;
            }
            // dont' allow text to be selected
            event.preventDefault();
            updateDragPosition({
                isDragStart: true,
                event,
                nodeRef,
                min,
                max,
                step,
                rangeMin,
                rangeMax,
                isRTL,
                reversed,
                vertical,
                setValue,
                setDragging,
                setDragPercentage,
                withinOffsetParent
            });
            // don't set dragging immediately so that click events can still happen
            draggingRef.current = true;
        }, [
            disabled,
            isRTL,
            isTouch,
            max,
            min,
            nodeRef,
            onMouseDown,
            rangeMax,
            rangeMin,
            reversed,
            setDragging,
            setValue,
            step,
            vertical,
            withinOffsetParent
        ]),
        onMouseMove: useCallback((event)=>{
            onMouseMove(event);
            if (disabled || isTouch || !draggingRef.current || dragging) {
                return;
            }
            updateDragPosition({
                isDragStart: true,
                event,
                nodeRef,
                min,
                max,
                step,
                rangeMin,
                rangeMax,
                isRTL,
                reversed,
                vertical,
                setValue,
                setDragging,
                setDragPercentage,
                withinOffsetParent
            });
            setDragging(true);
        }, [
            disabled,
            dragging,
            isRTL,
            isTouch,
            max,
            min,
            nodeRef,
            onMouseMove,
            rangeMax,
            rangeMin,
            reversed,
            setDragging,
            setValue,
            step,
            vertical,
            withinOffsetParent
        ]),
        onMouseUp: useCallback((event)=>{
            onMouseUp(event);
            if (disabled || isTouch) {
                return;
            }
            draggingRef.current = false;
        }, [
            disabled,
            isTouch,
            onMouseUp
        ])
    };
    const keyboardEventHandlers = {
        onKeyDown: useCallback((event)=>{
            onKeyDown(event);
            if (disabled) {
                return;
            }
            const decrementKey = vertical ? "ArrowUp" : "ArrowLeft";
            const incrementKey = vertical ? "ArrowDown" : "ArrowRight";
            switch(event.key){
                case decrementKey:
                    event.preventDefault();
                    decrement();
                    break;
                case incrementKey:
                    event.preventDefault();
                    increment();
                    break;
                case "Home":
                    event.preventDefault();
                    minimum();
                    break;
                case "End":
                    event.preventDefault();
                    maximum();
                    break;
            }
        }, [
            decrement,
            disabled,
            increment,
            maximum,
            minimum,
            onKeyDown,
            vertical
        ])
    };
    // touch devices are a bit weird and cause issues since the "start" event is
    // also used for scrolling. If the user quickly grabs the draggable element
    // and drags vertically, most of the time it will try to scroll instead of
    // dragging the element. The workaround is to being the drag events
    // immediately on touchstart and disable scroll behavior for the page.
    //
    // There are also some issues with calling `event.preventDefault()` within
    // touch events even while `{ passive: false } is manually set, so need to
    // also attach a touchmove event.
    useScrollLock(isTouch && dragging);
    const touchEventHandlers = {
        onTouchStart: useCallback((event)=>{
            onTouchStart(event);
            if (disabled || !isTouchDragStartEvent(event)) {
                return;
            }
            draggingRef.current = true;
            updateDragPosition({
                isDragStart: true,
                event,
                nodeRef,
                min,
                max,
                step,
                rangeMin,
                rangeMax,
                isRTL,
                reversed,
                vertical,
                setValue,
                setDragging,
                setDragPercentage,
                withinOffsetParent
            });
        }, [
            disabled,
            isRTL,
            max,
            min,
            nodeRef,
            onTouchStart,
            rangeMax,
            rangeMin,
            reversed,
            setDragging,
            setValue,
            step,
            vertical,
            withinOffsetParent
        ]),
        onTouchMove: useCallback((event)=>{
            onTouchMove(event);
            if (disabled || !draggingRef.current || !event.cancelable) {
                return;
            }
            // prevent the document's touchmove event from also firing
            event.stopPropagation();
            updateDragPosition({
                isDragStart: true,
                event,
                nodeRef,
                min,
                max,
                step,
                rangeMin,
                rangeMax,
                isRTL,
                reversed,
                vertical,
                setValue,
                setDragging,
                setDragPercentage,
                withinOffsetParent
            });
        }, [
            disabled,
            isRTL,
            max,
            min,
            nodeRef,
            onTouchMove,
            rangeMax,
            rangeMin,
            reversed,
            setDragging,
            setValue,
            step,
            vertical,
            withinOffsetParent
        ])
    };
    return {
        ...touchEventHandlers,
        ...mouseEventHandlers,
        ...keyboardEventHandlers,
        value,
        setValue,
        dragging,
        setDragging,
        maximum,
        minimum,
        increment,
        decrement,
        draggedOnce,
        draggableRef: ref,
        percentage,
        dragPercentage,
        touchEventHandlers,
        mouseEventHandlers,
        keyboardEventHandlers
    };
}

//# sourceMappingURL=useDraggable.js.map