'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

var React = require('react');
var throttle = require('lodash.throttle');

function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }

var throttle__default = /*#__PURE__*/_interopDefault(throttle);

/**
 * Подсчет смещения из-за паддингов.
 */
var getCalculatedOffset = function getCalculatedOffset(scrollEl, trackEl, axis) {
  var paddingProp = axis === 'x' ? 'paddingLeft' : 'paddingTop';
  return parseInt(getComputedStyle(scrollEl)[paddingProp], 10) + parseInt(getComputedStyle(trackEl)[paddingProp], 10);
};
var positionModByScrollAlign = function positionModByScrollAlign(_ref) {
  var scrollAlign = _ref.scrollAlign,
    position = _ref.position,
    carouselSize = _ref.carouselSize,
    itemSize = _ref.itemSize,
    offset = _ref.offset,
    scrollStart = _ref.scrollStart,
    axis = _ref.axis;
  if (scrollAlign === 'start') {
    var inaccuracy = 1;
    var paddingOffset = axis === 'y' ? offset - itemSize / 2 + inaccuracy : 0;
    return position + paddingOffset;
  }
  if (scrollAlign === 'center') {
    return position - carouselSize / 2 + itemSize / 2;
  }
  if (scrollAlign === 'end') {
    return position - carouselSize + itemSize + offset;
  }
  if (scrollAlign === 'activeDirection') {
    if (position >= scrollStart + carouselSize - itemSize) {
      return position - carouselSize + itemSize + offset;
    }
    if (position > scrollStart) {
      return scrollStart;
    }
  }
  return position;
};

/**
 * Подсчет скролла до переданного индекса.
 */
var getCalculatedPos = function getCalculatedPos(_ref2) {
  var scrollEl = _ref2.scrollEl,
    items = _ref2.items,
    axis = _ref2.axis,
    index = _ref2.index,
    offset = _ref2.offset,
    scrollAlign = _ref2.scrollAlign;
  var position = scrollAlign === 'center' ? offset : 0;
  var carouselSize;
  var itemSize;
  var scrollStart;
  if (items.item(index) === null) {
    return position;
  }
  for (var i = 0; i < index; i++) {
    if (axis === 'x') {
      var _items$item$offsetWid, _items$item;
      position += (_items$item$offsetWid = (_items$item = items.item(i)) === null || _items$item === void 0 ? void 0 : _items$item.offsetWidth) !== null && _items$item$offsetWid !== void 0 ? _items$item$offsetWid : 0;
    } else {
      var _items$item$offsetHei, _items$item2;
      position += (_items$item$offsetHei = (_items$item2 = items.item(i)) === null || _items$item2 === void 0 ? void 0 : _items$item2.offsetHeight) !== null && _items$item$offsetHei !== void 0 ? _items$item$offsetHei : 0;
    }
  }
  if (axis === 'x') {
    var _items$item$offsetWid2, _items$item3;
    carouselSize = scrollEl.offsetWidth;
    itemSize = (_items$item$offsetWid2 = (_items$item3 = items.item(index)) === null || _items$item3 === void 0 ? void 0 : _items$item3.offsetWidth) !== null && _items$item$offsetWid2 !== void 0 ? _items$item$offsetWid2 : 0;
    scrollStart = scrollEl.scrollLeft;
  } else {
    var _items$item$offsetHei2, _items$item4;
    carouselSize = scrollEl.offsetHeight;
    itemSize = (_items$item$offsetHei2 = (_items$item4 = items.item(index)) === null || _items$item4 === void 0 ? void 0 : _items$item4.offsetHeight) !== null && _items$item$offsetHei2 !== void 0 ? _items$item$offsetHei2 : 0;
    scrollStart = scrollEl.scrollTop;
  }
  return positionModByScrollAlign({
    scrollAlign: scrollAlign,
    position: position,
    carouselSize: carouselSize,
    itemSize: itemSize,
    offset: offset,
    scrollStart: scrollStart,
    axis: axis
  });
};
var DEFAULT_DURATION = 300;

// https://css-tricks.com/emulating-css-timing-functions-javascript/
var tfs = {
  linear: function linear(t) {
    return t;
  },
  // eslint-disable-next-line
  easeIn: function easeIn(t) {
    return Math.pow(t, 1.675);
  },
  // eslint-disable-next-line
  easeOut: function easeOut(t) {
    return 1 - Math.pow(1 - t, 1.675);
  },
  easeInOut: function easeInOut(t) {
    return 0.5 * (Math.sin((t - 0.5) * Math.PI) + 1);
  }
};
/**
 * Плавная прокрутка по горизонтали
 * @param {Element} elem
 * @param {number} pos
 * @param {number} duration
 * @param {string} timingFunction
 */
var animatedScrollToX = function animatedScrollToX(elem, pos) {
  var duration = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : DEFAULT_DURATION;
  var timingFunction = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'easeInOut';
  var startTime;
  var startX = elem.scrollLeft;
  var endX = Math.max(0, Math.min(elem.scrollWidth - elem.clientWidth, pos));
  var handleNewAnimationFrame = function handleNewAnimationFrame() {
    startTime = startTime || Date.now();
    var timePos = Math.min(1, Math.max(1, Date.now() - startTime) / duration);
    var scrollPos = tfs[timingFunction](timePos);
    var left = startX + (endX - startX) * scrollPos;
    elem.scrollTo({
      left: left
    });
    if (timePos !== 1) window.requestAnimationFrame(handleNewAnimationFrame);
  };
  window.requestAnimationFrame(handleNewAnimationFrame);
};

/**
 * Прокрутка к указанной позиции с анимацией или без.
 */
var scrollToPos = function scrollToPos(_ref3) {
  var scrollEl = _ref3.scrollEl,
    pos = _ref3.pos,
    axis = _ref3.axis,
    animated = _ref3.animated,
    duration = _ref3.duration,
    timingFunction = _ref3.timingFunction;
  if (axis === 'x' && Math.abs(pos - scrollEl.scrollLeft) > 1) {
    if (animated) {
      animatedScrollToX(scrollEl, pos, duration, timingFunction);
    } else {
      scrollEl.scrollTo({
        left: pos
      });
    }
  }
};
var round = function round(n) {
  return Math.round(n * 100) / 100;
};

/**
 * Получить позицию (слот) айтема в каруселе.
 * Каждый айтем имеет свой слот относительно вьюпорта карусели.
 */
var getItemSlot = function getItemSlot(itemIndex, itemEnd, itemSize, scrollStart, scrollSize, scrollAlign) {
  var prevIndex = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : 0;
  var offset = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : 0;
  /**
   * Граница и центр скролла (видимой части).
   * Смещение + размер.
   */
  var scrollEnd = scrollStart + scrollSize;
  var scrollCenter = scrollStart + scrollSize / 2;
  var itemCenter = itemEnd - itemSize / 2;
  if (scrollAlign === 'center') {
    return round((itemCenter - scrollCenter) / itemSize);
  }
  if (scrollAlign === 'start') {
    return round((itemEnd - itemSize - scrollStart) / itemSize);
  }
  if (scrollAlign === 'end') {
    return round((itemEnd - (scrollSize + scrollStart)) / itemSize);
  }
  if (scrollAlign === 'activeDirection') {
    var prevStart = offset + itemSize * prevIndex;
    var prevEnd = prevStart + itemSize;
    var prevVisible = prevEnd > scrollStart && prevStart < scrollEnd;
    if (!prevVisible) {
      if (prevIndex < itemIndex) {
        return round((itemEnd - (scrollSize + scrollStart)) / itemSize);
      }
      return round((itemEnd - itemSize - scrollStart) / itemSize);
    }
  }
  return null;
};
function getCarouselItems(track) {
  return track.children;
}
function useDebouncedFunction(func, delay, cleanUp) {
  var timeoutRef = React.useRef();

  /**
   * Очистка таймера
   */
  function clearTimer() {
    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current);
      timeoutRef.current = undefined;
    }
  }

  /**
   * Очищаем таймер при анмаунте компонента, если cleanUp выставлен в true
   * и тем самым отменяем последний запланированный вызов
   */
  React.useEffect(function () {
    return cleanUp ? clearTimer : undefined;
  }, [cleanUp]);
  return function () {
    for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
      args[_key] = arguments[_key];
    }
    clearTimer();
    timeoutRef.current = setTimeout(function () {
      return func.apply(void 0, args);
    }, delay);
  };
}
var THROTTLE_DEFAULT_MS = 100;
var DEBOUNCE_DEFAULT_MS = 150;
var useCarousel = function useCarousel(_ref4) {
  var index = _ref4.index,
    _ref4$axis = _ref4.axis,
    axis = _ref4$axis === void 0 ? 'x' : _ref4$axis,
    _ref4$detectActive = _ref4.detectActive,
    detectActive = _ref4$detectActive === void 0 ? false : _ref4$detectActive,
    _ref4$detectThreshold = _ref4.detectThreshold,
    detectThreshold = _ref4$detectThreshold === void 0 ? 0.5 : _ref4$detectThreshold,
    _ref4$scrollAlign = _ref4.scrollAlign,
    scrollAlign = _ref4$scrollAlign === void 0 ? 'center' : _ref4$scrollAlign,
    scaleCallback = _ref4.scaleCallback,
    scaleResetCallback = _ref4.scaleResetCallback,
    onIndexChange = _ref4.onIndexChange,
    onDetectActiveItem = _ref4.onDetectActiveItem,
    _ref4$animatedScrollB = _ref4.animatedScrollByIndex,
    animatedScrollByIndex = _ref4$animatedScrollB === void 0 ? false : _ref4$animatedScrollB,
    _ref4$throttleMs = _ref4.throttleMs,
    throttleMs = _ref4$throttleMs === void 0 ? THROTTLE_DEFAULT_MS : _ref4$throttleMs,
    _ref4$debounceMs = _ref4.debounceMs,
    debounceMs = _ref4$debounceMs === void 0 ? DEBOUNCE_DEFAULT_MS : _ref4$debounceMs;
  var prevIndex = React.useRef(null);
  var direction = React.useRef(null);
  var offset = React.useRef(0);
  var scrollRef = React.useRef(null);
  var trackRef = React.useRef(null);

  /**
   * Для того, чтобы не спамить изменениями индекса.
   * Задержка дебаунса слегка больше, чем у тротлинга.
   * Таким образом, событие срабатывает при завершении скролла.
   */
  var debouncedOnIndexChange = useDebouncedFunction(function (i) {
    return onIndexChange === null || onIndexChange === void 0 ? void 0 : onIndexChange(i);
  }, debounceMs);

  /**
   * Вычисление центрального элемента.
   * Подсчет: от 0 до 1, какое количество ширины/высоты
   * каждого элемента находится по центру скролла.
   */
  var throttledDetectActiveItem = React.useMemo(function () {
    return throttle__default.default(function () {
      if (!detectActive || scrollRef.current === null || trackRef.current === null) {
        return;
      }

      /**
       * Правая (или нижняя для Оу) граница элемента.
       */
      var itemEdge = offset.current;

      /**
       * Смещение (отрицательный или положительный отступ)
       * и размер карусели (для Ox - ширина, для Oy - высота).
       */
      var scrollPos = scrollRef.current[axis === 'x' ? 'scrollLeft' : 'scrollTop'];
      var scrollSize = scrollRef.current[axis === 'x' ? 'offsetWidth' : 'offsetHeight'];

      /**
       * Граница скролла (видимой части).
       * Смещение + размер.
       */
      var scrollEdge = scrollPos + scrollSize;

      /**
       * Элементы перед, после и в видимой части.
       * перед [ ВИДИМЫЕ ] после
       */
      var prevItems = [];
      var nextItems = [];
      var count = 0;
      var items = getCarouselItems(trackRef.current);

      /**
       * Проходим по всему списку, суммируя ширины элементов,
       * пока не найдем один элемент, чей центр будет в центре карусели.
       */
      for (var itemIndex = 0; itemIndex < items.length; itemIndex++) {
        var _prevIndex$current;
        var item = items.item(itemIndex);
        if (item === null) {
          // eslint-disable-next-line no-continue
          continue;
        }

        /**
         * Для Ox - ширина, для Oy - высота.
         */
        var itemSize = item[axis === 'x' ? 'offsetWidth' : 'offsetHeight'];

        /**
         * Все элементы правее вьюпорта выпадают из процедуры.
         * Сравниваем по предыдущему элементу.
         * [ ... ] ...|n| <- Левый край элемента за пределами начала видимой части
         */
        if (itemEdge > scrollEdge) {
          if (scaleCallback && scaleResetCallback) {
            nextItems.push(item);
          }
          // eslint-disable-next-line no-continue
          continue;
        }
        itemEdge += itemSize;

        /**
         * Все элементы левее вьюпорта выпадают из процедуры.
         * Сравниваем по текущему элементу.
         * Правый край элемента за пределами начала видимой части -> |p|... [ ... ]
         */
        if (scrollPos > itemEdge) {
          if (scaleCallback && scaleResetCallback) {
            prevItems.push(item);
          }
          // eslint-disable-next-line no-continue
          continue;
        }
        var itemSlot = getItemSlot(itemIndex, itemEdge, itemSize, scrollPos, scrollSize, scrollAlign, (_prevIndex$current = prevIndex.current) !== null && _prevIndex$current !== void 0 ? _prevIndex$current : 0, offset.current);
        if (itemSlot !== null) {
          if (detectThreshold && Math.abs(itemSlot) <= detectThreshold) {
            onDetectActiveItem === null || onDetectActiveItem === void 0 || onDetectActiveItem(itemIndex);
            debouncedOnIndexChange === null || debouncedOnIndexChange === void 0 || debouncedOnIndexChange(itemIndex);
          }
          if (scaleCallback) {
            scaleCallback(item, itemSlot);
            /**
             * Количество айтемов в видимой части.
             */
            count++;
          }
        }
      }
      if (scaleCallback && scaleResetCallback) {
        window.requestAnimationFrame(function () {
          if (direction.current) {
            if (nextItems.length) {
              nextItems.splice(0, count).forEach(function (elem) {
                return scaleCallback(elem, count);
              });
              if (nextItems.length) {
                nextItems.splice(0, count).forEach(function (elem) {
                  return scaleResetCallback(elem);
                });
              }
            }
          } else if (prevItems.length) {
            var prItemsRev = prevItems.reverse();
            prItemsRev.splice(0, count).forEach(function (elem) {
              return scaleCallback(elem, count * -1);
            });
            if (prItemsRev.length) {
              prItemsRev.splice(0, count).forEach(function (elem) {
                return scaleResetCallback(elem);
              });
            }
          }
        });
      }
    }, throttleMs);
  }, [axis, debouncedOnIndexChange, detectActive, detectThreshold, onDetectActiveItem, scaleCallback, scaleResetCallback, scrollAlign, throttleMs]);

  /**
   * Прокрутка до нужной позиции индекса.
   */
  var toIndex = React.useCallback(function (i) {
    var scrollEl = scrollRef.current;
    var items = trackRef.current ? getCarouselItems(trackRef.current) : null;
    if (scrollEl && items && items.length > 0 && i >= 0) {
      scrollToPos({
        scrollEl: scrollEl,
        pos: getCalculatedPos({
          scrollEl: scrollEl,
          items: items,
          axis: axis,
          index: i,
          offset: offset.current,
          scrollAlign: scrollAlign
        }),
        axis: axis,
        /**
         * Без анимации при переходе на другой конец списка
         */
        animated: animatedScrollByIndex && (prevIndex.current === null || Math.abs(i - prevIndex.current) !== items.length - 1)
      });
      prevIndex.current = i;
    }
  }, [animatedScrollByIndex, axis, scrollAlign]);
  React.useEffect(function () {
    if (scrollRef.current && trackRef.current) {
      offset.current = getCalculatedOffset(scrollRef.current, trackRef.current, axis);
    }
  }, [axis]);

  /**
   * Операции на маунте/анмаунте компонента.
   * Создать слушатели событи и т.п.
   */
  React.useEffect(function () {
    var carouselElement = scrollRef.current;
    if (carouselElement) {
      carouselElement.addEventListener('scroll', throttledDetectActiveItem);
    }
    return function () {
      if (carouselElement) {
        carouselElement.removeEventListener('scroll', throttledDetectActiveItem);
      }
    };
  }, [throttledDetectActiveItem]);

  /**
   * Нужно вызвать только при первом рендере
   */
  React.useEffect(function () {
    requestAnimationFrame(function () {
      /**
       * Прокрутка до начального индекса.
       */
      toIndex(index);

      /**
       * Если на момент запуска карусель уже находится на нужной позиции,
       * событие скролла не произойдет, не сработает и определение центра,
       * необходимо вызвать его вручную.
       */
      throttledDetectActiveItem();
    });

    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  /**
   * Прокрутка до нужной позиции индекса, если индекс изменился.
   */
  React.useEffect(function () {
    if (index !== prevIndex.current) {
      toIndex(index);
    }
  }, [index, toIndex]);
  return {
    scrollRef: scrollRef,
    trackRef: trackRef
  };
};

exports.animatedScrollToX = animatedScrollToX;
exports.getCalculatedOffset = getCalculatedOffset;
exports.getCalculatedPos = getCalculatedPos;
exports.getCarouselItems = getCarouselItems;
exports.getItemSlot = getItemSlot;
exports.scrollToPos = scrollToPos;
exports.useCarousel = useCarousel;
exports.useDebouncedFunction = useDebouncedFunction;
