<template>
  <div class="cpMenu">
    <div v-if="isDrawer" class="cpMenu__drawerOverlay" :class="drawerOverlayDynamicClass" />
    <div class="cpMenu__anchor">
      <div
        ref="trigger"
        :aria-controls="menuId"
        :aria-expanded="isOpen"
        :aria-haspopup="popupType"
        class="cpMenu__trigger"
        @click="toggle"
      >
        <slot name="trigger" />
      </div>
      <primevue-popover
        v-if="!isDrawer"
        ref="popover"
        close-on-escape
        dismissable
        :pt="popoverPt"
        @hide="onHide"
        @show="onShow"
      >
        <cp-menu-list v-if="hasItems" :id="menuId" :items="items" @async-complete="onItemClick" @click="onItemClick" />
        <slot v-else />
      </primevue-popover>
    </div>
    <primevue-drawer
      v-if="isDrawer"
      v-model:visible="isOpen"
      :auto-z-index="false"
      block-scroll
      close-on-escape
      dismissable
      position="bottom"
      :pt="drawerPt"
      :show-close-icon="false"
      @after-show="focusCloseButton"
      @hide="onHide"
    >
      <div class="cpMenu__drawerToolbar">
        <div class="cpMenu__drawerToolbarTitle">
          <slot name="title" />
        </div>
        <cp-button
          ref="closeButton"
          appearance="secondary"
          aria-label="Close drawer"
          color="neutral"
          is-square
          size="md"
          @click="hide"
        >
          <template #leading-icon>
            <cp-icon type="x" />
          </template>
        </cp-button>
      </div>
      <cp-menu-list v-if="hasItems" :id="menuId" :items="items" @async-complete="onItemClick" @click="onItemClick" />
      <slot v-else />
    </primevue-drawer>
  </div>
</template>

<script setup lang="ts">
import PrimevueDrawer from 'primevue/drawer'
import type { MenuItem } from 'primevue/menuitem'
import PrimevuePopover from 'primevue/popover'
import { computed, nextTick, ref, useId } from 'vue'

import { useIsBreakpoint } from '@/composables/useIsBreakpoint'

import CpButton from '@/components/CpButton.vue'
import CpIcon from '@/components/CpIcon.vue'
import CpMenuList from '@/components/CpMenuList.vue'

import { Breakpoints } from '@/constants/layout'
import { getKeyboardFocusableElements } from '@/helpers/dom'

export type CpMenuPlacement = 'start' | 'end'

export interface Props {
  class?: string
  drawerClass?: string
  forcePopover?: boolean
  isFullHeightDrawer?: boolean
  items?: MenuItem[]
  keepOpenOnClick?: boolean
  placement?: CpMenuPlacement
}

const props = withDefaults(defineProps<Props>(), {
  class: undefined,
  drawerClass: undefined,
  isFullHeightDrawer: false,
  items: undefined,
  keepOpenOnClick: false,
  placement: 'start',
})

const emits = defineEmits<{
  hide: []
  show: []
}>()

const trigger = ref<HTMLElement | null>(null)
const popover = ref<InstanceType<typeof PrimevuePopover>>()
const closeButton = ref<InstanceType<typeof CpButton> | null>(null)
const isOpen = ref(false)
const menuId = useId()

const isMobileViewport = useIsBreakpoint(Breakpoints.TABLET)

const isDrawer = computed(() => isMobileViewport.value && !props.forcePopover)
const hasItems = computed(() => props.items?.length)
const popupType = computed(() => (hasItems.value ? 'menu' : 'dialog'))

const popoverPt = computed(() => ({
  root: { class: ['cpMenu__menu', props.class] },
  content: { class: 'cpMenu__menuContent' },
  transition: { name: 'scale-elastic', duration: 100 },
}))

const drawerOverlayDynamicClass = computed(() => ({
  'cpMenu__drawerOverlay--isOpen': isOpen.value,
}))

const drawerPt = {
  root: {
    class: [
      'cpMenu__drawer',
      { 'cpMenu__drawer--isFullHeight': props.isFullHeightDrawer },
      props.drawerClass,
    ],
  },
  content: {
    class: 'cpMenu__drawerContent'
   },
  header: {
    class: 'cpMenu__drawerHeader'
   },
  transition: {
    name: 'drawer',
    duration: 300
   },
}

const isPopoverPlacementEnd = computed(() => props.placement === 'end')

const show = (event: Event) => {
  if (isDrawer.value) {
    isOpen.value = true
  } else {
    popover.value?.show(event, trigger.value)
  }
  emits('show')
}

const hide = () => {
  if (isDrawer.value) isOpen.value = false
  else popover.value?.hide()
  emits('hide')
}

const onItemClick = () => {
  if (!props.keepOpenOnClick) hide()
}

const toggle = (event: Event) => {
  if (isDrawer.value) {
    isOpen.value = !isOpen.value
  } else {
    popover.value?.toggle(event, trigger.value)
  }
}

const focusTrigger = () => {
  if (!trigger.value) return
  const [first] = getKeyboardFocusableElements(trigger.value) as HTMLElement[]
  first?.focus()
}

const focusCloseButton = () => {
  nextTick(() => (closeButton.value?.$el as HTMLElement | undefined)?.focus())
}

const onHide = () => {
  isOpen.value = false
  focusTrigger()
  emits('hide')
}

const alignPopoverEnd = () => {
  if (!popover.value) return

  const { container: popoverElement, target: triggerElement } = popover.value as unknown as {
    container: HTMLElement | null
    target: HTMLElement | null
  }

  if (!popoverElement || !triggerElement) return

  const triggerWidth = triggerElement.offsetWidth
  const popoverWidth = popoverElement.offsetWidth

  popoverElement.style.marginLeft = `${(popoverWidth - triggerWidth) * -1}px`
}

const onShow = async () => {
  isOpen.value = true
  if (isPopoverPlacementEnd.value) alignPopoverEnd()
  emits('show')
}

defineExpose({ show, hide, toggle })
</script>

<style lang="scss">
.cpMenu {
  display: contents;

  &__anchor {
    display: inline-flex;
  }

  &__trigger {
    display: inline-flex;
    cursor: pointer;
  }

  &__menu {
    min-width: calc(var(--cp-dimensions-1) * 62.5);
    border-radius: var(--cp-radius-md);
    margin-top: var(--cp-spacing-sm-md);
    background-color: var(--cp-background-primary);
    box-shadow: var(--cp-shadows-overlay);
    will-change: opacity, transform;

    &[data-p-popover-flipped="true"] {
      margin-top: calc(var(--cp-spacing-sm-md) * -1);
    }
  }

  &__menuContent {
    padding: var(--cp-spacing-sm) 0;
  }

  &__drawerOverlay {
    position: fixed;
    z-index: 100;
    display: flex;
    align-items: flex-end;
    justify-content: center;
    background-color: var(--cp-background-overlay);
    inset: 0;
    opacity: 0;
    pointer-events: none;
    transition: opacity 250ms ease;

    &--isOpen {
      opacity: 1;
    }
  }

  &__drawer {
    position: relative;
    z-index: 101;
    display: flex;
    width: 100%;
    max-height: 85vh;
    flex-direction: column;
    padding-bottom: env(safe-area-inset-bottom);
    border-radius: var(--cp-radius-lg) var(--cp-radius-lg) 0 0;
    background-color: var(--cp-background-primary);
    box-shadow: var(--cp-shadows-overlay);
  }

  &__drawer--isFullHeight {
    height: 100%;
    max-height: 100%;
    border-radius: 0;
  }

  &__drawerHeader {
    display: none;
  }

  &__drawerContent {
    flex: 1;
    padding-bottom: var(--cp-spacing-sm);
    overflow-y: auto;
  }

  &__drawerToolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--cp-spacing-md) var(--cp-spacing-xl);
  }

  &__drawerToolbarTitle {
    color: var(--cp-text-secondary);
    font-size: var(--cp-text-size-lg);
    font-weight: 500;
    line-height: var(--cp-line-height-lg);
  }
}
</style>
