import { cloneVNode, computed, defineComponent, h, onMounted, ref, VNode } from 'vue'

const CVirtualScroller = defineComponent({
  name: 'CVirtualScroller',
  props: {
    /**
     * Amount of visible items
     */
    visibleItems: {
      type: Number,
      default: 10,
    },
  },
  setup(props, { slots }) {
    const virtualScrollRef = ref<HTMLDivElement>()
    const virtualScrollContentRef = ref<HTMLDivElement>()

    const currentItemIndex = ref(1)
    const itemHeight = ref<number>(0)
    const itemsNumber = ref<number>(0)
    const viewportPadding = ref(0)

    const buffer = computed(() => Math.floor(props.visibleItems / 2))

    const maxHeight = computed(
      () => itemsNumber.value * itemHeight.value + 2 * viewportPadding.value,
    )

    const viewportHeight = computed(
      () =>
        Math.min(props.visibleItems, itemsNumber.value) * itemHeight.value +
        2 * viewportPadding.value,
    )

    onMounted(() => {
      if (virtualScrollRef.value) {
        viewportPadding.value = Number.parseFloat(
          getComputedStyle(virtualScrollRef.value).paddingTop,
        )
        // It's necessary to calculate heights of items
        virtualScrollRef.value.dispatchEvent(new CustomEvent('scroll'))
      }
    })

    const handleScroll = (scrollTop: number) => {
      currentItemIndex.value =
        itemHeight.value && Math.max(Math.ceil(scrollTop / itemHeight.value), 1)
    }

    return () => {
      const children: any = slots.default
        ? Array.isArray(slots.default()[0].children)
          ? slots.default()[0].children
          : slots.default()
        : []
      itemsNumber.value = children && children.length > 0 ? children.length : 0
      return h(
        'div',
        {
          class: ['virtual-scroller'],
          onScroll: (event: any) => handleScroll((event.target as HTMLDivElement).scrollTop),
          style: {
            height: `${viewportHeight.value}px`,
            overflowY: 'auto',
          },
          ref: virtualScrollRef,
        },
        h(
          'div',
          {
            class: 'virtual-scroller-content',
            style: {
              height: `${maxHeight.value}px`,
            },
            ref: virtualScrollContentRef,
          },
          children.map(
            (slot: VNode, index: number) =>
              index + 1 > Math.max(currentItemIndex.value - buffer.value, 0) &&
              index + 1 <= currentItemIndex.value + props.visibleItems + buffer.value &&
              cloneVNode(slot, {
                class: [
                  {
                    'virtual-scroller-item-preload':
                      index + 1 > currentItemIndex.value + props.visibleItems ||
                      index + 1 < currentItemIndex.value,
                  },
                ],
                style: {
                  ...(currentItemIndex.value > buffer.value && {
                    transform: `translateY(${
                      (currentItemIndex.value - buffer.value) * itemHeight.value
                    }px)`,
                  }),
                },
                ref: (node) => {
                  if (itemHeight.value === 0 && node && (node as HTMLElement).offsetHeight) {
                    itemHeight.value =
                      (node as HTMLElement).offsetHeight +
                      Number.parseFloat(getComputedStyle(node as HTMLElement).marginTop) +
                      Number.parseFloat(getComputedStyle(node as HTMLElement).marginBottom)
                  }
                },
              }),
          ),
        ),
      )
    }
  },
})
export { CVirtualScroller }
