<template>
  <div class="editable-table">
    <div class="editable-table-view-setting">
      <div
        v-if="!hideViewSettingBtn"
        class="editable-table-view-setting__btn-wrapper"
      >
        <div
          class="editable-table-view-setting__btn btn-pointer"
          @click="handleViewSettingShow"
        >
          <i class="el-icon-setting" />
          <div class="editable-table-view-setting__btn-text">
            显示设置
          </div>
        </div>
      </div>

      <biz-view-setting-dialog
        ref="bizViewSettingDialogRef"
        :props="props"
        :actual-columns="actualColumns"
        :showing-columns="showingColumns"
        :view-setting-drag-sort-options="viewSettingDragSortOptions"
        @update:leftFixedColumnCount="(val) => { leftFixedColumnCount = val }"
        @update:showingColumns="(val) => { showingColumns = val }"
        @update:viewSettingDragSortOptions="(val) => { viewSettingDragSortOptions = val }"
        @tableDoLayout="doTableLayout"
      />
    </div>

    <!-- 列表展示，属性透传，列编辑 -->
    <el-table
      ref="tableDomRef"
      v-loading="loading"
      :data="dataList"
      :row-style="setRowStyle"
      :row-class-name="setRowClassName"
      :cell-class-name="setCellClassName"
      :show-summary="summaryList.length > 0"
      :summary-method="tableSummaryMethod"
      v-bind="$attrs"
      border
      @selection-change="handleSelectionChange"
      @cell-mouse-enter="debouncedHoverHandler"
      @header-dragend="doTableLayout"
      @row-dblclick="handleRowDblClick"
    >
      <el-table-column
        v-if="rowDragAble"
        width="30px"
        class-name="editable-table__drag-cell no-inner-cell-border"
        :fixed="leftFixedColumnCount > 0 ? 'left' : false"
      >
        <template #default="scope">
          <div
            class="row-drag-target editable-table__drag-icon"
            :data-index="scope.$index"
            @mousedown="currScope = scope"
          >
            <div
              :data-index="scope.$index"
              class="row-drag-target editable-table-drag-icon"
            />
          </div>
        </template>
      </el-table-column>
      <!-- 展开行 -->
      <el-table-column
        v-if="hasExpandRow"
        width="30px"
        type="expand"
        :fixed="leftFixedColumnCount > 0 ? 'left' : false"
        class-name="no-inner-cell-border"
      >
        <template #default="scope">
          <slot
            name="expand"
            v-bind="scope"
          />
        </template>
      </el-table-column>
      <!-- 选择列 -->
      <el-table-column
        v-if="hasSelectionColumn"
        width="45px"
        align="center"
        type="selection"
        :fixed="leftFixedColumnCount > 0 ? 'left' : false"
        class-name="no-inner-cell-border"
      />
      <!-- 编号列 -->
      <el-table-column
        v-if="hasIndexColumn"
        min-width="30px"
        type="index"
        :fixed="leftFixedColumnCount > 0 ? 'left' : false"
        class-name="no-inner-cell-border"
      />
      <!-- 颜色选择列 -->
      <el-table-column
        v-if="colorList && colorList.length > 0"
        width="22px"
        class-name="editable-table__color-column no-inner-cell-border"
        :fixed="leftFixedColumnCount > 0 ? 'left' : false"
        :filtered-value="Array.isArray(filteredValue[colorFilterConfig?.prop]) ? filteredValue[colorFilterConfig?.prop] : []"
      >
        <template #header>
          <biz-table-header-popover
            v-if="colorFilterConfig"
            :head-active="isColumnHeadActive(colorFilterConfig)"
            :column="colorFilterConfig"
            :temp-summary-list="tempSummaryList"
            :temp-sort-prop="tempSortProp"
            :temp-sort-type="tempSortType"
            :temp-filtered-value="tempFilteredValue"
            @update:tempSummaryList="val => { tempSummaryList = val }"
            @update:tempFilteredValue="(key, value) => { $set(tempFilteredValue, key, value) }"
            @popover-show="() => handleHeaderPopoverShow(colorFilterConfig)"
            @update:sort="handleSort"
            @reset="() => handleHeaderOperationReset(colorFilterConfig)"
            @confirm="() => handleHeaderOperationConfirm(colorFilterConfig)"
          >
            <template #custom>
              <div class="editable-table__color-icon" />
            </template>
          </biz-table-header-popover>
          <div
            v-else
            class="editable-table__color-icon"
          />
        </template>
        <template #default="scope">
          <biz-color-select
            :color-list="colorList"
            :scope="scope"
            @row-bg-change="(params) => emit('row-bg-change', params)"
          />
        </template>
      </el-table-column>
      <!-- 这里的key很重要，必须保证每次生成的key都不一样，这样列排序功能才能生效，否则，即使actualColumns数组元素顺序发生变化，列顺序也不会改变。原因未知 -->
      <el-table-column
        v-for="(column, index) in actualColumns"
        :key="column.prop + index"
        resizable
        class-name="editable-table__data-column"
        :filtered-value="Array.isArray(filteredValue[column.prop]) ? filteredValue[column.prop] : []"
        v-bind="getColumnBindProps(column)"
      >
        <template
          #header
          v-if="showColumnHeadSortIcon(column)"
        >
          <biz-table-header-popover
            :head-active="isColumnHeadActive(column)"
            :column="column"
            :temp-summary-list="tempSummaryList"
            :temp-sort-prop="tempSortProp"
            :temp-sort-type="tempSortType"
            :temp-filtered-value="tempFilteredValue"
            @update:tempSummaryList="val => { tempSummaryList = val }"
            @update:tempFilteredValue="(key, value) => { $set(tempFilteredValue, key, value) }"
            @popover-show="() => handleOpenFilter(column)"
            @update:sort="handleSort"
            @reset="() => handleResetFilter(column)"
            @confirm="() => handleConfirmFilter(column)"
          >
            <template #summay-item>
              <slot
                :name="column.prop + '-summay-item'"
                v-bind="column"
              >
                {{ column.label }}
              </slot>
            </template>
            <!-- 动态地将父组件传递的所有slot下发给孙子组件（biz-table-header-popover），以实现slot的透传 -->
            <template
              v-for="(_, name) in $slots"
              #[name]="slotProps"
            >
              <slot
                :name="name"
                v-bind="slotProps || column"
              />
            </template>
          </biz-table-header-popover>
        </template>
        <!-- 默认操作按钮，defaultOperations属性不为空数组时展示。编辑状态下隐藏 -->
        <template
          v-if="column.prop === '$$operation'"
          #default="scope"
        >
          <biz-table-operate-popover
            v-if="editingRowIndex !== scope.$index"
            ref="operationPopoverRef"
            :default-operations="defaultOperations"
            :scope="scope"
            @edit="() => handleEdit(scope)"
            @delete="() => handleDelete(scope.row, scope.$index)"
            @rowPinToTop="() => handleRowPinToTop(scope)"
          >
            <slot
              name="custom-operation"
              v-bind="scope"
            />
          </biz-table-operate-popover>

          <div
            v-else
            class="editable-table-operation-popover__save-cancel"
          >
            <div
              class="btn-pointer editable-table-operation-popover__btn"
              @click="handleEditSave(scope.row)"
            >
              保存
            </div>
            <div
              class="btn-pointer editable-table-operation-popover__btn"
              @click="handleEditCancel(scope.row)"
            >
              取消
            </div>
          </div>
        </template>
        <!-- 默认渲染、非编辑态，不需要特殊处理，el-table负责渲染 -->
        <!-- 自定义插槽 -->
        <template
          v-else-if="column.slotName"
          #default="scope"
        >
          <!-- 非编辑态 -->
          <slot
            v-if="scope.$index !== editingRowIndex"
            v-bind="scope"
            :name="column.slotName"
          />
          <!-- 编辑态 -->
          <template v-else-if="scope.$index === editingRowIndex">
            <!-- 自定义编辑 -->
            <slot
              v-if="column.editSlotName"
              v-bind="returnAnyType({ scope, column })"
              :name="column.editSlotName"
            />
            <!-- 内置编辑类型 -->
            <biz-edit-cell
              v-else-if="column.editType"
              :value="editingRowData[column.prop]"
              :column="column"
              @input="val => { editingRowData[column.prop] = val }"
            />
            <!-- 不支持编辑 -->
            <slot
              v-else
              v-bind="scope"
              :name="column.slotName"
            />
          </template>
        </template>
        <!-- 默认渲染列，编辑态 -->
        <template
          v-else-if="editingRowIndex !== -1"
          #default="scope"
        >
          <!-- 当前行编辑中，内置编辑类型 -->
          <biz-edit-cell
            v-if="editingRowIndex === scope.$index && column.editType"
            :value="editingRowData[column.prop]"
            :column="column"
            @input="val => { editingRowData[column.prop] = val }"
          />
          <!-- 当前行编辑中，自定义编辑类型 -->
          <slot
            v-else-if="column.editSlotName && scope.$index === editingRowIndex"
            v-bind="scope"
            :name="column.editSlotName"
          />
          <!-- 当前行非编辑中 -->
          <template v-else>
            {{ column.formatter ? column.formatter(scope.row, column, scope.row[column.prop], scope.$index) : scope.row[column.prop] }}
          </template>
        </template>
        <!-- hover状态渲染 -->
        <template
          v-else-if="column.hoverSlotName"
          #default="scope"
        >
          <slot
            v-if="scope.$index === hoveringCellInfo.rowIndex && column.prop === hoveringCellInfo.columnProperty"
            v-bind="scope"
            :name="column.hoverSlotName"
          />
          <slot
            v-else-if="column.slotName"
            v-bind="scope"
            :name="column.slotName"
          />
          <template v-else>
            {{ column.formatter ? column.formatter(scope.row, column, scope.row[column.prop], scope.$index) : scope.row[column.prop] }}
          </template>
        </template>
      </el-table-column>
    </el-table>
    <div class="pagination-wrap">
      <div>共{{ total }}项数据</div>
      <el-pagination
        background
        layout="sizes, prev, pager, next, jumper"
        :page-sizes="pageSizes"
        :page-size.sync="pageSize"
        :pager-count="pagerCount"
        :current-page="currentPage"
        :total="total"
        @size-change="handlePageSizeChange"
        @current-change="handleCurrPageChange"
      />
    </div>
  </div>
</template>

<script lang="ts" setup>
import BizColorSelect from './features/bizColorSelect.vue';
import BizViewSettingDialog from './features/bizViewSettingDialog.vue'
import BizTableHeaderPopover from './features/bizTableHeaderPopover/index.vue'
import BizEditCell from './features/bizEditCell.vue'
import BizTableOperatePopover from './features/bizTableOperatePopover.vue'

import { computed, nextTick, ref, watch } from 'vue';
import { Message } from 'element-ui';
import omit from 'lodash/omit';

import {
  usePagination,
  useCellHover,
  useRowBgColor,
  useDefaultOperation,
  useColumnHeaderOperation,
  useDragSort,
} from './bizHooks'

import { ITableDataItem, IColumnConfig, IDefaultOperationType, IColorList, SettingStorgeMigrationConfigs } from './types';

// defineProps泛型参数如果从外部传入，编译报错
interface IProps {
  /** 表格数据 */
  dataList: ITableDataItem[];
  /** 列配置 */
  columnConfig: IColumnConfig[];
  /** 是否展示展开行 */
  hasExpandRow?: boolean;
  /** 是否展示序号 */
  hasIndexColumn?: boolean;
  /** 是否展示选择列 */
  hasSelectionColumn?: boolean;
  /** 是否支持行拖拽 */
  rowDragAble?: boolean;
  /** 表格总条数 */
  total: number;
  /** 自定义列操作，当前支持行编辑(edit)，删除(delete)，置顶(top)  */
  defaultOperations?: IDefaultOperationType[];
  /** 行背景色列表 */
  colorList?: IColorList;
  /** 自定义颜色列的表头筛选 */
  colorFilterConfig?: IColumnConfig;
  /** 左侧固定列数 */
  leftFixedCount?: number;
  /** 性能优化参数，调整拖拽的范围 */
  dragSemiRange?: number;
  /** 是否显示加载 */
  loading?: boolean;
  /** 是否隐藏显示设置按钮 */
  hideViewSettingBtn?: boolean
  /** 设置的缓存的key */
  settingStorgeKey?: string
  /** 前端排序，默认关闭 */
  localSort?: boolean
  /** 前端过滤，默认关闭 */
  localFilter?: boolean
  /** 页码 */
  currentPage: number
  /** 每页条数初始值 */
  pageSize?: number
  pageSizes?: number[]
  /** 分页器展示数量 */
  pagerCount?: number;
  /** 设置的迁移配置 */
  settingStorgeMigrationConfigs?: SettingStorgeMigrationConfigs
}

interface IEmits {
  /** 行选中 */
  (e: 'selection-change', selection: any): void
  /** 修改行背景色 */
  (e: 'row-bg-change', param: {colorId: number; row: ITableDataItem; rowIndex: number}): void
  /** 行拖拽放置事件 */
  (e: 'row-drag-drop', param: { row: any; fromIndex: number; toIndex: number; page: number; size: number;}): void
  /** 行删除 */
  (e: 'row-delete', param: { row: any; index: number; page: number; size: number; }): void
  /** 点击编辑按钮立即触发 */
  (e: 'row-edit', param: { row: any, index: number; page: number; size: number;}): void
  /** 行置顶 */
  (e: 'row-pin-to-top', param: { row: any, rawIndex: number; page: number; size: number;}): void
  /** 行编辑保存 */
  (e: 'row-edit-save', param: { page: number; size: number; row: any; changedData: Record<string, any>; }): void
  /** 行编辑取消 */
  (e: 'row-edit-cancel', param: { row: any; page: number; size: number;}): void
  /** 页码改变 */
  (e: 'page-change', param: { page: number, size: number }): void
  /** 查询 */
  (e: 'search', param: Record<string, any>): void
  /** 排序 */
  (e: 'sort-change', param: { order: 'descending' | 'ascending' | null, prop: string }): void
  /** 双击某一行 */
  (e: 'row-dblclick', param: any): void
  /** 筛选条件打开 */
  (e: 'open-filter', prop: string): void
  /** 重置筛选 */
  (e: 'reset-filter', prop: string): void
  /** 筛选条件确认 */
  (e: 'confirm-filter', prop: string): void
}

const props = withDefaults(defineProps<IProps>(), {
  dataList: () => [],
  columnConfig: () => [],
  hasExpandRow: false,
  hasIndexColumn: false,
  hasSelectionColumn: false,
  rowDragAble: false,
  total: 0,
  defaultOperations: () => [],
  colorList: () => [],
  colorFilterConfig: undefined,
  leftFixedCount: 1,
  dragSemiRange: 15,
  loading: false,
  hideViewSettingBtn: false,
  settingStorgeKey: '',
  localSort: false,
  localFilter: false,
  currentPage: 1,
  pageSize: 10,
  pageSizes: () => [10, 15, 30, 60, 100],
  pagerCount: 11,
});

// 同defineProps一样，不支持泛型参数从外部导入
const emit = defineEmits<IEmits>();

const showingColumns = ref<string[]>([]); // 表格中实际展示的列
const leftFixedColumnCount = ref(0) // 左侧固定列数量
const viewSettingDragSortOptions = ref<IColumnConfig[]>([])

const returnAnyType = (params): any => {
  return params
}

const actualColumns = computed(() => {
  const res: IColumnConfig[] = [];
  let cnt = leftFixedColumnCount.value;

  // 列排序和列过滤
  for (const prop of showingColumns.value) {
    const rawItem = props.columnConfig.find(c => c.prop === prop) ?? {} as IColumnConfig;
    const item: IColumnConfig = {
      ...rawItem,
      isColumnSortable: !!rawItem.sortable,
      sortable: inSorting.value ? !!rawItem.sortable : false,
      _sortable: rawItem.sortable,
    };

    // leftFixedColumnCount 控制覆盖的列数，优先级高于列配置中的 fixed 属性
    if (cnt > 0) {
      item.fixed = 'left';
      // eslint-disable-next-line no-plusplus
      cnt--;
    }

    res.push(item);
  }

  // 使用默认操作项，添加默认操作列。该列在编辑模式下隐藏
  if (props.defaultOperations && props.defaultOperations.length > 0) {
    res.push({
      label: '操作',
      prop: '$$operation',
      minWidth: '100px',
      fixed: 'right'
    });
  }

  return res;
});

const tableDomRef = ref<any>(null);
const currScope = ref<any>(null);

/************ 分页相关 *************/
const beforePageChange = () => {
  // searchValue.value = {};
};
const {
  pageSize,
  handleCurrPageChange,
  handlePageSizeChange,
} = usePagination({
  emit,
  beforePageChange,
  initialPageSize: props.pageSize,
});

watch(() => props.pageSize, (val) => {
  if (val !== pageSize.value) {
    pageSize.value = val;
  }
});

/************ 表格单元格hover事件相关 ************ */
const {
  hoveringCellInfo,
  setCellClassName,
  debouncedHoverHandler
} = useCellHover(tableDomRef);

/************ 行背景色设置相关 ************ */
const {
  setRowStyle,
} = useRowBgColor({
  colorList: props.colorList || [],
  emit
});

/************ 默认的操作相关 ************ */
const {
  operationPopoverRef,
  editingRowData,
  editingRowIndex,
  handleDelete,
  handleEdit,
  handleEditSave,
  handleEditCancel,
  handleRowPinToTop,
  closeAllExpandedRows
} = useDefaultOperation({
  emit,
  tableDomRef,
  pageSize,
  props,
  hasExpandRow: props.hasExpandRow
});

/************ 列头部操作相关 ************ */
const {
  setSort,
  clearSort,
  setSearchParams,
  isColumnHeadActive,
  handleSort,
  handleHeaderPopoverShow,
  handleHeaderOperationConfirm,
  handleHeaderOperationReset,
  summaryList,
  tableSummaryMethod,
  filteredValue,
  showColumnHeadSortIcon,
  sortProp,
  tempFilteredValue,
  tempSummaryList,
  tempSortType,
  tempSortProp,
  isColumnFiltering,
  inSorting,
} = useColumnHeaderOperation({
  tableDomRef,
  props,
  emit,
  showingColumns,
});

/************ 表格行拖拽和显示设置列拖拽 ************ */
const beforeDragStart = () => {
  // 如果有列存在排序，不允许拖拽
  if (sortProp.value) {
    Message.warning('已有列正在排序，不允许拖拽。');
    return false;
  }

  // 如果有列存在筛选，不允许拖拽
  if (isColumnFiltering.value) {
    Message.warning('已有列正在筛选，不允许拖拽。');
    return false;
  }

  editingRowIndex.value = -1; // 关闭编辑状态

  return true;
};

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore ts-plugin存在间歇性抛出「类型实例化过深，且可能无限」的错误
useDragSort({
  emit,
  props,
  viewSettingDragSortOptions,
  beforeDragStart,
  pageSize,
  currScope,
  tableDomRef,
});

const doTableLayout = async () => {
  await nextTick();
  tableDomRef.value?.doLayout();
};

// 过滤出自定义属性，将其它属性全部透传给 el-table-column
const getColumnBindProps = (column: IColumnConfig) => {
  // 过滤掉自定义属性，只保留 el-table-column 支持的属性
  return omit(column, [
    'editType',
    'slotName',
    'inputType',
    'options',
    'filters'
  ]);
};

const setRowClassName = (scope) => {
  return `
    custom-row-classname
    custom-row-classname-${scope.rowIndex}
    ${scope.row.isPinned ? 'custom-row-classname-pinned' : ''}
  `;
};

const handleSelectionChange = (e) => {
  emit('selection-change', e);
};

const handleRowDblClick = (e) => {
  emit('row-dblclick', e);
};

const handleOpenFilter = (column: IColumnConfig) => {
  emit('open-filter', column.prop);
  nextTick(() => {
    handleHeaderPopoverShow(column)
  })
}

const handleResetFilter = (column: IColumnConfig) => {
  emit('reset-filter', column.prop);
  nextTick(() => {
    handleHeaderOperationReset(column)
  })
}

const handleConfirmFilter = (column: IColumnConfig) => {
  emit('confirm-filter', column.prop);
  nextTick(() => {
    handleHeaderOperationConfirm(column)
  })
}

const bizViewSettingDialogRef = ref(null as unknown as InstanceType<typeof BizViewSettingDialog>)
const handleViewSettingShow = () => bizViewSettingDialogRef.value.open()

defineExpose({
  closeAllExpandedRows,
  openViewSetting: handleViewSettingShow,
  elTableRef: tableDomRef,
  setSort,
  clearSort,
  setSearchParams,
});

// loading 结束和页码变化时滚动到顶部
watch([
  () => props.loading,
  () => props.currentPage,
  () => pageSize.value,
], ([loading]) => {
  if (loading) return;
  tableDomRef.value.$el.querySelector('.el-table__body-wrapper').scrollTop = 0;
});

</script>

<style lang="less">
@import './index.less';
</style>
