<template>
  <div class="ans-table-wrapper">
    <!--隐藏元素-->
    <div ref="hiddenContent" class="hidden-content"><slot></slot></div>

    <div :class="wrapperClasses" :style="wrapperStyle">
      <x-scroller
        ref="scroller"
        :offset-top="layout.headerHeight"
        :offset-left="layout.isIE ? 0 : leftSumWidth"
        :offset-right="layout.isIE ? 0 : rightSumWidth"
        :content-height="layout.contentHeight || contentHeight"
        :content-width="layout.bodyWidth"
        @on-vertical-drag="onVerticalDrag"
        @on-horizontal-drag="onHorizontalDrag"
      >
        <virtual-scroll
          ref="headerRef"
          class="header-wrapper"
          :fix-size="defaultColumnWidth"
          :class="{ 'affix': activeAffix, 'hide-header': !showHeader }"
          :list="columns"
          :keeps="realYKeeps"
          :data-key="'id'"
          :style="headerStyle"
          direction="horizontal"
          @update-range="onRangeXChange"
        >
          <base-table ref="baseHeader" :show-header="true"></base-table>
        </virtual-scroll>
        <virtual-scroll
          ref="bodyRef"
          class="body-wrapper"
          :list="innerData"
          :style="[bodyStyle, { marginTop: bodyTopMargin }]"
          data-key="_uniRowKey"
          :keeps="realXKeeps"
          :fix-size="rowHeight"
          :page-mode="!restrict && !height"
          :pad-start="rangeX.padFront"
          :pad-end="rangeX.padBehind"
          @update-range="onRangeYChange"
        >
          <base-table :show-body="true" :cur-data="curData"></base-table>
        </virtual-scroll>
      </x-scroller>
      <div v-if="!hasData && (emptyText || $slots.empty)" class="table-empty-wrapper">
        <slot name="empty">
          <span class="table-empty-text">{{ emptyText }}</span>
        </slot>
      </div>
      <div v-show="resizeProxyVisible" ref="resizeProxy" class="table-column-resize-proxy"></div>
    </div>

    <div v-if="$slots.footer" ref="footerWrapper" class="footer-wrapper">
      <slot name="footer"></slot>
    </div>
  </div>
</template>

<script>
import VirtualScroll from './virtual/index'
import { LIB_NAME } from '../../../../src/util'
import BaseTable from './BaseTable'
import Store from './util/Store'
import TableLayout from './util/TableLayout'
import { tableProps } from './util/props'
import { sumBy } from 'lodash'
import { defaultAffixDistance } from './util/config'

let tableIdSeed = 1

import { doFlattenColumns, parseHeight, ASCENDING_ORDER } from './util/util'
import { debounce } from 'throttle-debounce'
import XScroller from './scroll/scroller'

const defaultKeeps = 30
export default {
  name: 'xTables',
  components: {
    XScroller,
    VirtualScroll,
    BaseTable
  },
  props: {
    ...tableProps
  },
  provide() {
    return { table: this }
  },
  data() {
    const store = this.store = new Store(this)
    const layout = new TableLayout({
      store,
      table: this,
      fit: this.fit,
      restrict: this.restrict,
      showHeader: this.showHeader
    })

    return {
      layout,
      // 虚拟滚动横向
      rangeX: {
        padFront: 0,
        padBehind: 0
      },
      // 虚拟滚动纵向
      rangeY: {
        padFront: 0,
        padBehind: 0
      },
      // 所有的列
      columns: [],
      // 头部行数
      headerRows: 1,
      // 根据传入的data 生成的内部可用的list
      innerData: [],
      // 选中的行[_uniqKey]
      selectedRows: [],
      // 展开的行
      expandRows: [],
      // 多选时是否全选
      isSelectAll: false,
      // 多选时 的一种状态
      isIndeterminate: false,
      // 当前排序的列prop
      currentSortProp: '',
      // 显示拖动线
      resizeProxyVisible: false,
      // 当前拖动宽度的列
      dragColumnId: '',
      // 树形时展开的列 [_uniqKey]
      unfoldedRows: [],
      // 是否触发吸顶样式
      activeAffix: false,
      // 滚动条位置
      scroll: {
        isScrollXStart: true,
        isScrollXEnd: false
      },
      // 当前点击行的index
      clickRowIndex: null,
      //
      hoverRowIndex: null,
      // 合并行列最大合并行数
      maxRowSpan: 1,
      // 最大合并列数
      maxColSpan: 1
    }
  },
  computed: {
    // 初始时初略计算内容高度
    // 后面高度变化时updateRowHeight中再从新计算
    contentHeight() {
      return this.innerData.length * this.rowHeight + this.layout.headerHeight
    },
    hasData() {
      return this.innerData && this.innerData.length
    },

    // 是否可展开行
    expandable() {
      return this.store.states.expandable
    },

    bodyStyle() { // eslint-disable-line
      if (!this.height && !this.restrict && !this.maxHeight) return {}

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

      if (this.height || this.restrict) {
        return {
          height: this.layout.bodyHeight + 'px'
        }
      }
    },

    bodyTopMargin() {
      return this.showHeader && this.activeAffix ? this.layout.headerHeight + 'px' : 0
    },

    wrapperStyle() { // eslint-disable-line
      if (!this.height && !this.restrict && !this.maxHeight) return {}

      if (this.maxHeight) {
        const maxHeight = parseHeight(this.maxHeight)
        if (typeof maxHeight === 'number') {
          return {
            'max-height': maxHeight + 'px'
          }
        }
      }

      if (this.height || this.restrict) {
        return {
          height: this.layout.tableHeight + 'px'
        }
      }
    },

    // 左侧固定列
    leftColumns() {
      if (!this.columns) return []
      return this.columns.filter(v => v.fixed === true || v.fixed === 'left')
    },

    // 右侧固定的列
    rightColumns() {
      if (!this.columns) return []
      return this.columns.filter(v => v.fixed === 'right')
    },

    // 非固定列
    notFixColumns() {
      if (!this.columns) return []
      return this.columns.filter(v => !v.fixed)
    },

    leftSumWidth() {
      return parseInt(sumBy(this.leftLeafColumns, 'currentWidth'))
    },
    rightSumWidth() {
      return parseInt(sumBy(this.rightLeafColumns, 'currentWidth'))
    },
    // 左边固定的叶子列
    leftLeafColumns() {
      const columns = doFlattenColumns(this.leftColumns)
      if (!columns.length) return []

      columns.map((v, i) => {
        v['tdClass'] = ['fix-left', (i === columns.length - 1) && 'last-fix-left']
        v['leftWidth'] = sumBy(columns.slice(0, i), 'currentWidth')
        return v
      })
      return columns
    },

    // 右边固定的叶子列
    rightLeafColumns() {
      const columns = doFlattenColumns(this.rightColumns)

      if (!columns.length) return []
      columns.map((v, i) => {
        v['tdClass'] = ['fix-right', !i && 'first-fix-right']
        v['rightWith'] = sumBy(columns.slice(i + 1, columns.length), 'currentWidth')
        return v
      })
      return columns
    },

    // 非固定的叶子列
    curLeafColumns() {
      return doFlattenColumns(this.curColumns)
    },

    // 切割非固定列
    curColumns() {
      if (!this.rangeX || !this.columns) return []
      const { start, end } = this.rangeX
      // const realStart = start + this.leftLeafColumns.length
      // const realEnd = end === this.columns.length ? end : end - this.rightLeafColumns.length
      return this.notFixColumns.slice(start, end + 1)
    },

    // 切割后的 当前叶子列
    currentColumns() {
      return [...this.leftLeafColumns, ...this.curLeafColumns, ...this.rightLeafColumns]
    },

    // 切割数据
    curData() {
      if (!this.rangeY) return []
      const { start, end } = this.rangeY
      return this.innerData.slice(start, end + 1)
    },

    bodyWrapper() {
      return this.$refs.bodyRef && this.$refs.bodyRef
    },

    headerWrapper() {
      return this.$refs.headerRef && this.$refs.headerRef
    },

    hasBorder() {
      return this.border || this.store.states.multiLayer
    },

    wrapperClasses() {
      const { scrollX, scrollY, isIE } = this.layout
      return [
        `${LIB_NAME}-table`,
        { [`${LIB_NAME}-table--border`]: this.hasBorder },
        { 'affix-table-header': this.affix },
        { 'scrollable-x': !isIE && scrollX },
        { 'scrollable-y': !isIE && scrollY },
        { 'hit-left': !isIE && scrollX && this.scroll.isScrollXStart },
        { 'hit-right': !isIE && scrollX && this.scroll.isScrollXEnd }
      ]
    },

    headerStyle() {
      if (!this.affix) return {}

      return {
        top: this.affixDistance + 'px',
        width: this.affixHeaderWidth
      }
    },

    /* 距离顶部的distance*/
    affixDistance() {
      return typeof this.affix === 'number' ? this.affix : defaultAffixDistance
    },

    /* 吸顶header的宽度*/
    affixHeaderWidth() {
      return this.activeAffix ? this.$el.clientWidth + 'px' : ''
    },

    /* 所有叶子列*/
    leafColumns() {
      return Object.freeze(doFlattenColumns(this.columns))
    },
    realXKeeps() {
      return (this.virtualScroll ? this.keeps : this.innerData.length) || defaultKeeps
    },
    realYKeeps() {
      return (this.virtualScroll ? this.keeps : this.leafColumns.length) || defaultKeeps
    }
  },
  watch: {
    // 监听数据变化
    data() {
      this.genInnerData()
      this.layout.updateElsHeight()
      this.debouncedCheckScrollable()
    },
    // 高度变化时
    height: {
      immediate: true,
      handler(val) {
        !!val && this.layout.setHeight(val)
      }
    },
    maxHeight: {
      immediate: true,
      handler(val) {
        !!val && this.layout.setMaxHeight(val)
      }
    }
  },
  beforeCreate() {
    // 生成tableId
    this.tableId = `${LIB_NAME}-table_${tableIdSeed++}`
    this.debouncedUpdateData = debounce(40, () => this.genInnerData())
    this.debouncedUpdateLayout = debounce(50, () => this.doLayout())
    this.debouncedCheckScrollable = debounce(5, () => this.checkScrollable())
  },
  created() {
    this.currentSortProp = this.defaultSort ? this.defaultSort.prop : ''
  },
  mounted() {
    this.$ready = true

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

    this.store.scheduleLayout()
    this.bindEvents()
  },
  beforeDestroy() {
    this.$destroyed = true
    this.unBindEvents()
  },
  methods: {
    onRangeXChange(val) {
      this.rangeX = val
    },
    onRangeYChange(val) {
      this.rangeY = val
    },

    setIndeterminate() {
      this.isIndeterminate = this.selectedRows.length > 0 && this.selectedRows.length < this.innerData.length
    },

    // 行高变化时处理
    updateRowHeight(_uniRowKey, size) {
      const { bodyRef } = this.$refs

      !!bodyRef && bodyRef.updateSize(_uniRowKey, size)
      this.layout.updateContentHeight()
    },
    sortData(prop, sortMethod = ASCENDING_ORDER) {
      this.innerData = this.store.sortData(this.innerData, prop, sortMethod)
    },

    findColumnById(columns, id) {
      return columns.find(v => v.id === id)
    },

    findOriginColumn(columns, col) {
      const item = this.findColumnById(columns, col.id)
      if (!item && col.parent) {
        const parent = this.findColumnById(columns, col.parent.id)
        return parent ? this.findColumnById(parent.children, col.id) : ''
      }
      return item || ''
    },
    updateColumns(colConfig, notEmit = false) {
      const item = this.findOriginColumn(this.columns, colConfig)

      !!item && Object.keys(colConfig).forEach(key => {
        this.$set(item, key, colConfig[key])
      })
      !notEmit && this.emitColWidthChange(this)
    },
    emitColWidthChange: debounce(200, (_this) => {
      _this.$emit('col-width-change', _this.leafColumns.map(v => {
        return { id: v.id, width: v.currentWidth, prop: v.prop }
      }))
      _this.doLayout(false)
    }),
    updateColWidth(_uniRowKey, size) {
      this.headerWrapper.updateSize(_uniRowKey, size)
    },
    doLayout(isHeight = true) {
      this.layout.updateColumnsWidth()
      if (isHeight) {
        this.layout.updateElsHeight()
      }
      this.debouncedCheckScrollable()
    },
    setActiveAffix(activeAffix) {
      this.activeAffix = activeAffix
    },
    setScrollPosition(position) {
      if (!this.$refs.scroller) return

      if (position === 'left') {
        this.$refs.scroller.setLeft(0)
      }

      if (position === 'top') {
        this.$refs.scroller.setTop(0)
      }
    },

    genInnerData() {
      const data = this.data.length ? this.store.genData(this.data, this.leafColumns) : []
      this.innerData = [...data]
      this._backInnerData = [...data]
    },
    resizeListener() {
      if (!this.$ready) return
      const { offsetWidth, offsetHeight } = this.$el
      const { width: oldWidth, height: oldHeight } = this.resizeState

      const shouldUpdateLayout = oldWidth !== offsetWidth || (this.height && oldHeight !== offsetHeight)
      if (shouldUpdateLayout || this.restrict) {
        this.resizeState.width = offsetWidth
        this.resizeState.height = offsetHeight
        this.doLayout()
      }
    },
    bindEvents() {
      if (this.fit) {
        window.addEventListener('resize', this.resizeListener)
      }
    },
    unBindEvents() {
      if (this.fit) {
        window.removeEventListener('resize', this.resizeListener)
      }
    },

    setScrollLeft(scrollLeft) {
      if (!this.headerWrapper || !this.bodyWrapper) return
      this.headerWrapper.scrollToOffset(scrollLeft)
      this.bodyWrapper.$el.scrollLeft = scrollLeft
    },

    setScrollTop(scrollTop) {
      if (!this.bodyWrapper) return

      this.bodyWrapper.scrollToOffset(scrollTop)
    },

    // 拖动纵向滚动bar
    onVerticalDrag(val, obj) {
      // if ((this.scroll.isScrollYStart && obj.isHitTop) || (
      //   this.scroll.isScrollYEnd && obj.isHitBottom
      // )) {
      //   return
      // }
      this.setScrollTop(val)
      this.scroll.isScrollYStart = obj.isHitTop
      this.scroll.isScrollYEnd = obj.isHitBottom
      this.scroll.isScrollYEnd && this.$emit('on-hit', 'bottom')
    },

    // 拖动横向滚动bar
    onHorizontalDrag(val, obj) {
      // if ((this.scroll.isScrollXEnd && obj.isHitRight) || (
      //   this.scroll.isScrollXStart && obj.isHitLeft
      // )) {
      //   return
      // }
      this.setScrollLeft(val)
      this.scroll.isScrollXStart = obj.isHitLeft
      this.scroll.isScrollXEnd = obj.isHitRight
      this.scroll.isScrollXEnd && this.$emit('on-hit', 'right')
    },

    checkScrollable() {
      this.$refs.scroller && this.$refs.scroller.checkScrollable()
    },

    // 改变树row行的展开收起状态
    toggleRowUnfolding(row) {
      if (!this.rowKey) {
        throw new Error('rowKey is required for tree Table unfold')
      }
      const innerRow = this.innerData.find(v => v.item[this.rowKey] === row[this.rowKey])
      !!innerRow && this.store.commit('onFoldStateChange', row, innerRow._uniRowKey)
    },

    toggleRowExpansion(row) {
      if (!this.rowKey) {
        throw new Error('rowKey is required')
      }
      const innerRow = this.innerData.find(v => v.item[this.rowKey] === row[this.rowKey])
      !!innerRow && this.store.commit('onExpandStateChange', row, innerRow._uniRowKey)
    },

    clearSelection() {
      this.store.commit('clearSelection')
    },

    // 清除排序状态 恢复初始状态
    clearSort() {
      this.currentSortProp = ''
      // 空间换时间.
      this.innerData = [...this._backInnerData]
    }
  }
}
</script>

<style scoped lang="scss">
  /*.ans-table-wrapper {*/
  /*  height: 100%;*/
  /*  overflow: hidden;*/
  /*  .ans-table {*/
  /*    width: 100%;*/
  /*    &.ans--border {*/
  /*      border: 1px solid #e4eaf1;*/
  /*      border-right: none;*/
  /*      border-bottom: none;*/
  /*      &::after {*/
  /*        display: none;*/
  /*      }*/
  /*    }*/
  /*  }*/
  /*}*/

  .header-wrapper {
    height: auto;
    overflow: hidden;
    // 暂时注释掉, 解决表格中poptip遮挡的问题
    /*transform: translateZ(0px);*/
    &.hide-header {
      height: 0;
      transform: translate(-100%, -100%);
    }
  }

  .body-wrapper {
    position: relative;
    overflow: hidden;
    /*transform: translateZ(0px);*/
  }

  .footer-wrapper {
    float: right;
    padding: 14px 0;
    background: #fff;
  }
</style>
