<template>
    <div class="floating-image-window" :style="windowStyle" @mousedown="emit('bring-to-front', state.id)">
        <div v-for="handle in handles" :key="handle.dir" class="floating-image-window__resize-handle"
            :style="handle.style" @mousedown="handleResizeMouseDown($event, handle.dir)" />

        <div class="floating-image-window__header" @mousedown="handleHeaderMouseDown">
            <span class="floating-image-window__title">
                {{ t('图片预览') }}<span v-if="hasMultipleImages">（{{ state.activeIndex + 1 }}/{{ state.imageList.length
                    }}）</span>
            </span>
            <button class="floating-image-window__close" type="button"
                @mousedown.stop="emit('bring-to-front', state.id)" @click.stop="emit('close', state.id)">
                <img :src="iconClose" alt="close" />
            </button>
        </div>

        <div class="floating-image-window__content" @mousedown="handleImgMouseDown" @wheel.prevent="handleWheel">
            <img :src="state.src" alt="preview" draggable="false" class="floating-image-window__image"
                :style="imageStyle" />
        </div>

        <div class="floating-image-window__toolbar" @mousedown.stop="emit('bring-to-front', state.id)">
            <button v-if="hasMultipleImages" class="toolbar-btn toolbar-btn--text" type="button"
                :disabled="!hasPrevImage" @click="handlePrevImage">
                {{ t('上一张') }}
            </button>

            <button v-if="hasMultipleImages" class="toolbar-btn toolbar-btn--text" type="button"
                :disabled="!hasNextImage" @click="handleNextImage">
                {{ t('下一张') }}
            </button>

            <button class="toolbar-btn" type="button" @click="updatePatch({ zoom: state.zoom + 0.1 })">
                <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.5">
                    <circle cx="11" cy="11" r="8" />
                    <line x1="21" y1="21" x2="16.65" y2="16.65" />
                    <line x1="11" y1="8" x2="11" y2="14" />
                    <line x1="8" y1="11" x2="14" y2="11" />
                </svg>
            </button>

            <button class="toolbar-btn" type="button" @click="updatePatch({ zoom: Math.max(0.1, state.zoom - 0.1) })">
                <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.5">
                    <circle cx="11" cy="11" r="8" />
                    <line x1="21" y1="21" x2="16.65" y2="16.65" />
                    <line x1="8" y1="11" x2="14" y2="11" />
                </svg>
            </button>

            <button class="toolbar-btn" type="button" @click="updatePatch({ rotate: state.rotate - 90 })">
                <img :src="iconRotateLeft" alt="rotate left" />
            </button>

            <button class="toolbar-btn" type="button" @click="updatePatch({ rotate: state.rotate + 90 })">
                <img :src="iconRotateRight" alt="rotate right" />
            </button>

            <button class="toolbar-btn" type="button" @click="handleDownload">
                <img :src="iconDownload" alt="download" />
            </button>
        </div>
    </div>
</template>

<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, type CSSProperties } from 'vue'
import { useI18n } from 'vue-i18n'

import iconDownload from '../../../assets/icons/download-black.svg'
import iconClose from '../../../assets/icons/icon-close-black.svg'
import iconRotateLeft from '../../../assets/icons/rotate-left-black.svg'
import iconRotateRight from '../../../assets/icons/rotate-right-black.svg'
import type { FloatingWindowState } from '../useFloatingImageManager'

const HANDLE_SIZE = 8
const CORNER_SIZE = 16
const MIN_WIDTH = 200
const MIN_HEIGHT = 160

type ResizeDir = 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw'

const props = defineProps<{
    state: FloatingWindowState
}>()

const emit = defineEmits<{
    close: [id: string]
    'bring-to-front': [id: string]
    update: [id: string, patch: Partial<FloatingWindowState>]
}>()

const { t } = useI18n()

const dragMoveRef = ref<{
    startX: number
    startY: number
    origX: number
    origY: number
    origW: number
    origH: number
} | null>(null)

const resizeRef = ref<{
    dir: ResizeDir
    startX: number
    startY: number
    origX: number
    origY: number
    origW: number
    origH: number
} | null>(null)

const imgDragRef = ref<{
    startX: number
    startY: number
    origOX: number
    origOY: number
} | null>(null)

const windowStyle = computed<CSSProperties>(() => ({
    position: 'fixed',
    left: `${props.state.x}px`,
    top: `${props.state.y}px`,
    width: `${props.state.width}px`,
    height: `${props.state.height}px`,
    zIndex: props.state.zIndex,
}))

const imageStyle = computed<CSSProperties>(() => ({
    transform: `translate(${props.state.imgOffsetX}px, ${props.state.imgOffsetY}px) scale(${props.state.zoom}) rotate(${props.state.rotate}deg)`,
}))

const hasMultipleImages = computed(() => props.state.imageList.length > 1)
const hasPrevImage = computed(() => hasMultipleImages.value && props.state.activeIndex > 0)
const hasNextImage = computed(() => {
    return hasMultipleImages.value && props.state.activeIndex < props.state.imageList.length - 1
})

const handles: { dir: ResizeDir; style: CSSProperties }[] = [
    {
        dir: 'n',
        style: {
            top: 0,
            left: HANDLE_SIZE,
            right: HANDLE_SIZE,
            height: HANDLE_SIZE,
            cursor: 'n-resize',
        },
    },
    {
        dir: 's',
        style: {
            bottom: 0,
            left: HANDLE_SIZE,
            right: HANDLE_SIZE,
            height: HANDLE_SIZE,
            cursor: 's-resize',
        },
    },
    {
        dir: 'w',
        style: {
            left: 0,
            top: HANDLE_SIZE,
            bottom: HANDLE_SIZE,
            width: HANDLE_SIZE,
            cursor: 'w-resize',
        },
    },
    {
        dir: 'e',
        style: {
            right: 0,
            top: HANDLE_SIZE,
            bottom: HANDLE_SIZE,
            width: HANDLE_SIZE,
            cursor: 'e-resize',
        },
    },
    {
        dir: 'nw',
        style: {
            top: 0,
            left: 0,
            width: CORNER_SIZE,
            height: CORNER_SIZE,
            cursor: 'nw-resize',
        },
    },
    {
        dir: 'ne',
        style: {
            top: 0,
            right: 0,
            width: CORNER_SIZE,
            height: CORNER_SIZE,
            cursor: 'ne-resize',
        },
    },
    {
        dir: 'sw',
        style: {
            bottom: 0,
            left: 0,
            width: CORNER_SIZE,
            height: CORNER_SIZE,
            cursor: 'sw-resize',
        },
    },
    {
        dir: 'se',
        style: {
            bottom: 0,
            right: 0,
            width: CORNER_SIZE,
            height: CORNER_SIZE,
            cursor: 'se-resize',
        },
    },
]

const updatePatch = (patch: Partial<FloatingWindowState>) => {
    emit('update', props.state.id, patch)
}

const switchImage = (nextIndex: number) => {
    const nextImage = props.state.imageList[nextIndex]
    if (!nextImage) {
        return
    }

    updatePatch({
        activeIndex: nextIndex,
        activeImageId: nextImage.id,
        src: nextImage.src,
        zoom: 1,
        rotate: 0,
        imgOffsetX: 0,
        imgOffsetY: 0,
    })
}

const handlePrevImage = () => {
    if (!hasPrevImage.value) {
        return
    }

    switchImage(props.state.activeIndex - 1)
}

const handleNextImage = () => {
    if (!hasNextImage.value) {
        return
    }

    switchImage(props.state.activeIndex + 1)
}

const handleHeaderMouseDown = (event: MouseEvent) => {
    event.preventDefault()
    event.stopPropagation()
    emit('bring-to-front', props.state.id)
    dragMoveRef.value = {
        startX: event.clientX,
        startY: event.clientY,
        origX: props.state.x,
        origY: props.state.y,
        origW: props.state.width,
        origH: props.state.height,
    }
}

const handleResizeMouseDown = (event: MouseEvent, dir: ResizeDir) => {
    event.preventDefault()
    event.stopPropagation()
    emit('bring-to-front', props.state.id)
    resizeRef.value = {
        dir,
        startX: event.clientX,
        startY: event.clientY,
        origX: props.state.x,
        origY: props.state.y,
        origW: props.state.width,
        origH: props.state.height,
    }
}

const handleImgMouseDown = (event: MouseEvent) => {
    event.preventDefault()
    event.stopPropagation()
    emit('bring-to-front', props.state.id)
    imgDragRef.value = {
        startX: event.clientX,
        startY: event.clientY,
        origOX: props.state.imgOffsetX,
        origOY: props.state.imgOffsetY,
    }
}

const handleMouseMove = (event: MouseEvent) => {
    if (dragMoveRef.value) {
        const dx = event.clientX - dragMoveRef.value.startX
        const dy = event.clientY - dragMoveRef.value.startY
        const newX = dragMoveRef.value.origX + dx
        const newY = dragMoveRef.value.origY + dy
        const { origW, origH } = dragMoveRef.value

        const boundedX = Math.max(10, Math.min(window.innerWidth - origW - 10, newX))
        const boundedY = Math.max(10, Math.min(window.innerHeight - origH - 10, newY))

        updatePatch({
            x: boundedX,
            y: boundedY,
        })
        return
    }

    if (resizeRef.value) {
        const { dir, startX, startY, origX, origY, origW, origH } = resizeRef.value
        const dx = event.clientX - startX
        const dy = event.clientY - startY
        let nextX = origX
        let nextY = origY
        let nextWidth = origW
        let nextHeight = origH

        if (dir?.includes('e')) {
            nextWidth = Math.max(MIN_WIDTH, origW + dx)
            nextWidth = Math.min(nextWidth, window.innerWidth - 10 - nextX)
        }
        if (dir?.includes('s')) {
            nextHeight = Math.max(MIN_HEIGHT, origH + dy)
            nextHeight = Math.min(nextHeight, window.innerHeight - 10 - nextY)
        }
        if (dir?.includes('w')) {
            nextX = origX + dx
            nextX = Math.max(10, nextX)
            nextX = Math.min(nextX, origX + origW - MIN_WIDTH)
            nextWidth = origX + origW - nextX
        }
        if (dir?.includes('n')) {
            nextY = origY + dy
            nextY = Math.max(10, nextY)
            nextY = Math.min(nextY, origY + origH - MIN_HEIGHT)
            nextHeight = origY + origH - nextY
        }

        updatePatch({
            x: nextX,
            y: nextY,
            width: nextWidth,
            height: nextHeight,
        })
        return
    }

    if (imgDragRef.value) {
        const dx = event.clientX - imgDragRef.value.startX
        const dy = event.clientY - imgDragRef.value.startY
        updatePatch({
            imgOffsetX: imgDragRef.value.origOX + dx,
            imgOffsetY: imgDragRef.value.origOY + dy,
        })
    }
}

const handleMouseUp = () => {
    dragMoveRef.value = null
    resizeRef.value = null
    imgDragRef.value = null
}

const handleWheel = (event: WheelEvent) => {
    event.preventDefault()
    updatePatch({
        zoom: Math.max(0.1, props.state.zoom + (event.deltaY > 0 ? -0.1 : 0.1)),
    })
}

const getDownloadFileName = () => {
    try {
        const pathname = new URL(props.state.src, window.location.href).pathname
        const fileName = pathname.split('/').pop()
        return fileName || 'image'
    } catch {
        return 'image'
    }
}

const handleDownload = async () => {
    if (!props.state.src) {
        return
    }

    const fileName = getDownloadFileName()

    try {
        const response = await fetch(props.state.src, {
            mode: 'cors',
            headers: new Headers({
                'Content-Type': 'application/x-www-form-urlencoded',
            }),
        })
        const blob = await response.blob()
        const anchor = document.createElement('a')
        const url = window.URL.createObjectURL(blob)
        anchor.href = url
        anchor.download = fileName
        anchor.click()
        window.URL.revokeObjectURL(url)
    } catch {
        const anchor = document.createElement('a')
        anchor.href = props.state.src
        anchor.target = '_blank'
        anchor.download = fileName
        anchor.click()
    }
}

onMounted(() => {
    window.addEventListener('mousemove', handleMouseMove)
    window.addEventListener('mouseup', handleMouseUp)
})

onBeforeUnmount(() => {
    window.removeEventListener('mousemove', handleMouseMove)
    window.removeEventListener('mouseup', handleMouseUp)
})
</script>

<style scoped>
.floating-image-window {
    position: fixed;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.28);
    border-radius: 8px;
    background: #f0f0f0;
    display: flex;
    flex-direction: column;
    user-select: none;
    overflow: visible;
}

.floating-image-window__resize-handle {
    position: absolute;
}

.floating-image-window__header {
    height: 36px;
    background: rgb(227, 227, 226);
    border-radius: 8px 8px 0 0;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 10px;
    cursor: move;
    flex-shrink: 0;
    z-index: 3;
}

.floating-image-window__title {
    color: #000;
    font-size: 12px;
    pointer-events: none;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: calc(100% - 36px);
}

.floating-image-window__close {
    width: 24px;
    height: 24px;
    border-radius: 50%;
    border: none;
    background: rgba(255, 255, 255, 0.15);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    flex-shrink: 0;
    padding: 0;
}

.floating-image-window__close img,
.toolbar-btn img {
    width: 22px;
    height: 22px;
}

.floating-image-window__close img {
    width: 12px;
    height: 12px;
}

.floating-image-window__content {
    flex: 1;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: grab;
    position: relative;
}

.floating-image-window__image {
    max-width: 100%;
    max-height: 100%;
    transition: transform 0.05s;
    pointer-events: none;
}

.floating-image-window__toolbar {
    height: 44px;
    background: rgb(227, 227, 226);
    border-radius: 0 0 8px 8px;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 24px;
    flex-shrink: 0;
    z-index: 3;
}

.toolbar-btn {
    width: 32px;
    height: 32px;
    border: none;
    border-radius: 6px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: background 0.15s;
    background: transparent;
    padding: 0;
}

.toolbar-btn--text {
    width: auto;
    min-width: 56px;
    padding: 0 10px;
    font-size: 12px;
}

.toolbar-btn:disabled {
    cursor: not-allowed;
    opacity: 0.4;
}

.toolbar-btn:hover {
    background: rgba(0, 0, 0, 0.06);
}
</style>