<script>
import { Table } from 'ant-design-vue'
import merge from 'lodash.merge'
import cloneDeep from 'lodash.clonedeep'
import { sdLocalStorage } from '../services/storage-service'
import { router } from '../router-ready'
import sdResizableTh from './sd-table/sd-resizable-th'
import sdTableHeader from './sd-table/sd-table-header'
import sdDraggable from './sd-draggable.vue'

/**
 * 在ATable的基础上，增加了列宽可拖动、可选列、数据可排序功能
 * @displayName SdTable 表格
 */
export default {
  name: 'SdTable',
  inheritAttrs: false,
  props: {
    /**
     * @ignore
     */
    columns: {
      type: Array,
      default: () => [],
    },
    /**
     * 表格的数据是否可以通过拖拽排序（只有pagination=false才生效）
     * @since 8.0.4
     */
    sortable: {
      type: Boolean,
      default: false,
    },
    /**
     * 显示所有列
     * @ignore
     */
    showAllColumns: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      width: 0,
      columnResizing: false,
      columnInfos: [],
      columnOrder: undefined,
      windowResizing: false,
      tableComponents: {},
    }
  },
  computed: {
    sortableComputed() {
      // 只有不分页的，才可以排序
      return this.sortable && this.$attrs.pagination === false
    },
    columnsComputed() {
      // 按照columnOrder重新排序
      const f = (columns, columns2) => {
        if (!columns2?.length) {
          return
        }
        columns.sort((a, b) => {
          return (
            (columns2.findIndex((i) => i.dataIndex === a.dataIndex) ?? 999) -
            (columns2.findIndex((i) => i.dataIndex === b.dataIndex) ?? 999)
          )
        })
        columns.forEach((column, index) => {
          if (column.children) {
            f(column.children, columns2?.[index]?.children)
          }
        })
      }
      f(this.columnInfos, this.columnOrder)
      return this.columnInfos
    },
    columnsToShow() {
      // 传给a-table的是选中要显示的列
      const columns = cloneDeep(this.columnsComputed)
      const f = (columns) => {
        const c = columns.filter((item) => item.show)
        columns.splice(0, 999)
        c.forEach((i) => columns.push(i))
        columns.forEach((column) => {
          if (column.children) {
            f(column.children)
          }
        })
      }
      f(columns)
      return columns
    },
    fingerPrint() {
      // 用列ID的集合作为table的指纹
      return this.columns
        .filter((column) => column.dataIndex !== 'sd-opt')
        .map((column) => column.dataIndex)
        .sort()
        .join(',')
    },
    leafColumns() {
      const leafColumns = []
      const f = (columns) => {
        columns.forEach((column) => {
          if (column.children) {
            f(column.children)
          } else if (column.show) {
            leafColumns.push(column)
          }
        })
      }
      f(this.columnsComputed)
      return leafColumns
    },
  },
  watch: {
    sortableComputed: {
      handler(newVal, oldVal) {
        this.tableComponents = {
          header: {
            wrapper: {
              functional: true,
              render: (createElement, context) => {
                // header.wrapper不支持自定义prop，所以使用jsx闭包的方式传递数据
                return (
                  <sdTableHeader
                    columns={this.columnsComputed}
                    vOn:reset={this.reset}
                    vOn:columnShowChange={this.columnShowChange}
                    vOn:columnOrderChange={this.columnOrderChange}
                    vOn:refresh={() => this['$' + 'emit']('refresh')}
                    class={context.data.class}
                  >
                    {context.children}
                  </sdTableHeader>
                )
              },
            },
            cell: sdResizableTh,
          },
          body: {
            wrapper: newVal
              ? {
                  functional: true,
                  render: (createElement, context) => {
                    return (
                      <sdDraggable
                        tag='tbody'
                        class='ant-table-tbody'
                        handle='.ant-table-selection-column'
                        value={this.$attrs['data-source']}
                        on={{
                          input: (data) => {
                            /**
                             * 数据排序后触发
                             * @property {array} data 排序后的 data-source 数组
                             */
                            this.$emit('update:data-source', data)
                          },
                        }}
                      >
                        {context.children}
                      </sdDraggable>
                    )
                  },
                }
              : undefined,
          },
        }
      },
      immediate: true,
    },
    columns: {
      handler() {
        // 为每一列增加和拖拽resize的属性
        this.columnInfos = cloneDeep(this.columns)
        const f = (columns) => {
          columns.forEach((column, index, columns) => {
            this.$set(column, 'dataIndex', column.dataIndex || index)
            this.$set(
              column,
              'show',
              this.showAllColumns || // 强制显示所有列
                (this.getTableInfo(column.dataIndex, 'show') ?? column.sdCandidate !== true)
            )
            this.$set(column, 'defaultShow', column.sdCandidate !== true)
            if (!column.children) {
              // 只有叶子节点可以设置宽度
              this.$set(column, 'width', column.width || '')
              this.$set(column, 'defaultWidth', column.width || '')
              column.customHeaderCell = (col) => {
                const index = this.leafColumns.findIndex((item) => item.dataIndex === col.dataIndex)
                const column = this.leafColumns[index]
                const nextColumn = this.leafColumns[index + 1]
                return {
                  class: col.sdClassName,
                  attrs: {
                    // 将以px为单位的宽度传入，百分比的不传入
                    width: column.width?.endsWith('px') ? parseFloat(column.width) : 0,
                  },
                  on: {
                    resizeStart: () => {
                      this.columnResizing = true
                    },
                    resize: (x) => {
                      this.columnResizing = false
                      // 最小列宽，避免布局被过度撑高
                      const minWidth = 50
                      x = Math.max(minWidth, x)
                      // 设置自己列的大小，并相应的改变下一列的大小
                      if (
                        nextColumn &&
                        column.width?.endsWith('px') &&
                        nextColumn.width?.endsWith('px')
                      ) {
                        const total = parseFloat(column.width) + parseFloat(nextColumn.width)
                        x = Math.min(total - minWidth, x)
                        nextColumn.width = total - x + 'px'
                      }
                      column.width = x + 'px'
                    },
                  },
                }
              }
            }
            if (column.children) {
              column.children.forEach((item, index) => {
                // dataIndex不是必须的，作为树的键值补齐
                this.$set(item, 'dataIndex', item.dataIndex || column.dataIndex + '-' + index)
              })
              f(column.children)
            }
          })
        }
        f(this.columnInfos)
      },
      immediate: true,
      deep: true,
    },
  },
  created() {
    // 取用户的列排序
    this.columnOrder = this.getTableInfo('_column_order_', 'array') || []

    window.addEventListener('resize', this.handleResize)
    this.$once('hook:beforeDestroy', function() {
      window.removeEventListener('resize', this.handleResize)
    })
  },
  mounted() {
    // 记录初始的表格宽度
    this.$nextTick(function() {
      this.width = this.$el.clientWidth
    })
  },
  methods: {
    // 列排序变化的处理
    columnOrderChange(data) {
      this.columnOrder = data
      this.setTableInfo('_column_order_', 'array', data)
    },
    // 显示或隐藏某一列时的处理
    columnShowChange(columnIds, show) {
      const f = (columns) => {
        columns.forEach((column) => {
          column.show = columnIds === '*' || columnIds.includes(column.dataIndex)
          // 保存到本地
          this.setTableInfo(column.dataIndex, 'show', column.show)
          // 列发生变化，忽略用户拖拽的宽度，重新按原始值计算
          column.width = column.defaultWidth
          if (column.children) {
            f(column.children)
          }
        })
      }
      f(this.columnInfos)
    },
    // 重置列信息
    reset() {
      const f = (columns) => {
        columns.forEach((col) => {
          col.width = col.defaultWidth
          col.show = col.defaultShow
          if (col.children) f(col.children)
        })
      }
      f(this.columnInfos)
      this.columnOrder = []
      this.clearTableInfo()
    },
    /**
     * 重新计算各列的宽度，容器宽度发生变化时，可以调用
     * @public
     * @since 0.15
     */
    handleResize() {
      // 处理窗口resize
      this.windowResizing = true
      setTimeout(() => {
        // 记录当前表格宽度
        const newWidth = this.$el.clientWidth
        this.windowResizing = false
        // 没有宽度，可能是tab页没激活，退出
        if (newWidth === 0) return
        // 每一列按比例改变宽度
        this.columnInfos.forEach((column) => {
          column.width = Math.floor((parseFloat(column.width) * newWidth) / this.width) + 'px'
        })
        this.width = newWidth
        // 等待300ms，因为窗口resize时，左侧导航宽度可能会变化。等动画完成后再计算
      }, 300)
    },
    getRoutePath() {
      const matched = router.currentRoute.matched
      /*
      不取fullpath，像webflow这种会每个文档都不同
      取路由中定义的path，例如：/sd-webflow/:type/:mode/:id
      */
      return matched[matched.length - 1].path
    },
    getTableInfo(columnId, key) {
      // 从localStorage获取列信息
      const value = JSON.parse(sdLocalStorage.getItem('table-info') || '{}')
      return value[this.getRoutePath()]?.[this.fingerPrint]?.[columnId]?.[key]
    },
    clearTableInfo() {
      // localStorage清除列信息
      const allTableInfo = JSON.parse(sdLocalStorage.getItem('table-info') || '{}')
      if (allTableInfo[this.getRoutePath()]?.[this.fingerPrint])
        allTableInfo[this.getRoutePath()][this.fingerPrint] = undefined
      sdLocalStorage.setItem('table-info', JSON.stringify(allTableInfo))
    },
    setTableInfo(columnId, key, value) {
      // 向localStorage写入列信息
      const allTableInfo = JSON.parse(sdLocalStorage.getItem('table-info') || '{}')
      sdLocalStorage.setItem(
        'table-info',
        JSON.stringify(
          merge(allTableInfo, {
            [this.getRoutePath()]: {
              [this.fingerPrint]: {
                [columnId]: {
                  [key]: value,
                },
              },
            },
          })
        )
      )
    },
  },
  // 用render函数处理scopedSlots更方便
  render(h) {
    const children = Object.keys(this.$slots).map((slot) =>
      h('template', { slot }, this.$slots[slot])
    )
    const { columns, components, ...restAttrs } = this.$attrs
    return h(
      Table,
      {
        props: {
          columns: this.columnsToShow,
          // 自定义渲染标题行的单元格
          components: this.tableComponents,
          tableLayout: 'fixed',
        },
        class: {
          [this.$style.autoLayout]: this.windowResizing,
        },
        attrs: restAttrs,
        on: this.$listeners,
        scopedSlots: this.$scopedSlots,
      },
      children
    )
  },
}
</script>

<style module lang="scss">
@use '@/common/design' as *;
// 窗口宽度由小变大时，表格由于各列指定了宽度，无法自动缩小
:global(.ant-form-item) .auto-layout table {
  table-layout: auto;
}
:global(.browser-ie) :global(.ant-form-item) .auto-layout table {
  display: none;
}

:global .ant-table-tbody:not(.sd-dragging) .ant-table-row:hover {
  /* stylelint-disable-next-line */
  .handle_sd-draggable_common::before {
    position: absolute;
    left: 5px;
    /* stylelint-disable-next-line */
    color: #999;
    content: '⋮⋮';
  }
}
</style>

<docs>
```vue
<template>
  <div>
    <sd-table :columns="columns" sortable :pagination="false" :data-source.sync="data" :row-selection="{}" />
    排序后的数据：{{this.data.map(item => item.name)}}
  </div>
</template>
<script>
const columns = [
  {
    title: 'Name',
    dataIndex: 'name',
  },
  {
    title: 'Age',
    dataIndex: 'age',
  },
  {
    title: 'Address',
    dataIndex: 'address',
  },
];
const data = [
  {
    key: '1',
    name: 'John Brown',
    age: 32,
    address: 'New York No. 1 Lake Park',
  },
  {
    key: '2',
    name: 'Jim Green',
    age: 42,
    address: 'London No. 1 Lake Park',
  },
  {
    key: '3',
    name: 'Joe Black',
    age: 32,
    address: 'Sidney No. 1 Lake Park',
  },
];

export default {
  data() {
    return {
      data,
      columns,
    };
  },
};
</script>
```
</docs>
