import { slicedToArray as _slicedToArray, objectSpread2 as _objectSpread2, defineProperty as _defineProperty } from '../../../../_virtual/_rollupPluginBabelHelpers.js';
import { useRef, useState, useCallback, useLayoutEffect, useEffect, useMemo } from 'react';
import { useVirtualizer, defaultRangeExtractor } from '@tanstack/react-virtual';
import { DEFAULT_ESTIMATED_SLIDE_WIDTH, VIRTUAL_OVERSCAN, ITEM_VIRTUAL_ATTRIBUTE } from '../Carousel.js';
import { getGapWidth } from '../utils/getGapWidth.js';
import { getCalculatedPos } from '../utils/getCalculatedPos.js';
import { getClosestIndex } from '../utils/getClosestIndex.js';

// Хук инкапсулирует scroll, swipe и виртуализацию трека карусели.
var useCarousel = function useCarousel(_ref) {
  var _virtualItems$0$start, _virtualItems$, _virtualItems$end, _virtualItems;
  var index = _ref.index,
    scrollAlign = _ref.scrollAlign,
    onChangeIndex = _ref.onChangeIndex,
    swipeEnabled = _ref.swipeEnabled,
    slidesAmount = _ref.slidesAmount,
    gap = _ref.gap,
    _ref$virtual = _ref.virtual,
    virtual = _ref$virtual === void 0 ? false : _ref$virtual;
  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 = useState(0),
    _useState2 = _slicedToArray(_useState, 2),
    virtualGap = _useState2[0],
    setVirtualGap = _useState2[1];
  var _useState3 = useState(DEFAULT_ESTIMATED_SLIDE_WIDTH),
    _useState4 = _slicedToArray(_useState3, 2),
    fallbackSlideWidth = _useState4[0],
    setFallbackSlideWidth = _useState4[1];
  var _useState5 = useState({}),
    _useState6 = _slicedToArray(_useState5, 2),
    measuredSlideWidths = _useState6[0],
    setMeasuredSlideWidths = _useState6[1];
  var _useState7 = useState(virtual && index > 0),
    _useState8 = _slicedToArray(_useState7, 2),
    isBootstrappingVirtual = _useState8[0],
    setIsBootstrappingVirtual = _useState8[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 = (_virtualItems$0$start = (_virtualItems$ = virtualItems[0]) === null || _virtualItems$ === void 0 ? void 0 : _virtualItems$.start) !== null && _virtualItems$0$start !== void 0 ? _virtualItems$0$start : 0;
  var afterWidth = slidesAmount ? Math.max(0, virtualizer.getTotalSize() - ((_virtualItems$end = (_virtualItems = virtualItems[virtualItems.length - 1]) === null || _virtualItems === void 0 ? void 0 : _virtualItems.end) !== null && _virtualItems$end !== void 0 ? _virtualItems$end : 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$curre;
        if (measuredElementsRef.current[virtualIndex] === element) {
          return;
        }
        measuredElementsRef.current[virtualIndex] = element;
        if (!element) {
          return;
        }
        (_virtualizerRef$curre = virtualizerRef.current) === null || _virtualizerRef$curre === void 0 || _virtualizerRef$curre.measureElement(element);
        var _element$getBoundingC = element.getBoundingClientRect(),
          width = _element$getBoundingC.width;
        setMeasuredSlideWidths(function (prevState) {
          if (prevState[virtualIndex] === width) {
            return prevState;
          }
          return _objectSpread2(_objectSpread2({}, prevState), {}, _defineProperty({}, 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$current2;
          (_scrollRef$current2 = scrollRef.current) === null || _scrollRef$current2 === void 0 || _scrollRef$current2.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$current3;
        (_scrollRef$current3 = scrollRef.current) === null || _scrollRef$current3 === void 0 || _scrollRef$current3.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(_ref2) {
      var clientX = _ref2.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(_ref3) {
      var clientX = _ref3.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
  };
};

export { useCarousel };
