<template>
  <el-table
    ref="table"
    v-bind="tableProps"
    v-el-table-infinite-scroll="scrollOver"
    :data="dealedTableData"
    @cell-click="(...args:any[]) => { emit('cellClick', ...args) }"
    @cell-contextmenu="(...args:any[]) => { emit('cellContextmenu', ...args) }"
    @cell-dblclick="(...args:any[]) => { emit('cellDblclick', ...args) }"
    @cell-mouse-enter="(...args:any[]) => { emit('cellMouseEnter', ...args) }"
    @cell-mouse-leave="(...args:any[]) => { emit('cellMouseLeave', ...args) }"
    @current-change="(...args:any[]) => { emit('currentChange', ...args) }"
    @expand-change="(...args:any[]) => { emit('expandChange', ...args) }"
    @filter-change="(...args:any[]) => { emit('filterChange', ...args) }"
    @header-click="(...args:any[]) => { emit('headerClick', ...args) }"
    @header-contextmenu="(...args:any[]) => { emit('headerContextmenu', ...args) }"
    @header-dragend="(...args:any[]) => { emit('headerDragend', ...args) }"
    @row-click="(...args:any[]) => { emit('rowClick', ...args) }"
    @row-contextmenu="(...args:any[]) => { emit('rowContextmenu', ...args) }"
    @row-dblclick="(...args:any[]) => { emit('rowDblclick', ...args) }"
    @select="(...args:any[]) => { emit('select', ...args) }"
    @select-all="(...args:any[]) => { emit('selectAll', ...args) }"
    @selection-change="(...args:any[]) => { emit('selectionChange', ...args) }"
    @sort-change="(...args:any[]) => { emit('sortChange', ...args) }"
  >
    <el-table-column
      v-if="sequence || customSequence"
      :prop="customSequence ? 'index' : undefined"
      :type="customSequence ? undefined : 'index'"
      :fixed="hasFixedColumn"
      :width="50"
      align="center"
    >
      <template #header>
        <TableHeaderCell label="序号" />
      </template>
    </el-table-column>
    <el-table-column
      v-for="column in tableColumns"
      :key="column.columnName"
      v-bind="computedColumnProps(column)"
    >
      <template #header>
        <TableHeaderCell
          v-model:filter-values="filterValueMap[column.columnName]"
          v-model:sort-rule="sortRuleMap[column.columnName]"
          :label="labelFormatter(column.columnLabel)"
          :desc="column.columnDescribe"
          :show-desc="column.showDesc"
          :filter-options="filterOptionMap[column.columnName]"
          :sortable="!!column.needSort"
          filter-style="arrow"
          @update:sort-rule="
            sortRule => {
              sortRuleChangeHandler(column.columnName, sortRule)
            }
          "
        />
      </template>
      <template v-if="column.customRender" #default="scope">
        <component :is="column.customRender(scope)" />
      </template>
    </el-table-column>
  </el-table>
</template>

<script setup lang="ts">
import TableHeaderCell from '@/components/TableHeaderCell/index.vue'
import type { CSSProperties, VNode } from 'vue'
import { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
import { CommonData, CommonTableColumn } from '~/common'
import { Options, useTableFilterAndSort } from '@/hooks/useTableFilterAndSort'
import { ElTable } from 'element-plus'
import { useTableSettingsStore } from '@/store/tableSettings'

type SummaryMethod<T> = (data: {
  columns: TableColumnCtx<T>[]
  data: T[]
}) => string[]
type ColumnCls<T> = string | ((data: { row: T; rowIndex: number }) => string)
type ColumnStyle<T> =
  | CSSProperties
  | ((data: { row: T; rowIndex: number }) => CSSProperties)
type CellCls<T> =
  | string
  | ((data: {
      row: T
      rowIndex: number
      column: TableColumnCtx<T>
      columnIndex: number
    }) => string)
type CellStyle<T> =
  | CSSProperties
  | ((data: {
      row: T
      rowIndex: number
      column: TableColumnCtx<T>
      columnIndex: number
    }) => CSSProperties)
type Sort = {
  prop: string
  order: 'ascending' | 'descending'
  init?: any
  silent?: any
}
type TreeNode = {
  expanded?: boolean
  loading?: boolean
  noLazyChildren?: boolean
  indent?: number
  level?: number
  display?: boolean
}
type Layout = 'fixed' | 'auto'
type TableColumnProps<T> = {
  type?: string
  index?: number | ((index: number) => number)
  columnKey?: string
  width?: string | number
  minWidth?: string | number
  fixed?: boolean | string
  renderHeader?: (data: { column: TableColumnCtx<T>; $index: number }) => VNode
  resizable?: boolean
  formatter?: (
    row: T,
    column: TableColumnCtx<T>,
    cellValue: any,
    index: number
  ) => VNode | string
  showOverflowTooltip?: boolean
  align?: string
  headerAlign?: string
  className?: string
  labelClassName?: string
  selectable?: (row: T, index: number) => boolean
  reserveSelection?: boolean
}

const props = withDefaults(
  defineProps<{
    data: CommonData[]
    size?: string
    width?: string | number
    height?: string | number
    maxHeight?: string | number
    fit?: boolean
    stripe?: boolean
    border?: boolean
    rowKey?: string | ((row: CommonData) => string)
    showHeader?: boolean
    showSummary?: boolean
    sumText?: string
    summaryMethod?: SummaryMethod<CommonData>
    rowClassName?: ColumnCls<CommonData>
    rowStyle?: ColumnStyle<CommonData>
    cellClassName?: CellCls<CommonData>
    cellStyle?: CellStyle<CommonData>
    headerRowClassName?: ColumnCls<CommonData>
    headerRowStyle?: ColumnStyle<CommonData>
    headerCellClassName?: CellCls<CommonData>
    headerCellStyle?: CellStyle<CommonData>
    highlightCurrentRow?: boolean
    currentRowKey?: string | number
    emptyText?: string
    expandRowKeys?: any[]
    defaultExpandAll?: boolean
    defaultSort?: Sort
    tooltipEffect?: string
    spanMethod?: (data: {
      row: CommonData
      rowIndex: number
      column: TableColumnCtx<CommonData>
      columnIndex: number
    }) =>
      | number[]
      | {
          rowspan: number
          colspan: number
        }
      | undefined
    selectOnIndeterminate?: boolean
    indent?: number
    treeProps?: {
      hasChildren?: string
      children?: string
    }
    lazy?: boolean
    load?: (
      row: CommonData,
      treeNode: TreeNode,
      resolve: (data: CommonData[]) => void
    ) => void
    className?: string
    style?: CSSProperties
    tableLayout?: Layout
    flexible?: boolean
    scrollbarAlwaysOn?: boolean
    columnProps?: TableColumnProps<CommonData>
    columns: (CommonTableColumn & TableColumnProps<CommonData>)[]
    sequence?: boolean
    customSequence?: boolean
    filterSortOptions?: Options
    cacheKeys?: string[]
    labelFormatter?: (label: string) => string
  }>(),
  {
    size: 'default',
    height: '100%',
    maxHeight: '100%',
    stripe: true,
    border: true,
    fit: true,
    showHeader: true,
    labelFormatter: (label: string) => label,
  }
)

const defaultSummaryMethod: SummaryMethod<CommonData> = ({ columns, data }) => {
  const sums: string[] = []
  columns.forEach((column, index) => {
    const countColumn = tableColumns.value.find(
      col => column.property === col.columnName && col.needCount
    )
    if (countColumn) {
      const sumNumber = data.reduce((prev: number, curr: CommonData) => {
        const cellData = curr[column.property]
        if (countColumn.countMode === 'all') {
          return prev + 1
        }
        if (countColumn.countMode === 'notNull') {
          return cellData ? prev + 1 : prev
        }
        const value = Number(cellData)
        if (!Number.isNaN(value)) {
          prev += value
        }
        return prev
      }, 0)
      sums[index] = sumNumber.toString()
    }
  })
  sums[0] = '合计：' + (sums[0] ?? '')
  return sums
}

const tableProps = computed(() => {
  const rawProps = toRaw(props)
  const result: { [x: string]: any } = {}
  Object.entries(rawProps).forEach(([key, value]) => {
    if (
      ![
        'columnProps',
        'columns',
        'sequence',
        'customSequence',
        'filterSortOptions',
        'cacheKeys',
        'labelFormatter',
      ].includes(key) &&
      (value ?? '') !== ''
    ) {
      result[key] = value
    }
    if (props.columns.some(column => column.needCount)) {
      result.showSummary = true
    }
    if (!result.summaryMethod) {
      result.summaryMethod = defaultSummaryMethod
    }
  })
  return result
})

const computedColumnProps = computed(() => {
  const defaultColumnProps: TableColumnProps<CommonData> = {
    align: 'center',
  }
  return (column: CommonTableColumn & TableColumnProps<CommonData>) => ({
    ...defaultColumnProps,
    ...props.columnProps,
    ...column,
  })
})

const tableColumns = ref<CommonTableColumn[]>([])
const tableData = ref<CommonData[]>([])
watchEffect(() => {
  tableColumns.value = props.columns.reduce(
    (prevColumns: CommonTableColumn[], column) => {
      if (!column.hidden) {
        prevColumns.push({
          label: column.columnLabel,
          prop: column.columnName,
          ...column,
        })
      }
      return prevColumns
    },
    []
  )
  tableData.value = props.data
})

const hasFixedColumn = computed(() =>
  tableColumns.value.some(column => column.fixed)
)

const {
  filterOptionMap,
  filterValueMap,
  sortRuleMap,
  dealedTableData,
  sortChangeHandler,
} = useTableFilterAndSort(tableColumns, tableData, props.filterSortOptions)

const { saveTableFilterValues } = useTableSettingsStore()

watch(
  sortRuleMap,
  map => {
    emit('sortRuleChange', map)
  },
  { deep: true }
)

const sortRuleChangeHandler = (columnName: string, sortRule: string) => {
  sortRuleMap[columnName] = sortRule
  sortChangeHandler(columnName, sortRule)
}

if (props.cacheKeys?.length) {
  watch(filterValueMap, map => {
    const values: { [x: string]: string[] } = {}
    props.cacheKeys!.forEach(columnName => {
      values[columnName] = map[columnName]
    })
    saveTableFilterValues(values)
  })
}

const emit = defineEmits([
  'select',
  'selectAll',
  'selectionChange',
  'cellMouseEnter',
  'cellMouseLeave',
  'cellClick',
  'cellDblclick',
  'cellContextmenu',
  'rowClick',
  'rowContextmenu',
  'rowDblclick',
  'headerClick',
  'headerContextmenu',
  'sortChange',
  'filterChange',
  'currentChange',
  'headerDragend',
  'expandChange',
  'sortRuleChange',
  'scrollOver',
])

const scrollOver = () => {
  emit('scrollOver')
}

const table = ref<InstanceType<typeof ElTable> | null>(null)
defineExpose({
  table,
})
</script>

<style scoped lang="scss">
.el-table :deep {
  .el-table__cell {
    padding: 0;
    height: 40px;
    &.cell-filter {
      position: relative;
      &::before {
        content: '';
        position: absolute;
        width: 100%;
        height: 100%;
        top: 0;
        left: 0;
        z-index: 1;
      }
      &.cell-filter-yellow::before {
        opacity: 0.47;
        background-color: #eef3d6;
      }
      &.cell-filter-green::before {
        opacity: 0.73;
        background-color: #eef3d6;
      }
      &.cell-filter-cyan::before {
        opacity: 0.73;
        background-color: #d6e6f3;
      }
      .cell {
        position: relative;
        z-index: 2;
      }
    }
    .cell {
      padding: 0;
      font-size: 14px;
      color: #101116;
      font-family: DIN, Microsoft YaHei;
      &:not(.el-tooltip) {
        white-space: pre-line;
      }
    }
  }
  .el-table__header .el-table__cell {
    background: #ffffff;
    font-weight: bold;
    .cell {
      height: 100%;
    }
  }
  .el-table__body {
    .el-table__row--striped .el-table__cell {
      background-color: #f0f3f7;
    }
    .el-table__cell.cell-click .cell {
      color: #2d67e3;
      cursor: pointer;
    }
  }

  .el-scrollbar__bar {
    &.is-horizontal {
      height: 15px;
    }
    &.is-vertical {
      width: 15px;
    }
  }
}
</style>
