/* eslint-disable unused-imports/no-unused-vars */
/**
 * Copyright (c) 2022 - present TinyVue Authors.
 * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
 *
 * Use of this source code is governed by an MIT-style license.
 *
 * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
 * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
 * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
 *
 */

import type { IDropdownRenderlessParams } from '@/types'
import { KEY_CODE } from '@opentiny/utils'
import { addClass, removeClass, on, off } from '@opentiny/utils'

export const watchVisible =
  ({ broadcast, emit, nextTick }: Pick<IDropdownRenderlessParams, 'broadcast' | 'emit' | 'nextTick'>) =>
  (value: boolean) => {
    broadcast('TinyDropdownMenu', 'visible', value)

    /**
     * 此处必须延时处理,不然 Vue3 环境下会从 nextTick 冒出一个错误:
     *
     * Uncaught (in promise) TypeError: Cannot read properties of null (reading 'length')
     *
     * 场景:visible-change 事件中,使用 Modal 静态方法
     */

    nextTick(() => emit('visible-change', value))
  }

export const watchFocusing = (parent: IDropdownRenderlessParams['parent']) => (value: boolean) => {
  const selfDefine: HTMLElement | null = parent.$el.querySelector('.tiny-dropdown-selfdefine')

  if (selfDefine) {
    value ? addClass(selfDefine, 'focusing') : removeClass(selfDefine, 'focusing')
  }
}

export const show =
  ({ props, state, emit }: Pick<IDropdownRenderlessParams, 'props' | 'state' | 'emit'>) =>
  () => {
    if (props.disabled) {
      return
    }

    if (state.visibleIsBoolean) {
      emit('update:visible', true)
    } else {
      clearTimeout(Number(state.timeout))

      state.timeout = setTimeout(
        () => {
          state.visible = true
        },
        state.trigger === 'click' || state.trigger === 'contextmenu' ? 0 : props.showTimeout
      )
    }
  }

export const hide =
  ({ api, props, state, emit }: Pick<IDropdownRenderlessParams, 'api' | 'props' | 'state' | 'emit'>) =>
  () => {
    if (props.disabled) {
      return
    }

    api.removeTabindex()

    if (props.tabindex >= 0 && state.triggerElm) {
      api.resetTabindex(state.triggerElm)
    }

    if (state.visibleIsBoolean) {
      emit('update:visible', false)
    } else {
      clearTimeout(Number(state.timeout))

      state.timeout = setTimeout(
        () => {
          state.visible = false
        },
        state.trigger === 'click' || state.trigger === 'contextmenu' ? 0 : props.hideTimeout
      )
    }
  }

export const handleClick =
  ({ api, props, state, emit }: Pick<IDropdownRenderlessParams, 'api' | 'props' | 'state' | 'emit'>) =>
  () => {
    if (props.disabled) {
      return
    }

    if (state.visibleIsBoolean) {
      emit('handle-click', props.visible)
    } else {
      emit('handle-click', state.visible)
      state.visible ? api.hide() : api.show()
    }
  }

export const handleTriggerKeyDown =
  ({ api, state }: Pick<IDropdownRenderlessParams, 'api' | 'state'>) =>
  (event: KeyboardEvent) => {
    const keyCode = event.keyCode

    if (~[KEY_CODE.ArrowUp, KEY_CODE.ArrowDown].indexOf(keyCode)) {
      api.removeTabindex()
      if (state.menuItems) {
        api.resetTabindex(state.menuItems[0])
        state.menuItems[0].focus()
      }

      event.preventDefault()
      event.stopPropagation()
    } else if (keyCode === KEY_CODE.Enter) {
      api.handleClick()
    } else if (~[KEY_CODE.Tab, KEY_CODE.Escape].indexOf(keyCode)) {
      api.hide()
    }
  }

export const handleItemKeyDown =
  ({ api, props, state, emit }: Pick<IDropdownRenderlessParams, 'api' | 'props' | 'state' | 'emit'>) =>
  (event: KeyboardEvent) => {
    const keyCode = event.keyCode
    const target = event.target as HTMLElement
    const currentIndex = state.menuItemsArray.indexOf(target)
    const max = state.menuItemsArray.length - 1

    let nextIndex

    if (~[KEY_CODE.ArrowUp, KEY_CODE.ArrowDown].indexOf(keyCode)) {
      if (keyCode === KEY_CODE.ArrowUp) {
        nextIndex = currentIndex !== 0 ? currentIndex - 1 : 0
      } else {
        nextIndex = currentIndex < max ? currentIndex + 1 : max
      }

      api.removeTabindex()
      if (state.menuItems) {
        api.resetTabindex(state.menuItems[nextIndex])
        state.menuItems[nextIndex].focus()
      }

      event.preventDefault()
      event.stopPropagation()
    } else if (keyCode === KEY_CODE.Enter) {
      api.triggerElmFocus()

      target?.click()

      if (props.hideOnClick) {
        state.visible = false
      }
    } else if (~[KEY_CODE.Tab, KEY_CODE.Escape].indexOf(keyCode)) {
      api.hide()
      api.triggerElmFocus()
    }
  }

export const resetTabindex = (api: IDropdownRenderlessParams['api']) => (el: HTMLElement) => {
  api.removeTabindex()

  el.setAttribute('tabindex', '0')
}

export const removeTabindex = (state: IDropdownRenderlessParams['state']) => () => {
  state.triggerElm?.setAttribute('tabindex', '-1')
  state.menuItemsArray?.forEach((item) => {
    item.setAttribute('tabindex', '-1')
  })
}

export const initAria =
  ({ state, props }: Pick<IDropdownRenderlessParams, 'state' | 'props'>) =>
  () => {
    state.dropdownElm?.setAttribute('id', state.listId)
    state.triggerElm?.setAttribute('aria-haspopup', 'menu')
    state.triggerElm?.setAttribute('aria-controls', state.listId)

    if (!props.splitButton || !props.singleButton) {
      state.triggerElm?.setAttribute('role', 'button')
      state.triggerElm?.setAttribute('tabindex', String(props.tabindex))
      addClass(state.triggerElm, 'tiny-dropdown-selfdefine')
    }
  }

export const toggleFocus =
  ({ state, value }) =>
  () => {
    state.focusing = value
  }

export const initEvent =
  ({ api, props, state, vm, mode }: Pick<IDropdownRenderlessParams, 'api' | 'props' | 'state' | 'vm' | 'mode'>) =>
  () => {
    let buttonValue = props.splitButton || props.singleButton

    state.triggerElm = buttonValue ? vm.$refs.trigger.$el : props.border ? vm.$refs.trigger.$el : vm.$refs.trigger

    on(state.triggerElm, 'keydown', api.handleTriggerKeyDown)
    state.dropdownElm?.addEventListener('keydown', api.handleItemKeyDown, true)

    if (!props.splitButton || !props.singleButton) {
      on(state.triggerElm, 'focus', api.toggleFocusOnTrue)
      on(state.triggerElm, 'blur', api.toggleFocusOnFalse)
      on(state.triggerElm, 'click', api.toggleFocusOnFalse)
    }

    if (state.visibleIsBoolean) {
      return
    }

    /** ---------------------------
     *  新增:右键触发 contextmenu
     * --------------------------- */
    if (state.trigger === 'contextmenu') {
      on(state.triggerElm, 'contextmenu', (e: MouseEvent) => {
        e.preventDefault()
        api.handleClick()
      })
    } else if (state.trigger === 'hover') {
      on(state.triggerElm, 'mouseenter', api.show)
      on(state.triggerElm, 'mouseleave', api.hide)
      on(state.dropdownElm, 'mouseenter', api.show)
      on(state.dropdownElm, 'mouseleave', api.hide)
    } else if (state.trigger === 'click') {
      on(state.triggerElm, 'click', api.handleClick)
    }

    if (mode === 'mobile-first') {
      if (props.splitButton || props.singleButton) {
        on(state.triggerElm, 'click', api.handleClick)
      }
    }
  }

export const handleMenuItemClick =
  ({ props, state, emit }: Pick<IDropdownRenderlessParams, 'props' | 'state' | 'emit'>) =>
  ({ itemData, vm, disabled }) => {
    if (props.hideOnClick && !disabled) {
      state.visible = false
    }

    if (!disabled) {
      const data = { itemData, vm }
      emit('item-click', data)
    }
  }

export const triggerElmFocus = (state: IDropdownRenderlessParams['state']) => () => {
  state.triggerElm?.focus && state.triggerElm.focus()
}

export const initDomOperation =
  ({ api, state, vm }: Pick<IDropdownRenderlessParams, 'api' | 'state' | 'vm'>) =>
  () => {
    state.dropdownElm = vm.popperElm
    state.menuItems = state.dropdownElm?.querySelectorAll('[tabindex="-1"]')
    state.menuItemsArray = [].slice.call(state.menuItems)

    api.initEvent()
    api.initAria()
  }

export const handleMainButtonClick =
  ({ api, emit }: Pick<IDropdownRenderlessParams, 'api' | 'emit'>) =>
  (event: MouseEvent) => {
    emit('button-click', event)
    api.hide()
  }

export const mounted =
  ({
    api,
    vm,
    state,
    broadcast,
    props
  }: Pick<IDropdownRenderlessParams, 'api' | 'vm' | 'state' | 'broadcast' | 'props'>) =>
  () => {
    if (state.showSelfIcon) {
      state.showIcon = false
    }

    vm.$on('menu-item-click', api.handleMenuItemClick)
    vm.$on('current-item-click', api.handleMenuItemClick)
    vm.$on('selected-index', (selectedIndex) => {
      broadcast('TinyDropdownMenu', 'menu-selected-index', selectedIndex)
    })
    if (!state.visibleIsBoolean) {
      vm.$on('is-disabled', api.clickOutside)
    } else if (props.visible) {
      broadcast('TinyDropdownMenu', 'visible', true)
    }
  }

export const beforeDistory =
  ({ vm, api, state }: Pick<IDropdownRenderlessParams, 'vm' | 'api' | 'state'>) =>
  () => {
    if (state.triggerElm) {
      off(state.triggerElm, 'keydown', api.handleTriggerKeyDown)
      off(state.triggerElm, 'focus', api.toggleFocusOnTrue)
      off(state.triggerElm, 'blur', api.toggleFocusOnFalse)
      off(state.triggerElm, 'click', api.toggleFocusOnFalse)
      off(state.triggerElm, 'mouseenter', api.show)
      off(state.triggerElm, 'mouseleave', api.hide)
      off(state.triggerElm, 'click', api.handleClick)
      off(state.triggerElm, 'contextmenu', api.handleClick) /** 右键清理 */
      state.triggerElm = null
    }

    if (state.dropdownElm) {
      state.dropdownElm.removeEventListener('keydown', api.handleItemKeyDown, true)

      off(state.dropdownElm, 'mouseenter', api.show)
      off(state.dropdownElm, 'mouseleave', api.hide)

      state.dropdownElm = null
    }

    vm.$off('menu-item-click')
    vm.$off('current-item-click')
    vm.$off('selected-index')
    vm.$off('is-disabled')
  }

export const clickOutside =
  ({ props, api }: Pick<IDropdownRenderlessParams, 'props' | 'api'>) =>
  (disabled: boolean) => {
    if (props.hideOnClick) {
      disabled ? api.show() : api.hide()
    }
  }