<script lang="ts" setup generic="T">
import { computed, ref } from 'vue'

interface Props {
  list: T[]
  columns?: number
  itemSize?: number
  gap?: number
}

interface Emits {
  (e: 'change', list: T[]): void
}

const props = withDefaults(defineProps<Props>(), {
  columns: 5,
  itemSize: 32,
  gap: 8,
})

const emit = defineEmits<Emits>()

const draggingIndex = ref<number>(-1)
const hoverIndex = ref<number>(-1)
const startPos = ref({ x: 0, y: 0 })
const currentPos = ref({ x: 0, y: 0 })
const isDragging = ref(false)

const containerWidth = computed(() => props.columns * props.itemSize + (props.columns - 1) * props.gap)
const containerHeight = computed(() => {
  const rows = Math.ceil(props.list.length / props.columns)
  return rows * props.itemSize + (rows - 1) * props.gap
})

function getPosition(index: number) {
  const row = Math.floor(index / props.columns)
  const col = index % props.columns
  return {
    x: col * (props.itemSize + props.gap),
    y: row * (props.itemSize + props.gap),
  }
}

function getIndexFromPosition(x: number, y: number) {
  const col = Math.round(x / (props.itemSize + props.gap))
  const row = Math.round(y / (props.itemSize + props.gap))
  const index = row * props.columns + col
  return Math.max(0, Math.min(index, props.list.length - 1))
}

function handleStart(e: any, index: number) {
  const touch = e.touches?.[0] || e.changedTouches?.[0] || e
  const pos = getPosition(index)

  startPos.value = { x: touch.clientX - pos.x, y: touch.clientY - pos.y }
  currentPos.value = pos
  draggingIndex.value = index
  hoverIndex.value = index
  isDragging.value = true
}

function handleMove(e: any) {
  if (draggingIndex.value === -1) return

  const touch = e.touches?.[0] || e.changedTouches?.[0] || e
  const x = touch.clientX - startPos.value.x
  const y = touch.clientY - startPos.value.y

  currentPos.value = { x, y }

  const newIndex = getIndexFromPosition(x, y)
  if (newIndex !== hoverIndex.value && newIndex >= 0 && newIndex < props.list.length) {
    hoverIndex.value = newIndex
  }
}

function handleEnd() {
  if (draggingIndex.value === -1) return

  if (draggingIndex.value !== hoverIndex.value) {
    const newList = [...props.list]
    const [item] = newList.splice(draggingIndex.value, 1)
    newList.splice(hoverIndex.value, 0, item)
    emit('change', newList)
  }

  draggingIndex.value = -1
  hoverIndex.value = -1
  isDragging.value = false
}

function getItemStyle(index: number) {
  const pos = getPosition(index)
  const isBeingDragged = index === draggingIndex.value
  const isShifted = isDragging.value && index !== draggingIndex.value

  let finalPos = pos
  if (isShifted) {
    if (draggingIndex.value < hoverIndex.value && index > draggingIndex.value && index <= hoverIndex.value) {
      finalPos = getPosition(index - 1)
    } else if (draggingIndex.value > hoverIndex.value && index < draggingIndex.value && index >= hoverIndex.value) {
      finalPos = getPosition(index + 1)
    }
  }

  if (isBeingDragged) {
    finalPos = currentPos.value
  }

  return {
    width: `${props.itemSize}px`,
    height: `${props.itemSize}px`,
    transform: `translate(${finalPos.x}px, ${finalPos.y}px)`,
    zIndex: isBeingDragged ? 10 : 1,
    opacity: isBeingDragged ? 0.8 : 1,
    transition: isBeingDragged ? 'none' : 'transform 0.2s ease, opacity 0.2s ease',
  }
}
</script>

<template>
  <view
    class="drag-sort-container"
    :style="{ width: containerWidth + 'px', height: containerHeight + 'px', position: 'relative' }"
    @touchmove.stop.prevent="handleMove"
    @touchend.stop="handleEnd"
    @touchcancel.stop="handleEnd"
    @mousemove.stop="handleMove"
    @mouseup.stop="handleEnd"
    @mouseleave.stop="handleEnd"
  >
    <view
      v-for="(item, index) in list"
      :key="index"
      class="drag-item"
      :style="getItemStyle(index)"
      @touchstart.stop="handleStart($event, index)"
      @mousedown.stop="handleStart($event, index)"
    >
      <slot name="item" :item="item" :index="index">
        {{ item }}
      </slot>
    </view>
  </view>
</template>

<style scoped>
.drag-sort-container {
  position: relative;
  touch-action: none;
  user-select: none;
}

.drag-item {
  position: absolute;
  top: 0;
  left: 0;
  cursor: move;
  touch-action: none;
}
</style>
