const TAP_MOVE_LIMIT = 10
const DUPLICATE_EVENT_LIMIT = 700
const FOCUSABLE_SELECTOR =
  'a,button,input,textarea,select,[tabindex],[role="button"],[contenteditable="true"]'
const EDITABLE_SELECTOR = 'input,textarea,select,[contenteditable="true"]'

const getEventPoint = (event) => {
  if (event.changedTouches?.length) return event.changedTouches[0]
  if (event.touches?.length) return event.touches[0]
  return event
}

const triggerBinding = (el, event) => {
  const binding = el.__touchTapBinding
  if (!binding) return

  if (!shouldTriggerBinding(el, event)) return

  if (binding.modifiers.stop) {
    event.stopPropagation()
  }
  if (binding.modifiers.prevent) {
    event.preventDefault()
  }

  if (typeof binding.value === 'function') {
    binding.value(event)
  }
}

const shouldTriggerBinding = (el, event) => {
  const binding = el.__touchTapBinding
  if (!binding) return false
  return !(binding.modifiers.self && event.target !== el)
}

const isEditableTarget = (target) => {
  return target instanceof Element && Boolean(target.closest(EDITABLE_SELECTOR))
}

export const vTouchTap = {
  mounted(el, binding) {
    el.__touchTapBinding = binding

    let startX = 0
    let startY = 0
    let moved = false
    let active = false
    let lastTouchTime = 0
    let lastActivationTime = 0

    if (!el.matches(FOCUSABLE_SELECTOR)) {
      el.setAttribute('role', 'button')
      el.setAttribute('tabindex', '0')
      el.__touchTapAddedA11y = true
    }

    const activate = (event) => {
      if (!shouldTriggerBinding(el, event)) return
      lastActivationTime = Date.now()
      triggerBinding(el, event)
    }

    // Pointer Events 同时覆盖移动端触摸和桌面鼠标；移动阈值用于避免滚动列表时误触。
    const onStart = (event) => {
      if (el.__touchTapBinding?.modifiers.self && event.target !== el) {
        active = false
        return
      }
      if (event.button !== undefined && event.button !== 0) return

      const point = getEventPoint(event)
      startX = point.clientX || 0
      startY = point.clientY || 0
      moved = false
      active = true
    }

    const onMove = (event) => {
      if (!active) return

      const point = getEventPoint(event)
      const deltaX = Math.abs((point.clientX || 0) - startX)
      const deltaY = Math.abs((point.clientY || 0) - startY)
      if (deltaX > TAP_MOVE_LIMIT || deltaY > TAP_MOVE_LIMIT) {
        moved = true
      }
    }

    const onEnd = (event) => {
      if (!active) return

      const point = getEventPoint(event)
      const deltaX = Math.abs((point.clientX || 0) - startX)
      const deltaY = Math.abs((point.clientY || 0) - startY)
      active = false

      if (moved || deltaX > TAP_MOVE_LIMIT || deltaY > TAP_MOVE_LIMIT) return
      activate(event)
    }

    const onTouchEnd = (event) => {
      lastTouchTime = Date.now()
      onEnd(event)
    }

    const onMouseEnd = (event) => {
      if (Date.now() - lastTouchTime < DUPLICATE_EVENT_LIMIT) return
      onEnd(event)
    }

    const onClick = (event) => {
      if (event.detail !== 0) return
      if (Date.now() - lastActivationTime < DUPLICATE_EVENT_LIMIT) return
      activate(event)
    }

    const onKeydown = (event) => {
      if (event.key !== 'Enter' && event.key !== ' ') return
      if (!shouldTriggerBinding(el, event) || isEditableTarget(event.target)) return
      if (event.key === ' ') {
        event.preventDefault()
      }
      activate(event)
    }

    const onCancel = () => {
      active = false
      moved = false
    }

    const hasPointerEvent = typeof window !== 'undefined' && typeof window.PointerEvent === 'function'
    const pointerOrTouchEvents = hasPointerEvent
      ? [
          ['pointerdown', onStart, { passive: true }],
          ['pointermove', onMove, { passive: true }],
          ['pointerup', onEnd, { passive: false }],
          ['pointercancel', onCancel, { passive: true }],
        ]
      : [
          ['touchstart', onStart, { passive: true }],
          ['touchmove', onMove, { passive: true }],
          ['touchend', onTouchEnd, { passive: false }],
          ['touchcancel', onCancel, { passive: true }],
          ['mousedown', onStart, { passive: true }],
          ['mousemove', onMove, { passive: true }],
          ['mouseup', onMouseEnd, { passive: false }],
        ]
    const events = [
      ...pointerOrTouchEvents,
      ['click', onClick, { passive: false }],
      ['keydown', onKeydown, { passive: false }],
    ]

    events.forEach(([name, handler, options]) => {
      el.addEventListener(name, handler, options)
    })

    el.__touchTapCleanup = () => {
      events.forEach(([name, handler, options]) => {
        el.removeEventListener(name, handler, options)
      })
    }
  },
  updated(el, binding) {
    el.__touchTapBinding = binding
  },
  beforeUnmount(el) {
    el.__touchTapCleanup?.()
    delete el.__touchTapBinding
    delete el.__touchTapCleanup
    if (el.__touchTapAddedA11y) {
      el.removeAttribute('role')
      el.removeAttribute('tabindex')
      delete el.__touchTapAddedA11y
    }
  },
}
