<!-- 右键菜单 -->
<template>
  <div class="menu-right">
    <Transition name="context-menu" @before-enter="onBeforeEnter" @after-leave="onAfterLeave">
      <div
        v-show="visible"
        :style="menuStyle"
        class="context-menu art-card-xs !shadow-xl min-w-[var(--menu-width)] w-[var(--menu-width)]"
      >
        <ul class="menu-list m-0 list-none" :style="menuListStyle">
          <template v-for="item in menuItems" :key="item.key">
            <!-- 普通菜单项 -->
            <li
              v-if="!item.children"
              class="menu-item relative flex-c c-p select-none rounded text-xs transition-colors duration-150 hover:bg-g-200"
              :class="{ 'is-disabled': item.disabled, 'has-line': item.showLine }"
              :style="menuItemStyle"
              @click="handleMenuClick(item)"
            >
              <ArtSvgIcon
                v-if="item.icon"
                class="mr-2 shrink-0 text-base text-g-800"
                :icon="item.icon"
              />
              <span
                class="menu-label flex-1 overflow-hidden text-ellipsis whitespace-nowrap text-g-800"
                >{{ item.label }}</span
              >
            </li>

            <!-- 子菜单 -->
            <li
              v-else
              class="menu-item submenu relative flex-c c-p select-none rounded text-xs transition-colors duration-150 hover:bg-g-200"
              :style="menuItemStyle"
            >
              <div class="submenu-title flex-c w-full">
                <ArtSvgIcon
                  v-if="item.icon"
                  class="mr-2 shrink-0 text-base text-g-800"
                  :icon="item.icon"
                />
                <span
                  class="menu-label flex-1 overflow-hidden text-ellipsis whitespace-nowrap text-g-800"
                  >{{ item.label }}</span
                >
                <ArtSvgIcon
                  icon="ri:arrow-right-s-line"
                  class="ubmenu-arrow ml-auto mr-0 text-base text-g-500 transition-transform duration-150"
                />
              </div>
              <ul
                class="submenu-list art-card-xs absolute left-full top-0 z-[2001] hidden w-max min-w-max list-none !shadow-xl"
                :style="submenuListStyle"
              >
                <li
                  v-for="child in item.children"
                  :key="child.key"
                  class="menu-item relative mx-1.5 flex-c c-p select-none rounded text-xs transition-colors duration-150 hover:bg-g-200"
                  :class="{ 'is-disabled': child.disabled, 'has-line': child.showLine }"
                  :style="menuItemStyle"
                  @click="handleMenuClick(child)"
                >
                  <ArtSvgIcon
                    v-if="child.icon"
                    class="r-2 shrink-0 text-base text-g-800 mr-1"
                    :icon="child.icon"
                  />
                  <span
                    class="menu-label flex-1 overflow-hidden text-ellipsis whitespace-nowrap text-g-800"
                    >{{ child.label }}</span
                  >
                </li>
              </ul>
            </li>
          </template>
        </ul>
      </div>
    </Transition>
  </div>
</template>

<script setup lang="ts">
  import type { CSSProperties } from 'vue'

  defineOptions({ name: 'ArtMenuRight' })

  export interface MenuItemType {
    /** 菜单项唯一标识 */
    key: string
    /** 菜单项标签 */
    label: string
    /** 菜单项图标 */
    icon?: string
    /** 菜单项是否禁用 */
    disabled?: boolean
    /** 菜单项是否显示分割线 */
    showLine?: boolean
    /** 子菜单 */
    children?: MenuItemType[]
    [key: string]: any
  }

  interface Props {
    menuItems: MenuItemType[]
    /** 菜单宽度 */
    menuWidth?: number
    /** 子菜单宽度 */
    submenuWidth?: number
    /** 菜单项高度 */
    itemHeight?: number
    /** 边界距离 */
    boundaryDistance?: number
    /** 菜单内边距 */
    menuPadding?: number
    /** 菜单项水平内边距 */
    itemPaddingX?: number
    /** 菜单圆角 */
    borderRadius?: number
    /** 动画持续时间 */
    animationDuration?: number
  }

  const props = withDefaults(defineProps<Props>(), {
    menuWidth: 120,
    submenuWidth: 150,
    itemHeight: 32,
    boundaryDistance: 10,
    menuPadding: 5,
    itemPaddingX: 6,
    borderRadius: 6,
    animationDuration: 100
  })

  const emit = defineEmits<{
    (e: 'select', item: MenuItemType): void
    (e: 'show'): void
    (e: 'hide'): void
  }>()

  const visible = ref(false)
  const position = ref({ x: 0, y: 0 })

  // 用于清理定时器和事件监听器
  let showTimer: number | null = null
  let eventListenersAdded = false

  // 计算菜单样式
  const menuStyle = computed(
    (): CSSProperties => ({
      position: 'fixed' as const,
      left: `${position.value.x}px`,
      top: `${position.value.y}px`,
      zIndex: 2000,
      width: `${props.menuWidth}px`
    })
  )

  // 计算菜单列表样式
  const menuListStyle = computed(
    (): CSSProperties => ({
      padding: `${props.menuPadding}px`
    })
  )

  // 计算菜单项样式
  const menuItemStyle = computed(
    (): CSSProperties => ({
      height: `${props.itemHeight}px`,
      padding: `0 ${props.itemPaddingX}px`,
      borderRadius: '4px'
    })
  )

  // 计算子菜单列表样式
  const submenuListStyle = computed(
    (): CSSProperties => ({
      minWidth: `${props.submenuWidth}px`,
      padding: `${props.menuPadding}px 0`,
      borderRadius: `${props.borderRadius}px`
    })
  )

  // 计算菜单高度（用于边界检测）
  const calculateMenuHeight = (): number => {
    let totalHeight = props.menuPadding * 2 // 上下内边距

    props.menuItems.forEach((item) => {
      totalHeight += props.itemHeight
      if (item.showLine) {
        totalHeight += 10 // 分割线额外高度
      }
    })

    return totalHeight
  }

  // 优化的位置计算函数
  const calculatePosition = (e: MouseEvent) => {
    const screenWidth = window.innerWidth
    const screenHeight = window.innerHeight
    const menuHeight = calculateMenuHeight()

    let x = e.clientX
    let y = e.clientY

    // 检查右边界 - 优先显示在鼠标右侧，如果空间不足则显示在左侧
    if (x + props.menuWidth > screenWidth - props.boundaryDistance) {
      x = Math.max(props.boundaryDistance, x - props.menuWidth)
    }

    // 检查下边界 - 优先显示在鼠标下方，如果空间不足则向上调整
    if (y + menuHeight > screenHeight - props.boundaryDistance) {
      y = Math.max(props.boundaryDistance, screenHeight - menuHeight - props.boundaryDistance)
    }

    // 确保不会超出边界
    x = Math.max(
      props.boundaryDistance,
      Math.min(x, screenWidth - props.menuWidth - props.boundaryDistance)
    )
    y = Math.max(
      props.boundaryDistance,
      Math.min(y, screenHeight - menuHeight - props.boundaryDistance)
    )

    return { x, y }
  }

  // 添加事件监听器
  const addEventListeners = () => {
    if (eventListenersAdded) return

    document.addEventListener('click', handleDocumentClick)
    document.addEventListener('contextmenu', handleDocumentContextmenu)
    document.addEventListener('keydown', handleKeydown)
    eventListenersAdded = true
  }

  // 移除事件监听器
  const removeEventListeners = () => {
    if (!eventListenersAdded) return

    document.removeEventListener('click', handleDocumentClick)
    document.removeEventListener('contextmenu', handleDocumentContextmenu)
    document.removeEventListener('keydown', handleKeydown)
    eventListenersAdded = false
  }

  // 处理文档点击事件
  const handleDocumentClick = (e: Event) => {
    // 检查点击是否在菜单内部
    const target = e.target as Element
    const menuElement = document.querySelector('.context-menu')
    if (menuElement && menuElement.contains(target)) {
      return
    }
    hide()
  }

  // 处理文档右键事件
  const handleDocumentContextmenu = () => {
    hide()
  }

  // 处理键盘事件
  const handleKeydown = (e: KeyboardEvent) => {
    if (e.key === 'Escape') {
      hide()
    }
  }

  const show = (e: MouseEvent) => {
    e.preventDefault()
    e.stopPropagation()

    // 清理之前的定时器
    if (showTimer) {
      window.clearTimeout(showTimer)
      showTimer = null
    }

    // 计算位置
    position.value = calculatePosition(e)
    visible.value = true

    emit('show')

    // 延迟添加事件监听器，避免立即触发关闭
    showTimer = window.setTimeout(() => {
      if (visible.value) {
        addEventListeners()
      }
      showTimer = null
    }, 50) // 减少延迟时间，提升响应性
  }

  const hide = () => {
    if (!visible.value) return

    visible.value = false
    emit('hide')

    // 清理定时器
    if (showTimer) {
      window.clearTimeout(showTimer)
      showTimer = null
    }

    // 移除事件监听器
    removeEventListeners()
  }

  const handleMenuClick = (item: MenuItemType) => {
    if (item.disabled) return
    emit('select', item)
    hide()
  }

  // 动画钩子函数
  const onBeforeEnter = (el: Element) => {
    const element = el as HTMLElement
    element.style.transformOrigin = 'top left'
  }

  const onAfterLeave = () => {
    // 确保清理所有资源
    removeEventListeners()
    if (showTimer) {
      window.clearTimeout(showTimer)
      showTimer = null
    }
  }

  // 组件卸载时清理资源
  onUnmounted(() => {
    removeEventListeners()
    if (showTimer) {
      window.clearTimeout(showTimer)
      showTimer = null
    }
  })

  // 导出方法供父组件调用
  defineExpose({
    show,
    hide,
    visible: computed(() => visible.value)
  })
</script>

<style scoped>
  .menu-right {
    --menu-width: v-bind('props.menuWidth + "px"');
    --border-radius: v-bind('props.borderRadius + "px"');
  }

  .menu-item.has-line {
    margin-bottom: 10px;
  }

  .menu-item.has-line::after {
    position: absolute;
    right: 0;
    bottom: -5px;
    left: 0;
    height: 1px;
    content: '';
    background-color: var(--art-gray-300);
  }

  .menu-item.is-disabled {
    color: var(--el-text-color-disabled);
    cursor: not-allowed;
  }

  .menu-item.is-disabled:hover {
    background-color: transparent !important;
  }

  .menu-item.is-disabled i:not(.submenu-arrow),
  .menu-item.is-disabled :deep(.art-svg-icon) {
    color: var(--el-text-color-disabled) !important;
  }

  .menu-item.is-disabled .menu-label {
    color: var(--el-text-color-disabled) !important;
  }

  .menu-item.submenu:hover .submenu-list {
    display: block;
  }

  .menu-item.submenu:hover .submenu-title .submenu-arrow {
    transform: rotate(90deg);
  }

  /* 动画样式 */
  .context-menu-enter-active,
  .context-menu-leave-active {
    transition: all v-bind('props.animationDuration + "ms"') ease-out;
  }

  .context-menu-enter-from,
  .context-menu-leave-to {
    opacity: 0;
    transform: scale(0.9);
  }

  .context-menu-enter-to,
  .context-menu-leave-from {
    opacity: 1;
    transform: scale(1);
  }
</style>
