<script setup lang="ts">
import type { TreeV2Instance } from 'element-plus';

import { nextTick, onMounted, ref, watch } from 'vue';

import { ElMessage, ElTreeV2 } from 'element-plus';

import { api } from '#/api/request';
import { $t } from '#/locales';
// 定义组件属性
const props = defineProps({
  // 直接传入树数据
  data: {
    type: Array<any>,
    default: undefined,
  },
  // 获取树数据的URL
  dataUrl: {
    type: String,
    default: '',
  },
  // 已选择的节点数组（支持双向绑定）
  modelValue: {
    type: Array<any>,
    default: () => [],
  },
  // 节点键名
  nodeKey: {
    type: String,
    default: 'id',
  },
  // 树形配置
  defaultProps: {
    type: Object,
    default: () => ({
      children: 'children',
      label: 'label',
    }),
  },
  // 是否默认展开所有节点
  defaultExpandAll: {
    type: Boolean,
    default: false,
  },
  // 是否严格遵循父子不互相关联
  checkStrictly: {
    type: Boolean,
    default: false,
  },
  // 树的高度
  height: {
    type: Number,
    default: 200,
  },
  disabled: {
    type: Boolean,
    default: false,
  },
  // 是否显示子节点数量
  showCount: {
    type: Boolean,
    default: true,
  },
  // 是否显示已选择区域
  showSelected: {
    type: Boolean,
    default: true,
  },
  // 加载提示文本
  loadingText: {
    type: String,
    default: $t('message.loading'),
  },
});

// 定义事件
const emit = defineEmits(['update:modelValue', 'change', 'check']);

// 响应式数据
const treeData = ref<any[]>([]);
const loading = ref(false);
const treeRef = ref<TreeV2Instance>();
const nodeMap = ref<Map<any, any>>(new Map()); // 用于存储节点键到节点数据的映射

watch(
  [() => props.modelValue, () => treeData.value],
  ([newVal, treeDataVal]) => {
    const value = newVal || [];
    if (treeDataVal && treeDataVal.length > 0) {
      nextTick(() => {
        if (treeRef.value) {
          treeRef.value.setCheckedKeys(value);
        }
      });
    }
  },
  { immediate: true, deep: true },
);

// 过滤节点方法
const filterNode = (value: any, data: any) => {
  if (!value) return true;
  return data[props.defaultProps.label]?.toString().includes(value);
};

// 处理节点选择变化
const handleCheck = (_: any, checkedInfo: any) => {
  const checkedKeys = checkedInfo.checkedKeys;
  emit('update:modelValue', checkedKeys);
  emit('change', checkedKeys);
  emit('check', {
    checkedNodes: checkedInfo.checkedNodes,
    checkedKeys,
    halfCheckedNodes: checkedInfo.halfCheckedNodes,
    halfCheckedKeys: checkedInfo.halfCheckedKeys,
  });
};

// 构建节点映射
const buildNodeMap = (nodes: any) => {
  nodes.forEach((node: any) => {
    nodeMap.value.set(node[props.nodeKey], node);
    if (node[props.defaultProps.children]) {
      buildNodeMap(node[props.defaultProps.children]);
    }
  });
};

function applyTreeData(data: any[]) {
  treeData.value = Array.isArray(data) ? data : [];
  nodeMap.value.clear();
  buildNodeMap(treeData.value);

  if (props.modelValue && props.modelValue.length > 0) {
    nextTick(() => {
      if (treeRef.value) {
        treeRef.value.setCheckedKeys(props.modelValue);
      }
    });
  }
}

watch(
  () => props.data,
  (newData) => {
    if (newData !== undefined) {
      applyTreeData(newData || []);
    }
  },
  { immediate: true, deep: true },
);

// 获取树数据
const fetchTreeData = async () => {
  if (props.data !== undefined || !props.dataUrl) return;

  loading.value = true;
  try {
    const res = await api.get(props.dataUrl);
    applyTreeData(res.data);
  } catch (error) {
    console.error('get data error:', error);
    ElMessage.error($t('message.getDataError'));
  } finally {
    loading.value = false;
  }
};

// 获取当前选中的节点
const getCheckedNodes = () => {
  return treeRef.value?.getCheckedNodes() || [];
};

// 获取当前选中的叶子节点
const getCheckedLeafNodes = () => {
  return treeRef.value?.getCheckedNodes(true) || [];
};

// 获取半选中的节点
const getHalfCheckedNodes = () => {
  return treeRef.value?.getHalfCheckedNodes() || [];
};

// 清空选择
const clearChecked = () => {
  treeRef.value?.setCheckedKeys([]);
};

// 设置选中节点
const setCheckedKeys = (keys: any) => {
  treeRef.value?.setCheckedKeys(keys);
};

// 根据键值获取节点数据
const getNodeByKey = (key: any) => {
  return nodeMap.value.get(key);
};

// 暴露方法给父组件
defineExpose({
  getCheckedNodes,
  getCheckedLeafNodes,
  getHalfCheckedNodes,
  clearChecked,
  setCheckedKeys,
  getNodeByKey,
  refresh: fetchTreeData,
});

// 组件挂载时获取数据
onMounted(() => {
  if (props.data === undefined) {
    fetchTreeData();
  }
});
</script>

<template>
  <div class="tree-select" :class="{ 'tree-select--disabled': disabled }">
    <div class="tree-wrapper">
      <ElTreeV2
        ref="treeRef"
        :data="treeData"
        :props="defaultProps"
        :node-key="nodeKey"
        :default-expand-all="defaultExpandAll"
        :filter-node-method="filterNode"
        :highlight-current="true"
        show-checkbox
        :check-strictly="checkStrictly"
        :height="height"
        @check="handleCheck"
        v-loading="loading"
        :element-loading-text="loadingText"
      >
        <template #default="{ node, data: itemData }">
          <span class="tree-node">
            <span class="node-label">{{ $t(node.label) }}</span>
            <span
              v-if="showCount && itemData[defaultProps.children]"
              class="node-count"
            >
              ({{ itemData[defaultProps.children].length }})
            </span>
          </span>
        </template>
      </ElTreeV2>
    </div>
  </div>
</template>

<style scoped>
.tree-select {
  width: 100%;
  overflow: hidden;
  background: var(--el-bg-color);
  background: linear-gradient(
    180deg,
    color-mix(in srgb, var(--el-color-primary-light-9) 32%, var(--el-bg-color))
      0%,
    var(--el-bg-color) 72%
  );
  border: 1px solid var(--el-border-color-lighter);
  border-radius: 14px;
  box-shadow: var(--el-box-shadow-lighter);
}

.tree-wrapper {
  padding: 10px;
}

.tree-select--disabled {
  background: linear-gradient(
    180deg,
    var(--el-fill-color-extra-light) 0%,
    var(--el-fill-color-blank) 72%
  );
}

.tree-node {
  display: flex;
  gap: 8px;
  align-items: center;
  min-width: 0;
}

.node-label {
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 14px;
  font-weight: 500;
  color: var(--el-text-color-primary);
  white-space: nowrap;
}

.node-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  height: 20px;
  padding: 0 6px;
  font-size: 12px;
  line-height: 1;
  color: var(--el-text-color-secondary);
  background: var(--el-fill-color);
  border-radius: 999px;
}

:deep(.el-tree) {
  color: inherit;
  background: transparent;
}

:deep(.el-tree-node__content) {
  height: 38px;
  padding: 0 10px;
  margin-bottom: 4px;
  border-radius: 10px;
  transition:
    background-color 0.18s ease,
    color 0.18s ease;
}

:deep(.el-tree-node__content:hover) {
  background: var(--el-fill-color-light);
}

:deep(.el-tree-node:focus > .el-tree-node__content) {
  background: var(--el-color-primary-light-9);
}

:deep(.el-tree-node.is-current > .el-tree-node__content) {
  background: var(--el-color-primary-light-8);
  background: color-mix(
    in srgb,
    var(--el-color-primary-light-8) 70%,
    var(--el-bg-color)
  );
}

:deep(.el-tree-node__expand-icon) {
  color: var(--el-text-color-secondary);
}

:deep(.el-checkbox) {
  margin-right: 8px;
}

:deep(.el-tree-node__children) {
  overflow: visible;
}

:deep(.el-tree-node__children .el-tree-node__content) {
  padding-left: 12px;
}

:deep(.el-scrollbar__bar.is-vertical) {
  width: 8px;
}

:deep(.el-scrollbar__thumb) {
  background: var(--el-text-color-placeholder);
  background: color-mix(
    in srgb,
    var(--el-text-color-secondary) 22%,
    transparent
  );
  border-radius: 999px;
}

.tree-select--disabled :deep(.el-tree-node__content) {
  cursor: not-allowed;
  opacity: 0.72;
}

.tree-select--disabled :deep(.el-checkbox),
.tree-select--disabled :deep(.el-tree-node__expand-icon),
.tree-select--disabled :deep(.el-tree-node__content) {
  pointer-events: none;
}
</style>
