<script setup lang="ts">
import { SwipeDirection } from '@vueuse/core'
import { useGesture } from '@vueuse/gesture'
import type { PermissiveMotionProperties } from '@vueuse/motion'
import { useReducedMotion } from '@vueuse/motion'
import type { mastodon } from 'masto'

const { media = [], threshold = 20 } = defineProps<{
  media?: mastodon.v1.MediaAttachment[]
  threshold?: number
}>()

const emit = defineEmits<{
  (event: 'close'): void
}>()

const { modelValue } = defineModel<{
  modelValue: number
}>()

const target = ref()

const animateTimeout = useTimeout(10)
const reduceMotion = process.server ? ref(false) : useReducedMotion()

const canAnimate = computed(() => !reduceMotion.value && animateTimeout.value)

const { motionProperties } = useMotionProperties(target, {
  cursor: 'grab',
  scale: 1,
  x: 0,
  y: 0,
})
const { set } = useSpring(motionProperties as Partial<PermissiveMotionProperties>)

function resetZoom() {
  set({ scale: 1 })
}

watch(modelValue, resetZoom)

const { width, height } = useElementSize(target)
const { isSwiping, lengthX, lengthY, direction } = useSwipe(target, {
  threshold: 5,
  passive: false,
  onSwipeEnd(e, direction) {
    // eslint-disable-next-line @typescript-eslint/no-use-before-define
    if (direction === SwipeDirection.RIGHT && Math.abs(distanceX.value) > threshold) {
      modelValue.value = Math.max(0, modelValue.value - 1)
      resetZoom()
    }

    // eslint-disable-next-line @typescript-eslint/no-use-before-define
    if (direction === SwipeDirection.LEFT && Math.abs(distanceX.value) > threshold) {
      modelValue.value = Math.min(media.length - 1, modelValue.value + 1)
      resetZoom()
    }

    // eslint-disable-next-line @typescript-eslint/no-use-before-define
    if (direction === SwipeDirection.UP && Math.abs(distanceY.value) > threshold)
      emit('close')
  },
})

useGesture({
  onPinch({ offset: [distance, angle] }) {
    set({ scale: Math.max(0.5, 1 + distance / 200) })
  },
  onMove({ movement: [x, y], dragging, pinching }) {
    if (dragging && !pinching)
      set({ x, y })
  },
}, {
  domTarget: target,
  eventOptions: {
    passive: true,
  },
})

const distanceX = computed(() => {
  if (width.value === 0)
    return 0

  if (!isSwiping.value || (direction.value !== SwipeDirection.LEFT && direction.value !== SwipeDirection.RIGHT))
    return modelValue.value * 100 * -1

  return (lengthX.value / width.value) * 100 * -1 + (modelValue.value * 100) * -1
})

const distanceY = computed(() => {
  if (height.value === 0 || !isSwiping.value || direction.value !== SwipeDirection.UP)
    return 0

  return (lengthY.value / height.value) * 100 * -1
})
</script>

<template>
  <div ref="target" flex flex-row max-h-full max-w-full overflow-hidden>
    <div flex :style="{ transform: `translateX(${distanceX}%) translateY(${distanceY}%)`, transition: isSwiping ? 'none' : canAnimate ? 'all 0.5s ease' : 'none' }">
      <div v-for="item in media" :key="item.id" p4 select-none w-full flex-shrink-0 flex flex-col place-items-center>
        <img max-h-full max-w-full :draggable="false" select-none :src="item.url || item.previewUrl" :alt="item.description || ''">
      </div>
    </div>
  </div>
</template>