<script setup lang="ts">
import { computed, ref, inject, provide, shallowReactive, watch, onScopeDispose } from 'vue'
import { whenever, useElementBounding, onClickOutside } from '@vueuse/core'

import { isMobileView, useScreenSize } from '~/composables/screen'
import { POPOVER_INJECTION_KEY, POPOVER_CONTEXT_INJECTION_KEY } from '~/injection-keys'
import { type ColorProps, type DefaultProps, propsToColor } from '~/composables/colors';

const open = defineModel('open', { default: false })

const { positioning = 'vertical', ...colorProps }  = defineProps<{
  positioning?:'horizontal' | 'vertical'
} & (ColorProps | DefaultProps)>()

// Template refs
const popover = ref()
const slot = ref()

// Click outside
const mobileClickOutside = (event: MouseEvent) => {
  const inPopover = !!(event.target as HTMLElement).closest('.funkwhale.popover')
  if (isMobile.value && !inPopover) {
    open.value = false
  }
}
onClickOutside(popover, async (event) => {
  const inPopover = !!(event.target as HTMLElement).closest('.funkwhale.popover')
  if (!isMobile.value && !inPopover) {
    open.value = false
  }
}, { ignore: [slot] })

// Auto positioning
const isMobile = isMobileView()
const { width, height, left, top, update } = useElementBounding(() => slot.value?.children[0])
const { width: popoverWidth, height: popoverHeight } = useElementBounding(popover, {
  windowScroll: false
})

whenever(open, update, { immediate: true })

const { width: screenWidth, height: screenHeight } = useScreenSize()

// TODO (basic functionality):
// - I can't operate the popup with a keyboard. Remove barrier for people not using a mouse (A11y)
// - Switching to submenus is error-prone. When moving cursor into freshly opened submenu, it should not close if the cursor crosses another menu item
// - Large menus disappear. When menus get big, they need to scroll.

const position = computed(() => {
  if (positioning === 'vertical' || isMobile.value) {
    let offsetTop = top.value + height.value
    if (offsetTop + popoverHeight.value > screenHeight.value) {
      offsetTop -= popoverHeight.value + height.value
    }

    let offsetLeft = left.value
    if (offsetLeft + popoverWidth.value > screenWidth.value) {
      offsetLeft -= popoverWidth.value - width.value
    }

    return {
      left: offsetLeft + 'px',
      top: offsetTop + 'px'
    }
  }

  let offsetTop = top.value
  if (offsetTop + popoverHeight.value > screenHeight.value) {
    offsetTop -= popoverHeight.value - height.value
  }

  let offsetLeft = left.value + width.value
  if (offsetLeft + popoverWidth.value > screenWidth.value) {
    offsetLeft -= popoverWidth.value + width.value
  }

  return {
    left: offsetLeft + 'px',
    top: offsetTop + 'px'
  }
})

// Popover close stack
let stack = inject(POPOVER_INJECTION_KEY, [ref(false)])
if (!stack) {
  provide(POPOVER_INJECTION_KEY, stack = shallowReactive([]))
}

stack.push(open)
onScopeDispose(() => {
  stack?.splice(stack.indexOf(open), 1)
})

// Provide context for child items
const hoveredItem = ref(-2)
provide(POPOVER_CONTEXT_INJECTION_KEY, {
  items: ref(0),
  hoveredItem
})

// Closing
const closeChild = () => {
  const ref = stack?.[stack.indexOf(open) + 1]
  if (!ref) return

  ref.value = false
}

// Recursively close popover tree
watch(open, (isOpen) => {
  if (isOpen) return
  closeChild()
})
</script>

<template>
  <div
    ref="slot"
    class="funkwhale popover-container"
  >
    <slot
      :isOpen="open"
      :toggleOpen="() => open = !open"
      :open="() => open = true"
      :close="() => open = false"
    />
  </div>

  <teleport v-if="open" to="body">
    <div
      :class="{ 'is-mobile': isMobile }"
      class="funkwhale popover-outer"
      @click.stop="mobileClickOutside"
    >
      <div
        ref="popover"
        :style="position"
        :class="{ 'is-mobile': isMobile }"
        class="funkwhale popover secondary"
         v-bind="propsToColor(colorProps)"
      >
        <slot name="items" />
      </div>
    </div>
  </teleport>
</template>

<style lang="scss">
@import './popover.scss'
</style>
