import {
  inject,
  ref,
  toRefs,
  InjectionKey,
  Slot,
  Ref,
  onUpdated,
  getCurrentInstance
} from 'vue'

const raf = window.requestAnimationFrame
const caf = window.cancelAnimationFrame
function throttleByRaf(cb: (...args: any[]) => void) {
  let timer = 0

  const throttle = (...args: any[]): void => {
    if (timer) {
      caf(timer)
    }
    timer = raf(() => {
      cb(...args)
      timer = 0
    })
  }

  throttle.cancel = () => {
    caf(timer)
    timer = 0
  }

  return throttle
}
type TreeNodeKey = number | string
export type DropPosition = -1 | 0 | 1
export const TreeInjectionKey: InjectionKey<TreeContext> =
  Symbol('TreeInjectionKey')
type TreeContext = Readonly<{
  onDragStart?: (key: TreeNodeKey, e: DragEvent) => void
  onDragEnd?: (key: TreeNodeKey, e: DragEvent) => void
  onDragOver?: (key: TreeNodeKey, e: DragEvent) => void
  onDragLeave?: (key: TreeNodeKey, e: DragEvent) => void
  onDrop?: (key: TreeNodeKey, dropPosition: DropPosition, e: DragEvent) => void
  allowDrop?: (key: TreeNodeKey, dropPosition: DropPosition) => boolean
}>

function useTreeContext(): Partial<TreeContext> {
  const treeContext = inject<TreeContext>(TreeInjectionKey)
  return treeContext || {}
}

const generateKey = (() => {
  let i = 0

  return () => {
    i += 1
    return `__arco_tree${i}`
  }
})()

export const useNodeKey = (): Ref<TreeNodeKey> => {
  const instance = getCurrentInstance()

  const getKey = () => (instance?.vnode.key ?? generateKey()) as TreeNodeKey

  const key = ref(getKey())

  onUpdated(() => {
    key.value = getKey()
  })

  return key
}

export default function useDraggable(props: {
  key: TreeNodeKey
  refTitle: HTMLElement | undefined
}) {
  const { key, refTitle } = toRefs(props)
  //   const treeContext = useTreeContext()

  const isDragOver = ref(false)
  const isDragging = ref(false)
  const isAllowDrop = ref(false)
  const dropPosition = ref<DropPosition>(0)

  const updateDropPosition = throttleByRaf((e: DragEvent) => {
    if (!refTitle.value) return

    const rect = refTitle.value.getBoundingClientRect()
    const offsetY = window.pageYOffset + rect.top
    const { pageY } = e
    const gapHeight = rect.height / 4
    const diff = pageY - offsetY

    dropPosition.value =
      // eslint-disable-next-line no-nested-ternary
      diff < gapHeight ? -1 : diff < rect.height - gapHeight ? 0 : 1
    // isAllowDrop.value = treeContext.allowDrop
    //   ? treeContext.allowDrop(key.value, dropPosition.value)
    //   : true
  })

  return {
    isDragOver,
    isDragging,
    isAllowDrop,
    dropPosition,
    setDragStatus(
      status: 'dragStart' | 'dragOver' | 'dragLeave' | 'dragEnd' | 'drop',
      e: DragEvent
    ) {
      switch (status) {
        case 'dragStart':
          isDragging.value = true
          dropPosition.value = 0
          //   treeContext.onDragStart && treeContext.onDragStart(key.value, e)
          break
        case 'dragEnd':
          isDragging.value = false
          isDragOver.value = false
          dropPosition.value = 0
          updateDropPosition.cancel()
          //   treeContext.onDragEnd && treeContext.onDragEnd(key.value, e)
          break
        case 'dragOver':
          isDragOver.value = true
          updateDropPosition(e)
          //   treeContext.onDragOver && treeContext.onDragOver(key.value, e)
          break
        case 'dragLeave':
          isDragOver.value = false
          dropPosition.value = 0
          updateDropPosition.cancel()
          //   treeContext.onDragLeave && treeContext.onDragLeave(key.value, e)
          break
        case 'drop':
          //   treeContext.onDrop &&
          //     treeContext.onDrop(key.value, dropPosition.value, e)
          isDragOver.value = false
          dropPosition.value = 0
          updateDropPosition.cancel()
          break
        default:
          break
      }
    }
  }
}
