import { defineComponent, ref, computed, createVNode, mergeProps } from 'vue';
import { Virtualizer } from 'virtua/vue';
import FScrollbar from '../scrollbar/scrollbar.js';

const virtualScrollerProps = {
  scrollbarProps: Object,
  dataSources: {
    type: Array,
    default: () => []
  },
  keeps: {
    type: Number,
    default: 30
  },
  direction: {
    type: String,
    default: 'vertical' // the other value is horizontal
  },
  wrapTag: {
    type: String,
    default: 'div'
  },
  wrapClass: {
    type: String,
    default: ''
  },
  wrapStyle: {
    type: Object
  },
  itemSize: {
    type: Number,
    required: true
  },
  itemProps: Function,
  itemTag: {
    type: String,
    default: 'div'
  },
  renderItemList: {
    type: Function
  },
  topThreshold: {
    type: Number,
    default: 0
  },
  bottomThreshold: {
    type: Number,
    default: 0
  }
};
var virtualScroller = defineComponent({
  name: 'FVirtualScroller',
  props: virtualScrollerProps,
  emits: ['toTop', 'toBottom', 'scroll'],
  setup(props, _ref) {
    let {
      slots,
      expose,
      emit
    } = _ref;
    const scrollRef = ref();
    const scrollContainerRef = ref();
    const virtualRef = ref();
    const isHorizontal = computed(() => {
      return props.direction === 'horizontal';
    });
    const directionKey = computed(() => {
      return isHorizontal.value ? 'scrollLeft' : 'scrollTop';
    });
    const getOffset = () => {
      const root = scrollContainerRef.value;
      return root ? Math.ceil(root[directionKey.value]) : 0;
    };
    const getScrollSize = () => {
      const key = isHorizontal.value ? 'scrollWidth' : 'scrollHeight';
      const root = scrollContainerRef.value;
      return root ? Math.ceil(root[key]) : 0;
    };
    // return client viewport size
    const getClientSize = () => {
      const key = isHorizontal.value ? 'clientWidth' : 'clientHeight';
      const root = scrollContainerRef.value;
      return root ? Math.ceil(root[key]) : 0;
    };

    // set current scroll position to bottom
    const scrollToBottom = () => {
      const root = scrollContainerRef.value;
      if (root) {
        var _virtualRef$value;
        (_virtualRef$value = virtualRef.value) === null || _virtualRef$value === void 0 || _virtualRef$value.scrollTo(getScrollSize());
        // check if it's really scrolled to the bottom
        // maybe list doesn't render and calculate to last range
        // so we need retry in next event loop until it really at bottom
        setTimeout(() => {
          if (getOffset() + getClientSize() < getScrollSize()) {
            scrollToBottom();
          }
        }, 10);
      }
    };
    expose({
      scrollRef,
      virtualRef,
      scrollToIndex: (index, opt) => {
        var _virtualRef$value2;
        (_virtualRef$value2 = virtualRef.value) === null || _virtualRef$value2 === void 0 || _virtualRef$value2.scrollToIndex(index, opt);
      },
      scrollToBottom,
      scrollTo: offset => {
        var _virtualRef$value3;
        (_virtualRef$value3 = virtualRef.value) === null || _virtualRef$value3 === void 0 || _virtualRef$value3.scrollTo(offset);
      },
      scrollBy: offset => {
        var _virtualRef$value4;
        (_virtualRef$value4 = virtualRef.value) === null || _virtualRef$value4 === void 0 || _virtualRef$value4.scrollBy(offset);
      },
      getItemOffset: index => {
        var _virtualRef$value5;
        return (_virtualRef$value5 = virtualRef.value) === null || _virtualRef$value5 === void 0 ? void 0 : _virtualRef$value5.getItemOffset(index);
      },
      getItemSize: index => {
        var _virtualRef$value6;
        return (_virtualRef$value6 = virtualRef.value) === null || _virtualRef$value6 === void 0 ? void 0 : _virtualRef$value6.getItemSize(index);
      },
      findStartIndex: () => {
        var _virtualRef$value7;
        (_virtualRef$value7 = virtualRef.value) === null || _virtualRef$value7 === void 0 || _virtualRef$value7.findStartIndex();
      },
      findEndIndex: () => {
        var _virtualRef$value8;
        (_virtualRef$value8 = virtualRef.value) === null || _virtualRef$value8 === void 0 || _virtualRef$value8.findEndIndex();
      },
      getOffset,
      getScrollSize,
      getClientSize
    });
    let lastScrollTop = 0;
    const onScroll = e => {
      emit('scroll', e);
      const currentScrollTop = e.target.scrollTop;
      const isScrollUp = currentScrollTop < lastScrollTop;
      lastScrollTop = currentScrollTop;
      const offset = getOffset();
      const scrollSize = getScrollSize();
      const clientSize = getClientSize();
      if (isScrollUp && offset <= props.topThreshold) {
        emit('toTop');
      }
      if (!isScrollUp && offset + clientSize >= scrollSize - props.bottomThreshold) {
        emit('toBottom');
      }
    };
    return () => {
      const virtualizer = createVNode(Virtualizer, {
        "ref": virtualRef,
        "data": props.dataSources,
        "scrollRef": scrollContainerRef.value,
        "horizontal": isHorizontal.value,
        "as": props.wrapTag,
        "class": props.wrapClass,
        "style": props.wrapStyle,
        "itemSize": props.itemSize,
        "itemProps": props.itemProps,
        "item": props.itemTag,
        "overscan": Math.ceil(props.keeps / 3)
      }, {
        default: _ref2 => {
          var _slots$default;
          let {
            item,
            index
          } = _ref2;
          return (_slots$default = slots.default) === null || _slots$default === void 0 ? void 0 : _slots$default.call(slots, {
            source: item,
            index
          });
        }
      });
      return createVNode(FScrollbar, mergeProps({
        "ref": e => {
          scrollRef.value = e;
          scrollContainerRef.value = e === null || e === void 0 ? void 0 : e.containerRef;
        }
      }, props.scrollbarProps, {
        "onScroll": onScroll
      }), {
        default: () => [props.renderItemList ? props.renderItemList(virtualizer) : virtualizer]
      });
    };
  }
});

export { virtualScroller as default, virtualScrollerProps };
