import type { Ref } from 'vue'
import { ref } from 'vue'
import type { TreeV2Instance } from 'element-plus'

type NodeType<T> = T & { id: string; label: string; children?: NodeType<T>[] }
type LocateResult<T> = { node: NodeType<T>; parent: NodeType<T> | null; container: NodeType<T>[]; index: number } | null

type DropType = 'before' | 'after' | 'inside'

const getDropType = (e: DragEvent): DropType => {
  const el = e.currentTarget as HTMLElement
  const rect = el.getBoundingClientRect()
  const offset = e.clientY - rect.top
  if (offset < rect.height * 0.25) return 'before'
  if (offset > rect.height * 0.75) return 'after'
  return 'inside'
}

export const useDragAndDrop = <T>(params: {
  dataRef: Ref<NodeType<T>[]>;
  treeRef: Ref<TreeV2Instance | undefined>;
  expandedKeysRef: Ref<string[]>;
  locate: (id: string) => LocateResult<T>;
}) => {
  const { dataRef, treeRef, expandedKeysRef, locate } = params
  const draggingId = ref<string | null>(null)
  const dropState = ref<{ id: string | null; type: DropType | null }>({ id: null, type: null })

  const contains = (sourceId: string, targetId: string) => {
    const src = locate(sourceId)
    if (!src) return false
    const stack: NodeType<T>[] = src.node.children ? [...src.node.children] : []
    while (stack.length) {
      const n = stack.pop()!
      if (n.id === targetId) return true
      if (n.children && n.children.length) stack.push(...n.children)
    }
    return false
  }

  const getNodeStyles = (node: NodeType<T>) => {
    const base: Record<string, string> = { display: 'block', width: '100%'}
    if (dropState.value.id === node.id && dropState.value.type) {
      if (dropState.value.type === 'before') base.borderTop = '2px solid #409eff'
      else if (dropState.value.type === 'after') base.borderBottom = '2px solid #409eff'
      else base.background = 'rgba(64,158,255,0.15)'
    }
    return base
  }

  const onDragStart = (node: NodeType<T>, e: DragEvent) => {
    draggingId.value = node.id
    e.dataTransfer?.setData('text/plain', node.id)
    if (e.dataTransfer) e.dataTransfer.effectAllowed = 'move'
  }

  const onDragOver = (node: NodeType<T>, e: DragEvent) => {
    if (!draggingId.value) return
    if (draggingId.value === node.id) { dropState.value = { id: null, type: null }; return }
    const type = getDropType(e)
    dropState.value = { id: node.id, type }
    if (e.dataTransfer) e.dataTransfer.dropEffect = 'move'
  }

  const onDrop = (node: NodeType<T>, e: DragEvent) => {
    if (!draggingId.value) return
    const sourceId = draggingId.value
    const type = getDropType(e)
    if (sourceId === node.id) { onDragEnd(); return }
    if (type === 'inside' && contains(sourceId, node.id)) { onDragEnd(); return }
    const src = locate(sourceId)
    const tgt = locate(node.id)
    if (!src || !tgt) { onDragEnd(); return }
    src.container.splice(src.index, 1)
    if (type === 'inside') {
      const nextChildren = tgt.node.children ? [...tgt.node.children] : []
      nextChildren.push(src.node as NodeType<T>)
      tgt.node.children = nextChildren
      expandedKeysRef.value = Array.from(new Set([...expandedKeysRef.value, tgt.node.id]))
      treeRef.value?.setExpandedKeys(expandedKeysRef.value)
      treeRef.value?.setCurrentKey?.(src.node.id)
    } else {
      let insertIndex = tgt.index
      if (type === 'after') insertIndex = tgt.index + 1
      const same = src.container === tgt.container
      if (same && src.index < tgt.index && type === 'before') insertIndex -= 1
      if (same && src.index < tgt.index && type === 'after') insertIndex -= 1
      tgt.container.splice(insertIndex, 0, src.node)
      treeRef.value?.setCurrentKey?.(src.node.id)
    }
    dataRef.value = [...dataRef.value]
    onDragEnd()
  }

  const onDragEnd = () => {
    draggingId.value = null
    dropState.value = { id: null, type: null }
  }

  return { draggingId, dropState, getNodeStyles, onDragStart, onDragOver, onDrop, onDragEnd }
}

