<template>
  <div
    ref="root"
    class="sk-virtual-list-container"
    @scroll.passive="handleScroll"
  >
    <div
      class="sk-virtual-list-scroll"
      :style="`height: ${scrollHeight}px;padding-top: ${paddingTop}px`"
    >
      <div
        v-for="item in pool"
        :key="item[dataKey]"
        class="sk-virtual-list-item-container"
        :style="`height: ${itemSize}px`"
      >
        <slot :item="item" :index="item._index"></slot>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import { ref, toRefs, defineComponent, onMounted, watch } from 'vue'
interface Props {
  data: any[]
  dataKey: string
  itemSize: number
  poolBuffer: number
}
export default defineComponent({
  name: 'SkVirtualList',
  props: {
    data: {
      type: Array,
      default: () => [],
    },
    dataKey: {
      type: String,
      default: () => 'id',
    },
    itemSize: {
      type: Number,
      default: () => 40,
    },
    poolBuffer: {
      type: Number,
      default: () => 50,
    },
  },
  emits: ['reachEnd'],
  setup(props: Props, { emit }): any {
    const { data, poolBuffer, itemSize } = toRefs(props)
    const root = ref<HTMLElement | null>(null)
    const pool = ref<any[]>([])
    const scrollHeight = ref(data.value.length * itemSize.value)
    watch(data, cData => {
      scrollHeight.value = cData.length * itemSize.value
    }, { deep: true })
    watch(itemSize, cItemSize => {
      scrollHeight.value = data.value.length * cItemSize
    })
    let containerSize = 0
    const paddingTop = ref(0)
    let isScrollBusy = false
    let isLock = false

    const sliceArr = (data, star, end) => {
      return data
        .slice(star, end)
        .map((v, i) => ({ ...v, _index: star + i }))
    }

    const handleScroll = () => {
      if (!root.value) return
      if (isScrollBusy) return
      isScrollBusy = true
      requestAnimationFrame(() => {
        isScrollBusy = false
        if (!root.value) return
        const range: number[] = []
        const halfPool = Math.floor(poolBuffer.value / 2)
        const viewNum = Math.floor(root.value.clientHeight / itemSize.value)
        range[0] = Math.floor(root.value.scrollTop / itemSize.value) - halfPool
        range[0] = Math.max(range[0], 0)
        range[1] = range[0] + viewNum + poolBuffer.value
        range[1] = Math.min(range[1], data.value.length)
        pool.value = sliceArr(data.value, range[0], range[1])
        paddingTop.value = range[0] * itemSize.value

        const lastNum = halfPool + viewNum + 1
        const isEnd =  lastNum * itemSize.value + paddingTop.value === scrollHeight.value
        if (isEnd && !isLock) {
          isLock = true
          emit('reachEnd')
        }
        if (!isEnd) isLock = false
      })
    }
    onMounted(() => {
      if (!root.value) return
      containerSize = root.value.clientHeight
      const contentLines = Math.ceil(containerSize / itemSize.value)
      const totalLines = contentLines + poolBuffer.value
      const range = [0, totalLines]
      pool.value = sliceArr(data.value, range[0], range[0] + range[1])
    })
    return {
      pool,
      scrollHeight,
      root,
      handleScroll,
      paddingTop,
    }
  },
})
</script>

<style scoped>

</style>
