<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import FdsLabel from '../Typography/FdsLabel/FdsLabel.vue'
import FdsButtonIcon from '../Buttons/FdsButtonIcon/FdsButtonIcon.vue'
import type { FdsPopoverProps } from './types'

const props = withDefaults(defineProps<FdsPopoverProps>(), {
  open: false,
  label: '',
  body: '',
  position: 'bottom',
  offset: 8,
  offsetX: 0,
  offsetY: 0,
  closeAriaLabel: 'Close popover',
  dataTestid: undefined,
})

const emit = defineEmits<{
  (e: 'close'): void
  (e: 'update:open', value: boolean): void
}>()

const popoverRef = ref<HTMLElement | null>(null)
const isOpen = ref(props.open)
const popoverId = `fds-popover-${Math.random().toString(36).slice(2, 11)}`
const anchorName = `--${popoverId}-anchor`

const triggerClasses = computed(() => ['inline-flex'])
const triggerStyle = computed(() => ({
  anchorName,
}))

const areaByPosition: Record<NonNullable<FdsPopoverProps['position']>, string> = {
  top: '[position-area:top]',
  right: '[position-area:right]',
  bottom: '[position-area:bottom]',
  left: '[position-area:left]',
}

const popoverClasses = computed(() => [
  'm-0 border-none p-4 rounded-md shadow-[0px_4px_8px_0px_rgba(12,72,153,0.12)] bg-white',
  'min-w-[200px] max-w-[320px]',
  '[position-try-fallbacks:flip-block,flip-inline]',
  '[inset:auto]',
  areaByPosition[props.position],
])

const popoverStyle = computed(() => {
  const styles: Record<string, string> = {}
  styles.positionAnchor = anchorName
  const mainOffset = `${props.offset}px`
  if (props.position === 'bottom') styles.marginTop = mainOffset
  if (props.position === 'top') styles.marginBottom = mainOffset
  if (props.position === 'right') styles.marginLeft = mainOffset
  if (props.position === 'left') styles.marginRight = mainOffset

  // Fine tune position in two axes after main offset is applied.
  styles.transform = `translate(${props.offsetX}px, ${props.offsetY}px)`
  return styles
})

const showPopover = () => {
  const el = popoverRef.value
  if (!el || el.matches(':popover-open')) return
  el.showPopover()
}

const hidePopover = () => {
  const el = popoverRef.value
  if (!el || !el.matches(':popover-open')) return
  el.hidePopover()
}

const togglePopover = () => {
  if (isOpen.value) {
    hidePopover()
    return
  }
  showPopover()
}

const closePopover = () => {
  hidePopover()
}

const onNativeToggle = () => {
  const open = !!popoverRef.value?.matches(':popover-open')
  if (open === isOpen.value) return
  isOpen.value = open
  emit('update:open', open)
  if (!open) {
    emit('close')
  }
}

watch(
  () => props.open,
  (open) => {
    if (open === isOpen.value) return
    isOpen.value = open
    if (open) {
      showPopover()
      return
    }
    hidePopover()
  },
)

onMounted(() => {
  popoverRef.value?.addEventListener('toggle', onNativeToggle)
  if (isOpen.value) {
    showPopover()
  }
})

onBeforeUnmount(() => {
  popoverRef.value?.removeEventListener('toggle', onNativeToggle)
})

const popoverAttrs = computed(() => {
  const attrs: Record<string, string> = {}
  if (props.dataTestid) {
    attrs['data-testid'] = props.dataTestid
  }
  return attrs
})
</script>

<template>
  <div class="inline-flex">
    <span :class="triggerClasses" :style="triggerStyle">
      <span
        class="inline-flex"
        @click.stop="togglePopover"
        @keydown.enter.prevent.stop="togglePopover"
        @keydown.space.prevent.stop="togglePopover"
      >
        <slot name="trigger" />
      </span>
    </span>

    <div
      :id="popoverId"
      ref="popoverRef"
      :class="popoverClasses"
      :style="popoverStyle"
      role="dialog"
      aria-modal="false"
      :aria-label="label || 'Popover'"
      v-bind="{ ...popoverAttrs, popover: 'auto' }"
    >
      <template v-if="label">
        <div class="mb-1 flex items-start justify-between gap-2">
          <FdsLabel class="mb-0! min-w-0 grow balance-text whitespace-normal">
            {{ label }}
          </FdsLabel>
          <FdsButtonIcon icon="cross" :size="28" :aria-label="closeAriaLabel" @click="closePopover" />
        </div>
        <p class="mb-0! min-w-0 whitespace-normal balance-text pr-2">
          <slot>
            {{ body }}
          </slot>
        </p>
      </template>

      <template v-else>
        <div class="flex items-center gap-2">
          <p class="mb-0! min-w-0 grow whitespace-normal wrap-break-word">
            <slot>
              {{ body }}
            </slot>
          </p>
          <FdsButtonIcon icon="cross" :size="28" :aria-label="closeAriaLabel" @click="closePopover" />
        </div>
      </template>
    </div>
  </div>
</template>
