<template>
  <Teleport to="body">
    <Transition name="dropdown">
      <div
        v-if="modelValue"
        ref="floatingEl"
        class="dropdown-menu"
        :class="{ 'dropdown-menu--center': align === 'center' }"
        :style="floatingStyle"
      >
        <ul class="dropdown-menu__list">
          <li
            v-for="(item, i) in items"
            :key="item.key ?? item.label ?? i"
            class="dropdown-menu__item-wrap"
          >
            <button
              type="button"
              class="dropdown-menu__item"
              :class="{
                'dropdown-menu__item--danger': item.danger,
                'dropdown-menu__item--colored': item.color,
              }"
              :style="{ color: item.color }"
              :disabled="item.disabled"
              @click="handleClick(item)"
            >
              <SvgIcon
                v-if="item.icon"
                :name="item.icon"
                :size="16"
                :color="item.iconColor ?? item.color ?? 'currentColor'"
                class="dropdown-menu__icon"
              />
              <span class="dropdown-menu__label">{{ item.label }}</span>
            </button>
          </li>
        </ul>
      </div>
    </Transition>
  </Teleport>
</template>

<script setup>
import { onBeforeUnmount, ref, watch } from 'vue'
import SvgIcon from '@/components/icons/SvgIcon.vue'
import {
  autoUpdate,
  computePosition,
  flip,
  offset as offsetMiddleware,
  shift,
} from '@floating-ui/dom'

const props = defineProps({
  modelValue: { type: Boolean, default: false },
  reference: { type: [Object, null], default: null },
  items: { type: Array, default: () => [] },
  placement: { type: String, default: 'bottom-end' },
  offset: { type: Number, default: 8 },
  minWidth: { type: String, default: '12rem' },
  align: { type: String, default: 'start' },
})

const emit = defineEmits(['update:modelValue', 'select'])

const floatingEl = ref(null)
const floatingStyle = ref({ position: 'fixed', top: '0', left: '0', minWidth: props.minWidth })
let cleanup = null

const close = () => emit('update:modelValue', false)

const mountFloating = async () => {
  await Promise.resolve()
  const referenceEl = props.reference
  const floating = floatingEl.value
  if (!referenceEl || !floating) return

  cleanup = autoUpdate(referenceEl, floating, () => {
    computePosition(referenceEl, floating, {
      placement: props.placement,
      strategy: 'fixed',
      middleware: [offsetMiddleware(props.offset), flip(), shift({ padding: 8 })],
    }).then(({ x, y }) => {
      floatingStyle.value = {
        position: 'fixed',
        top: `${y}px`,
        left: `${x}px`,
        minWidth: props.minWidth,
      }
    })
  })

  document.addEventListener('mousedown', handleOutsideClick)
  document.addEventListener('keydown', handleEsc)
}

const unmountFloating = () => {
  cleanup?.()
  cleanup = null
  document.removeEventListener('mousedown', handleOutsideClick)
  document.removeEventListener('keydown', handleEsc)
}

const handleOutsideClick = (event) => {
  const referenceEl = props.reference
  const floating = floatingEl.value
  if (referenceEl?.contains(event.target)) return
  if (floating?.contains(event.target)) return
  close()
}

const handleEsc = (event) => {
  if (event.key === 'Escape') close()
}

const handleClick = (item) => {
  if (item.disabled) return
  emit('select', item)
  item.onClick?.(item)
  close()
}

watch(
  () => props.modelValue,
  (open) => {
    if (open) {
      setTimeout(mountFloating, 0)
    } else {
      unmountFloating()
    }
  }
)

onBeforeUnmount(unmountFloating)
</script>

<style lang="scss" scoped>
.dropdown-menu {
  z-index: 9999;
  background: #fff;
  border-radius: 0.875rem;
  box-shadow: 0 10px 25px -8px rgba(0, 0, 0, 12%), 0 4px 10px -4px rgba(0, 0, 0, 8%);
  padding: 0.25rem;
  max-height: 70vh;
  overflow-y: auto;
  overscroll-behavior: contain;

  &__list {
    list-style: none;
    margin: 0;
    padding: 0;
  }

  &__item-wrap {
    & + & {
      border-top: 1px solid #ececec;
    }
  }

  &__item {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 0.75rem;
    width: 100%;
    padding: 0.5rem 0.75rem;
    border: none;
    border-radius: 0.625rem;
    background: transparent;
    cursor: pointer;
    font-family: var(--font-family-fa);
    font-size: 0.85rem;
    color: var(--color-sec-gray, #363636);
    transition: background-color 0.15s ease;

    &:hover {
      background-color: #f7f7f7;
    }

    &:disabled {
      opacity: 0.5;
      cursor: not-allowed;
    }

    &--danger {
      color: var(--color-error);
    }
  }

  &__label {
    flex: 1;
    text-align: right;
    white-space: nowrap;
  }

  &__icon {
    flex-shrink: 0;
  }

  &__item--colored &__icon {
    :deep([stroke]) {
      stroke: currentcolor;
    }
  }

  &--center &__item {
    justify-content: center;
  }

  &--center &__label {
    flex: 0 1 auto;
    text-align: center;
  }
}

.dropdown-enter-active,
.dropdown-leave-active {
  transition: opacity 0.15s ease, transform 0.15s ease;
}

.dropdown-enter-from,
.dropdown-leave-to {
  opacity: 0;
  transform: translateY(-6px);
}
</style>
