function _array_like_to_array(arr, len) {
    if (len == null || len > arr.length) len = arr.length;
    for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
    return arr2;
}
function _array_with_holes(arr) {
    if (Array.isArray(arr)) return arr;
}
function _define_property(obj, key, value) {
    if (key in obj) {
        Object.defineProperty(obj, key, {
            value: value,
            enumerable: true,
            configurable: true,
            writable: true
        });
    } else {
        obj[key] = value;
    }
    return obj;
}
function _iterable_to_array_limit(arr, i) {
    var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
    if (_i == null) return;
    var _arr = [];
    var _n = true;
    var _d = false;
    var _s, _e;
    try {
        for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){
            _arr.push(_s.value);
            if (i && _arr.length === i) break;
        }
    } catch (err) {
        _d = true;
        _e = err;
    } finally{
        try {
            if (!_n && _i["return"] != null) _i["return"]();
        } finally{
            if (_d) throw _e;
        }
    }
    return _arr;
}
function _non_iterable_rest() {
    throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _object_spread(target) {
    for(var i = 1; i < arguments.length; i++){
        var source = arguments[i] != null ? arguments[i] : {};
        var ownKeys = Object.keys(source);
        if (typeof Object.getOwnPropertySymbols === "function") {
            ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
                return Object.getOwnPropertyDescriptor(source, sym).enumerable;
            }));
        }
        ownKeys.forEach(function(key) {
            _define_property(target, key, source[key]);
        });
    }
    return target;
}
function ownKeys(object, enumerableOnly) {
    var keys = Object.keys(object);
    if (Object.getOwnPropertySymbols) {
        var symbols = Object.getOwnPropertySymbols(object);
        if (enumerableOnly) {
            symbols = symbols.filter(function(sym) {
                return Object.getOwnPropertyDescriptor(object, sym).enumerable;
            });
        }
        keys.push.apply(keys, symbols);
    }
    return keys;
}
function _object_spread_props(target, source) {
    source = source != null ? source : {};
    if (Object.getOwnPropertyDescriptors) {
        Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
    } else {
        ownKeys(Object(source)).forEach(function(key) {
            Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
        });
    }
    return target;
}
function _sliced_to_array(arr, i) {
    return _array_with_holes(arr) || _iterable_to_array_limit(arr, i) || _unsupported_iterable_to_array(arr, i) || _non_iterable_rest();
}
function _unsupported_iterable_to_array(o, minLen) {
    if (!o) return;
    if (typeof o === "string") return _array_like_to_array(o, minLen);
    var n = Object.prototype.toString.call(o).slice(8, -1);
    if (n === "Object" && o.constructor) n = o.constructor.name;
    if (n === "Map" || n === "Set") return Array.from(n);
    if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
}
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { defaultRangeExtractor, useVirtualizer } from "@tanstack/react-virtual";
import { getGapWidth, getClosestIndex, getCalculatedPos } from "../utils";
import { VIRTUAL_OVERSCAN, DEFAULT_ESTIMATED_SLIDE_WIDTH, ITEM_VIRTUAL_ATTRIBUTE } from "../Carousel";
// Хук инкапсулирует scroll, swipe и виртуализацию трека карусели.
export var useCarousel = function useCarousel(param) {
    var index = param.index, scrollAlign = param.scrollAlign, onChangeIndex = param.onChangeIndex, swipeEnabled = param.swipeEnabled, slidesAmount = param.slidesAmount, gap = param.gap, _param_virtual = param.virtual, virtual = _param_virtual === void 0 ? false : _param_virtual;
    var _ref, _ref1;
    var _virtualItems_, _virtualItems_1;
    var scrollRef = useRef(null);
    var trackRef = useRef(null);
    var isFirstRender = useRef(true);
    var isInteracting = useRef(false);
    var pendingVirtualIndexRef = useRef(virtual ? index : null);
    var virtualizerRef = useRef(null);
    var measuredElementsRef = useRef({});
    var measureCallbacksRef = useRef({});
    var _useState = _sliced_to_array(useState(0), 2), virtualGap = _useState[0], setVirtualGap = _useState[1];
    var _useState1 = _sliced_to_array(useState(DEFAULT_ESTIMATED_SLIDE_WIDTH), 2), fallbackSlideWidth = _useState1[0], setFallbackSlideWidth = _useState1[1];
    var _useState2 = _sliced_to_array(useState({}), 2), measuredSlideWidths = _useState2[0], setMeasuredSlideWidths = _useState2[1];
    var _useState3 = _sliced_to_array(useState(virtual && index > 0), 2), isBootstrappingVirtual = _useState3[0], setIsBootstrappingVirtual = _useState3[1];
    var runWithoutScrollAnimation = useCallback(function(callback) {
        var scrollElement = scrollRef.current;
        if (!scrollElement) {
            callback();
            return;
        }
        var initialScrollBehavior = scrollElement.style.scrollBehavior;
        scrollElement.style.scrollBehavior = 'auto';
        callback();
        window.requestAnimationFrame(function() {
            if (scrollRef.current) {
                scrollRef.current.style.scrollBehavior = initialScrollBehavior;
            }
        });
    }, []);
    // Считывает gap и стартовую ширину viewport для первичной оценки размера слайда.
    useLayoutEffect(function() {
        var _scrollRef_current;
        if (!virtual) {
            setVirtualGap(0);
            setFallbackSlideWidth(DEFAULT_ESTIMATED_SLIDE_WIDTH);
            return;
        }
        setVirtualGap(getGapWidth(trackRef.current));
        setFallbackSlideWidth(((_scrollRef_current = scrollRef.current) === null || _scrollRef_current === void 0 ? void 0 : _scrollRef_current.clientWidth) || DEFAULT_ESTIMATED_SLIDE_WIDTH);
    }, [
        gap,
        virtual
    ]);
    // Сбрасывает измерения при смене набора слайдов или режима виртуализации.
    useEffect(function() {
        setMeasuredSlideWidths({});
    }, [
        slidesAmount,
        virtual
    ]);
    // Строит estimateSize для virtualizer по реальным измерениям или fallback-ширине.
    var estimatedSlideWidth = useMemo(function() {
        var measuredWidths = Object.values(measuredSlideWidths);
        if (measuredWidths.length === 0) {
            return fallbackSlideWidth;
        }
        return measuredWidths.reduce(function(sum, width) {
            return sum + width;
        }, 0) / measuredWidths.length;
    }, [
        fallbackSlideWidth,
        measuredSlideWidths
    ]);
    // Настраивает virtualizer.
    var virtualizer = useVirtualizer({
        count: slidesAmount,
        enabled: virtual,
        horizontal: true,
        overscan: VIRTUAL_OVERSCAN,
        gap: virtualGap,
        indexAttribute: ITEM_VIRTUAL_ATTRIBUTE,
        getScrollElement: function getScrollElement() {
            return scrollRef.current;
        },
        estimateSize: function estimateSize() {
            return estimatedSlideWidth;
        },
        rangeExtractor: function rangeExtractor(range) {
            if (!isBootstrappingVirtual) {
                return defaultRangeExtractor(range);
            }
            var bootstrapEnd = Math.min(slidesAmount - 1, Math.max(range.endIndex, index + VIRTUAL_OVERSCAN));
            return Array.from({
                length: bootstrapEnd + 1
            }, function(_, currentIndex) {
                return currentIndex;
            });
        }
    });
    virtualizerRef.current = virtualizer;
    // Переизмеряет virtualizer после уточнения средней ширины виртуальных слайдов.
    useEffect(function() {
        if (virtual) {
            virtualizer.measure();
        }
    }, [
        estimatedSlideWidth,
        virtual,
        virtualizer
    ]);
    var virtualItems = virtualizer.getVirtualItems();
    var beforeWidth = (_ref = (_virtualItems_ = virtualItems[0]) === null || _virtualItems_ === void 0 ? void 0 : _virtualItems_.start) !== null && _ref !== void 0 ? _ref : 0;
    var afterWidth = slidesAmount ? Math.max(0, virtualizer.getTotalSize() - ((_ref1 = (_virtualItems_1 = virtualItems[virtualItems.length - 1]) === null || _virtualItems_1 === void 0 ? void 0 : _virtualItems_1.end) !== null && _ref1 !== void 0 ? _ref1 : 0)) : 0;
    var beforeSpacerWidth = beforeWidth > 0 ? Math.max(0, beforeWidth - virtualGap) : 0;
    var afterSpacerWidth = afterWidth > 0 ? Math.max(0, afterWidth - virtualGap) : 0;
    // Измеряет реальную ширину виртуального слайда и сохраняет её для последующих расчётов.
    var measureVirtualItem = useCallback(function(virtualIndex) {
        if (!measureCallbacksRef.current[virtualIndex]) {
            measureCallbacksRef.current[virtualIndex] = function(element) {
                var _virtualizerRef_current;
                if (measuredElementsRef.current[virtualIndex] === element) {
                    return;
                }
                measuredElementsRef.current[virtualIndex] = element;
                if (!element) {
                    return;
                }
                (_virtualizerRef_current = virtualizerRef.current) === null || _virtualizerRef_current === void 0 ? void 0 : _virtualizerRef_current.measureElement(element);
                var width = element.getBoundingClientRect().width;
                setMeasuredSlideWidths(function(prevState) {
                    if (prevState[virtualIndex] === width) {
                        return prevState;
                    }
                    return _object_spread_props(_object_spread({}, prevState), _define_property({}, virtualIndex, width));
                });
            };
        }
        return measureCallbacksRef.current[virtualIndex];
    }, []);
    // Прокручивает карусель при смене index.
    // Строго в layout-phase, чтобы стартовый кадр сразу был на нужном слайде до первого пеинта.
    useLayoutEffect(function() {
        if (!scrollRef.current || !trackRef.current || isInteracting.current) {
            return;
        }
        if (virtual) {
            if (isFirstRender.current) {
                return;
            }
            pendingVirtualIndexRef.current = index;
            virtualizer.scrollToIndex(index, {
                align: scrollAlign,
                behavior: 'smooth'
            });
            return;
        }
        var itemsCollection = trackRef.current.children;
        if (itemsCollection.length > 0) {
            var pos = getCalculatedPos({
                scrollRef: scrollRef,
                trackRef: trackRef,
                itemsCollection: itemsCollection,
                index: index,
                scrollAlign: scrollAlign,
                itemAttribute: ITEM_VIRTUAL_ATTRIBUTE
            });
            if (isFirstRender.current) {
                runWithoutScrollAnimation(function() {
                    var _scrollRef_current;
                    (_scrollRef_current = scrollRef.current) === null || _scrollRef_current === void 0 ? void 0 : _scrollRef_current.scrollTo({
                        left: pos,
                        behavior: 'auto'
                    });
                });
            } else {
                scrollRef.current.scrollTo({
                    left: pos,
                    behavior: 'smooth'
                });
            }
        }
        if (isFirstRender.current) {
            isFirstRender.current = false;
        }
    }, [
        index,
        scrollAlign,
        virtual,
        virtualizer
    ]);
    // После появления целевого виртуального слайда в DOM выполняет точный scroll с учётом scrollAlign.
    useLayoutEffect(function() {
        if (!virtual || !scrollRef.current || !trackRef.current || pendingVirtualIndexRef.current !== index) {
            return;
        }
        var targetElement = trackRef.current.querySelector("[".concat(ITEM_VIRTUAL_ATTRIBUTE, '="').concat(index, '"]'));
        if (!targetElement) {
            return;
        }
        var itemsCollection = trackRef.current.children;
        var pos = getCalculatedPos({
            scrollRef: scrollRef,
            trackRef: trackRef,
            itemsCollection: itemsCollection,
            index: index,
            scrollAlign: scrollAlign,
            itemAttribute: ITEM_VIRTUAL_ATTRIBUTE
        });
        if (isFirstRender.current) {
            runWithoutScrollAnimation(function() {
                var _scrollRef_current;
                (_scrollRef_current = scrollRef.current) === null || _scrollRef_current === void 0 ? void 0 : _scrollRef_current.scrollTo({
                    left: pos,
                    behavior: 'auto'
                });
            });
        } else {
            scrollRef.current.scrollTo({
                left: pos,
                behavior: 'smooth'
            });
        }
        pendingVirtualIndexRef.current = null;
        if (isFirstRender.current) {
            isFirstRender.current = false;
        }
        if (isBootstrappingVirtual) {
            setIsBootstrappingVirtual(false);
        }
    }, [
        index,
        isBootstrappingVirtual,
        scrollAlign,
        virtual,
        virtualItems
    ]);
    // Подключает pointer drag/swipe и доводит карусель до ближайшего слайда после завершения жеста.
    useEffect(function() {
        var scrollElement = scrollRef.current;
        if (!scrollElement) {
            return;
        }
        var startX = 0;
        var startScrollLeft = 0;
        var activePointerId = null;
        var initialScrollBehavior = '';
        var initialScrollSnapType = '';
        // Возвращает inline-настройки scroll после завершения drag-взаимодействия.
        var restoreScrollBehavior = function restoreScrollBehavior() {
            scrollElement.style.scrollBehavior = initialScrollBehavior;
            scrollElement.style.scrollSnapType = initialScrollSnapType;
        };
        // Сбрасывает служебное состояние drag/swipe.
        var resetInteraction = function resetInteraction() {
            activePointerId = null;
            isInteracting.current = false;
            restoreScrollBehavior();
        };
        // Находит ближайший слайд и завершает drag точным скроллом к нему.
        var finishInteraction = function finishInteraction() {
            var _trackRef_current;
            var nextIndex = getClosestIndex({
                scrollRef: scrollRef,
                trackRef: trackRef,
                scrollAlign: scrollAlign,
                itemAttribute: ITEM_VIRTUAL_ATTRIBUTE
            });
            var itemsCollection = (_trackRef_current = trackRef.current) === null || _trackRef_current === void 0 ? void 0 : _trackRef_current.children;
            var nextPos = itemsCollection && itemsCollection.length ? getCalculatedPos({
                scrollRef: scrollRef,
                trackRef: trackRef,
                itemsCollection: itemsCollection,
                index: nextIndex,
                scrollAlign: scrollAlign,
                itemAttribute: ITEM_VIRTUAL_ATTRIBUTE
            }) : scrollElement.scrollLeft;
            resetInteraction();
            scrollElement.scrollTo({
                left: nextPos,
                behavior: 'smooth'
            });
            onChangeIndex(nextIndex);
        };
        // Запоминает стартовую точку drag и временно отключает smooth/snap на время жеста.
        var startInteraction = function startInteraction(param) {
            var clientX = param.clientX;
            startX = clientX;
            startScrollLeft = scrollElement.scrollLeft;
            initialScrollBehavior = scrollElement.style.scrollBehavior;
            initialScrollSnapType = scrollElement.style.scrollSnapType;
            scrollElement.style.scrollBehavior = 'auto';
            scrollElement.style.scrollSnapType = 'none';
            isInteracting.current = true;
        };
        // Сдвигает scrollLeft в соответствии с текущим смещением указателя.
        var moveInteraction = function moveInteraction(param) {
            var clientX = param.clientX;
            if (activePointerId === null) {
                return;
            }
            var diffX = clientX - startX;
            scrollElement.scrollLeft = startScrollLeft - diffX;
        };
        // Обрабатывает перемещение активного указателя во время drag.
        var onPointerMove = function onPointerMove(event) {
            if (event.pointerId !== activePointerId) {
                return;
            }
            moveInteraction({
                clientX: event.clientX
            });
            if (event.cancelable) {
                event.preventDefault();
            }
        };
        // Завершает pointer-взаимодействие и отпускает pointer capture.
        var stopPointerInteraction = function stopPointerInteraction(event) {
            if (event.pointerId !== activePointerId) {
                return;
            }
            if (scrollElement.hasPointerCapture(event.pointerId)) {
                scrollElement.releasePointerCapture(event.pointerId);
            }
            finishInteraction();
        };
        // Стартует drag только для допустимых primary-pointer событий.
        var onPointerDown = function onPointerDown(event) {
            if (!swipeEnabled || !event.isPrimary || event.pointerType === 'mouse' && event.button !== 0) {
                return;
            }
            activePointerId = event.pointerId;
            startInteraction({
                clientX: event.clientX
            });
            scrollElement.setPointerCapture(event.pointerId);
        };
        if (swipeEnabled) {
            scrollElement.addEventListener('pointerdown', onPointerDown);
            scrollElement.addEventListener('pointermove', onPointerMove);
            scrollElement.addEventListener('pointerup', stopPointerInteraction);
            scrollElement.addEventListener('pointercancel', stopPointerInteraction);
        }
        return function() {
            resetInteraction();
            scrollElement.removeEventListener('pointerdown', onPointerDown);
            scrollElement.removeEventListener('pointermove', onPointerMove);
            scrollElement.removeEventListener('pointerup', stopPointerInteraction);
            scrollElement.removeEventListener('pointercancel', stopPointerInteraction);
        };
    }, [
        onChangeIndex,
        scrollAlign,
        swipeEnabled
    ]);
    return {
        scrollRef: scrollRef,
        trackRef: trackRef,
        virtualItems: virtualItems,
        beforeSpacerWidth: beforeSpacerWidth,
        afterSpacerWidth: afterSpacerWidth,
        measureVirtualItem: measureVirtualItem
    };
};
