<template>
    <view :class="['cropper-wrapper', { show: visible }]">
        <!-- 显示层：用 image 展示图片，裁剪框覆盖其上，避免真机上 canvas 把裁剪框盖住 -->
        <view
            class="display-layer"
            :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"
            @touchstart="onTouchStart"
            @touchmove="onTouchMove"
            @touchend="onTouchEnd"
        >
            <image
                v-if="imageSrc"
                :src="imageSrc"
                class="display-image"
                mode="aspectFit"
            ></image>
        </view>

        <!-- 工作 canvas：放到屏幕外，只用于真正的绘制和导出 -->
        <canvas
            id="imageCropperCanvas"
            canvas-id="imageCropperCanvas"
            :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"
            class="work-canvas"
        ></canvas>

        <!-- 用于导出裁剪结果的离屏 canvas（必须有真实节点） -->
        <canvas
            id="tempCropCanvas"
            canvas-id="tempCropCanvas"
            :style="{ width: exportSize.width + 'px', height: exportSize.height + 'px' }"
            class="temp-canvas"
        ></canvas>

        <view
            class="crop-frame"
            :style="{
                left: cropFrame.x + 'px',
                top: cropFrame.y + 'px',
                width: cropFrame.width + 'px',
                height: cropFrame.height + 'px',
            }"
            @touchstart.stop.prevent="onFrameTouchStart"
            @touchmove.stop.prevent="onFrameTouchMove"
            @touchend.stop="onFrameTouchEnd"
        >
            <view class="resize-handle top-left" data-direction="tl"></view>
            <view class="resize-handle top-right" data-direction="tr"></view>
            <view class="resize-handle bottom-left" data-direction="bl"></view>
            <view class="resize-handle bottom-right" data-direction="br"></view>
        </view>

        <view class="crop-controls" :style="{ bottom: 8 + safeAreaBottom + 'px' }">
            <button @tap="cancelCrop">取消</button>
            <button :disabled="isProcessing" @tap="confirmCrop">
                {{ isProcessing ? '处理中...' : '确定' }}
            </button>
        </view>
    </view>
</template>

<script setup lang="ts">
import { ref, watch, onMounted, nextTick, getCurrentInstance } from 'vue';

const props = withDefaults(
    defineProps<{
        visible: boolean;
        imageSrc: string;
        outputWidth?: number; // 裁剪输出宽度
        outputHeight?: number; // 裁剪输出高度
    }>(),
    {
        outputWidth: 750, // 默认输出宽度
        outputHeight: 750, // 默认输出高度
    }
);

const emit = defineEmits<{
    (e: 'update:visible', value: boolean): void;
    (e: 'confirm', filePath: string): void;
    (e: 'cancel'): void;
}>();

const ctx = ref<UniApp.CanvasContext | null>(null);
const imgInfo = ref<UniApp.GetImageInfoSuccessData | null>(null);

const canvasWidth = ref(0);
const canvasHeight = ref(0);
const imageDisplay = ref({ x: 0, y: 0, width: 0, height: 0 }); // 图片在canvas上的显示位置和尺寸
const controlsReservePx = 64; // 给底部按钮预留空间，避免真机遮挡图片

const cropFrame = ref({ x: 0, y: 0, width: 0, height: 0 }); // 裁剪框位置和尺寸
const isDraggingImage = ref(false);
const isResizingFrame = ref(false);
const isMovingFrame = ref(false);
const startX = ref(0);
const startY = ref(0);
const lastFrame = ref({ x: 0, y: 0, width: 0, height: 0 }); // 用于记录上一次裁剪框的位置和大小
const resizeDirection = ref('');
const isProcessing = ref(false); // 裁剪处理中状态

const safeAreaTop = ref(0);
const safeAreaBottom = ref(0);
const componentProxy = getCurrentInstance()?.proxy as any;
const exportSize = ref({ width: 0, height: 0 });

const rpxToPx = (rpx: number) => {
    // 按小程序 rpx 规则：750rpx = windowWidth(px)
    if (!canvasWidth.value) return Math.max(1, Math.round(rpx));
    return Math.max(1, Math.round((canvasWidth.value / 750) * rpx));
};

const getTouchPoint = (e: any) => {
    const t = e?.touches?.[0] || e?.changedTouches?.[0] || {};
    const x = t.x ?? t.clientX ?? t.pageX ?? 0;
    const y = t.y ?? t.clientY ?? t.pageY ?? 0;
    return { x, y };
};

const syncViewportSize = (reloadWhenVisible = false) => {
    uni.getSystemInfo({
        success: (res) => {
            safeAreaTop.value = res.safeArea?.top || 0;
            safeAreaBottom.value =
                (res.screenHeight && res.safeArea ? res.screenHeight - res.safeArea.bottom : 0) || 0;
            const bottomReserve = controlsReservePx + safeAreaBottom.value;
            const fallbackWidth = res.windowWidth;
            const fallbackHeight = res.windowHeight;

            const applySize = (width: number, height: number) => {
                const nextWidth = Math.max(1, Math.round(width));
                const nextHeight = Math.max(1, Math.round(height - bottomReserve));
                const sizeChanged = nextWidth !== canvasWidth.value || nextHeight !== canvasHeight.value;
                canvasWidth.value = nextWidth;
                canvasHeight.value = nextHeight;
                if (!ctx.value) {
                    ctx.value = uni.createCanvasContext('imageCropperCanvas', componentProxy);
                }
                if (props.visible && props.imageSrc && (reloadWhenVisible || sizeChanged)) {
                    tryInitAndLoad();
                }
            };

            if (props.visible) {
                nextTick(() => {
                    uni.createSelectorQuery()
                        .in(componentProxy)
                        .select('.cropper-wrapper')
                        .boundingClientRect((rect: any) => {
                            if (rect?.width && rect?.height) {
                                applySize(rect.width, rect.height);
                                return;
                            }
                            applySize(fallbackWidth, fallbackHeight);
                        })
                        .exec();
                });
                return;
            }

            applySize(fallbackWidth, fallbackHeight);
        },
    });
};

const tryInitAndLoad = () => {
    if (!props.visible) return;
    if (!props.imageSrc) return;
    if (!ctx.value) return;
    if (!canvasWidth.value || !canvasHeight.value) return;

    nextTick(() => {
        exportSize.value = {
            width: rpxToPx(props.outputWidth),
            height: rpxToPx(props.outputHeight),
        };
        loadImage();
    });
};

onMounted(() => {
    // 横屏真机下 getSystemInfo 可能拿到旋转前尺寸，因此优先同步可视区域真实尺寸
    syncViewportSize(false);
});

watch(
    [() => props.visible, () => props.imageSrc, canvasWidth, canvasHeight, ctx],
    () => {
        if (!props.visible) {
            imgInfo.value = null;
            return;
        }
        syncViewportSize(true);
        tryInitAndLoad();
    },
    { immediate: true }
);

const loadImage = () => {
    uni.showLoading({ title: '加载中...' });
    uni.getImageInfo({
        src: props.imageSrc,
        success: (res) => {
            imgInfo.value = res;
            drawImage();
            uni.hideLoading();
        },
        fail: (err) => {
            uni.hideLoading();
            uni.showToast({ title: '图片加载失败', icon: 'none' });
            console.error('图片加载失败', err);
            cancelCrop();
        },
    });
};

const drawImage = () => {
    if (!ctx.value || !imgInfo.value) return;

    const { path, width: originalWidth, height: originalHeight } = imgInfo.value;

    // 计算图片在Canvas上的显示尺寸，使其适应Canvas，并居中
    const canvasRatio = canvasWidth.value / canvasHeight.value;
    const imageRatio = originalWidth / originalHeight;

    let displayWidth = 0;
    let displayHeight = 0;

    if (imageRatio > canvasRatio) {
        displayWidth = canvasWidth.value;
        displayHeight = canvasWidth.value / imageRatio;
    } else {
        displayHeight = canvasHeight.value;
        displayWidth = canvasHeight.value * imageRatio;
    }

    imageDisplay.value = {
        x: (canvasWidth.value - displayWidth) / 2,
        y: (canvasHeight.value - displayHeight) / 2,
        width: displayWidth,
        height: displayHeight,
    };

    ctx.value.clearRect(0, 0, canvasWidth.value, canvasHeight.value);
    ctx.value.drawImage(
        path,
        imageDisplay.value.x,
        imageDisplay.value.y,
        imageDisplay.value.width,
        imageDisplay.value.height
    );

    // 初始化裁剪框为图片显示区域的大小
    cropFrame.value = {
        x: imageDisplay.value.x,
        y: imageDisplay.value.y,
        width: imageDisplay.value.width,
        height: imageDisplay.value.height,
    };

    ctx.value.draw();
};

const onTouchStart = (e: any) => {
    if (!imgInfo.value) return;
    const { x, y } = getTouchPoint(e);
    startX.value = x;
    startY.value = y;

    // 判断是否在拖动图片
    if (
        x > imageDisplay.value.x &&
        x < imageDisplay.value.x + imageDisplay.value.width &&
        y > imageDisplay.value.y &&
        y < imageDisplay.value.y + imageDisplay.value.height
    ) {
        isDraggingImage.value = true;
        // 记录图片当前位置，用于计算偏移
        lastFrame.value = { ...imageDisplay.value };
    }
};

const onTouchMove = (e: any) => {
    if (!isDraggingImage.value || !imgInfo.value || !ctx.value) return;

    const { x, y } = getTouchPoint(e);
    const deltaX = x - startX.value;
    const deltaY = y - startY.value;

    let newX = lastFrame.value.x + deltaX;
    let newY = lastFrame.value.y + deltaY;

    // 边界检查：确保图片不会完全移出canvas
    if (newX > canvasWidth.value - imageDisplay.value.x) {
        newX = canvasWidth.value - imageDisplay.value.x;
    }
    if (newY > canvasHeight.value - imageDisplay.value.y) {
        newY = canvasHeight.value - imageDisplay.value.y;
    }

    if (newX < -imageDisplay.value.width + imageDisplay.value.x) {
        newX = -imageDisplay.value.width + imageDisplay.value.x;
    }
    if (newY < -imageDisplay.value.height + imageDisplay.value.y) {
        newY = -imageDisplay.value.height + imageDisplay.value.y;
    }

    imageDisplay.value.x = newX;
    imageDisplay.value.y = newY;

    ctx.value.clearRect(0, 0, canvasWidth.value, canvasHeight.value);
    ctx.value.drawImage(
        imgInfo.value.path,
        imageDisplay.value.x,
        imageDisplay.value.y,
        imageDisplay.value.width,
        imageDisplay.value.height
    );
    ctx.value.draw(true); // true表示保留上一次绘制内容
};

const onTouchEnd = () => {
    isDraggingImage.value = false;
};

// eslint-disable-next-line complexity
const onFrameTouchMove = (e: any) => {
    if (!imgInfo.value) return;
    const { x, y } = getTouchPoint(e);
    const deltaX = x - startX.value;
    const deltaY = y - startY.value;

    if (isMovingFrame.value) {
        // 移动裁剪框
        let newX = lastFrame.value.x + deltaX;
        let newY = lastFrame.value.y + deltaY;

        // 边界检查
        if (newX < 0) newX = 0;
        if (newY < 0) newY = 0;
        if (newX + cropFrame.value.width > canvasWidth.value)
            newX = canvasWidth.value - cropFrame.value.width;
        if (newY + cropFrame.value.height > canvasHeight.value)
            newY = canvasHeight.value - cropFrame.value.height;

        cropFrame.value.x = newX;
        cropFrame.value.y = newY;
    } else if (isResizingFrame.value) {
        // 缩放裁剪框
        let newX = lastFrame.value.x;
        let newY = lastFrame.value.y;
        let newWidth = lastFrame.value.width;
        let newHeight = lastFrame.value.height;

        const minSize = 50; // 最小尺寸

        switch (resizeDirection.value) {
            case 'tl': // Top-left
                newX = Math.min(x, lastFrame.value.x + lastFrame.value.width - minSize);
                newY = Math.min(y, lastFrame.value.y + lastFrame.value.height - minSize);
                newWidth = lastFrame.value.x + lastFrame.value.width - newX;
                newHeight = lastFrame.value.y + lastFrame.value.height - newY;
                break;
            case 'tr': // Top-right
                newX = lastFrame.value.x;
                newY = Math.min(startY.value, lastFrame.value.y + lastFrame.value.height - minSize, y);
                newWidth = Math.max(minSize, lastFrame.value.width + deltaX);
                newHeight = lastFrame.value.y + lastFrame.value.height - newY;
                break;
            case 'bl': // Bottom-left
                newX = Math.min(startX.value, lastFrame.value.x + lastFrame.value.width - minSize, x);
                newY = lastFrame.value.y;
                newWidth = lastFrame.value.x + lastFrame.value.width - newX;
                newHeight = Math.max(minSize, lastFrame.value.height + deltaY);
                break;
            case 'br': // Bottom-right
                newX = lastFrame.value.x;
                newY = lastFrame.value.y;
                newWidth = Math.max(minSize, lastFrame.value.width + deltaX);
                newHeight = Math.max(minSize, lastFrame.value.height + deltaY);
                break;
        }

        // 裁剪框不能超出canvas边界
        if (newX < 0) newX = 0;
        if (newY < 0) newY = 0;
        if (newX + newWidth > canvasWidth.value) newWidth = canvasWidth.value - newX;
        if (newY + newHeight > canvasHeight.value) newHeight = canvasHeight.value - newY;
        
        cropFrame.value = { x: newX, y: newY, width: newWidth, height: newHeight };
    }
};

const onFrameTouchEnd = () => {
    isMovingFrame.value = false;
    isResizingFrame.value = false;
    resizeDirection.value = '';
};

// 阻止事件冒泡，因为canvas的touchmove事件会在外部被阻止
// 这里需要处理裁剪框的触摸事件，不阻止冒泡会导致裁剪框无法拖动和缩放
const onFrameTouchStart = (e: any) => {
    const { x, y } = getTouchPoint(e);
    startX.value = x;
    startY.value = y;
    lastFrame.value = { ...cropFrame.value }; // 记录当前裁剪框状态

    const targetDataset = e.target.dataset;
    if (targetDataset && targetDataset.direction) {
        isResizingFrame.value = true;
        resizeDirection.value = targetDataset.direction;
    } else {
        isMovingFrame.value = true;
    }
};

const confirmCrop = () => {
    if (!ctx.value || !imgInfo.value || isProcessing.value) return;

    isProcessing.value = true;
    uni.showLoading({ title: '裁剪中...', mask: true });

    const { path: imagePath, width: originalWidth, height: originalHeight } = imgInfo.value;

    // 计算实际图片上的裁剪区域
    // 图片显示区域与原始图片的比例
    const scaleX = originalWidth / imageDisplay.value.width;
    const scaleY = originalHeight / imageDisplay.value.height;

    const sourceX = (cropFrame.value.x - imageDisplay.value.x) * scaleX;
    const sourceY = (cropFrame.value.y - imageDisplay.value.y) * scaleY;
    const sourceWidth = cropFrame.value.width * scaleX;
    const sourceHeight = cropFrame.value.height * scaleY;

    // 创建一个新的canvas用于裁剪，确保尺寸和输出尺寸匹配
    const tempCanvasId = 'tempCropCanvas';
    const tempCtx = uni.createCanvasContext(tempCanvasId, componentProxy);
    
    // 设置裁剪输出尺寸
    const outputWidthPx = rpxToPx(props.outputWidth);
    const outputHeightPx = rpxToPx(props.outputHeight);
    exportSize.value = { width: outputWidthPx, height: outputHeightPx };

    // 绘制图片到临时canvas，进行裁剪和缩放
    tempCtx.clearRect(0, 0, outputWidthPx, outputHeightPx);
    tempCtx.drawImage(
        imagePath,
        sourceX,
        sourceY,
        sourceWidth,
        sourceHeight,
        0,
        0,
        outputWidthPx,
        outputHeightPx
    );
    
    // 确保绘制完成
    tempCtx.draw(false, () => {
        let done = false;
        const finish = (ok: boolean, payload?: any) => {
            if (done) return;
            done = true;
            isProcessing.value = false;
            uni.hideLoading();
            if (!ok) return;
            emit('confirm', payload);
            emit('update:visible', false);
        };

        // 兜底：防止部分机型 canvasToTempFilePath 不回调导致“一直处理中”
        const timer = setTimeout(() => {
            uni.showToast({ title: '裁剪超时，请重试', icon: 'none' });
            finish(false);
        }, 4000);

        // 延迟一段时间，确保canvas内容已经渲染
        setTimeout(() => {
            uni.canvasToTempFilePath({
                canvasId: tempCanvasId,
                x: 0,
                y: 0,
                width: outputWidthPx,
                height: outputHeightPx,
                destWidth: outputWidthPx,
                destHeight: outputHeightPx,
                success: (res) => {
                    clearTimeout(timer);
                    finish(true, res.tempFilePath);
                },
                fail: (err) => {
                    clearTimeout(timer);
                    uni.showToast({ title: '裁剪失败', icon: 'none' });
                    console.error('canvasToTempFilePath failed', err);
                    finish(false);
                },
            }, componentProxy);
        }, 100); // 短暂延迟
    });
};

const cancelCrop = () => {
    emit('cancel');
    emit('update:visible', false);
};
</script>

<style lang="scss" scoped>
.cropper-wrapper {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 9999;
    background-color: #000;
    display: none;
    pointer-events: none;
    overflow: hidden; // 隐藏超出部分

    &.show {
        display: block;
        pointer-events: auto;
    }
}

.display-layer {
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
    z-index: 1;
    overflow: hidden;
}

.display-image {
    width: 100%;
    height: 100%;
}

.work-canvas {
    position: fixed;
    left: -99999px;
    top: -99999px;
    width: 1px;
    height: 1px;
    opacity: 0;
}

.temp-canvas {
    position: fixed;
    left: -99999px;
    top: -99999px;
    width: 1px;
    height: 1px;
    opacity: 0;
}

.crop-frame {
    position: absolute;
    border: 2px solid #00c0ff;
    box-sizing: border-box;
    cursor: grab;
    touch-action: none; // 禁用浏览器默认触摸行为
    z-index: 2;
}

.resize-handle {
    position: absolute;
    width: 32px;
    height: 32px;
    background-color: #00c0ff;
    border: 2px solid #fff;
    box-sizing: border-box;
    opacity: 0.9;
    border-radius: 4px;
}

.top-left {
    top: -16px;
    left: -16px;
    cursor: nwse-resize;
}
.top-right {
    top: -16px;
    right: -16px;
    cursor: nesw-resize;
}
.bottom-left {
    bottom: -16px;
    left: -16px;
    cursor: nesw-resize;
}
.bottom-right {
    bottom: -16px;
    right: -16px;
    cursor: nwse-resize;
}

.crop-controls {
    position: absolute;
    // left: 12px;
    right: 12px;
    z-index: 3;
    display: flex;
    justify-content: space-between;
    gap: 15px;
    padding: 6px 10px;
    box-sizing: border-box;
    background: rgba(0, 0, 0, 0.25);
    border-radius: 12px;
    backdrop-filter: blur(4px);
}

.crop-controls button {

    min-width: 30px;
    padding: 5px 10px;
    border: none;
    border-radius: 10px;
    background: #007aff;
    color: #fff;
    font-size: 14px;
}

.crop-controls button:first-child {
    background: #3c3c3c;
}

.crop-controls button[disabled] {
    opacity: 0.6;
}
</style>