<template>
  <div class="db-tree-container">
    <ElInput
      v-if="isSearchEnabled"
      v-model="query"
      style="width: 100%"
      placeholder="请输入关键字"
      @input="onQueryChanged"
    />
    <ElTreeV2
      class="treeLine-2"
      :indent="0"
      ref="treeRef"
      style="max-width: 600px"
      :data="data"
      :props="props"
      :filter-method="filterMethod"
      :default-expanded-keys="defaultExpandedKeys"
      :height="treeHeight"
      @node-expand="onNodeExpand"
      @node-collapse="onNodeCollapse"
      @contextmenu="openBlankContextMenu"
      highlight-current
      scrollbar-always-on
    >
      <template #default="{ node, data: nodeData }">
        <!-- 根据层级生成占位符，level 1 不需要占位 -->
        <span v-for="i in (node.level - 1)" :key="i" class="node_nestingLevel_empty"></span>
        <div class="node-content-wrapper" :style="{ paddingLeft: node.isLeaf ? '0px' : '11px' }">
          <span class="node-icon-wrapper">
            <IconifyIcon 
              v-if="!node.isLeaf" 
              :icon="node.expanded ? 'ep:remove' : 'ep:circle-plus'" 
              class="custom-expand-icon"
            />
          </span>
          <template v-if="editingId === nodeData.id">
            <ElInput
              :id="`edit-${nodeData.id}`"
              v-model="editingLabel"
              @blur="saveEdit"
              @keydown.enter.prevent="saveEdit"
              @keydown.esc.prevent="cancelEdit"
              @click.stop
              size="small"
              class="node-edit-input"
            />
          </template>
          <template v-else>
            <span
              class="node-label"
              :style="getNodeStyles(nodeData)"
              draggable="true"
              @dragstart="onDragStart(nodeData, $event)"
              @dragover.prevent="onDragOver(nodeData, $event)"
              @drop.prevent="onDrop(nodeData, $event)"
              @dragend="onDragEnd"
              @contextmenu.stop="(e) => openContextMenu(e, nodeData)"
              @click="onNodeSingleClick(nodeData, $event)"
              @dblclick.stop="onNodeDblClick(nodeData, $event)"
            >
              {{ nodeData.label }}
            </span>
          </template>
        </div>
      </template>
    </ElTreeV2>
    <ElDropdown
      ref="dropdownRef"
      :virtual-ref="triggerRef"
      :show-arrow="false"
      virtual-triggering
      trigger="contextmenu"
      placement="bottom-start"
      @command="onGlobalCommand"
    >
      <template #dropdown>
        <ElDropdownMenu>
          <ElDropdownItem
            v-for="item in getMenuItems()"
            :key="item.key"
            :command="item.key"
            :disabled="item.disabled"
          >
            {{ item.text }}
          </ElDropdownItem>
        </ElDropdownMenu>
      </template>
    </ElDropdown>
  </div>
</template>

<script lang="ts" setup>
import { ref, nextTick, computed, watch } from 'vue'
import { ElTreeV2, ElDropdown, ElDropdownMenu, ElDropdownItem, ElInput } from 'element-plus'
import type { TreeNodeData, TreeV2Instance } from 'element-plus'
import { IconifyIcon } from '@vben/icons'
import { useContextMenu } from './contextMenu'
import type { ContextMenuHandler, HierarchyConfig } from './contextMenu'
import { useInlineEdit } from './inlineEdit'
import { useDragAndDrop } from './draggable'

defineOptions({ name: 'DbTree' });
const componentProps = defineProps<{ height?: number; data?: Tree[]; defaultExpandedKeys?: number | string | string[]; contextMenuHandler?: ContextMenuHandler<Tree> | HierarchyConfig; search?: boolean }>()
const emit = defineEmits<{ (e: 'select', node: Tree): void }>()

interface Tree {
  id: string
  label: string
  children?: Tree[]
}

const query = ref('')
const treeRef = ref<TreeV2Instance>()
const data = ref<Tree[]>(componentProps.data ?? [])
const expandedKeys = ref<string[]>([])

watch(
  () => componentProps.data,
  (val) => { data.value = Array.isArray(val) ? [...val] : [] }
)

const treeHeight = computed(() => componentProps.height ?? 400)
const isSearchEnabled = computed(() => componentProps.search ?? true)
const props = {
  value: 'id',
  label: 'label',
  children: 'children',
}

//默认展开层级
const defaultExpandedKeys = computed(() => {
  const param = componentProps.defaultExpandedKeys
  if (param === undefined) return []
  if (Array.isArray(param)) return param
  let level = 0
  if (typeof param === 'number') {
    level = Number.isFinite(param) && param > 0 ? Math.floor(param) : 0
  } else if (typeof param === 'string') {
    const n = parseInt(param, 10)
    level = Number.isFinite(n) && n > 0 ? Math.floor(n) : 0
  }
  if (level <= 0) return []
  const keys: string[] = []
  const stack: Array<{ node: Tree; depth: number }> = []
  for (const n of data.value) stack.push({ node: n, depth: 1 })
  while (stack.length) {
    const { node, depth } = stack.pop()!
    const children = node.children ?? []
    if (children.length && depth <= level) keys.push(node.id)
    for (const c of children) stack.push({ node: c, depth: depth + 1 })
  }
  return Array.from(new Set(keys))
})

watch(
  [data, () => componentProps.defaultExpandedKeys],
  () => {
    const param = componentProps.defaultExpandedKeys
    if (param === undefined) return
    const keys = defaultExpandedKeys.value
    expandedKeys.value = [...keys]
    nextTick(() => {
      treeRef.value?.setExpandedKeys(expandedKeys.value)
    })
  },
  { immediate: true }
)

const onQueryChanged = (query: string) => {
  treeRef.value!.filter(query)
}
const filterMethod = (query: string, node: TreeNodeData) =>
  node.label!.includes(query)

type LocateResult = { node: Tree; parent: Tree | null; container: Tree[]; index: number }

const locate = (id: string): LocateResult | null => {
  const stack: { children: Tree[]; parent: Tree | null }[] = [{ children: data.value, parent: null }]
  while (stack.length) {
    const { children, parent } = stack.pop()!
    for (let i = 0; i < children.length; i++) {
      const n = children[i]
      if (n.id === id) return { node: n, parent, container: children, index: i }
      if (n.children && n.children.length) stack.push({ children: n.children, parent: n })
    }
  }
  return null
}

const { editingId, editingLabel, editingOriginalLabel, startEdit, saveEdit, cancelEdit } = useInlineEdit<Tree>({ dataRef: data, locate })
const { dropdownRef, position, triggerRef, currentNode, openContextMenu, openBlankContextMenu, closeContextMenu, onGlobalCommand, getMenuItems } = useContextMenu<Tree>({ dataRef: data, treeRef, expandedKeysRef: expandedKeys, locate, startEdit }, componentProps.contextMenuHandler)
const { draggingId, dropState, getNodeStyles, onDragStart, onDragOver, onDrop, onDragEnd } = useDragAndDrop<Tree>({ dataRef: data, treeRef, expandedKeysRef: expandedKeys, locate })

//防止误触发
let clickTimer: ReturnType<typeof setTimeout> | null = null
const clickDelayMs = 250
const triggerSingleClick = (node: Tree) => {
  closeContextMenu()
  emit('select', node)
}
const onNodeSingleClick = (node: Tree, e: MouseEvent) => {
  if (clickTimer) {
    clearTimeout(clickTimer)
    clickTimer = null
  }
  clickTimer = setTimeout(() => {
    triggerSingleClick(node)
    clickTimer = null
  }, clickDelayMs)
}
const onNodeDblClick = (node: Tree, e: MouseEvent) => {
  if (clickTimer) {
    clearTimeout(clickTimer)
    clickTimer = null
  }
  startEdit(node)
}

const onNodeExpand = (data: TreeNodeData) => {
  const key = (data as any)[props.value] as string
  if (!key) return
  if (!expandedKeys.value.includes(key)) {
    expandedKeys.value = [...expandedKeys.value, key]
  }
}

const onNodeCollapse = (data: TreeNodeData) => {
  const key = (data as any)[props.value] as string
  if (!key) return
  if (expandedKeys.value.includes(key)) {
    expandedKeys.value = expandedKeys.value.filter((k) => k !== key)
  }
}

</script>

<style lang="scss" scoped>
.db-tree-container {
  // width: 100%;
  // height: 100%;
  // display: flex;
  // flex-direction: column;
  // gap: 8px;
}
.treeLine-2 {
    .node_nestingLevel_empty {
      display: inline-block;
      padding-left: 18px;
    }
    .node_nestingLevel_empty::before {
      content: '';
      position: absolute;
      top: -6px;
      height: 26px;
      width: 1px;
      border-left: 1px solid #ababab;
    }
    
    // 横线 - 从竖线连接到节点
    .node_nestingLevel_empty::after {
      content: '';
      position: absolute;
      top: 13px;
      width: 13px;
      height: 1px;
      border-top: 1px solid #ababab;
    }
    .node-content-wrapper {
      display: flex;
      align-items: center;
      gap: 0px;
      line-height: 1;
    }
    
    .node-icon-wrapper {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      width: 18px;
      height: 18px;
      flex-shrink: 0;
    }
    
    .custom-expand-icon {
      width: 16px;
      height: 16px;
      font-size: 16px;
      cursor: pointer;
      user-select: none;
      display: block;
    }
    
    .node-label {
      display: inline-block;
      line-height: 1.0;
      user-select: none;
      cursor: pointer;
      // font-size: 11px;
    }
    
    .node-edit-input {
      flex: 1;
      min-width: 100px;
    }
    /* 选择后面还有 ht_nestingLevel_empty 的元素（不是最后一个） */
    .node_nestingLevel_empty:has(+ .node_nestingLevel_empty)::after {
      /* 你的样式 */
      width: 0px;
    }
    :deep(.el-tree-node){
      left: -9px !important;
    }
    :deep(.el-tree-node__expand-icon){
      display: none !important;
    }
  //  :deep(.el-tree-node__content){
  //     display: none !important;
  //   }
}
</style>
