<script lang="ts" setup>
import { get } from "@/common/http/request";

interface TreeNode {
  id: number | string;
  label: string;
  children?: TreeNode[];
}

const props = defineProps({
  url: String,
  data: Array as () => TreeNode[],
  placeholder: String,
  nodeKey: { type: String, default: "id" },
  labelKey: { type: String, default: "label" },
  childrenKey: { type: String, default: "children" },
  clearable: { type: Boolean, default: true },
  disabled: { type: Boolean, default: false },
});

const modelValue = defineModel<number | string | undefined>({ required: true });
const treeData = ref<TreeNode[]>([]);
const treeSelectRef = ref();
const popoverVisible = ref(false);
const selectedLabel = ref("");

const defaultProps = {
  children: props.childrenKey,
  label: props.labelKey,
};

const loadData = async () => {
  if (props.url) {
    const rawData = await get(props.url).then(res => res.data);
    treeData.value = rawData || [];
  } else if (props.data) {
    treeData.value = props.data;
  }
};

const findNodeLabel = (nodes: TreeNode[], targetId: number | string): string => {
  for (const node of nodes) {
    if (node[props.nodeKey as keyof TreeNode] === targetId) {
      return node[props.labelKey as keyof TreeNode] as string;
    }
    if (node.children && node.children.length > 0) {
      const label = findNodeLabel(node.children, targetId);
      if (label) return label;
    }
  }
  return "";
};

const handleNodeClick = (node: TreeNode) => {
  modelValue.value = node[props.nodeKey as keyof TreeNode] as number | string;
  selectedLabel.value = node[props.labelKey as keyof TreeNode] as string;
  popoverVisible.value = false;
};

const handleClear = () => {
  modelValue.value = undefined;
  selectedLabel.value = "";
};

watch(
  () => modelValue.value,
  newVal => {
    if (newVal !== undefined && newVal !== null) {
      selectedLabel.value = findNodeLabel(treeData.value, newVal);
    } else {
      selectedLabel.value = "";
    }
  },
  { immediate: true }
);

watch(
  () => treeData.value,
  () => {
    if (modelValue.value !== undefined && modelValue.value !== null) {
      selectedLabel.value = findNodeLabel(treeData.value, modelValue.value);
    }
  }
);

onMounted(loadData);

defineExpose({
  reload: loadData,
});
</script>

<template>
  <el-popover
    ref="treeSelectRef"
    v-model:visible="popoverVisible"
    placement="bottom-start"
    :width="200"
    trigger="click"
    :disabled="disabled"
    popper-class="tree-select-popover"
  >
    <template #reference>
      <el-input
        v-model="selectedLabel"
        readonly
        :placeholder="placeholder"
        :disabled="disabled"
        :clearable="clearable"
        @clear="handleClear"
        class="tree-select-input"
      >
        <template #suffix>
          <el-icon class="el-input__icon">
            <arrow-down :class="{ 'is-reverse': popoverVisible }" />
          </el-icon>
        </template>
      </el-input>
    </template>
    <div class="tree-select-dropdown">
      <el-tree
        :data="treeData"
        :props="defaultProps"
        :node-key="nodeKey"
        default-expand-all
        highlight-current
        :current-node-key="modelValue"
        @node-click="handleNodeClick"
      />
    </div>
  </el-popover>
</template>

<style scoped>
.tree-select-input {
  width: 100%;
}

.tree-select-input :deep(.el-input__inner) {
  cursor: pointer;
}

.tree-select-input :deep(.el-input__icon) {
  transition: transform 0.3s;
}

.tree-select-input :deep(.el-input__icon.is-reverse) {
  transform: rotate(180deg);
}

.tree-select-dropdown {
  max-height: 300px;
  overflow-y: auto;
}
</style>
