import { defineComponent, computed, ref, watch, onMounted, createElementBlock, openBlock, withDirectives, createElementVNode, normalizeStyle, Fragment, renderList, renderSlot, unref, nextTick } from "vue";
import "../scrollbar/index.mjs";
import { virtualListProps } from "./types.mjs";
import { isUndefined } from "../_utils/is.mjs";
import { vOnResize } from "../directives/on-resize.mjs";
import { debounceRAF } from "../_utils/helper.mjs";
import { vScrollbar } from "../scrollbar/vScrollbar.mjs";
const _hoisted_1 = { class: "o-virtual-list" };
const _hoisted_2 = {
  key: 1,
  class: "o-virtual-render-item"
};
const _sfc_main = /* @__PURE__ */ defineComponent({
  __name: "OVirtualList",
  props: virtualListProps,
  emits: ["renderChange"],
  setup(__props, { expose: __expose, emit: __emit }) {
    const props = __props;
    const emits = __emit;
    const scrollbarProps = computed(() => {
      if (props.scrollbar === true) {
        return {
          showType: "always",
          size: "medium"
        };
      }
      return props.scrollbar;
    });
    const listData = ref([]);
    watch(
      () => props.list,
      (value) => {
        listData.value = value.map((item, index) => ({
          id: item.id,
          data: item,
          index
        }));
      },
      {
        immediate: true
      }
    );
    const defaultStartIndex = computed(() => {
      if (isUndefined(props.defaultStartIndex)) {
        return 0;
      }
      return Math.max(Math.min(props.defaultStartIndex, props.list.length - 1), 0);
    });
    const visibleStartIndex = ref(defaultStartIndex.value ?? 0);
    let visibleStartId;
    const renderCount = ref(1);
    const startIndex = computed(() => {
      return Math.max(visibleStartIndex.value - props.buffer, 0);
    });
    const endIndex = computed(() => {
      return Math.min(visibleStartIndex.value + renderCount.value + props.buffer - 1, listData.value.length - 1);
    });
    let lastVisibleStartIndex = visibleStartIndex.value;
    let lastRenderCount = renderCount.value;
    const emitRenderChange = () => {
      if (lastVisibleStartIndex !== visibleStartIndex.value || lastRenderCount !== renderCount.value) {
        emits("renderChange", {
          start: startIndex.value,
          end: endIndex.value,
          count: renderCount.value,
          visible: visibleStartIndex.value
        });
        lastVisibleStartIndex = visibleStartIndex.value;
        lastRenderCount = renderCount.value;
      }
    };
    const renderList$1 = computed(() => {
      return listData.value.slice(startIndex.value, endIndex.value + 1);
    });
    watch(listData, (value) => {
      if (!isUndefined(visibleStartId) && wrapperRef.value) {
        const top = wrapperRef.value.scrollTop - listMetaData[visibleStartIndex.value].top;
        const index = value.findIndex((item) => item.id === visibleStartId);
        if (index >= 0) {
          visibleStartIndex.value = index;
          wrapperRef.value.scrollTop = listMetaData[index].top + top;
        }
      }
    });
    const wrapperRef = ref();
    const contentSize = ref((props.itemSize ? props.itemSize : props.defaultItemSize) * listData.value.length);
    const containerSize = ref({
      height: 0,
      width: 0
    });
    const onContainerResize = () => {
      if (!wrapperRef.value) {
        return;
      }
      containerSize.value.height = wrapperRef.value.offsetHeight;
      containerSize.value.width = wrapperRef.value.offsetWidth;
      if (containerSize.value.height === 0) {
        offset.value = 0;
      }
      if (!initialScroll) {
        if (contentSize.value < containerSize.value.height) {
          visibleStartIndex.value = 0;
        }
        return;
      }
      const scrollTop = wrapperRef.value.scrollTop;
      for (let i = visibleStartIndex.value; i >= 0; i--) {
        const meta = listMetaData[i];
        if (meta.top <= scrollTop) {
          visibleStartIndex.value = i;
          break;
        }
      }
      let count = renderCount.value;
      for (let i = endIndex.value; i < listMetaData.length; i++) {
        const meta = listMetaData[i];
        if (meta.top < scrollTop + containerSize.value.height) {
          count++;
        }
      }
      renderCount.value = count;
      emitRenderChange();
    };
    const contentStyle = computed(() => ({
      "--content-height": `${contentSize.value}px`
    }));
    const offset = ref(0);
    const renderListStyle = computed(() => {
      return {
        "--offsetY": `${offset.value}px`
      };
    });
    let initialScroll = props.itemSize ? true : false;
    const scrollToView = (index, align = "start", behavior = "instant") => {
      if (!wrapperRef.value) {
        return;
      }
      const toIndex = Math.max(Math.min(listMetaData.length - 1, index), 0);
      const item = listMetaData[toIndex];
      const itemTop = item.top;
      const cSize = wrapperRef.value.offsetHeight;
      let _align = align;
      if (_align === "nearest") {
        const currScrollTop = wrapperRef.value.scrollTop;
        if (currScrollTop > itemTop) {
          _align = "start";
        } else if (currScrollTop + cSize < itemTop) {
          _align = "end";
        } else {
          return;
        }
      }
      let scrollTop = itemTop;
      if (_align !== "start") {
        const itemSize = listMetaData[toIndex].size;
        if (_align === "center") {
          scrollTop = itemTop - cSize / 2 + itemSize / 2;
        } else if (_align === "end") {
          scrollTop = itemTop - cSize + itemSize;
        } else if (typeof _align === "number") {
          scrollTop = itemTop - _align;
        }
      }
      wrapperRef.value.scrollTo({
        top: scrollTop,
        behavior: props.itemSize ? behavior : "instant"
      });
    };
    let listMetaData = [];
    watch(
      [() => props.itemSize, () => listData.value],
      ([propSize, dataList]) => {
        const itemSize = propSize ? propSize : props.defaultItemSize;
        let lastTop = 0;
        listMetaData = dataList.map((item, index) => {
          if (!propSize) {
            const m = listMetaData.find((mItem) => mItem.id === item.id);
            if (m && m.measured) {
              lastTop = m.bottom;
              return m;
            }
          }
          const metaItem = {
            id: item.id,
            index,
            size: itemSize,
            top: lastTop,
            bottom: lastTop + itemSize,
            measured: propSize ? true : false,
            isScrolling: false
          };
          lastTop += itemSize;
          return metaItem;
        });
        contentSize.value = listMetaData[listMetaData.length - 1].bottom;
      },
      {
        immediate: true
      }
    );
    const updateMeta = (start = 0) => {
      for (let i = start + 1; i < listMetaData.length; i++) {
        const lastMeta = listMetaData[i - 1];
        const meta = listMetaData[i];
        meta.top = lastMeta.bottom;
        meta.bottom = meta.top + meta.size;
      }
      const last = listMetaData[listMetaData.length - 1];
      last.bottom = last.top + last.size;
      contentSize.value = last.bottom;
    };
    const updateVisibleCount = (scrollOffset) => {
      var _a;
      let scrollSize = scrollOffset;
      if (isUndefined(scrollSize)) {
        scrollSize = ((_a = wrapperRef.value) == null ? void 0 : _a.scrollTop) ?? 0;
      }
      const { height: containerHeight } = containerSize.value;
      if (!wrapperRef.value || !containerHeight) {
        return;
      }
      let render = 1;
      for (let i = visibleStartIndex.value + 1; i < listMetaData.length; i++) {
        const meta = listMetaData[i];
        if (meta.top < scrollSize + containerHeight) {
          render++;
        }
      }
      renderCount.value = render;
      emitRenderChange();
    };
    const debounceUpdateVisibleCount = debounceRAF(updateVisibleCount);
    const getStartIndex = (scrollOffset) => {
      let start = 0;
      let end = listMetaData.length - 1;
      while (start < end) {
        const mid = Math.floor((start + end) / 2);
        const { top, bottom } = listMetaData[mid];
        if (top <= scrollOffset && bottom > scrollOffset) {
          return mid;
        } else if (bottom === scrollOffset) {
          return mid + 1;
        } else if (bottom < scrollOffset) {
          start = mid;
        } else if (top > scrollOffset) {
          end = mid;
        }
      }
      return start;
    };
    const onScroll = () => {
      var _a;
      const scrollOffset = ((_a = wrapperRef.value) == null ? void 0 : _a.scrollTop) ?? 0;
      if (props.itemSize) {
        visibleStartIndex.value = Math.floor(scrollOffset / props.itemSize);
      } else {
        visibleStartIndex.value = getStartIndex(scrollOffset);
      }
      offset.value = listMetaData[startIndex.value].top;
      visibleStartId = listMetaData[visibleStartIndex.value].id;
      debounceUpdateVisibleCount(scrollOffset);
    };
    const onItemResize = (en, index) => {
      const el = en.target;
      const meta = listMetaData[index];
      const size = el.offsetHeight;
      if (meta.measured && meta.size === size) {
        return;
      }
      if (meta.measured === false && wrapperRef.value && wrapperRef.value.scrollTop > meta.top) {
        wrapperRef.value.scrollTop += size - meta.size;
      }
      meta.size = size;
      meta.measured = true;
      meta.bottom = meta.top + meta.size;
      updateMeta(index);
      if (index === defaultStartIndex.value && !initialScroll) {
        nextTick(() => {
          scrollToView(defaultStartIndex.value);
          initialScroll = true;
        });
      }
      debounceUpdateVisibleCount();
    };
    const init = () => {
      if (!wrapperRef.value) {
        return;
      }
      if (props.itemSize) {
        scrollToView(defaultStartIndex.value);
      }
    };
    onMounted(() => {
      init();
    });
    __expose({
      scrollToView
    });
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1, [
        withDirectives((openBlock(), createElementBlock(
          "div",
          {
            class: "o-virtual-list-wrapper",
            ref_key: "wrapperRef",
            ref: wrapperRef,
            onScrollPassive: onScroll
          },
          [
            createElementVNode(
              "div",
              {
                class: "o-virtual-body",
                style: normalizeStyle(contentStyle.value)
              },
              [
                createElementVNode(
                  "div",
                  {
                    class: "o-virtual-render-list",
                    style: normalizeStyle(renderListStyle.value)
                  },
                  [
                    (openBlock(true), createElementBlock(
                      Fragment,
                      null,
                      renderList(renderList$1.value, (item) => {
                        return openBlock(), createElementBlock(
                          Fragment,
                          {
                            key: item.index
                          },
                          [
                            props.itemSize ? (openBlock(), createElementBlock(
                              "div",
                              {
                                key: 0,
                                class: "o-virtual-render-item",
                                style: normalizeStyle({ height: props.itemSize + "px" })
                              },
                              [
                                renderSlot(_ctx.$slots, "default", {
                                  item: item.data,
                                  index: item.index
                                })
                              ],
                              4
                              /* STYLE */
                            )) : withDirectives((openBlock(), createElementBlock("div", _hoisted_2, [
                              renderSlot(_ctx.$slots, "default", {
                                item: item.data,
                                index: item.index
                              })
                            ])), [
                              [unref(vOnResize), (en) => onItemResize(en, item.index)]
                            ])
                          ],
                          64
                          /* STABLE_FRAGMENT */
                        );
                      }),
                      128
                      /* KEYED_FRAGMENT */
                    ))
                  ],
                  4
                  /* STYLE */
                )
              ],
              4
              /* STYLE */
            )
          ],
          32
          /* NEED_HYDRATION */
        )), [
          [unref(vOnResize), onContainerResize],
          [unref(vScrollbar), scrollbarProps.value]
        ])
      ]);
    };
  }
});
export {
  _sfc_main as default
};
