<template>
  <div class="comp-content comp-tree-table" @click="cancelEditFun">
    <TableSearch
      v-show="searchList.length > 0"
      ref="tableSearchRef"
      :searchItems="searchList"
      :searchLimitNum="searchLimitNum"
      :searchParams="searchParams"
      @search="searchFun"
      @reset="resetSearchFun"
      @change="changeFun"
      @load="searchLoadFun"
    >
      <template v-for="name in Object.keys($slots)" :key="name" #[name]="scope">
        <slot :name="name" v-bind="scope" />
      </template>
    </TableSearch>
    <div class="options">
      <div class="item">
        <div v-if="listTitle" class="list-title">{{ listTitle }}</div>
      </div>
      <div class="item">
        <div class="btns">
          <slot name="leftOptions" />
          <el-select
            v-if="showExpandSelect"
            v-model="expandLevel"
            class="expand-select"
            @visible-change="expandVisibleChangeFun"
            @change="expandLevelFun"
          >
            <el-option
              v-for="item in expandOptions"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
          <slot name="iconOptions" />
          <el-tooltip v-if="exportApi" :content="$t('表格.导出')" placement="top">
            <div v-if="exportApi" class="icon-btn" @click="exportFun">
              <el-icon :size="18">
                <Download />
              </el-icon>
            </div>
          </el-tooltip>
          <el-tooltip v-if="showImport" :content="$t('表格.导入')" placement="top">
            <div v-if="showImport" class="icon-btn" @click="formDiaVisible = true">
              <el-icon :size="18">
                <Upload />
              </el-icon>
            </div>
          </el-tooltip>
          <el-tooltip :content="$t('表格.列表字段设置')" placement="top">
            <div class="icon-btn" @click="formDiaVisible = true">
              <el-icon :size="18">
                <Setting />
              </el-icon>
            </div>
          </el-tooltip>
        </div>
      </div>
    </div>
    <div class="table">
      <vxe-table
        ref="vxeTableRef"
        :loading="loading"
        :data="formatData"
        height="100%"
        v-bind="$attrs"
        keepSource
        :seq-config="{ startIndex: (current - 1) * size }"
        :virtual-x-config="{
          enabled: true,
        }"
        :virtual-y-config="{
          enabled: true,
          gt: 20,
          oSize: 20,
        }"
        :tree-config="{
          transform: true,
          expandAll: true,
          rowField: 'fakeId',
          parentField: 'parentId',
          childrenField: 'children',
        }"
        :checkbox-config="{
          checkStrictly: true,
          showHeader: false,
          checkMethod: checkMethod,
        }"
        :column-config="{
          drag: true,
          resizable: true,
        }"
        :column-drag-config="{
          showIcon: false,
          trigger: 'cell',
        }"
      >
        <vxe-column
          v-if="showCheckbox"
          type="checkbox"
          width="60"
          align="left"
          header-align="left"
          fixed="left"
        ></vxe-column>
        <vxe-column
          v-if="showIndex"
          type="seq"
          width="80"
          align="left"
          header-align="left"
          fixed="left"
        />
        <vxe-column
          v-for="item in tableHeadVisible"
          :key="item.key"
          :field="item.key"
          :title="item.title"
          :min-width="item.width"
          align="left"
          header-align="left"
          :tree-node="item.key === 'nodeName'"
          :fixed="item.key === 'nodeName' ? 'left' : ''"
          show-overflow
        >
          <template #header="{ column }">
            <HeadSearch
              v-model:data="filterData"
              :column="column"
              @search="filterFun"
              @reset="filterResetFun"
            />
          </template>
          <template #default="{ row, column, $rowIndex }">
            <template v-if="$slots[`${item.key}-edit`] || $slots[item.key]">
              <template
                v-if="
                  $slots[item.key] &&
                  !(editData[$rowIndex] && editData[$rowIndex][`${item.key}-edit`])
                "
              >
                <span
                  class="table-cell"
                  :class="{ edit: !!$slots[`${item.key}-edit`] }"
                  @dblclick="dblclickFun($rowIndex, item, !!$slots[`${item.key}-edit`])"
                >
                  <slot :name="item.key" :row="row" :column="column" :icon="item.tableIcon" />
                </span>
              </template>
              <template
                v-if="
                  $slots[`${item.key}-edit`] &&
                  editData[$rowIndex] &&
                  editData[$rowIndex][`${item.key}-edit`]
                "
              >
                <span @click.stop>
                  <slot :name="`${item.key}-edit`" :row="row" :column="column" />
                </span>
              </template>
            </template>
            <template v-else>
              <span class="td-text">
                <span v-if="item.tableIcon" class="icon">
                  <el-icon :size="16">
                    <component :is="item.tableIcon" />
                  </el-icon>
                </span>
                {{ row[item.key] }}
              </span>
            </template>
          </template>
        </vxe-column>
        <vxe-column
          v-if="actionList.length > 0"
          title="操作"
          :width="actionAutoWidth"
          align="left"
          header-align="left"
          fixed="right"
        >
          <template #default="{ row, rowIndex }">
            <div class="actions">
              <template v-for="(action, aIndex) in actionList" :key="aIndex">
                <el-link
                  v-if="!(action.hide && action.hide(row)) && findVisibleIndex(row, action) <= 2"
                  class="action-item"
                  :type="action.type"
                  @click="actionClickFun(row, action, rowIndex)"
                >
                  {{ action.title }}
                </el-link>
              </template>
              <el-dropdown v-if="visibleNum(row) > 2">
                <el-icon class="more-icon">
                  <MoreFilled />
                </el-icon>
                <template #dropdown>
                  <el-dropdown-menu>
                    <template v-for="(action, aIndex) in actionList" :key="aIndex">
                      <el-dropdown-item
                        v-if="
                          !(action.hide && action.hide(row)) && findVisibleIndex(row, action) > 2
                        "
                        @click="actionClickFun(row, action, rowIndex)"
                      >
                        <el-link :type="action.type">{{ action.title }}</el-link>
                      </el-dropdown-item>
                    </template>
                  </el-dropdown-menu>
                </template>
              </el-dropdown>
            </div>
          </template>
        </vxe-column>
      </vxe-table>
    </div>
    <TableFormDia
      v-model="formDiaVisible"
      :head="tableHead"
      :name="tableName"
      @update="getHeadDataFun"
    />
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, watch, nextTick, inject, computed } from 'vue';
import { ElMessageBox } from 'element-plus';
import { Download, Upload, MoreFilled } from '@element-plus/icons-vue';
import type { TableHead } from './types';
import TableSearch from './tableSearch.vue';
import TableFormDia from './tableFormDia.vue';
import { getFormConfigureApi } from '@/api/system/systemData';
import { formOptionsFormat } from './lib';
import { uniqBy, cloneDeep } from 'lodash-es';
import { exportFile } from '@/utils/file';
import HeadSearch from './treeHeadSearch.vue';
import { flatNode, traverseTree } from '@/utils/node';
import { useI18n } from 'vue-i18n';

const emit = defineEmits([
  'searchChange',
  'load',
  'settingConfirm',
  'filterReset',
  'filterConfirm',
]);

interface Props {
  tableName: string;
  exportApi?: any;
  exportFileName?: string;
  exportParams?: any;
  params?: any;
  head?: any;
  searchItems?: any[];
  searchLimitNum?: number;
  showCheckbox?: boolean;
  showIndex?: boolean;
  searchParams?: any;
  showExport?: boolean;
  showImport?: boolean;
  data: any;
  editMode?: boolean;
  listTitle?: string;
  actionList?: any;
  showExpandSelect?: boolean; // 是否显示展开级别下拉框
}

const props = withDefaults(defineProps<Props>(), {
  tableName: '',
  exportApi: undefined,
  exportFileName: '',
  exportParams: {},
  params: () => {},
  head: null,
  searchItems: () => [] as any[],
  searchLimitNum: 0,
  showCheckbox: false,
  showIndex: false,
  searchParams: {},
  showExport: false,
  showImport: false,
  data: [],
  editMode: false,
  listTitle: '',
  actionList: [],
  showExpandSelect: true, // 默认显示展开级别下拉框
});
const { t } = useI18n();
const checkMethod: any = inject('checkMethod', () => {
  return true;
});

const tableData = ref<any[]>([]);
const tableHead = ref<TableHead[]>([]);
const tableHeadVisible = ref<TableHead[]>([]);
const searchData = ref<object>({});
const current = ref(1);
const size = ref(10);
const formDiaVisible = ref(false);
const loading = ref(false);
const vxeTableRef = ref<any>();
const tableSearchRef = ref<any>();
const searchList = ref<any>(props.searchItems);
const editRowIndex = ref(0);
const editRowKey = ref('');
const editData = ref<any>([]);
const formatData = ref<any>([]);
const filterData = ref<any>({});
let originData: any = [];
const actionAutoWidth = ref(95);

const expandLevel = ref<string>('all');
const maxTreeLevel = ref(1);
const expandOptions = computed(() => {
  const options: { label: string; value: string }[] = [
    { label: t('表格.全部展开'), value: 'all' },
    { label: t('表格.全部折叠'), value: 'collapse' },
  ];
  for (let i = 1; i <= maxTreeLevel.value; i++) {
    options.push({ label: t('表格.显示级', { level: i }), value: String(i) });
  }
  return options;
});

const findVisibleIndex = (row: any, action: any) => {
  let index = 0;
  props.actionList.some((item: any) => {
    if (!(item.hide && item.hide(row))) {
      index++;
    }
    if (action.title === item.title) {
      return true;
    }
  });
  return index;
};

const visibleNum = (row: any) => {
  // 展示按钮数量
  return props.actionList.filter((d: any) => !(d.hide && d.hide(row))).length;
};

watch(
  () => props.actionList,
  (list: any) => {
    let width = 20; // cell内边距
    list.some((item: any, index: number) => {
      if (index < 2) {
        width += item.title.length * 14; // 一个汉字14
        width += 8; // 内边距
      } else {
        return true;
      }
    });
    if (list.length > 2) {
      width += 30; // 更多宽度
    }
    actionAutoWidth.value = width;
  },
  { immediate: true }
);

watch(
  () => props.data,
  (data) => {
    data.forEach((item: any) => {
      for (const key in item) {
        if (key === 'extras' && item.extras) {
          item[key].forEach((val: any) => {
            item[val.propertyName] = val.propertyValue;
          });
        }
      }
    });
    formatData.value = data;
    originData = cloneDeep(props.data);
    nextTick(() => {
      calcMaxLevelFun();
    });
  },
  { immediate: true }
);

const calcMaxLevelFun = () => {
  if (!vxeTableRef.value) {
    return;
  }
  const { fullData } = vxeTableRef.value.getTableData();
  if (!fullData || fullData.length === 0) {
    maxTreeLevel.value = 1;
    return;
  }
  let maxLevel = 1;
  const levelMap = new Map<any, number>();
  traverseTree(
    fullData,
    (node: any, parent: any) => {
      const level = parent ? (levelMap.get(parent) || 1) + 1 : 1;
      levelMap.set(node, level);
      if (level > maxLevel) {
        maxLevel = level;
      }
      if (!node.parentId) {
        node.parentId = parent ? parent.fakeId : null;
      }
    },
    null
  );
  maxTreeLevel.value = maxLevel;
};

const expandVisibleChangeFun = (visible: boolean) => {
  if (visible) {
    calcMaxLevelFun();
  }
};

const collectRowsToExpand = (data: any[], targetLevel: number) => {
  const rowsToExpand: any[] = [];
  const levelMap = new Map<any, number>();
  if (!Array.isArray(data) || !targetLevel) {
    return rowsToExpand;
  }
  traverseTree(
    data,
    (node: any, parent: any) => {
      const nodeLevel = parent ? (levelMap.get(parent) || 1) + 1 : 1;
      levelMap.set(node, nodeLevel);
      if (nodeLevel < targetLevel) {
        rowsToExpand.push(node);
      }
    },
    null
  );
  return rowsToExpand;
};
const expandLevelFun = (level: string) => {
  if (!vxeTableRef.value) {
    return;
  }
  expandLevel.value = level;
  const { fullData = [], visibleData = [] } = vxeTableRef.value.getTableData();
  const flattedVisibleData = flatNode(visibleData);
  if (level === 'all') {
    vxeTableRef.value.setTreeExpand(flattedVisibleData, true);
  } else if (level === 'collapse') {
    vxeTableRef.value.setTreeExpand(flattedVisibleData, false);
  } else {
    const targetLevel = parseInt(level, 10);
    const rowsToExpand = collectRowsToExpand(fullData, targetLevel);
    vxeTableRef.value.setTreeExpand(flattedVisibleData, false);
    nextTick(() => {
      vxeTableRef.value.setTreeExpand(rowsToExpand, true);
    });
  }
};

watch(
  () => props.params,
  (val: any) => {
    resetSearchFun(val);
  },
  { deep: true }
);

watch(
  () => props.searchItems,
  (val) => {
    searchList.value = val;
  },
  { deep: true, immediate: true }
);

onMounted(() => {
  initFun();
});

const initFun = () => {
  current.value = 1;
  searchData.value = {};
  if (!props.head) {
    getHeadDataFun();
  }
};

// 获取表头数据
const getHeadDataFun = () => {
  const params = {
    formName: props.tableName,
  };
  tableHeadVisible.value = []; // vxe-table动态头部顺序改变时视图不更新，置空强制更新
  getFormConfigureApi(params).then((res: any) => {
    if (res.code === 200) {
      const formConfig = JSON.parse(res.data.formConfig);
      tableHead.value = formConfig;
      tableHeadVisible.value = formConfig.filter((item: any) => item.isShow);
      if (props.searchItems.length === 0) {
        // 没有传入搜索配置，则默认搜索配置
        const searchBuild: any[] = [];
        formConfig.forEach((item: any) => {
          if (item.tableSearch) {
            searchBuild.push(item);
          }
        });
        searchList.value = searchBuild;
        formOptionsFormat(searchList.value);
      }
      getTableDataFun();
    }
    emit('settingConfirm');
  });
};
// 获取表单数据
const getTableDataFun = () => {};
// 手动设置表单数据
const setDataFun = (data: any) => {
  tableData.value = data;
};
// 搜索
const searchFun = (data: object) => {
  current.value = 1;
  searchData.value = data;
  getTableDataFun();
};
// 重置搜索
const resetSearchFun = (data: object) => {
  current.value = 1;
  searchData.value = data;
  getTableDataFun();
};
// 重置表单
const resetFun = (reload?: boolean) => {
  if (reload) {
    current.value = 1;
  }
  getTableDataFun();
};
watch(
  () => props.head,
  (val) => {
    if (val) {
      tableHead.value = val;
      tableHeadVisible.value = val.filter((item: any) => item.isShow);
      resetSearchFun({});
    }
  },
  { deep: true, immediate: true }
);
// 获取搜索参数
const getSearchParamsFun = () => {
  if (tableSearchRef.value) {
    return tableSearchRef.value.getSearchParamsFun();
  }
};
// 设置搜索参数
const setSearchParamsFun = (data: any) => {
  if (tableSearchRef.value) {
    return tableSearchRef.value.setSearchParamsFun(data);
  }
};
// 通过key获取搜索参数
const getSearchParamByKeyFun = (key: string) => {
  if (tableSearchRef.value) {
    return tableSearchRef.value.getSearchParamByKeyFun(key);
  }
};
// 通过key设置搜索参数
const setSearchParamByKeyFun = (key: string, data: any) => {
  if (tableSearchRef.value) {
    return tableSearchRef.value.setSearchParamByKeyFun(key, data);
  }
};
// 搜索变化
const changeFun = (data: any) => {
  emit('searchChange', data);
};
// 重置筛选
const filterResetFun = (data: any) => {
  filterData.value[data.field] = '';
  filterFun(data, true);
};
// 筛选
const filterFun = (data: any, isReset: boolean = false) => {
  const idMap: any = {};
  const matchedIds = new Set<any>();
  originData.forEach((item: any) => {
    idMap[item.fakeId] = item;
  });
  originData.forEach((item: any) => {
    const marchList: any = [];
    let marchNum = 0;
    for (const key in filterData.value) {
      marchNum++;
      const oldData = String(item[key] || '');
      const matchData = filterData.value[key] || '';
      if (!matchData) {
        marchList.push(key);
      } else {
        if (oldData.indexOf(matchData) > -1) {
          marchList.push(key);
        }
      }
    }
    if (marchList.length === marchNum) {
      matchedIds.add(item.fakeId);
    }
  });
  const keepIds = new Set(matchedIds);
  matchedIds.forEach((fakeId: any) => {
    let currentId = fakeId;
    while (currentId != null) {
      const currentItem = idMap[currentId];
      if (!currentItem) break;
      const parentId = currentItem.parentId;
      if (parentId == null || parentId === 0 || parentId === -1) {
        keepIds.add(currentId);
        break;
      }
      keepIds.add(currentId);
      currentId = parentId;
    }
  });
  formatData.value = originData.filter((item: any) => keepIds.has(item.fakeId));
  nextTick(() => {
    vxeTableRef.value.setAllTreeExpand(true);
  });
  if (isReset) {
    emit('filterReset', { filterData: filterData.value, formatData: formatData.value });
  } else {
    emit('filterConfirm', { filterData: filterData.value, formatData: formatData.value });
  }
};
// 双击编辑
const dblclickFun = (rowIndex: any, item: any, hasEdit: boolean) => {
  if (!props.editMode || !hasEdit) {
    return;
  }
  editRowIndex.value = rowIndex;
  editRowKey.value = item.key;
  if (!editData.value[rowIndex]) {
    editData.value[rowIndex] = {};
  }
  editData.value[rowIndex][`${item.key}-edit`] = true;
};
// 关闭编辑
const cancelEditFun = () => {
  if (editData.value[editRowIndex.value]) {
    editData.value[editRowIndex.value][`${editRowKey.value}-edit`] = false;
  }
};
const setOptionsFun = (key: string, options: any[]) => {
  if (tableSearchRef.value) {
    return tableSearchRef.value.setOptionsFun(key, options);
  }
};
const searchLoadFun = () => {
  emit('load');
};
const getCheckboxRecordsFun = (isFull: boolean) => {
  if (vxeTableRef.value) {
    return vxeTableRef.value.getCheckboxRecords(isFull);
  }
};
const removeFun = (data: any) => {
  if (vxeTableRef.value) {
    return vxeTableRef.value.remove(data);
  }
};
const editRecords = ref<any>({
  insertRecords: [],
  removeRecords: [],
  updateRecords: [],
});
// 获取表格增删改的行
const getRecordSetFun = () => {
  const { insertRecords, removeRecords, updateRecords } = vxeTableRef.value?.getRecordset();

  return {
    insertRecords: uniqBy([...insertRecords, ...editRecords.value.insertRecords], 'fakeId'),
    removeRecords: uniqBy([...removeRecords, ...editRecords.value.removeRecords], 'fakeId'),
    updateRecords: uniqBy([...updateRecords, ...editRecords.value.updateRecords], 'fakeId'),
  };
};

const actionClickFun = (row: any, action: any, index: number) => {
  const { click, needConfirm, confirmTip } = action;
  if (click) {
    if (needConfirm) {
      ElMessageBox.confirm(confirmTip || '确定操作吗？', '提示', {
        type: 'warning',
      })
        .then(() => {
          click(row, index);
        })
        .catch(() => {});
    } else {
      click(row, index);
    }
  }
};

const exportFun = () => {
  exportFile(props.exportApi, props.tableName, props.exportFileName, props.exportParams);
};

watch(
  () => props.tableName,
  () => {
    initFun();
  }
);

defineExpose({
  tableData,
  treeTableRef: vxeTableRef,
  resetFun,
  setDataFun,
  getSearchParamsFun,
  getSearchParamByKeyFun,
  setSearchParamsFun,
  setSearchParamByKeyFun,
  setOptionsFun,
  getCheckboxRecordsFun,
  removeFun,
  getRecordSetFun,
  cancelEditFun,
});
</script>

<style lang="scss" scoped>
.comp-tree-table {
  width: 100%;
  height: 100%;
  .td-text {
    .icon {
      margin-right: 2px;
      .el-icon {
        transform: translateY(2px);
      }
    }
  }
  .options {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 10px;
    .item {
      display: flex;
      align-items: center;
      .list-title {
        font-size: 16px;
        color: var(--el-text-color-primary);
        font-weight: bold;
      }
      .btns {
        display: flex;
        align-items: center;
        padding: 0 var(--margin-small);
        .expand-select {
          width: 110px;
          margin-left: var(--margin-small);
        }
        .icon-btn {
          display: flex;
          align-items: center;
          justify-content: center;
          width: 24px;
          height: 24px;
          margin: 4px 0 4px 10px;
          border-radius: 4px;
          color: var(--el-text-color-secondary);
          cursor: pointer;
          &:hover {
            background-color: var(--el-border-color-light);
          }
        }
      }
    }
  }
  .actions {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    &::after {
      content: '';
      position: absolute;
      top: 0;
      bottom: 0;
      left: 0;
      width: 1px;
      background-color: var(--el-text-color-primary);
      filter: blur(5px);
    }
    .action-item {
      padding: 0 4px;
    }
    .more-icon {
      margin-left: 10px;
    }
  }
  .table {
    width: 100%;
    height: calc(100% - 32px - 10px);
    .table-cell {
      &.edit {
        cursor: pointer;
      }
    }
  }
}
</style>
