<script lang="tsx">
    import { computed, defineComponent, reactive, ref, unref, watchEffect } from 'vue'
    import { CloseOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons-vue'
    import resumeSvg from '/@/assets/svg/preview/resume.svg'
    import rotateSvg from '/@/assets/svg/preview/p-rotate.svg'
    import scaleSvg from '/@/assets/svg/preview/scale.svg'
    import unScaleSvg from '/@/assets/svg/preview/unscale.svg'
    import unRotateSvg from '/@/assets/svg/preview/unrotate.svg'

    enum StatueEnum {
        LOADING,
        DONE,
        FAIL,
    }
    interface ImgState {
        currentUrl: string
        imgScale: number
        imgRotate: number
        imgTop: number
        imgLeft: number
        currentIndex: number
        status: StatueEnum
        moveX: number
        moveY: number
        show: boolean
    }
    const props = {
        show: {
            type: Boolean as PropType<boolean>,
            default: false,
        },
        imageList: {
            type: [Array] as PropType<string[]>,
            default: null,
        },
        index: {
            type: Number as PropType<number>,
            default: 0,
        },
        scaleStep: {
            type: Number as PropType<number>,
        },
        defaultWidth: {
            type: Number as PropType<number>,
        },
        maskClosable: {
            type: Boolean as PropType<boolean>,
        },
        rememberState: {
            type: Boolean as PropType<boolean>,
        },
    }

    const prefixCls = 'img-preview'
    export default defineComponent({
        name: 'ImagePreview',
        props,
        emits: ['img-load', 'img-error'],
        setup(props, { expose, emit }) {
            interface stateInfo {
                scale: number
                rotate: number
                top: number
                left: number
            }
            const stateMap = new Map<string, stateInfo>()
            const imgState = reactive<ImgState>({
                currentUrl: '',
                imgScale: 1,
                imgRotate: 0,
                imgTop: 0,
                imgLeft: 0,
                status: StatueEnum.LOADING,
                currentIndex: 0,
                moveX: 0,
                moveY: 0,
                show: props.show,
            })

            const wrapElRef = ref<HTMLDivElement | null>(null)
            const imgElRef = ref<HTMLImageElement | null>(null)

            // 初始化
            function init() {
                initMouseWheel()
                const { index, imageList } = props

                if (!imageList || !imageList.length) {
                    throw new Error('imageList is undefined')
                }
                imgState.currentIndex = index
                handleIChangeImage(imageList[index])
            }

            // 重置
            function initState() {
                imgState.imgScale = 1
                imgState.imgRotate = 0
                imgState.imgTop = 0
                imgState.imgLeft = 0
            }

            // 初始化鼠标滚轮事件
            function initMouseWheel() {
                const wrapEl = unref(wrapElRef)
                if (!wrapEl) {
                    return
                }
                ;(wrapEl as any).onmousewheel = scrollFunc
                // 火狐浏览器没有onmousewheel事件，用DOMMouseScroll代替
                document.body.addEventListener('DOMMouseScroll', scrollFunc)
                // 禁止火狐浏览器下拖拽图片的默认事件
                document.ondragstart = function () {
                    return false
                }
            }

            const getScaleStep = computed(() => {
                const scaleStep = props?.scaleStep ?? 0
                if (scaleStep ?? (scaleStep > 0 && scaleStep < 100)) {
                    return scaleStep / 100
                } else {
                    return imgState.imgScale / 10
                }
            })

            // 监听鼠标滚轮
            function scrollFunc(e: any) {
                e = e || window.event
                e.delta = e.wheelDelta || -e.detail

                e.preventDefault()
                if (e.delta > 0) {
                    // 滑轮向上滚动
                    scaleFunc(getScaleStep.value)
                }
                if (e.delta < 0) {
                    // 滑轮向下滚动
                    scaleFunc(-getScaleStep.value)
                }
            }
            // 缩放函数
            function scaleFunc(num: number) {
                if (imgState.imgScale <= 0.2 && num < 0) return
                imgState.imgScale += num
            }

            // 旋转图片
            function rotateFunc(deg: number) {
                imgState.imgRotate += deg
            }

            // 鼠标事件
            function handleMouseUp() {
                const imgEl = unref(imgElRef)
                if (!imgEl) return
                imgEl.onmousemove = null
            }

            // 更换图片
            function handleIChangeImage(url: string) {
                imgState.status = StatueEnum.LOADING
                const img = new Image()
                img.src = url
                img.onload = (e: Event) => {
                    if (imgState.currentUrl !== url) {
                        const ele: any[] = e.composedPath()
                        if (props.rememberState) {
                            // 保存当前图片的缩放信息
                            stateMap.set(imgState.currentUrl, {
                                scale: imgState.imgScale,
                                top: imgState.imgTop,
                                left: imgState.imgLeft,
                                rotate: imgState.imgRotate,
                            })
                            // 如果之前已存储缩放信息，就应用
                            const stateInfo = stateMap.get(url)
                            if (stateInfo) {
                                imgState.imgScale = stateInfo.scale
                                imgState.imgTop = stateInfo.top
                                imgState.imgRotate = stateInfo.rotate
                                imgState.imgLeft = stateInfo.left
                            } else {
                                initState()
                                if (props.defaultWidth) {
                                    imgState.imgScale = props.defaultWidth / ele[0].naturalWidth
                                }
                            }
                        } else {
                            if (props.defaultWidth) {
                                imgState.imgScale = props.defaultWidth / ele[0].naturalWidth
                            }
                        }

                        ele &&
                            emit('img-load', {
                                index: imgState.currentIndex,
                                dom: ele[0] as HTMLImageElement,
                                url,
                            })
                    }
                    imgState.currentUrl = url
                    imgState.status = StatueEnum.DONE
                }
                img.onerror = (e: Event) => {
                    const ele: EventTarget[] = e.composedPath()
                    ele &&
                        emit('img-error', {
                            index: imgState.currentIndex,
                            dom: ele[0] as HTMLImageElement,
                            url,
                        })
                    imgState.status = StatueEnum.FAIL
                }
            }

            // 关闭
            function handleClose(e: MouseEvent) {
                e && e.stopPropagation()
                close()
            }

            function close() {
                imgState.show = false
                // 移除火狐浏览器下的鼠标滚动事件
                document.body.removeEventListener('DOMMouseScroll', scrollFunc)
                // 恢复火狐及Safari浏览器下的图片拖拽
                document.ondragstart = null
            }

            // 图片复原
            function resume() {
                initState()
            }

            expose({
                resume,
                close,
                prev: handleChange.bind(null, 'left'),
                next: handleChange.bind(null, 'right'),
                setScale: (scale: number) => {
                    if (scale > 0 && scale <= 10) imgState.imgScale = scale
                },
                setRotate: (rotate: number) => {
                    imgState.imgRotate = rotate
                },
            })

            // 上一页下一页
            function handleChange(direction: 'left' | 'right') {
                const { currentIndex } = imgState
                const { imageList } = props
                if (direction === 'left') {
                    imgState.currentIndex--
                    if (currentIndex <= 0) {
                        imgState.currentIndex = imageList.length - 1
                    }
                }
                if (direction === 'right') {
                    imgState.currentIndex++
                    if (currentIndex >= imageList.length - 1) {
                        imgState.currentIndex = 0
                    }
                }
                handleIChangeImage(imageList[imgState.currentIndex])
            }

            function handleAddMoveListener(e: MouseEvent) {
                e = e || window.event
                imgState.moveX = e.clientX
                imgState.moveY = e.clientY
                const imgEl = unref(imgElRef)
                if (imgEl) {
                    imgEl.onmousemove = moveFunc
                }
            }

            function moveFunc(e: MouseEvent) {
                e = e || window.event
                e.preventDefault()
                const movementX = e.clientX - imgState.moveX
                const movementY = e.clientY - imgState.moveY
                imgState.imgLeft += movementX
                imgState.imgTop += movementY
                imgState.moveX = e.clientX
                imgState.moveY = e.clientY
            }

            // 获取图片样式
            const getImageStyle = computed(() => {
                const { imgScale, imgRotate, imgTop, imgLeft } = imgState
                return {
                    transform: `scale(${imgScale}) rotate(${imgRotate}deg)`,
                    marginTop: `${imgTop}px`,
                    marginLeft: `${imgLeft}px`,
                    maxWidth: props.defaultWidth ? 'unset' : '100%',
                }
            })

            const getIsMultipleImage = computed(() => {
                const { imageList } = props
                return imageList.length > 1
            })

            watchEffect(() => {
                if (props.show) {
                    init()
                }
                if (props.imageList) {
                    initState()
                }
            })

            const handleMaskClick = (e: MouseEvent) => {
                if (
                    props.maskClosable &&
                    e.target &&
                    (e.target as HTMLDivElement).classList.contains(`${prefixCls}-content`)
                ) {
                    handleClose(e)
                }
            }

            const renderClose = () => {
                return (
                    <div class={`${prefixCls}__close`} onClick={handleClose}>
                        <CloseOutlined class={`${prefixCls}__close-icon`} />
                    </div>
                )
            }

            const renderIndex = () => {
                if (!unref(getIsMultipleImage)) {
                    return null
                }
                const { currentIndex } = imgState
                const { imageList } = props
                return (
                    <div class={`${prefixCls}__index`}>
                        {currentIndex + 1} / {imageList.length}
                    </div>
                )
            }

            const renderController = () => {
                return (
                    <div class={`${prefixCls}__controller`}>
                        <div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(-getScaleStep.value)}>
                            <img src={unScaleSvg} />
                        </div>
                        <div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(getScaleStep.value)}>
                            <img src={scaleSvg} />
                        </div>
                        <div class={`${prefixCls}__controller-item`} onClick={resume}>
                            <img src={resumeSvg} />
                        </div>
                        <div class={`${prefixCls}__controller-item`} onClick={() => rotateFunc(-90)}>
                            <img src={unRotateSvg} />
                        </div>
                        <div class={`${prefixCls}__controller-item`} onClick={() => rotateFunc(90)}>
                            <img src={rotateSvg} />
                        </div>
                    </div>
                )
            }

            const renderArrow = (direction: 'left' | 'right') => {
                if (!unref(getIsMultipleImage)) {
                    return null
                }
                return (
                    <div class={[`${prefixCls}__arrow`, direction]} onClick={() => handleChange(direction)}>
                        {direction === 'left' ? <LeftOutlined /> : <RightOutlined />}
                    </div>
                )
            }

            return () => {
                return (
                    imgState.show && (
                        <div class={prefixCls} ref={wrapElRef} onMouseup={handleMouseUp} onClick={handleMaskClick}>
                            <div class={`${prefixCls}-content`}>
                                {/* <Spin */}
                                {/*  indicator={<LoadingOutlined style="font-size: 24px" spin />} */}
                                {/*  spinning={true} */}
                                {/*  class={[ */}
                                {/*    `${prefixCls}-image`, */}
                                {/*    { */}
                                {/*      hidden: imgState.status !== StatueEnum.LOADING, */}
                                {/*    }, */}
                                {/*  ]} */}
                                {/* /> */}
                                <img
                                    style={unref(getImageStyle)}
                                    class={[`${prefixCls}-image`, imgState.status === StatueEnum.DONE ? '' : 'hidden']}
                                    ref={imgElRef}
                                    src={imgState.currentUrl}
                                    onMousedown={handleAddMoveListener}
                                />
                                {renderClose()}
                                {renderIndex()}
                                {renderController()}
                                {renderArrow('left')}
                                {renderArrow('right')}
                            </div>
                        </div>
                    )
                )
            }
        },
    })
</script>

<style lang="less">
    .img-preview {
        position: fixed;
        inset: 0;
        z-index: @preview-comp-z-index;
        background: rgb(0 0 0 / 50%);
        user-select: none;

        &-content {
            display: flex;
            width: 100%;
            height: 100%;
            color: @white;
            justify-content: center;
            align-items: center;
        }

        &-image {
            cursor: pointer;
            transition: transform 0.3s;
        }

        &__close {
            position: absolute;
            top: -40px;
            right: -40px;
            width: 80px;
            height: 80px;
            overflow: hidden;
            color: @white;
            cursor: pointer;
            background-color: rgb(0 0 0 / 50%);
            border-radius: 50%;
            transition: all 0.2s;

            &-icon {
                position: absolute;
                top: 46px;
                left: 16px;
                font-size: 16px;
            }

            &:hover {
                background-color: rgb(0 0 0 / 80%);
            }
        }

        &__index {
            position: absolute;
            bottom: 5%;
            left: 50%;
            padding: 0 22px;
            font-size: 16px;
            background: rgb(109 109 109 / 60%);
            border-radius: 15px;
            transform: translateX(-50%);
        }

        &__controller {
            position: absolute;
            bottom: 10%;
            left: 50%;
            display: flex;
            width: 260px;
            height: 44px;
            padding: 0 22px;
            margin-left: -139px;
            background: rgb(109 109 109 / 60%);
            border-radius: 22px;
            justify-content: center;

            &-item {
                display: flex;
                height: 100%;
                padding: 0 9px;
                font-size: 24px;
                cursor: pointer;
                transition: all 0.2s;

                &:hover {
                    transform: scale(1.2);
                }

                img {
                    width: 1em;
                }
            }
        }

        &__arrow {
            position: absolute;
            top: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            width: 50px;
            height: 50px;
            font-size: 28px;
            cursor: pointer;
            background-color: rgb(0 0 0 / 50%);
            border-radius: 50%;
            transition: all 0.2s;

            &:hover {
                background-color: rgb(0 0 0 / 80%);
            }

            &.left {
                left: 50px;
            }

            &.right {
                right: 50px;
            }
        }
    }
</style>
