<template>
  <div class="el-table"
    :class="[{
      'el-table--empty': (store.states.data || []).length === 0,
      'el-table--fit': fit,
      'el-table--striped': stripe,
      'el-table--border': border || isGroup,
      'el-table--hidden': isHidden,
      'el-table--group': isGroup,
      'el-table--fluid-height': maxHeight,
      'el-table--regular-height': height,
      'el-table--regular-width': width,
      'el-table--scrollable-x': layout.scrollX,
      'el-table--scrollable-y': layout.scrollY,
      'el-table--scrollable-y-scroll': isBodyScroll,
      'el-table--enable-row-hover': !store.states.isComplex,
      'el-table--enable-row-transition': (store.states.data || []).length !== 0 && (store.states.data || []).length < 100,
      'el-table--no-verticalLine': verticalLine,
    },
    tableSize ? `el-table--${ tableSize }` : '', textSize ? `el-table--${ textSize }` : '',
    layout.scrollY ? `el-table-scrolling-y-${scrollYPosition}` : ''
    ]"
    @mouseleave="handleMouseLeave($event)">
    <div class="hidden-columns" ref="hiddenColumns"><slot></slot></div>
    <div
      v-if="showHeader"
      v-mousewheel="handleHeaderFooterMousewheel"
      class="el-table__header-wrapper"
      ref="headerWrapper">
      <table-header
        ref="tableHeader"
        :store="store"
        :border="border"
        :default-sort="defaultSort"
        :style="{
          width: layout.bodyWidth ? layout.bodyWidth + 'px' : ''
        }">
      </table-header>
    </div>
    <div
      class="el-table__body-wrapper"
      ref="bodyWrapper"
      :class="[layout.scrollX ? `is-scrolling-x-${scrollXPosition}` : 'is-scrolling-x-none']"
      :style="[bodyHeight]">
      <el-scrollbar class="el-table__body-scrollbar" :style="[bodyScrollHeight]" :wrap-style="[bodyScrollWrapHeight]" ref="bodyWrapperScrollbar">
        <table-body
          :context="context"
          :store="store"
          :stripe="stripe"
          :row-class-name="rowClassName"
          :row-style="rowStyle"
          :highlight="highlightCurrentRow"
          :style="{
            width: bodyWidth
          }">
        </table-body>
        <!-- 补丁，虚拟列表该元素位置有问题 -->
        <Teleport :toEl="$refs.bodyWrapperScrollbar" v-if="!tableShowData || tableShowData.length === 0">
          <div
            class="el-table__empty-block"
            ref="emptyBlock"
            :style="emptyBlockStyle">
            <span class="el-table__empty-text" >
              <slot name="empty">{{ emptyText || t('el.table.emptyText') }}</slot>
            </span>
          </div>
        </Teleport>
        <div
          v-if="$slots.append"
          class="el-table__append-wrapper"
          ref="appendWrapper">
          <slot name="append"></slot>
        </div>
      </el-scrollbar>
    </div>
    <div
      v-if="showSummary"
      v-show="tableShowData && tableShowData.length > 0"
      v-mousewheel="handleHeaderFooterMousewheel"
      class="el-table__footer-wrapper"
      ref="footerWrapper">
      <table-footer
        :store="store"
        :border="border"
        :sum-text="sumText || t('el.table.sumText')"
        :summary-method="summaryMethod"
        :default-sort="defaultSort"
        :style="{
          width: layout.bodyWidth ? layout.bodyWidth + 'px' : ''
        }">
      </table-footer>
    </div>
    <div
      v-if="fixedColumns.length > 0"
      v-mousewheel="handleFixedMousewheel"
      class="el-table__fixed"
      ref="fixedWrapper"
      :style="[{
        width: layout.fixedWidth ? layout.fixedWidth + 'px' : ''
      },
      fixedHeight]">
      <div
        v-if="showHeader"
        class="el-table__fixed-header-wrapper"
        ref="fixedHeaderWrapper" >
        <table-header
          ref="fixedTableHeader"
          fixed="left"
          :border="border"
          :store="store"
          :style="{
            width: bodyWidth
          }"></table-header>
      </div>
      <div
        class="el-table__fixed-body-wrapper"
        ref="fixedBodyWrapper"
        :style="[{
          top: layout.headerHeight + 'px'
        },
        fixedBodyHeight]">
        <table-body
          fixed="left"
          :store="store"
          :stripe="stripe"
          :highlight="highlightCurrentRow"
          :row-class-name="rowClassName"
          :row-style="rowStyle"
          :style="{
            width: bodyWidth
          }">
        </table-body>
        <div
          v-if="$slots.append"
          class="el-table__append-gutter"
          :style="{ height: layout.appendHeight + 'px'}"></div>
      </div>
      <div
        v-if="showSummary"
        v-show="tableShowData && tableShowData.length > 0"
        class="el-table__fixed-footer-wrapper"
        ref="fixedFooterWrapper">
        <table-footer
          fixed="left"
          :border="border"
          :sum-text="sumText || t('el.table.sumText')"
          :summary-method="summaryMethod"
          :store="store"
          :style="{
            width: bodyWidth
          }"></table-footer>
      </div>
    </div>
    <div
      v-if="rightFixedColumns.length > 0"
      v-mousewheel="handleFixedMousewheel"
      class="el-table__fixed-right"
      ref="rightFixedWrapper"
      :style="[{
        width: layout.rightFixedWidth ? layout.rightFixedWidth + 'px' : '',
        right: layout.scrollY ? (border ? layout.gutterWidth : (layout.gutterWidth || 0)) + 'px' : ''
      },
      fixedHeight]">
      <div v-if="showHeader"
        class="el-table__fixed-header-wrapper"
        ref="rightFixedHeaderWrapper">
        <table-header
          ref="rightFixedTableHeader"
          fixed="right"
          :border="border"
          :store="store"
          :style="{
            width: bodyWidth
          }"></table-header>
      </div>
      <div
        class="el-table__fixed-body-wrapper"
        ref="rightFixedBodyWrapper"
        :style="[{
          top: layout.headerHeight + 'px'
        },
        fixedBodyHeight]">
        <table-body
          fixed="right"
          :store="store"
          :stripe="stripe"
          :row-class-name="rowClassName"
          :row-style="rowStyle"
          :highlight="highlightCurrentRow"
          :style="{
            width: bodyWidth
          }">
        </table-body>
         <div
          v-if="$slots.append"
          class="el-table__append-gutter"
          :style="{ height: layout.appendHeight + 'px' }"></div>
      </div>
      <div
        v-if="showSummary"
        v-show="tableShowData && tableShowData.length > 0"
        class="el-table__fixed-footer-wrapper"
        ref="rightFixedFooterWrapper">
        <table-footer
          fixed="right"
          :border="border"
          :sum-text="sumText || t('el.table.sumText')"
          :summary-method="summaryMethod"
          :store="store"
          :style="{
            width: bodyWidth
          }"></table-footer>
      </div>
    </div>
    <div
      v-if="rightFixedColumns.length > 0"
      class="el-table__fixed-right-patch"
      ref="rightFixedPatch"
      :style="{
        width: layout.scrollY ? layout.gutterWidth + 'px' : '0',
        height: layout.headerHeight + 'px'
      }"></div>
    <div class="el-table__column-resize-proxy" ref="resizeProxy" v-show="resizeProxyVisible"></div>
  </div>
</template>

<script type="text/babel">
  import ElCheckbox from 'modesign-ui/packages/checkbox';
  import { debounce, throttle } from 'throttle-debounce';
  import { addResizeListener, removeResizeListener } from 'modesign-ui/src/utils/resize-event';
  import Mousewheel from 'modesign-ui/src/directives/mousewheel';
  import Locale from 'modesign-ui/src/mixins/locale';
  import Migrating from 'modesign-ui/src/mixins/migrating';
  import { createStore, mapStates } from './store/helper';
  import TableLayout from './table-layout';
  import TableBody from './table-body';
  import TableHeader from './table-header';
  import TableFooter from './table-footer';
  import { parseHeight } from './util';
  // 导入el-scrollbar组件
  import ElScrollbar from 'modesign-ui/packages/scrollbar'
  // 获取浏览器滚动条宽度方法
  import scrollbarWidth from 'modesign-ui/src/utils/scrollbar-width';
  // 虚拟表格
  import virtualScroll from './virtual-scroll.vue';

  let tableIdSeed = 1;

  export default {
    name: 'ElTable',

    mixins: [Locale, Migrating, virtualScroll],

    directives: {
      Mousewheel
    },

    props: {
      data: {
        type: Array,
        default: function() {
          return [];
        }
      },

      size: String,

      width: [String, Number],

      height: [String, Number],

      maxHeight: [String, Number],

      fit: {
        type: Boolean,
        default: true
      },

      stripe: Boolean,

      border: {
        type: Boolean,
        default: true
      },

      verticalLine: {
        type: Boolean,
        default: false
      },

      textSize: {
        type: String,
        default: '14'
      },

      rowKey: [String, Function],

      context: {},

      showHeader: {
        type: Boolean,
        default: true
      },

      showSummary: Boolean,

      sumText: String,

      summaryMethod: Function,

      rowClassName: [String, Function],

      rowStyle: [Object, Function],

      cellClassName: [String, Function],

      cellStyle: [Object, Function],

      headerRowClassName: [String, Function],

      headerRowStyle: [Object, Function],

      headerCellClassName: [String, Function],

      headerCellStyle: [Object, Function],

      highlightCurrentRow: Boolean,

      currentRowKey: [String, Number],

      emptyText: String,

      expandRowKeys: Array,

      defaultExpandAll: Boolean,

      defaultSort: Object,

      tooltipEffect: String,

      spanMethod: Function,

      selectOnIndeterminate: {
        type: Boolean,
        default: true
      },

      indent: {
        type: Number,
        default: 16
      },

      treeProps: {
        type: Object,
        default() {
          return {
            hasChildren: 'hasChildren',
            children: 'children'
          };
        }
      },

      lazy: Boolean,

      load: Function,
      
      openDelay: Number
    },

    components: {
      TableHeader,
      TableFooter,
      TableBody,
      ElCheckbox,
      ElScrollbar,
      Teleport: {
        props: ['toEl'],
        data() {
          return {
            isMounted: false
          }
        },
        mounted() {
          this.$nextTick(() => {
            this.isMounted = true
            this.toEl && this.toEl.$refs.wrap.appendChild(this.$el)
          })
        },
        activated() {
          if (this.isMounted) return
          this.toEl && this.toEl.$refs.wrap.appendChild(this.$el)
        },
        deactivated() {
          this.isMounted = false
          this.$el && this.$el.parentNode && this.$el.parentNode.removeChild(this.$el)
        },
        render() {
          return this.$scopedSlots.default()
        },
        destroyed() {
          this.isMounted = false
          this.$el && this.$el.parentNode && this.$el.parentNode.removeChild(this.$el)
        }
      }
    },

    methods: {
      getMigratingConfig() {
        return {
          events: {
            expand: 'expand is renamed to expand-change'
          }
        };
      },

      setCurrentRow(row) {
        this.store.commit('setCurrentRow', row);
      },

      toggleRowSelection(row, selected) {
        this.store.toggleRowSelection(row, selected, false);
        this.store.updateAllSelected();
      },

      toggleRowExpansion(row, expanded) {
        this.store.toggleRowExpansionAdapter(row, expanded);
      },

      clearSelection() {
        this.store.clearSelection();
      },

      clearFilter(columnKeys) {
        this.store.clearFilter(columnKeys);
      },

      clearSort() {
        this.store.clearSort();
      },

      handleMouseLeave() {
        this.store.commit('setHoverRow', null);
        if (this.hoverState) this.hoverState = null;
      },

      updateScrollY() {
        const changed = this.layout.updateScrollY();
        if (changed) {
          this.layout.notifyObservers('scrollable');
          this.layout.updateColumnsWidth();
        }
      },

      handleBodyMousewheel() {
        if (!this.layout.bodyHeight) return
        const bodyWrapper = this.bodyWrapper
        const currentScrollTop = bodyWrapper.scrollTop
        this.isBodyScroll = currentScrollTop > 0
      },

      handleFixedMousewheel(event, data) {
        const bodyWrapper = this.bodyWrapperScrollbar;
        if (Math.abs(data.spinY) > 0) {
          const currentScrollTop = bodyWrapper.scrollTop;
          if (data.pixelY < 0 && currentScrollTop !== 0) {
            event.preventDefault();
          }
          if (data.pixelY > 0 && bodyWrapper.scrollHeight - bodyWrapper.clientHeight > currentScrollTop) {
            event.preventDefault();
          }
          bodyWrapper.scrollTop += Math.ceil(data.pixelY / 5);
        } else {
          bodyWrapper.scrollLeft += Math.ceil(data.pixelX / 5);
        }
      },

      handleHeaderFooterMousewheel(event, data) {
        const { pixelX, pixelY } = data;
        if (Math.abs(pixelX) >= Math.abs(pixelY)) {
          this.bodyWrapper.scrollLeft += data.pixelX / 5;
        }
      },

      // TODO 使用 CSS transform
      syncPostion() {
        const { scrollLeft, scrollTop, offsetWidth, scrollWidth, scrollHeight, offsetTop, clientHeight } = this.bodyWrapperScrollbar;
        const { headerWrapper, footerWrapper, fixedBodyWrapper, rightFixedBodyWrapper } = this.$refs;
        if (headerWrapper) headerWrapper.scrollLeft = scrollLeft;
        if (footerWrapper) footerWrapper.scrollLeft = scrollLeft;
        if (fixedBodyWrapper) fixedBodyWrapper.scrollTop = scrollTop;
        if (rightFixedBodyWrapper) rightFixedBodyWrapper.scrollTop = scrollTop;
        const maxScrollLeftPosition = scrollWidth - offsetWidth - 1;
        const maxScrollTopPosition = scrollHeight - clientHeight - 1;
        if (scrollLeft >= maxScrollLeftPosition) {
          this.scrollXPosition = 'right';
        } else if (scrollLeft === 0) {
          this.scrollXPosition = 'left';
        } else {
          this.scrollXPosition = 'middle';
        }
      

        if (scrollTop === 0) {
          this.scrollYPosition = 'top'
        } else if (scrollTop >0 && scrollTop <= maxScrollTopPosition) {
          this.scrollYPosition = 'middle'
        } else {
          this.scrollYPosition = 'bottom'
        }

      },

      throttleSyncPostion: throttle(16, function() {
        this.syncPostion();
      }),

      onScroll(evt) {
        let raf = window.requestAnimationFrame;
        if (!raf) {
          this.throttleSyncPostion();
        } else {
          raf(this.syncPostion);
        }
      },

      bindEvents() {
        // 监听scrollbar滚动事件
        this.bodyWrapperScrollbar.addEventListener('scroll', this.onScroll, {passive: true});
        // this.bodyWrapper.addEventListener('scroll', this.syncPostion, { passive: true });
        if (this.fit) {
          addResizeListener(this.$el, this.resizeListener);
        }
      },

      unbindEvents() {
        // 解除监听
        this.bodyWrapperScrollbar.removeEventListener('scroll', this.onScroll, {passive: true});
        // this.bodyWrapper.removeEventListener('scroll', this.syncPostion, { passive: true });
        if (this.fit) {
          removeResizeListener(this.$el, this.resizeListener);
        }
      },

      resizeListener() {
        if (!this.$ready) return;
        let shouldUpdateLayout = false;
        const el = this.$el;
        const { width: oldWidth, height: oldHeight } = this.resizeState;

        const width = el.offsetWidth;
        if (oldWidth !== width) {
          shouldUpdateLayout = true;
        }

        const height = el.offsetHeight;
        if ((this.height || this.shouldUpdateHeight) && oldHeight !== height) {
          shouldUpdateLayout = true;
        }

        if (shouldUpdateLayout) {
          this.resizeState.width = width;
          this.resizeState.height = height;
          this.doLayout();
        }
      },

      doLayout() {
        if (this.shouldUpdateHeight) {
          this.layout.updateElsHeight();
        }
        this.layout.updateColumnsWidth();
      },

      sort(prop, order) {
        this.store.commit('sort', { prop, order });
      },

      toggleAllSelection() {
        this.store.commit('toggleAllSelection');
      }

    },

    computed: {
      bodyWrapperScrollbar() {
        return this.$refs.bodyWrapperScrollbar.wrap;
      },

      // 这个方法是当el-table设置max-height时，el-scroll height:100%失效，导致el-scroll-wrap的margin-right失效，所以给el-scroll设置height为max-height的高度
      bodyScrollHeight() {
        const { headerHeight = 0, footerHeight = 0} = this.layout;
        if (this.maxHeight) {
          const maxHeight = parseHeight(this.maxHeight);
          if (typeof maxHeight === 'number') {
            return {
              'max-height': (maxHeight - footerHeight - (this.showHeader ? headerHeight : 0)) + 'px'
            };
          }
        }
        return {height: '100%'};
      },

      // 这个方法就用到了scrollbarWidth，当el-table设置height为固定高度时，el-scroll-wrap的100%和固定高度一样，导致margin-bottom失效，这里直接把el-scroll-wrap的高度算好，是el-table固定高度加上滚动条宽度
      bodyScrollWrapHeight() {
        const { headerHeight = 0, bodyHeight, footerHeight = 0} = this.layout;
        if (this.height) {
          return {
            height: bodyHeight ? bodyHeight + scrollbarWidth() + 'px' : ''
          };
        } else if (this.maxHeight) {
          const maxHeight = parseHeight(this.maxHeight);
          if (typeof maxHeight === 'number') {
            return {
              'max-height': (maxHeight - footerHeight - (this.showHeader ? headerHeight : 0) + scrollbarWidth()) + 'px'
            };
          }
        }
        return {};
      },

      tableSize() {
        return this.size || (this.$ELEMENT || {}).size;
      },

      bodyWrapper() {
        return this.$refs.bodyWrapper;
      },

      shouldUpdateHeight() {
        return this.height ||
          this.maxHeight ||
          this.fixedColumns.length > 0 ||
          this.rightFixedColumns.length > 0;
      },

      bodyWidth() {
        const { bodyWidth, scrollY, gutterWidth } = this.layout;
        return bodyWidth ? bodyWidth - (scrollY ? gutterWidth : 0) + 'px' : '';
      },

      bodyHeight() {
        const { headerHeight = 0, bodyHeight, footerHeight = 0} = this.layout;
        if (this.height) {
          return {
            height: bodyHeight ? bodyHeight + 'px' : ''
          };
        } else if (this.maxHeight) {
          const maxHeight = parseHeight(this.maxHeight);
          if (typeof maxHeight === 'number') {
            return {
              'max-height': (maxHeight - footerHeight - (this.showHeader ? headerHeight : 0)) + 'px'
            };
          }
        }
        return {};
      },

      fixedBodyHeight() {
        if (this.height) {
          return {
            height: this.layout.fixedBodyHeight ? this.layout.fixedBodyHeight + 'px' : ''
          };
        } else if (this.maxHeight) {
          let maxHeight = parseHeight(this.maxHeight);
          if (typeof maxHeight === 'number') {
            maxHeight = this.layout.scrollX ? maxHeight - this.layout.gutterWidth : maxHeight;
            if (this.showHeader) {
              maxHeight -= this.layout.headerHeight;
            }
            maxHeight -= this.layout.footerHeight;
            return {
              'max-height': maxHeight + 'px'
            };
          }
        }
        return {};
      },

      fixedHeight() {
        if (this.maxHeight) {
          if (this.showSummary) {
            return {
              bottom: 0
            };
          }
          return {
            bottom: (this.layout.scrollX && this.tableShowData.length) ? this.layout.gutterWidth + 'px' : ''
          };
        } else {
          if (this.showSummary) {
            return {
              height: this.layout.tableHeight ? this.layout.tableHeight + 'px' : ''
            };
          }
          return {
            height: this.layout.viewportHeight ? this.layout.viewportHeight + 'px' : ''
          };
        }
      },

      emptyBlockStyle() {
        if (this.$MODesignConfig.tableEmptyFixedHeight) {
          return {
            width: this.bodyWidth,
            height: '48px'
          };
        } else {
          const { bodyHeight, appendHeight} = this.layout;
          if (this.tableShowData && this.tableShowData.length) return null;
          // let height = '100%';
          let height = bodyHeight + 'px';
          if (this.layout.appendHeight) {
            // height = `calc(100% - ${this.layout.appendHeight}px)`;
            height = bodyHeight - appendHeight + 'px';
          }
          return {
            width: this.bodyWidth,
            height
          };
        }  
      },

      ...mapStates({
        selection: 'selection',
        columns: 'columns',
        tableData: 'data',
        fixedColumns: 'fixedColumns',
        rightFixedColumns: 'rightFixedColumns'
      })
    },

    watch: {
      resizeState: {
        deep: true,
        handler() {
          this.$nextTick(() => {
            this.$refs.bodyWrapperScrollbar.update()
          })
        }
      },
      columns: {
        deep: true,
        handler() {
          this.$nextTick(() => {
            this.$refs.bodyWrapperScrollbar.update()
          })
        }
      },
      height: {
        immediate: true,
        handler(value) {
          this.layout.setHeight(value);
        }
      },

      maxHeight: {
        immediate: true,
        handler(value) {
          this.layout.setMaxHeight(value);
        }
      },

      currentRowKey: {
        immediate: true,
        handler(value) {
          if (!this.rowKey) return;
          this.store.setCurrentRowKey(value);
        }
      },

      data: {
        immediate: true,
        handler(value) {
          this.store.commit('setOrignData', value);
        }
      },

      tableShowData: {
        immediate: true,
        handler(value) {
          this.store.commit('setData', value);
        }
      },

      expandRowKeys: {
        immediate: true,
        handler(newVal) {
          if (newVal) {
            this.store.setExpandRowKeysAdapter(newVal);
          }
        }
      }
    },

    created() {
      this.tableId = 'el-table_' + tableIdSeed++;
      this.debouncedUpdateLayout = debounce(50, () => this.doLayout());
    },

    mounted() {
      this.bindEvents();
      this.store.updateColumns();
      this.doLayout();

      this.resizeState = {
        width: this.$el.offsetWidth,
        height: this.$el.offsetHeight
      };

      // init filters
      this.store.states.columns.forEach(column => {
        if (column.filteredValue && column.filteredValue.length) {
          this.store.commit('filterChange', {
            column,
            values: column.filteredValue,
            silent: true
          });
        }
      });

      this.$ready = true;
    },

    destroyed() {
      this.unbindEvents();
    },

    data() {
      const { hasChildren = 'hasChildren', children = 'children' } = this.treeProps;
      this.store = createStore(this, {
        rowKey: this.rowKey,
        defaultExpandAll: this.defaultExpandAll,
        selectOnIndeterminate: this.selectOnIndeterminate,
        // TreeTable 的相关配置
        indent: this.indent,
        lazy: this.lazy,
        lazyColumnIdentifier: hasChildren,
        childrenColumnName: children
      });
      const layout = new TableLayout({
        store: this.store,
        table: this,
        fit: this.fit,
        showHeader: this.showHeader
      });
      return {
        layout,
        isHidden: false,
        isBodyScroll: false, // body scroll
        renderExpanded: null,
        resizeProxyVisible: false,
        resizeState: {
          width: null,
          height: null
        },
        // 是否拥有多级表头
        isGroup: false,
        scrollXPosition: 'left',
        scrollYPosition: 'top'
      };
    }
  };
</script>
