var __rest = (this && this.__rest) || function (s, e) {
    var t = {};
    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
        t[p] = s[p];
    if (s != null && typeof Object.getOwnPropertySymbols === "function")
        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                t[p[i]] = s[p[i]];
        }
    return t;
};
/** biome-ignore-all lint/correctness/useHookAtTopLevel: False positive because of the way forwardRef() is added */
import React, { forwardRef, useCallback, useEffect, useMemo, useRef, useState, } from "react";
import { useId } from "../../../utils-external/index.js";
import { Floating } from "../../../utils/components/floating/Floating.js";
import { cl } from "../../../utils/helpers/index.js";
import DragAndDropItem from "../item/DragAndDropItem.js";
import { DragAndDropProvider } from "./DragAndDrop.context.js";
/**
 * TODO:
 *
 * Backlog:
 * [ ] Quick nav (< > samtidig) - få piltastene til å fungere - ignore?
 * [ ] Look at instructions text
 *
 */
const DRAG_THRESHOLD = 4; // Minimum movement in pixels to start dragging
function DragAndDropInner(_a, forwardedRef) {
    var _b;
    var { items, setItems, renderItem, className } = _a, rest = __rest(_a, ["items", "setItems", "renderItem", "className"]);
    const instructionsId = useId();
    const [activeItem, setActiveItem] = useState(null);
    const [dropTarget, setDropTarget] = useState(null);
    const [dragHandlerActive, setDragHandlerActive] = useState(null);
    const [overlayWidth, setOverlayWidth] = useState(null);
    const [announcer, setAnnouncer] = useState("");
    const initialItemsRef = useRef(null);
    const virtualPositionRef = useRef({ x: 0, y: 0 });
    const itemsById = useMemo(() => new Map(items.map((item) => [item.id, item])), [items]);
    const activeData = activeItem ? itemsById.get(activeItem.id) : undefined;
    const activeItemRef = useRef(null);
    const dropTargetRef = useRef(null);
    const virtualRef = useMemo(() => ({
        getBoundingClientRect: () => DOMRect.fromRect({
            width: 0,
            height: 0,
            x: virtualPositionRef.current.x,
            y: virtualPositionRef.current.y,
        }),
    }), []);
    const pendingDragStartRef = useRef(null);
    const saveInitialItems = useCallback(() => {
        initialItemsRef.current = items;
    }, [items]);
    const keyboardDragStart = (item) => {
        if (item) {
            saveInitialItems();
        }
        else {
            initialItemsRef.current = null;
        }
        setDragHandlerActive(item);
    };
    const startPendingDrag = (event, item, element) => {
        pendingDragStartRef.current = {
            item,
            element: element || null,
            pointerId: event.pointerId,
            startX: event.clientX,
            startY: event.clientY,
        };
    };
    const setCombinedActiveItem = useCallback((item) => {
        activeItemRef.current = item;
        setActiveItem(item);
    }, []);
    const setCombinedDropTarget = useCallback((item) => {
        const previous = dropTargetRef.current;
        if ((previous === null || previous === void 0 ? void 0 : previous.id) === (item === null || item === void 0 ? void 0 : item.id) && (previous === null || previous === void 0 ? void 0 : previous.index) === (item === null || item === void 0 ? void 0 : item.index)) {
            return;
        }
        dropTargetRef.current = item;
        setDropTarget(item);
    }, []);
    const reorderItems = useCallback((fromIndex, toIndex) => {
        const currentItems = items;
        if (fromIndex === toIndex ||
            fromIndex < 0 ||
            toIndex < 0 ||
            fromIndex >= currentItems.length ||
            toIndex >= currentItems.length) {
            return;
        }
        const newItems = [...currentItems];
        const [movedItem] = newItems.splice(fromIndex, 1);
        if (!movedItem) {
            return;
        }
        newItems.splice(toIndex, 0, movedItem);
        setItems(newItems);
    }, [items, setItems]);
    const cancelDrag = useCallback((resetOrder = false) => {
        if (resetOrder && initialItemsRef.current) {
            setItems(initialItemsRef.current);
        }
        setOverlayWidth(null);
        setDragHandlerActive(null);
        setCombinedActiveItem(null);
        setCombinedDropTarget(null);
        pendingDragStartRef.current = null;
        initialItemsRef.current = null;
    }, [setItems, setCombinedActiveItem, setCombinedDropTarget]);
    useEffect(() => {
        /* This useEffect is used to toggle a class on the html element when dragging,
          to prevent cursor issues when dragging over interactive elements,
          and to prevent text selection during dragging. */
        if (activeItem) {
            document.documentElement.setAttribute("data-dragging-cursor", "true");
            document.body.style.userSelect = "none";
        }
        else {
            document.documentElement.removeAttribute("data-dragging-cursor");
            document.body.style.userSelect = "";
        }
        return () => {
            document.documentElement.removeAttribute("data-dragging-cursor");
            document.body.style.userSelect = "";
        };
    }, [activeItem]);
    useEffect(() => {
        const handlePointerMove = (event) => {
            var _a, _b, _c;
            const pendingStart = pendingDragStartRef.current;
            const activeRef = activeItemRef.current;
            const element = pendingStart === null || pendingStart === void 0 ? void 0 : pendingStart.element;
            if (!activeRef && pendingStart) {
                const deltaX = Math.abs(event.clientX - pendingStart.startX);
                const deltaY = Math.abs(event.clientY - pendingStart.startY);
                if (deltaX >= DRAG_THRESHOLD || deltaY >= DRAG_THRESHOLD) {
                    if (element) {
                        element.setPointerCapture(pendingStart.pointerId);
                    }
                    setOverlayWidth((_a = element === null || element === void 0 ? void 0 : element.getBoundingClientRect().width) !== null && _a !== void 0 ? _a : null);
                    saveInitialItems();
                    setCombinedActiveItem(pendingStart.item);
                    setCombinedDropTarget(pendingStart.item);
                    pendingDragStartRef.current = null;
                }
                return;
            }
            const active = activeItemRef.current;
            if (!active)
                return;
            virtualPositionRef.current = { x: event.clientX, y: event.clientY };
            const elements = document.elementsFromPoint(event.clientX, event.clientY);
            const matchingElements = elements.filter((el) => el instanceof HTMLElement && Boolean(el.closest("[data-dnd-id]")));
            const itemElements = matchingElements
                .map((el) => el.closest("[data-dnd-id]"))
                .filter((el) => el instanceof HTMLElement);
            const uniqueItemElements = Array.from(new Set(itemElements));
            const targetElement = (_c = (_b = uniqueItemElements.find((el) => el.dataset.dndId !== active.id)) !== null && _b !== void 0 ? _b : uniqueItemElements.find((el) => el.dataset.dndId === active.id)) !== null && _c !== void 0 ? _c : null;
            if (!targetElement) {
                setCombinedDropTarget(null);
                return;
            }
            const hoveredId = targetElement.dataset.dndId;
            const hoveredIndex = Number(targetElement.dataset.dndIndex);
            if (!hoveredId || Number.isNaN(hoveredIndex)) {
                setCombinedDropTarget(null);
                return;
            }
            setCombinedDropTarget({ id: hoveredId, index: hoveredIndex });
        };
        const handlePointerUp = () => {
            if (!activeItemRef.current) {
                pendingDragStartRef.current = null;
                return;
            }
            const active = activeItemRef.current;
            const target = dropTargetRef.current;
            if (active && target && active.id !== target.id) {
                reorderItems(active.index, target.index);
            }
            cancelDrag();
        };
        const handlePointerCancel = () => cancelDrag(true);
        window.addEventListener("pointermove", handlePointerMove);
        window.addEventListener("pointerup", handlePointerUp);
        window.addEventListener("pointercancel", handlePointerCancel);
        return () => {
            window.removeEventListener("pointermove", handlePointerMove);
            window.removeEventListener("pointerup", handlePointerUp);
            window.removeEventListener("pointercancel", handlePointerCancel);
        };
    }, [
        setCombinedDropTarget,
        setCombinedActiveItem,
        reorderItems,
        saveInitialItems,
        cancelDrag,
    ]);
    const describedBy = cl(rest["aria-describedby"], instructionsId);
    const onKeyboardDragEnd = (diff, label) => {
        if (!dragHandlerActive)
            return;
        const targetIndex = dragHandlerActive.index + diff;
        if (targetIndex < 0 || targetIndex >= items.length) {
            return;
        }
        setAnnouncer(`${label}. Plass ${targetIndex + 1} av ${items.length}.`);
        reorderItems(dragHandlerActive.index, targetIndex);
        setDragHandlerActive(Object.assign(Object.assign({}, dragHandlerActive), { index: targetIndex }));
    };
    return (React.createElement(DragAndDropProvider, { activeItem: activeItem, setActiveItem: setCombinedActiveItem, dropTarget: dropTarget, setDropTarget: setCombinedDropTarget, dragHandlerActive: dragHandlerActive, onKeyboardDragStart: keyboardDragStart, onKeyboardDragEnd: onKeyboardDragEnd, startPendingDrag: startPendingDrag, cancelDrag: cancelDrag, setAnnouncer: setAnnouncer, itemAmount: items.length },
        React.createElement("span", { id: instructionsId, className: "aksel-sr-only" }, "Bruk Tab for \u00E5 fokusere p\u00E5 en kolonne. Trykk mellomrom eller enter for \u00E5 starte flytting, bruk piltastene for \u00E5 flytte kolonnen, trykk mellomrom eller enter for \u00E5 slippe, eller Escape for \u00E5 avbryte."),
        React.createElement("div", { "aria-live": "assertive", className: "aksel-sr-only", "aria-atomic": true }, announcer),
        React.createElement("ul", Object.assign({}, rest, { ref: forwardedRef, "aria-label": (_b = rest["aria-label"]) !== null && _b !== void 0 ? _b : "Kolonneinnstillinger", "aria-describedby": describedBy, className: cl("aksel-data-table__drag-and-drop-root", className) }), items.map((item, index) => {
            return (React.createElement(DragAndDropItem, { key: item.id, id: item.id, index: index, itemLabel: item.label }, renderItem(item, index)));
        })),
        activeItem && activeData && (React.createElement(Floating, null,
            React.createElement(Floating.Anchor, { virtualRef: virtualRef },
                React.createElement("span", null)),
            React.createElement(Floating.Content, { align: "start", updatePositionStrategy: "always", "aria-hidden": true, style: {
                    pointerEvents: "none",
                    boxSizing: "border-box",
                    width: overlayWidth ? `${overlayWidth}px` : "fit-content",
                } },
                React.createElement(DragAndDropItem, { id: activeItem.id, index: activeItem.index, isOverlay: true, itemLabel: activeData.label }, renderItem(activeData, activeItem.index)))))));
}
const DragAndDrop = forwardRef(DragAndDropInner);
export { DragAndDrop, DragAndDropItem };
export default DragAndDrop;
//# sourceMappingURL=DragAndDropRoot.js.map