﻿<template>
  <div class="table-layout-container">
    <div class="card-content-wrapper">
      <div
        v-if="showSideQuery"
        class="side-query-wrapper"
        :class="{ collapsed: sideQueryCollapsed }"
      >
        <div v-if="!sideQueryCollapsed" class="side-query-header">
          <el-input v-model="sideSearchKeyword" placeholder="搜索树节点" clearable size="small">
            <template #prefix>
              <el-icon><Search /></el-icon>
            </template>
          </el-input>
        </div>
        <div v-if="!sideQueryCollapsed" class="side-query-content">
          <el-tree
            ref="treeRef"
            :data="treeDataWithAll"
            :props="defaultProps"
            :node-key="treeNodeKey"
            :expand-on-click-node="false"
            default-expand-all
            highlight-current
            @node-click="handleNodeClick"
            @current-change="handleCurrentChange"
          ></el-tree>
        </div>
      </div>

      <div v-if="showSideQuery" class="collapse-divider">
        <el-button
          circle
          size="small"
          class="collapse-btn"
          @click="sideQueryCollapsed = !sideQueryCollapsed"
        >
          <el-icon>
            <ArrowRight v-if="sideQueryCollapsed" />
            <ArrowLeft v-else />
          </el-icon>
        </el-button>
      </div>

      <!-- 主内容区域 -->
      <div
        class="main-content-wrapper"
        :class="{ 'with-side-query': showSideQuery && !sideQueryCollapsed }"
      >
        <Filter
          v-if="showTopQuery"
          ref="queryFormComponentRef"
          :query-params="queryParams"
          :form-items="formItems"
          :show-default-buttons="showDefaultButtons"
          @query="handleQuery"
          @reset="resetQuery"
        >
          <template
            v-for="item in customFormItems"
            :key="item.prop"
            v-slot:[item.slotName]="slotProps"
          >
            <slot :name="item.slotName" :item="slotProps.item" :queryParams="props.queryParams" />
          </template>
          <template #default="{ queryParams, handleQuery, resetQuery }">
            <slot
              name="topQuery"
              :queryParams="queryParams"
              :handleQuery="handleQuery"
              :resetQuery="resetQuery"
            />
          </template>
        </Filter>

        <!-- 操作按钮区域 -->
        <div class="table-operation-bar">
          <slot name="operations" />
        </div>

        <!-- 表格区域 -->
        <Table
          :table-data="tableData"
          :loading="loading"
          :border="border"
          :stripe="stripe"
          :size="size"
          :table-height="tableHeight"
          :max-height="maxHeight"
          :row-key="rowKey"
          :highlight-current-row="highlightCurrentRow"
          :table-columns="tableColumns"
          :show-pagination="showPagination"
          :total="total"
          :page-no="props.queryParams.pageNo"
          :page-size="props.queryParams.pageSize"
          @row-click="handleRowClick"
          @checkbox-change="handleSelectionChange"
          @sort-change="handleSortChange"
          @pagination="handlePagination"
        >
          <template v-for="(_, slotName) in $slots" :key="slotName" #[slotName]="slotProps">
            <slot :name="slotName" v-bind="slotProps" />
          </template>
        </Table>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import {computed, ref} from 'vue';
import Filter from './Filter.vue';
import Table from './Table.vue';
import type {TableLayoutProps, TreeNodeData} from '../types/TableLayout.d';
import type {FilterExpose} from '../types/Filter.d';

defineOptions({
  name: 'TableLayout',
});

const props = withDefaults(defineProps<TableLayoutProps>(), {
  tableData: () => [],
  loading: false,
  total: 0,
  queryParams: () => ({
    pageNo: 1,
    pageSize: 20,
  }),
  sideQueryParams: () => ({}),
  formItems: () => [],
  showTopQuery: true,
  showSideQuery: false,
  showPagination: true,
  showDefaultButtons: true,
  sideWidth: 6,
  border: true,
  stripe: false,
  size: 'default',
  highlightCurrentRow: false,
  siderData: () => [],
  treeNodeKey: 'id',
  tableColumns: () => [],
});

const emit = defineEmits<{
  query: [queryParams: Record<string, any>];
  reset: [];
  pagination: [pagination: { page: number; limit: number }];
  'row-click': [row: Record<string, any>, column: any, event: Event];
  'selection-change': [selection: Record<string, any>[]];
  'sort-change': [sortInfo: { column: any; prop: string; order: string }];
  'side-query': [node: TreeNodeData];
  'reset-side-query': [];
}>();

const queryFormRef = ref<InstanceType<typeof import('element-plus').ElForm> | null>(null);
const queryFormComponentRef = ref<FilterExpose | null>(null);
const sideSearchKeyword = ref<string>('');
const treeRef = ref<InstanceType<typeof import('element-plus').ElTree> | null>(null);
const currentTreeNode = ref<TreeNodeData | null>(null);
const sideQueryCollapsed = ref<boolean>(false);

const customFormItems = computed(() => {
  return props.formItems
    .filter((item) => item.type === 'custom')
    .map((item) => ({
      ...item,
      slotName: item.slot || item.prop,
    }));
});

const defaultProps = {
  children: 'children',
  label: 'label',
};

const filteredSiderData = computed(() => {
  if (!sideSearchKeyword.value || !props.siderData || props.siderData.length === 0) {
    return props.siderData;
  }

  const keyword = sideSearchKeyword.value.toLowerCase();

  const filterTree = (nodes: TreeNodeData[]): TreeNodeData[] => {
    if (!nodes || nodes.length === 0) return [];

    return nodes
      .map((node: TreeNodeData) => {
        const label = (node[defaultProps.label] || '').toLowerCase();
        const match = label.includes(keyword);
        const children = node[defaultProps.children];

        let filteredChildren: TreeNodeData[] | null = null;
        if (children && children.length > 0) {
          filteredChildren = filterTree(children);
        }

        if (match || (filteredChildren && filteredChildren.length > 0)) {
          return {
            ...node,
            [defaultProps.children]: filteredChildren,
          };
        }

        return null;
      })
      .filter(Boolean) as TreeNodeData[];
  };

  return filterTree(props.siderData);
});

const treeDataWithAll = computed(() => {
  const children = filteredSiderData.value || [];
  return [
    {
      [props.treeNodeKey]: '__ALL__',
      [defaultProps.label]: '全部',
      [defaultProps.children]: children || [],
    },
  ];
});

const handleQuery = () => {
  props.queryParams.pageNo = 1;
  emit('query', props.queryParams);
  if (currentTreeNode.value) {
    emit('side-query', currentTreeNode.value);
  }
};

const handleNodeClick = (data: TreeNodeData, node: any) => {
  currentTreeNode.value = data;
  if (treeRef.value && data && data[props.treeNodeKey]) {
    treeRef.value.setCurrentKey(data[props.treeNodeKey]);
  }
  handleQuery();
};

const handleCurrentChange = (data: TreeNodeData, node: any) => {
  currentTreeNode.value = data;
};

const resetQuery = () => {
  if (queryFormComponentRef.value?.queryFormRef) {
    queryFormComponentRef.value.queryFormRef.resetFields();
  }
  if (props.queryParams) {
    Object.keys(props.queryParams).forEach((key) => {
      if (key !== 'pageNo' && key !== 'pageSize') {
        if (Array.isArray(props.queryParams[key])) {
          props.queryParams[key] = [];
        } else if (typeof props.queryParams[key] === 'object' && props.queryParams[key] !== null) {
          props.queryParams[key] = null;
        } else {
          props.queryParams[key] = '';
        }
      }
    });
    if (Object.prototype.hasOwnProperty.call(props.queryParams, 'pageNo')) {
      props.queryParams.pageNo = 1;
    }
  }
  emit('reset');
  handleQuery();
};

const handlePagination = (pagination) => {
  if (props.queryParams) {
    props.queryParams.pageNo = pagination.page;
    props.queryParams.pageSize = pagination.limit;
  }
  emit('pagination', pagination);
  emit('query', props.queryParams);
  if (currentTreeNode.value) {
    emit('side-query', currentTreeNode.value);
  }
};

const handleRowClick = (row, column, event) => {
  emit('row-click', row, column, event);
};

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

const handleSortChange = ({ column, prop, order }) => {
  emit('sort-change', { column, prop, order });
};

defineExpose({
  queryFormRef: computed(() => queryFormComponentRef.value?.queryFormRef),
  handleQuery,
  resetQuery,
});
</script>

<style scoped lang="scss">
.table-layout-container {
  height: 100%;
  padding: 8px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  box-sizing: border-box;

  .main-content-card {
    height: 100%;
    width: 100%;
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: visible;

    :deep(.el-card__body) {
      flex: 1;
      display: flex;
      flex-direction: column;
      padding: 16px 16px 8px 16px;
      min-height: 0;
      overflow: visible;
    }
  }

  .card-content-wrapper {
    flex: 1;
    display: flex;
    gap: 0;
    min-height: 0;
    position: relative;
  }

  .collapse-divider {
    flex-shrink: 0;
    width: 1px;
    background-color: #ebeef5;
    position: relative;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    margin: 0 12px;

    .collapse-btn {
      position: absolute;
      left: 50%;
      top: 18px;
      transform: translateX(-50%);
      background-color: #fff;
      border: 1px solid #ebeef5;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
      z-index: 10;
      width: 24px;
      height: 24px;

      &:hover {
        background-color: #3B82F6;
        color: #fff;
        border-color: #3B82F6;
      }
    }
  }

  .side-query-wrapper {
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    transition: width 0.3s, opacity 0.3s;
    overflow: hidden;

    &.collapsed {
      width: 0;
      opacity: 0;
      padding: 0;
      border: none;
    }

    .side-query-header {
      flex-shrink: 0;
      margin-bottom: 12px;
      display: flex;
      align-items: center;
    }

    .side-query-content {
      flex: 1;
      min-height: 0;
      overflow-y: auto;

      :deep(.el-tree--highlight-current) {
        background-color: #fff !important;
      }
    }
  }

  .main-content-wrapper {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-height: 0;
    min-width: 0;
    overflow: hidden;
  }

  .table-operation-bar {
    flex-shrink: 0;
    margin-bottom: 8px;
  }
}
</style>
