import { ref, onUnmounted } from 'vue'

export interface DraggableEvent extends MouseEvent {
	oldIndex: number
	newIndex: number
	item?: any
}

export interface DraggableOptions {
	group?: string
	animation?: number
	disabled?: boolean
	handle?: string
	ghostClass?: string
	dragClass?: string
	mode?: 'ghost' | 'line'
	dropIndicatorClass?: string
	items?: any[]
	onStart?: (event: MouseEvent) => void
	onEnd?: (event: DraggableEvent) => void
}

export interface DraggableElement extends HTMLElement {
	__drag_group?: string
	__drag_index?: number
	__drag_list?: DraggableElement[]
	__intended_index?: number
	__drag_data?: any
}

export function useDraggable(options: DraggableOptions = {}) {
	const isDragging = ref(false)
	const dragElement = ref<DraggableElement | null>(null)
	const ghostElement = ref<HTMLElement | null>(null)
	const dropIndicator = ref<HTMLElement | null>(null)
	const dropTarget = ref<DraggableElement | null>(null)

	// Only run in browser environment
	const isBrowser = typeof window !== 'undefined'

	// Move style injection into setup
	if (isBrowser) {
		const existingStyle = document.getElementById('draggable-style')
		if (!existingStyle) {
			const style = document.createElement('style')
			style.id = 'draggable-style'
			style.textContent = `
				.draggable-ghost {
					opacity: 0.5;
					box-shadow: 0 2px 10px rgba(0,0,0,0.1);
					transition: transform 0.1s;
				}
			`
			document.head.appendChild(style)
		}
	}

	const defaultOptions: DraggableOptions = {
		animation: 150,
		mode: 'line',
		ghostClass: 'draggable-ghost',
		dragClass: 'draggable-dragging',
		dropIndicatorClass: 'draggable-drop-indicator',
		...options
	}

	let startX = 0
	let startY = 0
	let initialX = 0
	let initialY = 0
	let shiftAmount = 0

	let cleanupFn: (() => void) | undefined
	let scrollRAF: number | null = null

	let lastDropIndex = -1
	let lastUpdateTime = 0
	const UPDATE_THRESHOLD = 150 // ms between updates
	const POSITION_CACHE = new Map<number, { top: number, bottom: number }>()

	function createGhost(el: DraggableElement) {
		if (!isBrowser) { return null }
		const ghost = el.cloneNode(true) as HTMLElement
		const rect = el.getBoundingClientRect()
		ghost.style.position = 'fixed'
		ghost.style.margin = '0'
		ghost.style.top = `${rect.top}px`
		ghost.style.left = `${rect.left}px`
		ghost.style.width = `${rect.width}px`
		ghost.style.height = `${rect.height}px`
		ghost.style.transform = 'translate3d(0, 0, 0)'
		ghost.style.pointerEvents = 'none'
		ghost.style.zIndex = '9999'
		ghost.style.cursor = 'grabbing'
		ghost.style.willChange = 'transform'
		ghost.classList.add(defaultOptions.ghostClass!)
		document.body.appendChild(ghost)
		return ghost
	}

	function createDropIndicator(el: DraggableElement) {
		if (!isBrowser) { return null }
		const line = document.createElement('div')
		line.classList.add(defaultOptions.dropIndicatorClass!)
		line.style.position = 'fixed'
		line.style.pointerEvents = 'none'
		line.style.zIndex = '9999'
		line.style.height = '2px'
		line.style.background = 'var(--primary-color, #0066ff)'
		line.style.boxShadow = '0 0 4px rgba(0, 102, 255, 0.5)'
		line.style.transition = 'all 0.15s ease'
		line.style.display = 'none' // Start hidden

		// Add the dot indicator
		const dot = document.createElement('div')
		dot.style.position = 'absolute'
		dot.style.left = '-4px'
		dot.style.top = '-3px'
		dot.style.width = '8px'
		dot.style.height = '8px'
		dot.style.borderRadius = '50%'
		dot.style.background = 'var(--primary-color, #0066ff)'

		line.appendChild(dot)
		document.body.appendChild(line)
		return line
	}

	function updateDropIndicator(target: DraggableElement, isAfter: boolean) {
		if (!dropIndicator.value) { return }

		const rect = target.getBoundingClientRect()
		dropIndicator.value.style.display = 'block'
		dropIndicator.value.style.width = `${rect.width}px`
		dropIndicator.value.style.left = `${rect.left}px`

		// Move indicator to match the actual drop position
		if (isAfter) {
			dropIndicator.value.style.top = `${rect.bottom}px`
		} else {
			dropIndicator.value.style.top = `${rect.top}px`
		}
	}

	function findDraggableParent(element: Element | null): DraggableElement | null {
		let current = element
		while (current && (!('__drag_group' in current) || (current as any).dataset.draggable === 'false')) {
			current = current.parentElement
		}
		return current as DraggableElement | null
	}

	function handleGhostMode(dragEl: DraggableElement) {
		dragEl.style.opacity = '0.4'
		dragEl.style.transform = 'scale(0.95)'
		return dragEl.offsetHeight
	}

	function handleLineMode(dragEl: DraggableElement) {
		dragEl.style.opacity = '0.5'
		return 2
	}

	// function resetDragStyles(dragEl: DraggableElement) {
	// 	if (defaultOptions.mode === 'ghost') {
	// 		dragEl.style.opacity = ''
	// 		dragEl.style.transform = ''
	// 	} else {
	// 		dragEl.style.opacity = ''
	// 	}
	// }

	function cacheElementPositions(draggedList: DraggableElement[]) {
		POSITION_CACHE.clear()
		draggedList.forEach((el, index) => {
			const rect = el.getBoundingClientRect()
			POSITION_CACHE.set(index, {
				top: rect.top,
				bottom: rect.bottom
			})
		})
	}

	function calculateDropIndex(e: MouseEvent, draggedList: DraggableElement[], dragIndex: number, newDropTarget: DraggableElement) {
		const now = Date.now()
		if (now - lastUpdateTime < UPDATE_THRESHOLD) {
			// Return last calculated index if we're within threshold
			return lastDropIndex !== -1 ? lastDropIndex : dragIndex
		}

		// Use cached positions if available, otherwise calculate new ones
		if (POSITION_CACHE.size === 0) {
			cacheElementPositions(draggedList)
		}

		const mouseY = e.clientY
		const targetIndex = draggedList.indexOf(newDropTarget)
		const targetPos = POSITION_CACHE.get(targetIndex)

		if (!targetPos) { return dragIndex }

		const elementHeight = targetPos.bottom - targetPos.top
		const relativeY = mouseY - targetPos.top

		// Calculate sticky zones
		const upperSticky = elementHeight * 0.3
		const lowerSticky = elementHeight * 0.7

		let dropIndex = targetIndex

		// Different behavior based on context
		if (targetIndex === dragIndex) {
			// On original element - require significant movement
			if (relativeY > lowerSticky) { dropIndex += 1 }
		} else if (targetIndex === lastDropIndex) {
			// On previously targeted element - use sticky zones
			if (targetIndex > dragIndex) {
				if (relativeY < upperSticky) { dropIndex = targetIndex }
				else { dropIndex = targetIndex + 1 }
			} else {
				if (relativeY > lowerSticky) { dropIndex = targetIndex + 1 }
				else { dropIndex = targetIndex }
			}
		} else {
			// On new element - use 60/40 split
			if (relativeY > elementHeight * 0.6) { dropIndex += 1 }
		}

		// Update state
		lastDropIndex = dropIndex
		lastUpdateTime = now

		return dropIndex
	}

	function cleanup() {
		if (!isBrowser) { return }

		document.removeEventListener('mousemove', onDragMove)
		document.removeEventListener('mouseup', onDragEnd)
		document.removeEventListener('keydown', onKeyDown)
		document.removeEventListener('touchmove', onTouchMove)
		document.removeEventListener('touchend', onTouchEnd)
		document.removeEventListener('touchcancel', onTouchEnd)
		window.removeEventListener('blur', cleanup)

		// Remove elements with animation
		if (ghostElement.value) {
			ghostElement.value.style.opacity = '0'
			ghostElement.value.style.transform += ' scale(0.8)'
			setTimeout(() => {
				ghostElement.value?.parentNode?.removeChild(ghostElement.value)
				ghostElement.value = null
			}, 150)
		}

		if (dropIndicator.value) {
			dropIndicator.value.style.opacity = '0'
			setTimeout(() => {
				dropIndicator.value?.parentNode?.removeChild(dropIndicator.value)
				dropIndicator.value = null
			}, 150)
		}

		// Reset all elements
		if (dragElement.value) {
			const list = dragElement.value.__drag_list
			list?.forEach((el) => {
				el.style.transition = ''
				el.style.transform = ''
				el.style.opacity = ''
			})
			dragElement.value.classList.remove(defaultOptions.dragClass!)
			dragElement.value = null
		}

		isDragging.value = false
		dropTarget.value = null

		if (isBrowser) {
			document.body.style.userSelect = ''
			document.body.style.webkitUserSelect = ''
			document.body.style.cursor = ''
		}

		if (scrollRAF) {
			cancelAnimationFrame(scrollRAF)
			scrollRAF = null
		}

		lastDropIndex = -1
		lastUpdateTime = 0
		POSITION_CACHE.clear()
	}

	function onKeyDown(e: KeyboardEvent) {
		if (e.key === 'Escape' && isDragging.value) {
			cleanup()
		}
	}

	function onDragStart(e: MouseEvent) {
		if (!isBrowser || defaultOptions.disabled) { return }
		if (e.button !== 0) { return } // Only left mouse button
		if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) { return }

		const dragEl = findDraggableParent(e.target as Element)
		if (!dragEl || dragEl.dataset.draggable === 'false') { return }

		// If handle is specified, check if clicked element matches
		if (defaultOptions.handle) {
			const handleEl = (e.target as Element).closest(defaultOptions.handle)
			if (!handleEl || !dragEl.contains(handleEl)) { return }
		}

		e.preventDefault()
		isDragging.value = true
		dragElement.value = dragEl

		// Store initial positions
		startX = e.clientX
		startY = e.clientY
		const rect = dragEl.getBoundingClientRect()
		initialX = rect.left
		initialY = rect.top

		// Create ghost element that follows mouse
		ghostElement.value = createGhost(dragEl)
		if (ghostElement.value) {
			ghostElement.value.style.left = `${initialX}px`
			ghostElement.value.style.top = `${initialY}px`
		}

		// Only create drop indicator in line mode
		if (defaultOptions.mode === 'line') {
			dropIndicator.value = createDropIndicator(dragEl)
		}

		// Apply mode-specific styles and get shift amount
		dragEl.classList.add(defaultOptions.dragClass!)
		shiftAmount = defaultOptions.mode === 'ghost'
			? handleGhostMode(dragEl)
			: handleLineMode(dragEl)

		defaultOptions.onStart?.(e)

		// Prevent text selection during drag
		if (isBrowser) {
			document.body.style.userSelect = 'none'
			document.body.style.webkitUserSelect = 'none'
			document.body.style.cursor = 'grabbing'

			window.addEventListener('blur', cleanup)
			document.addEventListener('mousemove', onDragMove)
			document.addEventListener('mouseup', onDragEnd)
			document.addEventListener('keydown', onKeyDown)
		}
	}

	function findScrollParent(element: Element): Element {
		let parent = element.parentElement
		while (parent) {
			const { overflow, overflowY } = window.getComputedStyle(parent)
			if (/auto|scroll/.test(overflow + overflowY)) {
				return parent
			}
			parent = parent.parentElement
		}
		return document.scrollingElement || document.documentElement || document.body
	}

	function autoScroll(e: MouseEvent) {
		if (!dragElement.value) { return }
		if (scrollRAF) { cancelAnimationFrame(scrollRAF) }

		scrollRAF = requestAnimationFrame(() => {
			const scrollParent = findScrollParent(dragElement.value!) as HTMLElement
			const rect = scrollParent.getBoundingClientRect()
			const margin = 50
			const speed = Math.min(20, rect.height * 0.1) // Adjust speed based on container height

			const { scrollTop } = scrollParent
			const maxScroll = scrollParent.scrollHeight - scrollParent.clientHeight

			// Use requestAnimationFrame for smoother scrolling
			if (e.clientY - rect.top < margin) {
				const newScroll = Math.max(0, scrollTop - speed)
				scrollParent.scrollTop = newScroll
			} else if (rect.bottom - e.clientY < margin) {
				const newScroll = Math.min(maxScroll, scrollTop + speed)
				scrollParent.scrollTop = newScroll
			}
		})
	}

	function onDragMove(e: MouseEvent) {
		if (!isDragging.value || !dragElement.value) { return }

		// Move ghost element with cursor
		if (ghostElement.value) {
			const dx = e.clientX - startX
			const dy = e.clientY - startY
			ghostElement.value.style.transform = `translate3d(${dx}px, ${dy}px, 0)`
		}

		// Get the draggable list and current positions
		const list = dragElement.value.__drag_list!
		const draggedList = list.filter(el => el.dataset.draggable !== 'false')
		const dragIndex = draggedList.indexOf(dragElement.value)

		// Find the element at cursor position
		const elementAtPoint = document.elementFromPoint(e.clientX, e.clientY)
		const newDropTarget = findDraggableParent(elementAtPoint)

		// Validate drop target
		if (!newDropTarget || newDropTarget.__drag_group !== dragElement.value.__drag_group) {
			if (dropIndicator.value) {
				dropIndicator.value.style.display = 'none'
			}
			return
		}

		const dropIndex = calculateDropIndex(e, draggedList, dragIndex, newDropTarget)

		// Only update if position actually changed
		if (dropTarget.value !== newDropTarget || dropTarget.value.__intended_index !== dropIndex) {
			dropTarget.value = newDropTarget
			dropTarget.value.__intended_index = dropIndex

			// Update line indicator
			if (dropIndicator.value && defaultOptions.mode === 'line') {
				const targetIndex = draggedList.indexOf(newDropTarget)
				const isAfter = dropIndex > targetIndex
				updateDropIndicator(newDropTarget, isAfter)
			}

			// Update element positions
			const shift = shiftAmount
			draggedList.forEach((el) => {
				// Use transform3d for better performance
				el.style.transition = `transform ${defaultOptions.animation}ms cubic-bezier(0.2, 0, 0, 1)`
				el.style.transform = 'translate3d(0, 0, 0)'

				if (el === dragElement.value) {
					if (defaultOptions.mode === 'ghost') {
						const offset = (dropIndex - dragIndex) * shift
						el.style.transform = `translate3d(0, ${offset}px, 0)`
					}
				} else {
					const currentIndex = draggedList.indexOf(el)
					if (dragIndex < dropIndex) {
						if (currentIndex > dragIndex && currentIndex <= dropIndex) {
							el.style.transform = `translate3d(0, -${shift}px, 0)`
						}
					} else if (dragIndex > dropIndex) {
						if (currentIndex >= dropIndex && currentIndex < dragIndex) {
							el.style.transform = `translate3d(0, ${shift}px, 0)`
						}
					}
				}
			})
		}

		autoScroll(e)
	}

	function onDragEnd(e: MouseEvent) {
		if (!isDragging.value || !dragElement.value || !dropTarget.value) {
			cleanup()
			return
		}

		const list = dragElement.value.__drag_list
		if (!list || !list.length) {
			cleanup()
			return
		}

		const draggedList = list.filter(el => el.dataset.draggable !== 'false')
		const actualDragIndex = draggedList.indexOf(dragElement.value)
		const actualDropIndex = dropTarget.value.__intended_index!

		if (actualDragIndex !== -1 && actualDropIndex !== -1) {
			list.forEach((el) => {
				el.style.transition = 'none'
				el.style.transform = ''
			})

			void document.body.offsetHeight

			const dragEvent = new MouseEvent('mouseup', e) as DraggableEvent
			dragEvent.oldIndex = actualDragIndex
			dragEvent.newIndex = actualDropIndex
			dragEvent.item = dragElement.value.__drag_data

			defaultOptions.onEnd?.(dragEvent)
		}

		cleanup()
	}

	function onTouchStart(e: TouchEvent) {
		if (!isBrowser || defaultOptions.disabled || !e.target) { return }

		const touch = e.touches[0]
		if (!touch) { return }

		const target = e.target as Element
		const draggableParent = findDraggableParent(target)

		if (!draggableParent) { return }

		const { handle } = defaultOptions
		if (handle && !target.closest(handle)) { return }

		e.preventDefault() // Prevent scrolling
		isDragging.value = true
		dragElement.value = draggableParent

		startX = touch.clientX
		startY = touch.clientY
		const rect = draggableParent.getBoundingClientRect()
		initialX = rect.left
		initialY = rect.top

		// Rest of the setup same as onDragStart...
		ghostElement.value = createGhost(draggableParent)
		if (ghostElement.value) {
			ghostElement.value.style.left = `${initialX}px`
			ghostElement.value.style.top = `${initialY}px`
		}

		// Only create drop indicator in line mode
		if (defaultOptions.mode === 'line') {
			dropIndicator.value = createDropIndicator(draggableParent)
		}

		// Apply mode-specific styles and get shift amount
		dragElement.value.classList.add(defaultOptions.dragClass!)
		shiftAmount = defaultOptions.mode === 'ghost'
			? handleGhostMode(dragElement.value)
			: handleLineMode(dragElement.value)

		defaultOptions.onStart?.(e as unknown as MouseEvent)

		if (isBrowser) {
			// Prevent text selection during drag
			document.body.style.userSelect = 'none'
			document.body.style.webkitUserSelect = 'none'
			document.body.style.cursor = 'grabbing'

			window.addEventListener('blur', cleanup)
			document.addEventListener('touchmove', onTouchMove, { passive: false })
			document.addEventListener('touchend', onTouchEnd)
			document.addEventListener('touchcancel', onTouchEnd)
		}
	}

	function onTouchMove(e: TouchEvent) {
		e.preventDefault() // Prevent scrolling
		const touch = e.touches[0]
		if (!touch) { return }

		const mouseEvent = {
			clientX: touch.clientX,
			clientY: touch.clientY,
			target: document.elementFromPoint(touch.clientX, touch.clientY)
		} as MouseEvent

		onDragMove(mouseEvent)
		autoScroll(mouseEvent)
	}

	function onTouchEnd(e: TouchEvent) {
		const touch = e.changedTouches[0]
		if (!touch) { return }

		const mouseEvent = {
			clientX: touch.clientX,
			clientY: touch.clientY,
			target: document.elementFromPoint(touch.clientX, touch.clientY)
		} as MouseEvent

		onDragEnd(mouseEvent)
		document.removeEventListener('touchmove', onTouchMove)
		document.removeEventListener('touchend', onTouchEnd)
		document.removeEventListener('touchcancel', onTouchEnd)
	}

	function initDraggableContainer(container: HTMLElement) {
		if (!isBrowser) { return }
		cleanupFn?.()

		// Reset state
		isDragging.value = false
		dragElement.value = null
		ghostElement.value = null
		dropIndicator.value = null
		dropTarget.value = null

		const elements = Array.from(container.children) as DraggableElement[]

		elements.forEach((el) => {
			delete el.__drag_group
			delete el.__drag_index
			delete el.__drag_list
		})

		let currentIndex = 0
		elements.forEach((el, index) => {
			if (el.dataset.draggable !== 'false') {
				el.style.userSelect = 'none'
				el.style.webkitUserSelect = 'none'
				el.__drag_group = options.group
				el.__drag_index = currentIndex++
				el.__drag_list = elements.filter(e => e.dataset.draggable !== 'false')

				if (options.items) {
					el.__drag_data = options.items[index]
				}

				el.addEventListener('mousedown', onDragStart)
				el.addEventListener('touchstart', onTouchStart)
			}
		})

		cleanupFn = () => {
			elements.forEach((el) => {
				el.removeEventListener('mousedown', onDragStart)
				el.removeEventListener('touchstart', onTouchStart)
				delete el.__drag_group
				delete el.__drag_index
				delete el.__drag_list
			})
			cleanup()
		}
	}

	// Automatically clean up when component is unmounted
	onUnmounted(() => {
		cleanupFn?.()
	})

	return {
		isDragging,
		dragElement,
		initDraggableContainer
	}
}
