import { createVNode as _createVNode, isVNode as _isVNode } from "vue";
import useMemo from '../_util/hooks/useMemo';
import { defineComponent, computed, ref, reactive, watch, cloneVNode, onUnmounted } from 'vue';
import { monitorResize } from '../align/util';
import { getSlot } from '../_util/props-util';
import ScrollBar from './ScrollBar';

function _isSlot(s) {
  return typeof s === 'function' || Object.prototype.toString.call(s) === '[object Object]' && !_isVNode(s);
}

const prefixCls = 'x-virtual-list';
const EMPTY_DATA = [];
const VirtualList = defineComponent({
  name: prefixCls,
  props: {
    data: Array,
    virtual: Boolean,
    height: {
      default: 256
    },
    itemKey: [String, Number, Function],
    itemsWrap: [Object, Function],
    onScroll: Function
  },
  emits: ['scroll'],

  setup(props, {
    expose,
    emit
  }) {
    // ================================= 计算第一行大小 =================================
    const firstNode = ref(),
          h = ref(0);
    let sizeMonitor;

    const saveFirstNode = dom => {
      var _sizeMonitor;

      if (firstNode.value === dom) return;
      firstNode.value = dom;
      (_sizeMonitor = sizeMonitor) === null || _sizeMonitor === void 0 ? void 0 : _sizeMonitor();
      sizeMonitor = monitorResize(firstNode.value, () => {
        const {
          marginTop,
          marginBottom
        } = window.getComputedStyle(firstNode.value);
        h.value = dom.offsetHeight + parseFloat(marginTop) + parseFloat(marginBottom);
      });
    };

    const itemHeight = useMemo(() => h.value);
    onUnmounted(() => {
      var _sizeMonitor2;

      (_sizeMonitor2 = sizeMonitor) === null || _sizeMonitor2 === void 0 ? void 0 : _sizeMonitor2();
    }); // ================================= MISC =================================

    const dataRef = computed(() => props.data || EMPTY_DATA);
    const scrollHeight = computed(() => dataRef.value.length * itemHeight.value);
    const containerRef = ref();
    const barRef = ref();
    const calState = reactive({
      si: 0,
      ei: dataRef.value.length
    });
    const showList = computed(() => dataRef.value.slice(calState.si, calState.ei));
    const state = reactive({
      scrollTop: 0
    });

    function getIndex(index) {
      return calState.si + index;
    } // ================================= Item Key =============================


    function getKey(item, index) {
      var _item$props$itemKey;

      if (typeof props.itemKey === 'function') return props.itemKey(item);
      return (_item$props$itemKey = item[props.itemKey]) !== null && _item$props$itemKey !== void 0 ? _item$props$itemKey : index;
    } // ================================= Scroll ===============================


    watch([() => state.scrollTop, () => props.virtual, dataRef, () => props.height, () => itemHeight.value], () => {
      if (!props.virtual) {
        calState.si = 0;
        calState.ei = dataRef.value.length;
        return;
      }

      const {
        scrollTop
      } = state;
      let si = ~~(scrollTop / itemHeight.value),
          ei = Math.ceil((scrollTop + props.height) / itemHeight.value);
      calState.si = si;
      calState.ei = itemHeight.value === 0 ? si + 1 : ei; // 如果height == 0，则只显示一条
    }, {
      immediate: true
    }); // ========================================================================

    function syncSCroll(top) {
      if (state.scrollTop == top) return;
      top = Math.min(top, scrollHeight.value - props.height);
      if (top < 0) top = 0;
      containerRef.value.scrollTop = top;
      state.scrollTop = top;
      if (containerRef.value) containerRef.value.scrollTop = top;
      if (barRef.value) barRef.value.setScrollTop(top);
    }

    function onScroll(e) {
      const {
        scrollTop
      } = e.currentTarget;
      syncSCroll(scrollTop);
      emit('scroll', e);
    }

    function scrollTo(arg) {
      if (arg === undefined) return;

      if (typeof arg === 'number') {
        syncSCroll(arg);
      } else if (typeof arg === 'object') {
        let index = 0;

        if (typeof arg.index === 'number') {
          index = arg.index;
        } else {
          index = dataRef.value.findIndex((e, i) => getKey(e, i) === arg.key);
        }

        if (index === undefined) return; // scroll center

        syncSCroll(index * itemHeight.value - (props.height - itemHeight.value >> 1));
      }
    } // ================================= Expose ===============================


    expose({
      scrollTo
    }); // ================================= Render ===============================

    return vm => {
      const {
        virtual,
        height,
        itemsWrap
      } = props;
      const comStyle = virtual ? `overflow-y:auto; overflow-anchor:none;` : 'overflow:auto;';

      const childRender = (item, index) => getSlot(vm, 'default', {
        data: showList.value,
        item,
        index: getIndex(index)
      })[0];

      const itemsNode = showList.value.map((item, index) => cloneVNode(childRender(item, index), {
        key: getKey(item, getIndex(index)),
        class: `${prefixCls}_item`,
        ref: index == 0 ? saveFirstNode : undefined
      }));
      let wrapNode = itemsWrap ? _createVNode(itemsWrap, null, _isSlot(itemsNode) ? itemsNode : {
        default: () => [itemsNode]
      }) : _createVNode("div", {
        "style": "display:flex; flex-direction:column;"
      }, [itemsNode]);
      wrapNode = cloneVNode(wrapNode, {
        style: `transform:translateY(${calState.si * itemHeight.value}px);`
      });
      return _createVNode("div", {
        "class": prefixCls
      }, [_createVNode("div", {
        "ref": containerRef,
        "class": `${prefixCls}_wrapper`,
        "style": `max-height:${height}px;` + comStyle,
        "onScroll": onScroll
      }, [_createVNode("div", {
        "style": `height:${scrollHeight.value}px; position:relative;`
      }, [wrapNode])]), _createVNode(ScrollBar, {
        "ref": barRef,
        "prefixCls": prefixCls,
        "height": props.height,
        "scrollHeight": scrollHeight.value,
        "onScroll": scrollTo
      }, null)]);
    };
  }

});

VirtualList.install = app => {
  app.component(VirtualList.name, VirtualList);
};

export default VirtualList;