<script setup>
const openMenuId = defineModel('openMenuId', {
  type: String,
  default: ''
})

const props = defineProps({
  /** 이 메뉴 인스턴스의 고유 id */
  itemId: {
    type: String,
    required: true
  },
  /** 트리거 비활성화 */
  disabled: {
    type: Boolean,
    default: false
  },
  /** 처리 중(… 표시) */
  busy: {
    type: Boolean,
    default: false
  },
  /** 접근성 라벨 접두사 */
  menuLabel: {
    type: String,
    default: '메뉴'
  },
  /** 트리거 크기: md(테이블) | sm(배지·사이드) */
  size: {
    type: String,
    default: 'md'
  },
  /** 어두운 배경 위 트리거(미디어 폴더 선택 행 등) */
  inverse: {
    type: Boolean,
    default: false
  }
})

const triggerRef = ref(null)
const popoverStyle = ref({})

/** @type {import('vue').ComputedRef<boolean>} */
const isOpen = computed(() => openMenuId.value === props.itemId)

/** @type {import('vue').ComputedRef<string>} */
const triggerSizeClass = computed(() => (props.size === 'sm' ? 'size-7' : 'size-9'))

/** @type {import('vue').ComputedRef<string>} */
const iconSizeClass = computed(() => (props.size === 'sm' ? 'size-5' : 'size-6'))

/** @type {import('vue').ComputedRef<string>} */
const triggerToneClass = computed(() => (
  props.inverse
    ? 'text-white hover:bg-white/15 focus-visible:ring-white/40'
    : 'text-[#394047] hover:bg-[#eceff2]'
))

/**
 * 행 메뉴 위치를 화면 기준으로 계산한다.
 * @returns {void}
 */
const updatePopoverPosition = () => {
  if (!import.meta.client || !triggerRef.value) {
    return
  }

  const rect = triggerRef.value.getBoundingClientRect()
  const menuWidth = props.size === 'sm' ? 176 : 176
  const estimatedHeight = 112
  const margin = 8
  const left = Math.max(margin, Math.min(rect.right - menuWidth, window.innerWidth - menuWidth - margin))
  const opensUp = rect.bottom + estimatedHeight + margin > window.innerHeight
  const top = opensUp
    ? Math.max(margin, rect.top - estimatedHeight - 4)
    : rect.bottom + 4

  popoverStyle.value = {
    left: `${left}px`,
    top: `${top}px`,
    width: `${menuWidth}px`
  }
}

/**
 * 메뉴 열기/닫기
 * @returns {void}
 */
const toggleMenu = () => {
  if (props.disabled) {
    return
  }

  openMenuId.value = isOpen.value ? '' : props.itemId
}

watch(isOpen, async (open) => {
  if (!open) {
    return
  }

  await nextTick()
  updatePopoverPosition()
})

onMounted(() => {
  window.addEventListener('resize', updatePopoverPosition)
  window.addEventListener('scroll', updatePopoverPosition, true)
})

onBeforeUnmount(() => {
  window.removeEventListener('resize', updatePopoverPosition)
  window.removeEventListener('scroll', updatePopoverPosition, true)
})
</script>

<template>
  <div
    class="admin-row-more-menu relative inline-flex justify-end"
    data-admin-row-menu
    @mousedown.stop
  >
    <button
      ref="triggerRef"
      class="admin-row-more-menu__trigger inline-flex items-center justify-center rounded transition-colors focus-visible:outline focus-visible:ring-2 focus-visible:ring-offset-1 disabled:pointer-events-none disabled:opacity-40"
      :class="[triggerSizeClass, triggerToneClass]"
      type="button"
      :disabled="disabled"
      :aria-expanded="isOpen"
      aria-haspopup="menu"
      :aria-label="isOpen ? `${menuLabel} 닫기` : menuLabel"
      @mousedown.stop
      @click.stop="toggleMenu"
    >
      <span
        v-if="busy"
        class="text-[10px] font-semibold text-muted"
        aria-hidden="true"
      >…</span>
      <svg
        v-else
        class="admin-row-more-menu__icon shrink-0"
        :class="iconSizeClass"
        xmlns="http://www.w3.org/2000/svg"
        viewBox="0 -960 960 960"
        fill="currentColor"
        aria-hidden="true"
      >
        <path d="M480-160q-33 0-56.5-23.5T400-240q0-33 23.5-56.5T480-320q33 0 56.5 23.5T560-240q0 33-23.5 56.5T480-160Zm0-240q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Zm0-240q-33 0-56.5-23.5T400-720q0-33 23.5-56.5T480-800q33 0 56.5 23.5T560-720q0 33-23.5 56.5T480-640Z" />
      </svg>
    </button>
    <Teleport to="body">
      <div
        v-if="isOpen"
        class="admin-row-more-menu__popover fixed z-[80] overflow-hidden rounded-xl border border-[#e2e5e9] bg-white py-2 text-sm text-[#3f4650] shadow-[0_18px_50px_rgba(15,23,42,0.16)]"
        :style="popoverStyle"
        role="menu"
        data-admin-row-menu
      >
        <slot />
      </div>
    </Teleport>
  </div>
</template>

<style scoped>
.admin-row-more-menu__popover :deep(.admin-row-more-menu__item) {
  display: block;
  width: 100%;
  padding: 0.625rem 1rem;
  text-align: left;
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: #3f4650;
}

.admin-row-more-menu__popover :deep(.admin-row-more-menu__item:hover) {
  background: #f3f5f7;
}

.admin-row-more-menu__popover :deep(.admin-row-more-menu__item:disabled) {
  opacity: 0.5;
  cursor: not-allowed;
}

.admin-row-more-menu__popover :deep(.admin-row-more-menu__item--danger) {
  color: #c0392b;
}

.admin-row-more-menu__popover :deep(.admin-row-more-menu__item--danger:hover) {
  background: #fef2f2;
}
</style>
