import type { ComputedRef, Ref } from 'vue'
import { unrefElement } from '@vueuse/core'
import { onBeforeUnmount, onMounted, reactive, ref, watchEffect } from 'vue'
/**
 * fork: https://github.com/element-plus/element-plus/blob/dev/packages/hooks/use-draggable/index.ts
 * reference: https://github.com/vbenjs/vue-vben-admin/blob/main/packages/%40core/ui-kit/popup-ui/src/modal/use-modal-draggable.ts
 */

export function useDraggable(
  targetRef: Ref<HTMLElement | undefined>,
  dragRef: Ref<HTMLElement | undefined>,
  draggable: ComputedRef<boolean>,
  overflow?: ComputedRef<boolean>,
) {
  const isDragging = ref(false)
  const transform = reactive({
    offsetX: 0,
    offsetY: 0,
  })

  const onMousedown = (e: MouseEvent) => {
    const downX = e.clientX
    const downY = e.clientY
    const { offsetX, offsetY } = transform

    const targetRect = targetRef.value!.getBoundingClientRect()
    const targetLeft = targetRect.left
    const targetTop = targetRect.top
    const targetWidth = targetRect.width
    const targetHeight = targetRect.height

    const clientWidth = document.documentElement.clientWidth
    const clientHeight = document.documentElement.clientHeight

    const minLeft = -targetLeft + offsetX
    const minTop = -targetTop + offsetY
    const maxLeft = clientWidth - targetLeft - targetWidth + offsetX
    const maxTop = clientHeight - targetTop - targetHeight + offsetY

    const onMousemove = (e: MouseEvent) => {
      let moveX = offsetX + e.clientX - downX
      let moveY = offsetY + e.clientY - downY

      if (!overflow?.value) {
        moveX = Math.min(Math.max(moveX, minLeft), maxLeft)
        moveY = Math.min(Math.max(moveY, minTop), maxTop)
      }

      transform.offsetX = moveX
      transform.offsetY = moveY

      if (targetRef.value) {
        isDragging.value = true
        targetRef.value.style.transform = `translate(${moveX}px, ${moveY}px)`
      }
    }

    const onMouseup = () => {
      isDragging.value = false
      document.removeEventListener('mousemove', onMousemove)
      document.removeEventListener('mouseup', onMouseup)
    }

    document.addEventListener('mousemove', onMousemove)
    document.addEventListener('mouseup', onMouseup)
  }

  const onDraggable = () => {
    const dragRefElement = unrefElement(dragRef)
    if (dragRefElement && targetRef.value) {
      dragRefElement.addEventListener('mousedown', onMousedown)
    }
  }

  const offDraggable = () => {
    const dragRefElement = unrefElement(dragRef)
    if (dragRefElement && targetRef.value) {
      dragRefElement.removeEventListener('mousedown', onMousedown)
    }
  }

  const resetPosition = () => {
    transform.offsetX = 0
    transform.offsetY = 0
    if (targetRef.value) {
      targetRef.value.style.transform = 'none'
    }
  }

  onMounted(() => {
    watchEffect(() => {
      if (draggable.value) {
        onDraggable()
      }
      else {
        offDraggable()
      }
    })
  })

  onBeforeUnmount(() => {
    offDraggable()
  })

  return {
    isDragging,
    transform,
    resetPosition,
  }
}
