import bus from '@/utils/bus'
import { _stopPropagation } from '@/utils/index'
import { SlideType } from '@/utils/const_var'
import { nextTick } from 'vue'
import { _css } from '@/utils/dom'

type SlidePointerEvent = PointerEvent & { touches: TouchList }

function checkEvent(e: PointerEvent): e is SlidePointerEvent {
  const isMobile = /Mobi|Android|iPhone/i.test(navigator.userAgent)
  if (!isMobile || (isMobile && e instanceof PointerEvent)) {
    const pe = e as SlidePointerEvent
    pe.touches = [
      {
        clientX: e.clientX,
        clientY: e.clientY,
        pageX: e.pageX,
        pageY: e.pageY,
      },
    ] as unknown as TouchList
  }
  return true
}

export function slideInit(el: HTMLElement, state: Record<string, unknown>) {
  state.wrapper = state.wrapper || { width: 0, height: 0, childrenLength: 0 }
  const wrapper = state.wrapper as { width: number; height: number; childrenLength: number }
  wrapper.width = _css(el, 'width') as number
  wrapper.height = _css(el, 'height') as number
  nextTick(() => {
    wrapper.childrenLength = el.children.length
  })
  const t = getSlideOffset(state, el)
  let dx1 = 0
  let dx2 = 0
  if (state.type === SlideType.HORIZONTAL) dx1 = t
  else dx2 = t
  _css(el, 'transform', `translate3d(${dx1}px, ${dx2}px, 0)`)
}

export function canSlide(state: Record<string, unknown>) {
  if (state.needCheck) {
    const move = state.move as { x: number; y: number }
    const judgeValue = state.judgeValue as number
    if (Math.abs(move.x) > judgeValue || Math.abs(move.y) > judgeValue) {
      const angle = (Math.abs(move.x) * 10) / (Math.abs(move.y) * 10)
      state.next = state.type === SlideType.HORIZONTAL ? angle > 1 : angle <= 1
      state.needCheck = false
    } else {
      return false
    }
  }
  return state.next
}

function canNext(state: Record<string, unknown>, isNext: boolean) {
  const wrapper = state.wrapper as { childrenLength: number }
  const localIndex = state.localIndex as number
  return !((localIndex === 0 && !isNext) || (localIndex === wrapper.childrenLength - 1 && isNext))
}

export function slideTouchStart(e: PointerEvent, el: HTMLElement, state: Record<string, unknown>) {
  if (!checkEvent(e)) return
  const pe = e as SlidePointerEvent
  _css(el, 'transition-duration', '0ms')
  const start = state.start as { x: number; y: number; time: number }
  start.x = pe.touches[0].pageX
  start.y = pe.touches[0].pageY
  start.time = Date.now()
  state.isDown = true
}

export function slideTouchMove(
  e: PointerEvent,
  el: HTMLElement,
  state: Record<string, unknown>,
  canNextCb: typeof canNext | null = null,
  notNextCb: (() => void) | null = null,
) {
  if (!checkEvent(e)) return
  if (!state.isDown) return
  const pe = e as SlidePointerEvent
  const move = state.move as { x: number; y: number }
  const start = state.start as { x: number; y: number }
  move.x = pe.touches[0].pageX - start.x
  move.y = pe.touches[0].pageY - start.y
  const canSlideRes = canSlide(state)
  const isNext = state.type === SlideType.HORIZONTAL ? move.x < 0 : move.y < 0
  if (canSlideRes) {
    if (!canNextCb) canNextCb = canNext
    if (canNextCb(state, isNext)) {
      ;(window as Window).isMoved = true
      _stopPropagation(e)
      if (state.type === SlideType.HORIZONTAL) {
        bus.emit((state.name as string) + '-moveX', move.x)
      }
      const t = getSlideOffset(state, el) + (isNext ? (state.judgeValue as number) : -(state.judgeValue as number))
      let dx1 = 0
      let dx2 = 0
      if (state.type === SlideType.HORIZONTAL) dx1 = t + move.x
      else dx2 = t + move.y
      _css(el, 'transition-duration', '0ms')
      _css(el, 'transform', `translate3d(${dx1}px, ${dx2}px, 0)`)
    } else {
      notNextCb?.()
    }
  }
}

export function slideTouchEnd(
  e: PointerEvent,
  state: Record<string, unknown>,
  canNextCb: typeof canNext | null = null,
  nextCb: ((isNext: boolean) => void) | null = null,
  notNextCb: (() => void) | null = null,
) {
  if (!checkEvent(e)) return
  if (!state.isDown) return
  if (state.next) {
    const move = state.move as { x: number; y: number }
    const wrapper = state.wrapper as { width: number; height: number }
    const isHorizontal = state.type === SlideType.HORIZONTAL
    const isNext = isHorizontal ? move.x < 0 : move.y < 0
    if (!canNextCb) canNextCb = canNext
    if (canNextCb(state, isNext)) {
      const endTime = Date.now()
      const start = state.start as { time: number }
      let gapTime = endTime - start.time
      const distance = isHorizontal ? move.x : move.y
      const judgeValue = isHorizontal ? wrapper.width : wrapper.height
      if (Math.abs(distance) < 20) gapTime = 1000
      if (Math.abs(distance) > judgeValue / 3) gapTime = 100
      if (gapTime < 150) {
        const localIndex = state.localIndex as number
        if (isNext) state.localIndex = localIndex + 1
        else state.localIndex = localIndex - 1
        return nextCb?.(isNext)
      }
    } else {
      return notNextCb?.()
    }
  } else {
    notNextCb?.()
  }
}

export function slideReset(
  e: PointerEvent,
  el: HTMLElement,
  state: Record<string, unknown>,
  emit: ((event: 'update:index', index: number) => void) | null = null,
) {
  if (!checkEvent(e)) return
  _css(el, 'transition-duration', '300ms')
  const t = getSlideOffset(state, el)
  let dx1 = 0
  let dx2 = 0
  if (state.type === SlideType.HORIZONTAL) {
    bus.emit((state.name as string) + '-end', state.localIndex)
    dx1 = t
  } else {
    bus.emit((state.name as string) + '-end')
    dx2 = t
  }
  _css(el, 'transform', `translate3d(${dx1}px, ${dx2}px, 0)`)
  const start = state.start as { x: number; y: number; time: number }
  const move = state.move as { x: number; y: number }
  start.x = start.y = start.time = move.x = move.y = 0
  state.next = false
  state.needCheck = true
  state.isDown = false
  setTimeout(() => {
    ;(window as Window).isMoved = false
  }, 200)
  emit?.('update:index', state.localIndex as number)
}

export function getSlideOffset(state: Record<string, unknown>, el: HTMLElement) {
  const localIndex = state.localIndex as number
  if (state.type === SlideType.HORIZONTAL) {
    const widths: number[] = []
    Array.from(el.children).forEach((v) => widths.push(v.getBoundingClientRect().width))
    const slice = widths.slice(0, localIndex)
    if (slice.length) return -slice.reduce((a, b) => a + b, 0)
    return 0
  }
  const wrapper = state.wrapper as { height: number }
  if (state.type === SlideType.VERTICAL_INFINITE) {
    return -localIndex * wrapper.height
  }
  const heights: number[] = []
  Array.from(el.children).forEach((v) => heights.push(v.getBoundingClientRect().height))
  const slice = heights.slice(0, localIndex)
  if (slice.length) return -slice.reduce((a, b) => a + b, 0)
  return 0
}
