// Styles
import './VMenu.sass'

// Components
import { VDialogTransition } from '@/components/transitions'
import { VDefaultsProvider } from '@/components/VDefaultsProvider'
import { VOverlay } from '@/components/VOverlay'
import { makeVOverlayProps } from '@/components/VOverlay/VOverlay'

// Composables
import { forwardRefs } from '@/composables/forwardRefs'
import { useRtl } from '@/composables/locale'
import { useProxiedModel } from '@/composables/proxiedModel'
import { useScopeId } from '@/composables/scopeId'

// Utilities
import {
  computed,
  inject,
  mergeProps,
  onBeforeUnmount,
  onDeactivated,
  provide,
  ref,
  shallowRef, toRef,
  useId,
  watch,
} from 'vue'
import { VMenuSymbol } from './shared'
import {
  focusableChildren,
  focusChild,
  genericComponent,
  getNextElement,
  omit,
  propsFactory,
  useRender,
} from '@/util'

// Types
import type { OverlaySlots } from '@/components/VOverlay/VOverlay'

export const makeVMenuProps = propsFactory({
  // TODO
  // disableKeys: Boolean,
  id: String,
  submenu: Boolean,

  ...omit(makeVOverlayProps({
    captureFocus: true,
    closeDelay: 250,
    closeOnContentClick: true,
    locationStrategy: 'connected' as const,
    location: undefined,
    openDelay: 300,
    scrim: false,
    scrollStrategy: 'reposition' as const,
    transition: { component: VDialogTransition },
  }), ['absolute']),
}, 'VMenu')

export const VMenu = genericComponent<OverlaySlots>()({
  name: 'VMenu',

  props: makeVMenuProps(),

  emits: {
    'update:modelValue': (value: boolean) => true,
  },

  setup (props, { slots }) {
    const isActive = useProxiedModel(props, 'modelValue')
    const { scopeId } = useScopeId()
    const { isRtl } = useRtl()

    const uid = useId()
    const id = toRef(() => props.id || `v-menu-${uid}`)

    const overlay = ref<VOverlay>()

    const parent = inject(VMenuSymbol, null)
    const openChildren = shallowRef(new Set<string>())
    provide(VMenuSymbol, {
      register () {
        openChildren.value.add(uid)
      },
      unregister () {
        openChildren.value.delete(uid)
      },
      closeParents (e) {
        const clickedOutside = !e || overlay.value?.contentEl?._clickOutside?.lastMousedownWasOutside

        setTimeout(() => {
          if (!openChildren.value.size &&
            !props.persistent &&
            clickedOutside
          ) {
            isActive.value = false
            parent?.closeParents(e)
          }
        }, 40)
      },
    })

    onBeforeUnmount(() => parent?.unregister())
    onDeactivated(() => isActive.value = false)

    watch(isActive, val => {
      val
        ? parent?.register()
        : parent?.unregister()
    }, { immediate: true })

    function onClickOutside (e: MouseEvent) {
      parent?.closeParents(e)
    }

    function onKeydown (e: KeyboardEvent) {
      if (props.disabled) return

      if (e.key === 'Tab') {
        const nextElement = getNextElement(
          focusableChildren(overlay.value?.contentEl as Element, false),
          e.shiftKey ? 'prev' : 'next',
          (el: HTMLElement) => el.tabIndex >= 0
        )
        if (!nextElement && !props.retainFocus) {
          isActive.value = false
        }
      } else if (props.submenu && e.key === (isRtl.value ? 'ArrowRight' : 'ArrowLeft')) {
        isActive.value = false
      }
    }

    function onActivatorKeydown (e: KeyboardEvent) {
      if (props.disabled || e.isComposing) return

      const el = overlay.value?.contentEl
      if (el && isActive.value) {
        if (e.key === 'ArrowDown') {
          e.preventDefault()
          e.stopImmediatePropagation()
          focusChild(el, 'next')
        } else if (e.key === 'ArrowUp') {
          e.preventDefault()
          e.stopImmediatePropagation()
          focusChild(el, 'prev')
        } else if (props.submenu) {
          if (e.key === (isRtl.value ? 'ArrowRight' : 'ArrowLeft')) {
            isActive.value = false
          } else if (e.key === (isRtl.value ? 'ArrowLeft' : 'ArrowRight')) {
            e.preventDefault()
            focusChild(el, 'first')
          }
        }
      } else if (
        props.submenu
          ? e.key === (isRtl.value ? 'ArrowLeft' : 'ArrowRight')
          : ['ArrowDown', 'ArrowUp'].includes(e.key)
      ) {
        isActive.value = true
        e.preventDefault()
        setTimeout(() => setTimeout(() => onActivatorKeydown(e)))
      }
    }

    const activatorProps = computed(() =>
      mergeProps({
        'aria-haspopup': 'menu',
        'aria-expanded': String(isActive.value),
        'aria-controls': id.value,
        'aria-owns': id.value,
        onKeydown: onActivatorKeydown,
      }, props.activatorProps)
    )

    useRender(() => {
      const overlayProps = VOverlay.filterProps(props)

      return (
        <VOverlay
          ref={ overlay }
          id={ id.value }
          class={[
            'v-menu',
            props.class,
          ]}
          style={ props.style }
          { ...overlayProps }
          v-model={ isActive.value }
          absolute
          activatorProps={ activatorProps.value }
          location={ props.location ?? (props.submenu ? 'end' : 'bottom') }
          onClick:outside={ onClickOutside }
          onKeydown={ onKeydown }
          { ...scopeId }
        >
          {{
            activator: slots.activator,
            default: (...args) => (
              <VDefaultsProvider root="VMenu">
                { slots.default?.(...args) }
              </VDefaultsProvider>
            ),
          }}
        </VOverlay>
      )
    })

    return forwardRefs({ id, ΨopenChildren: openChildren }, overlay)
  },
})

export type VMenu = InstanceType<typeof VMenu>
