<template>
	<view class="lime-clipper open">
		<view class="lime-clipper-mask"
			ref="clipperMaskRef"
			@touchstart="clipTouchStart" 
			@touchmove="clipTouchMove" 
			@touchend="clipTouchEnd">
			<!-- #ifndef APP -->
			<view class="lime-clipper__content" ref="clipperRef" :style="clipStyle">
				<view class="lime-clipper__edge" :class="'nth-child-' + (index + 1)"  v-for="(item, index) in [0, 0, 0, 0]" :key="index"></view>
			</view>
			<!-- #endif -->
		</view>
		<image
			ref="imageRef"
			class="lime-clipper-image"
			@error="imageError"
			@load="imageLoad"
			 v-if="state.image != null"
			:src="state.image"
			:style="imageStyle"
			@touchstart="imageTouchStart"
			@touchmove="imageTouchMove"
			@touchend="imageTouchEnd"/>
		<canvas 
			ref="canvasRef" 
			canvas-id="lime-clipper" 
			id="lime-clipper" 
			disable-scroll
			:style="{
				width: state.canvasWidth + 'px',
				height: state.canvasHeight + 'px',
			}"
			class="lime-clipper-canvas">
		</canvas>
		<view class="lime-clipper-tools" v-if="(isShowCancelBtn || isShowPhotoBtn || isShowRotateBtn || isShowConfirmBtn)">
			<view class="lime-clipper-tools__btns" v-if="(isShowCancelBtn || isShowPhotoBtn || isShowRotateBtn || isShowConfirmBtn)">
				<view v-if="isShowCancelBtn" @tap="cancel">
					<slot name="cancel">
						<text class="text cancel">取消</text>
					</slot>
				</view>
				<view v-if="isShowPhotoBtn" @tap="uploadImage">
					<slot name="photo">
						<text class="text icon photo">&#xe76c;</text>
					</slot>
				</view>
				<view v-if="isShowRotateBtn" @tap="rotate">
					<slot name="rotate">
						<text class="text icon rotate">&#xe76d;</text>
					</slot>
				</view>
				<view v-if="isShowConfirmBtn" @tap="confirm" >
					<slot name="confirm">
						<text class="text confirm" :style="[confirmBgColor != null ? {background: confirmBgColor}: {}]">确定</text>
					</slot>
				</view>
			</view>
			<slot></slot>
		</view>
	</view>
</template>

<script setup lang="uts">
	/**
	 * Clipper 图片裁剪组件
	 * @description 用于实现图片裁剪功能的交互式组件，支持缩放、旋转、比例锁定等高级功能
	 * <br> 插件类型：LClipperComponentPublicInstance 
	 * @tutorial https://ext.dcloud.net.cn/plugin?name=lime-clipper
	 * 
	 * @property {string} customStyle 自定义容器样式（支持CSS字符串）
	 * @property {number} zIndex 画布层级（默认：999）
	 * @property {string} imageUrl 源图片地址（支持本地/网络路径）
	 * @property {'jpg' | 'png'} fileType 输出图片格式（默认：jpg）
	 * @property {number} quality 图片压缩质量（0-1，默认0.8）
	 * @property {number} fixedBoxWidth 固定裁剪区域宽度（单位rpx）
	 * @property {number} width 裁剪区域默认宽度（单位rpx）
	 * @property {number} height 裁剪区域默认高度（单位rpx）
	 * @property {number} minWidth 最小裁剪宽度（默认：50px）
	 * @property {number} maxWidth 最大裁剪宽度（默认：屏幕宽度）
	 * @property {number} destWidth 输出图片宽度（默认按裁剪尺寸）
	 * @property {number} destHeight 输出图片高度（默认按裁剪尺寸）
	 * @property {number} minHeight 最小裁剪高度（默认：50px）
	 * @property {number} maxHeight 最大裁剪高度（默认：屏幕高度）
	 * @property {boolean} isLockWidth 锁定裁剪宽度（禁止调整）
	 * @property {boolean} isLockHeight 锁定裁剪高度（禁止调整）
	 * @property {boolean} isLockRatio 锁定宽高比（默认false）
	 * @property {number} scaleRatio 初始缩放比例（默认1.0）
	 * @property {number} minRatio 最小缩放比例（默认0.5）
	 * @property {number} maxRatio 最大缩放比例（默认3.0）
	 * @property {boolean} isDisableScale 禁用缩放功能
	 * @property {boolean} isDisableRotate 禁用旋转功能
	 * @property {boolean} isLimitMove 限制移动不超出图片范围
	 * @property {boolean} isShowPhotoBtn 显示相册选择按钮
	 * @property {boolean} isShowRotateBtn 显示旋转按钮
	 * @property {boolean} isShowConfirmBtn 显示确认按钮
	 * @property {boolean} isShowCancelBtn 显示取消按钮
	 * @property {number} rotateAngle 初始旋转角度（单位度）
	 * @property {any} source 自定义图片源（预留扩展）
	 * @property {string} confirmBgColor 确认按钮背景色
	 * @property {string} canvasId 画布唯一标识（多实例时必填）
	 * @event {Function} ready 准完成触发
	 * @event {Function} change 变化时触发
	 * @event {Function} rotate 旋转时触发
	 * @event {Function} cancel 取消时触发
	 * @event {Function} success 点击确定时成功生成图片后触发
	 */
	
	import { ClipperProps, ClipperState, ClipBoxSizes, Point, ClipperclipStart, Rectangle } from './type';
	import { 
		getPointPositionInRectangle,
		isPointInRotatedRectangle,
		calcImageSize, 
		calcImageScale, 
		calcImageOffset, 
		calcPythagoreanTheorem, 
		imageTouchMoveOfCalcOffset,
		clamp, 
		determineDirection, 
		clipTouchMoveOfCalculate } from './utils.uts';
	
	const emit = defineEmits(['ready', 'change', 'rotate', 'cancel', 'input', 'success'])
	const props = withDefaults(defineProps<ClipperProps>(), {
		fileType: 'png',
		quality: 1,
		// fixedBoxWidth: 400,
		width: 400,
		height: 400,
		minWidth: 200,
		minHeight: 200,
		maxWidth: 600,
		maxHeight: 600,
		isLockWidth: false,
		isLockHeight: false,
		isLockRatio: true,
		scaleRatio: 1,
		minRatio: 0.5,
		maxRatio: 2,
		isDisableScale: false,
		isDisableRotate: false,
		isLimitMove: false,
		isShowPhotoBtn: true,
		isShowRotateBtn: true,
		isShowConfirmBtn: true,
		isShowCancelBtn: true,
		rotateAngle: 90,
		// source: ():UTSJSONObject => ({})
		source: {
			album: '从相册中选择',
			camera: '拍照',
			// #ifdef MP-WEIXIN
			message: '从微信中选择'
			// #endif
		} //as UTSJSONObject
	})

	const state = reactive<ClipperState>({
		canvasWidth: 0,
		canvasHeight: 0,
		clipX: -1,
		clipY: -1,
		clipWidth: 0,
		clipHeight: 0,
		animation: false,
		imageWidth: 0,
		imageHeight: 0,
		imageTop: 0,
		imageLeft: 0,
		scale: 1,
		angle: 0,
		image: '',
		imageInit: false,
		
		originY: -1,
		originX: -1,
		// flagClipTouch: false,
	})
	
	let touchRelative:Point[] = [];
	let clipStart:ClipperclipStart = {
			width: 0,
			height: 0,
			x: 0,
			y: 0,
			clipY: 0,
			clipX: 0,
			corner: 0
	}
	let hypotenuseLength = 0;
	let throttleTimer = -1;
	let timeClipCenter = -1;
	let animationTimer = -1;
	let flagEndTouch = false;
	let flagClipTouch = false;
	let throttleFlag = true;
	let _image = '';
	
	let ctx : CanvasRenderingContext2D  | null = null;
	let canvas : UniCanvasElement  | null = null;
	let canvasContext : CanvasContext  | null = null;
	const instance = getCurrentInstance()!
	const canvasId = 'lime-clipper'
	const canvasRef = ref<UniCanvasElement | null>(null)
	
	
	let { windowHeight, windowWidth, pixelRatio} = uni.getWindowInfo();
	const clipBoxSizes = computed(():ClipBoxSizes=>{
		
		const width = uni.rpx2px(props.width);
		const height = uni.rpx2px(props.height);
		const minWidth = uni.rpx2px(props.minWidth);
		const minHeight = uni.rpx2px(props.minHeight);
		const maxWidth = uni.rpx2px(props.maxWidth);
		const maxHeight = uni.rpx2px(props.maxHeight);
		const fixedBoxWidth = uni.rpx2px(props.fixedBoxWidth ?? 0);
		// #ifdef APP || WEB
		// #endif
		
		// #ifndef APP || WEB
		// const width = uni.upx2px(props.width);
		// const height = uni.upx2px(props.height);
		// const minWidth = uni.upx2px(props.minWidth);
		// const minHeight = uni.upx2px(props.minHeight);
		// const maxWidth = uni.upx2px(props.maxWidth);
		// const maxHeight = uni.upx2px(props.maxHeight);
		// #endif
		return {
			fixedBoxWidth,
			width,
			height,
			minWidth,
			minHeight,
			maxWidth,
			maxHeight
		} as ClipBoxSizes
	})
	
	const clipStyle = computed(():Map<string, any>=>{
		const style = new Map<string, any>();
		// #ifndef APP
		style.set('width', state.clipWidth + 'px')
		style.set('height', state.clipHeight + 'px')
		style.set('transition-property', state.animation ? '': 'background')
		style.set('left', state.clipX + 'px')
		style.set('top', state.clipY + 'px')
		// #endif
		return style
	})
	const imageStyle = computed(():Map<string, any>=>{
		const style = new Map<string, any>();
		// #ifndef APP
		
		style.set('width', state.imageWidth != 0 ? state.imageWidth + 'px': 'auto')
		style.set('height', state.imageHeight != 0 ? state.imageHeight + 'px': 'auto')
		
		style.set('transform', `translateX(${state.imageLeft - state.imageWidth / 2}px) translateY(${state.imageTop - state.imageHeight / 2}px) scale(${state.scale}) rotate(${state.angle}deg)`)
		style.set('transition-duration', state.animation ? '0.35s': '0s')
		// #endif
		return style
	})
	
	const hidpi = (width: number, height: number) => {
		if(canvas == null) return
		// 处理高清屏逻辑
		const dpr = pixelRatio;
		canvas!.width = width * dpr;
		canvas!.height = height * dpr;
		ctx!.scale(dpr, dpr); // 仅需调用一次，当调用 reset 方法后需要再次 scale
	}
	const setClipInfo = () => {
		let clipWidth = clipBoxSizes.value.width;
		let clipHeight = clipBoxSizes.value.height;
		
		if(props.fixedBoxWidth != null) {
			clipWidth = clipBoxSizes.value.fixedBoxWidth
			clipHeight = clipBoxSizes.value.width / clipHeight * clipWidth
		}
		
		const clipY = (windowHeight - clipHeight) * 0.5;
		const clipX = (windowWidth - clipWidth) * 0.5;

		const imageLeft = windowWidth * 0.5;
		const imageTop = windowHeight * 0.5;

		state.clipWidth = clipWidth;
		state.clipHeight = clipHeight;
		state.clipX = clipX;
		state.clipY = clipY;
		state.canvasHeight = clipHeight;
		state.canvasWidth = clipWidth;
		state.imageLeft = imageLeft;
		state.imageTop = imageTop;

		if (canvasRef.value == null) return
		// 异步调用方式, 跨平台写法
		nextTick(()=>{
			uni.createCanvasContextAsync({
				id: canvasId,
				component: instance.proxy!,
				success: (context : CanvasContext) => {
					canvasContext = context;
					ctx = context.getContext('2d')!;
					canvas = ctx!.canvas;
					hidpi(clipWidth, clipHeight)
				}
			})
		})
	}
	const setClipCenter = () => {
		let clipY = (windowHeight - state.clipHeight) * 0.5;
		let clipX = (windowWidth - state.clipWidth) * 0.5;
		state.imageTop = state.imageTop - state.clipY + clipY;
		state.imageLeft = state.imageLeft - state.clipX + clipX;
		state.clipY = clipY;
		state.clipX = clipX;
		
	}
	const calcClipSize = () => {
		if(state.clipWidth > windowWidth) {
			state.clipWidth = windowWidth
		} else if(state.clipWidth + state.clipX > windowWidth) {
			state.clipX = windowWidth - state.clipX
		}
		
		if(state.clipHeight > windowHeight) {
			state.clipHeight = windowHeight
		} else if(state.clipHeight + state.clipY > windowHeight) {
			state.clipY = windowHeight - state.clipY
		}
	}
	const cutDetectionPosition = () => {
		// 辅助函数，用于确保裁剪区域在窗口内
		const ensureWithinBounds = (value: number, maxValue: number, size: number):number => {
		    if (value < 0) {
				return 0;
		    } else if (value > maxValue - size) {
				return maxValue - size;
		    }
		    return value;
		};
		// 计算中心位置
		const centerPosition = (maxValue: number, size: number):number => (maxValue - size) * 0.5;
		const newClipY = state.clipY == -1 ? centerPosition(windowHeight, state.clipHeight) : ensureWithinBounds(state.clipY, windowHeight, state.clipHeight);
		const newClipX = state.clipX == -1 ? centerPosition(windowWidth, state.clipWidth) : ensureWithinBounds(state.clipX, windowWidth, state.clipWidth);
		state.clipY = newClipY
		state.clipX = newClipX
	}
	const imgMarginDetectionPosition = (scale: number) => {
		if (!props.isLimitMove) return;
		const [left, top, currentScale] = calcImageOffset(
			state.imageLeft, 
			state.imageTop, 
			state.imageWidth, 
			state.imageHeight, 
			state.clipX, 
			state.clipY, 
			state.clipWidth, 
			state.clipHeight, 
			state.angle, 
			scale);
		state.imageLeft = left
		state.imageTop = top
		state.scale = currentScale
		
	}

	const imgMarginDetectionScale = (scale: number) => {
		if (!props.isLimitMove) return;
		const currentScale = calcImageScale(
			state.imageWidth, 
			state.imageHeight, 
			state.clipWidth,
			state.clipHeight,
			state.angle,
			scale);
		imgMarginDetectionPosition(currentScale)	
	}
	const imgComputeSize = (width: number, height: number) => {
		const [imageWidth, imageHeight] = calcImageSize(
			width, height, 
			clipBoxSizes.value.width, clipBoxSizes.value.height,
			state.clipWidth, state.clipHeight);
			
		state.imageWidth = imageWidth;
		state.imageHeight = imageHeight;
		state.originX = imageWidth / 2;
		state.originY = imageHeight / 2;
	}
	const imageReset = () => {
		state.scale = 1;
		state.angle = 0;
		
		state.imageTop = windowHeight * 0.5;
		state.imageLeft = windowWidth * 0.5;
	}
	const moveStop = () => {
		clearTimeout(timeClipCenter);
		timeClipCenter = setTimeout(() => {
			if (!state.animation) {
				state.animation = true
				state.imageInit = true
			}
			nextTick(setClipCenter)
		}, 800);
	}
	let touchTarget:'image' | 'clip' | null = null
	const restoreToFixedWidth = ()=> {
		const fixedWidth = uni.rpx2px(props.fixedBoxWidth??0)
		// 1. 计算宽度变化比例
		const scaleRatio = fixedWidth / state.clipWidth
		
		// 2. 保存当前裁剪框中心点
		const oldImageCenterX = state.imageLeft;
		const oldImageCenterY = state.imageTop;
		const oldClipCenterX = state.clipX + state.clipWidth / 2;
		const oldClipCenterY = state.clipY + state.clipHeight / 2;
		
		// 3. 计算图片中心点相对于裁剪框中心点的偏移向量
		const imageOffsetX = oldImageCenterX - oldClipCenterX;
		const imageOffsetY = oldImageCenterY - oldClipCenterY;
		// 4. 计算当前裁剪框的宽高比例
		const aspectRatio = state.clipHeight / state.clipWidth	  
		// 应用动画效果
		state.animation = true
		// 5. 更新裁剪框尺寸（宽度固定，高度保持比例）
		state.clipWidth = fixedWidth
		state.clipHeight = fixedWidth * aspectRatio
		
		  
		// 6. 更新裁剪框位置（保持中心点不变）
		const newClipX = (windowWidth - fixedWidth) / 2;
		const newClipY = (windowHeight - state.clipHeight) / 2;
		// 7. 新裁剪框的中心点
		const newClipCenterX = newClipX + fixedWidth / 2;
		const newClipCenterY = newClipY + state.clipHeight / 2;
		const currentRatio = state.scale * scaleRatio
		// 8. 计算图片缩放比例变化量
		state.scale =  Math.min(currentRatio, props.maxRatio);
		
		// 9. 计算图片位置的变化：
		  //  a. 缩放导致的偏移（缩放中心是裁剪框中心）
		  //  b. 裁剪框位置变化带来的偏移
		state.imageTop = currentRatio > props.maxRatio ? oldImageCenterY : newClipCenterY + imageOffsetY * scaleRatio;
		state.imageLeft = currentRatio > props.maxRatio ? oldImageCenterX :newClipCenterX + imageOffsetX * scaleRatio;
		
		// 10. 更新裁剪框位置
		state.clipX = newClipX;
		state.clipY = newClipY;
		
		
		// 12. 更新canvas尺寸
		// state.canvasWidth = fixedWidth;
		// state.canvasHeight = state.clipHeight;
		
	}
	const imageTouchStart = (e: UniTouchEvent) => {
		e.preventDefault();
		flagEndTouch = false;
		state.animation = false;
		const { imageLeft, imageTop } = state;
		const [touch0] = e.touches;
		 // 计算触摸点相对于图片的相对位置
		const getTouchRelative = (touch: UniTouch):Point =>  (
		{
			x: touch.clientX - imageLeft,
			y: touch.clientY - imageTop
		} as Point)
		// 存储触摸点的相对位置
		touchRelative = [getTouchRelative(touch0)];
		
		if(e.touches.length > 1) {
			const touch1 = e.touches[1];
			const touch1Relative = getTouchRelative(touch1);
			
			// 计算两点之间的距离
			const width = Math.abs(touch0.clientX - touch1.clientX);
			const height = Math.abs(touch0.clientY - touch1.clientY);
			hypotenuseLength = Math.hypot(width, height);
			touchRelative.push(touch1Relative);
		}
	}
	const imageTouchMove = (e: UniTouchEvent) => {
		e.preventDefault();
		if (flagEndTouch || !throttleFlag) return;
		throttleFlag = true;
		clearTimeout(timeClipCenter);
		const [touch0] = e.touches;
		const { clientX: clientXForLeft, clientY: clientYForLeft } = touch0;
		if(e.touches.length == 1) {
			const [imageLeft, imageTop] = imageTouchMoveOfCalcOffset(touchRelative[0], clientXForLeft, clientYForLeft);
			state.imageLeft = imageLeft;
			state.imageTop = imageTop;
			imgMarginDetectionPosition(state.scale)
		} else if(e.touches.length > 1) {
			
			const touch1 = e.touches[1];
			const { clientX: clientXForRight, clientY: clientYForRight } = touch1;
			const width = Math.abs(clientXForLeft - clientXForRight);
			const height = Math.abs(clientYForLeft - clientYForRight);
			const hypotenuse = Math.hypot(width, height);
			let scale = state.scale * (hypotenuse / hypotenuseLength);
			
			if(props.isDisableScale) {
				scale = 1;
			} else {
				scale = clamp(scale, props.minRatio, props.maxRatio);
				emit('change', { width: state.imageWidth * scale, height: state.imageHeight * scale });
			}
			if(state.scale == scale) return
			imgMarginDetectionScale(scale);
			hypotenuseLength = hypotenuse;
			state.scale = scale;
		}
	}
	const imageTouchEnd = (e: UniTouchEvent) => {
		flagEndTouch = true;
		
		moveStop()
		// flagClipTouch = false
		// touchTarget = null
	}
	const clipTouchStart = (e: UniTouchEvent) => {
		e.preventDefault();
		if (state.image == null) {
			uni.showToast({
				title: '请选择图片',
				icon: 'none'
			});
			return;
		}
		const clientX = e.touches[0].clientX;
		const clientY = e.touches[0].clientY;
		
		// #ifdef APP
		const point:Point = {x: clientX, y: clientY}
		const clipRectangle: Rectangle = {x: state.clipX, y: state.clipY, width: state.clipWidth, height: state.clipHeight}
		
		const corner = getPointPositionInRectangle(
			point,
			clipRectangle
		)
		if(corner != null) {
			touchTarget = 'clip'
		} else {
			const imageRectangle: Rectangle = {
				x: state.imageLeft - state.imageWidth / 2, 
				y: state.imageTop - state.imageHeight / 2,
				width: state.imageWidth, 
				height: state.imageHeight} 
			const isImage = isPointInRotatedRectangle(point, imageRectangle, state.scale, state.angle)
			if(isImage) {
				touchTarget = 'image'
				imageTouchStart(e)
			}
		}
		if(corner == null) return;
		// #endif
		// #ifndef APP
		const corner = determineDirection(state.clipX, state.clipY, state.clipWidth, state.clipHeight, clientX, clientY);
		if(corner == -1) return;
		// #endif
		clearTimeout(timeClipCenter);
		
		clipStart = {
			width: state.clipWidth,
			height: state.clipHeight,
			x: clientX,
			y: clientY,
			clipX : state.clipX,
			clipY : state.clipY,
			corner
		} as ClipperclipStart
		flagClipTouch = true;
		flagEndTouch = true;
	}
	const clipTouchMove = (e: UniTouchEvent) => {
		// #ifdef APP
		if(touchTarget == 'image') {
			imageTouchMove(e)
			return
		}
		if(touchTarget != 'clip') return
		// #endif
		e.stopPropagation()
		e.preventDefault()
		if (state.image == null) {
			uni.showToast({
				title: '请选择图片',
				icon: 'none'
			});
			return;
		}
		// 只针对单指点击做处理
		if (e.touches.length != 1) return;
		if(flagClipTouch && throttleFlag) {
			const [touch] = e.touches
			const { isLockRatio, isLockHeight, isLockWidth } = props;
			if (isLockRatio && (isLockWidth || isLockHeight)) return;
			// throttleFlag = false;
			throttleFlag = true;
			const clipData = clipTouchMoveOfCalculate(
				state.clipWidth,
				state.clipHeight,
				state.clipX,
				state.clipY,
				clipBoxSizes.value.minWidth,
				clipBoxSizes.value.maxWidth,
				clipBoxSizes.value.minHeight,
				clipBoxSizes.value.maxHeight,
				clipStart,
				props.isLockRatio,
				touch);
				
			if(clipData == null) return
			const [width, height, clipX, clipY] = clipData;	
			if(!isLockWidth && !isLockHeight) {
				state.clipWidth = width
				state.clipHeight = height
				state.clipX = clipX
				state.clipY = clipY
			} else if(!isLockWidth) {
				state.clipWidth = width
				state.clipX = clipX
			} else if(!isLockHeight) {
				state.clipHeight = height
				state.clipY = clipY
			}
			imgMarginDetectionScale(state.scale)
		}
	}
	const clipTouchEnd = (e: UniTouchEvent) => {
		// #ifdef APP
		if(touchTarget == 'image') {
			imageTouchEnd(e)
			return
		}
		if(touchTarget != 'clip') return
		// #endif
		if (props.fixedBoxWidth != null && flagClipTouch) {
		    restoreToFixedWidth()
		}
		moveStop()
		flagClipTouch = false
		touchTarget = null
	}
	
	const imageLoad = (e: UniImageLoadEvent) => {
		imageReset()
		uni.hideLoading();
		emit('ready', e);
	}
	const imageError = (e: UniImageErrorEvent) => {
		imageReset()
		uni.hideLoading();
	}
	
	const uploadImage = (e:UniPointerEvent) => {
		uni.chooseImage({
			count: 1,
			sizeType: ['original', 'compressed'],
			sourceType: ['album','camera'],
			success(res) {
				state.image = res.tempFilePaths[0]
			}
		})
	}
	const rotate = (e:UniPointerEvent) => {
		if (props.isDisableRotate) return;
		if (state.image == null) {
			uni.showToast({
				title: '请选择图片',
				icon: 'none'
			});
			return;
		}
		// const { rotateAngle } = props;
		// const originAngle = state.angle
		// const type = event.currentTarget.dataset.type;
		// if (type === 'along') {
		// 	this.angle = originAngle + rotateAngle
		// } else {
			if(!state.animation) {
				state.animation = true;
				nextTick(()=>{
					state.angle = state.angle - props.rotateAngle
				})
			} else {
				state.angle = state.angle - props.rotateAngle
			}
			
		// }
		emit('rotate', state.angle);
	}
	const cancel = (e:UniPointerEvent) => {
		emit('cancel', false)
		emit('input', false)
		uni.hideLoading()
	}
	const confirm = (e:UniPointerEvent) => {
		if (state.image == null) {
			uni.showToast({
				title: '请选择图片',
				icon: 'none'
			});
			return;
		}
		uni.showLoading({
			title: '加载中'
		});
		const { 
			canvasHeight, 
			canvasWidth, 
			clipHeight, 
			clipWidth, 
			scale, 
			imageLeft, 
			imageTop, 
			clipX, 
			clipY, 
			angle, 
		} = state;
		const dpr = props.scaleRatio
		const draw = () => {
			if(ctx == null || canvas == null) return
			const img = canvasContext!.createImage()
			// #ifdef WEB
			// @ts-ignore
			img.crossOrigin = 'Anonymous';
			// #endif
			// @ts-ignore
			img.onload = () => {
				const imageWidth = state.imageWidth * scale * dpr;
				const imageHeight = state.imageHeight * scale * dpr;
				const xpos = imageLeft - clipX;
				const ypos = imageTop - clipY;
				ctx!.translate(xpos * dpr, ypos * dpr);
				ctx!.rotate((angle * Math.PI) / 180);
				ctx!.drawImage(img, -imageWidth / 2, -imageHeight / 2, imageWidth, imageHeight);
				// 鸿蒙next 渲染需要时间，故延长一下
				setTimeout(()=>{
					const url = canvas!.toDataURL();
					uni.hideLoading()
					emit('success', {
							url,
							width: clipWidth * dpr,
							height: clipHeight * dpr
					});
					emit('input', false)
				},1000)
			}
			// @ts-ignore
			img.onerror = () => {
				uni.hideLoading()
			}
			// #ifdef MP-WEIXIN
			if(_image.startsWith('static')) {
				_image = `/${_image}`
			}
			// #endif
			img.src = _image
		}
		if(canvasWidth != clipWidth || canvasHeight != clipHeight) {
			state.canvasWidth = clipWidth
			state.canvasHeight = clipHeight
			nextTick(()=>{
				hidpi(clipWidth, clipHeight)
				nextTick(draw)
			})
		} else {
			nextTick(draw)
		}
	}
	
	
	
	// #ifdef APP
	const imageRef = ref<UniImageElement | null>(null)
	// const imageRef = ref<UniElement | null>(null)
	watchEffect(()=>{
		if(imageRef.value == null) return;
		
		imageRef.value!.style.setProperty('width', state.imageWidth != 0 ? state.imageWidth + 'px': 'auto')
		imageRef.value!.style.setProperty('height', state.imageHeight != 0 ? state.imageHeight + 'px': 'auto')
		imageRef.value!.style.setProperty('transform', `translateX(${state.imageLeft - state.imageWidth / 2}px) translateY(${state.imageTop - state.imageHeight / 2}px) scale(${state.scale}) rotate(${state.angle}deg)`)
		imageRef.value!.style.setProperty('transition-duration', state.animation ? '0.35s': '0s')
	})
	const clipperRef = ref<UniElement|null>(null)
	const clipperMaskRef = ref<UniElement|null>(null)
	let maskCtx:DrawableContext|null = null;
	const drawMaskClip = () => {
		if(clipperMaskRef.value == null) return
		if(maskCtx == null) {
			maskCtx = clipperMaskRef.value!.getDrawableContext()
		}
		const _maskCtx = maskCtx!
		const rect = clipperMaskRef.value!.getBoundingClientRect()
		_maskCtx.reset()
		// 遮罩
		_maskCtx.fillStyle = 'rgba(0, 0, 0, 0.5)';
		_maskCtx.beginPath()
		_maskCtx.moveTo(0, 0)
		_maskCtx.lineTo(rect.width, 0)
		_maskCtx.lineTo(rect.width, rect.height)
		_maskCtx.lineTo(0, rect.height)
		_maskCtx.lineTo(state.clipX, state.clipY + state.clipHeight)
		_maskCtx.lineTo(state.clipX + state.clipWidth, state.clipY + state.clipHeight)
		_maskCtx.lineTo(state.clipX + state.clipWidth, state.clipY)
		_maskCtx.lineTo(state.clipX, state.clipY)
		_maskCtx.lineTo(state.clipX, state.clipY + state.clipHeight)
		_maskCtx.lineTo(0, rect.height)
		_maskCtx.lineTo(0, 0)
		_maskCtx.closePath()
		_maskCtx.fill();
		// 描边
		_maskCtx.beginPath()
		_maskCtx.setLineDash([4, 4])
		_maskCtx.lineWidth = 1
		_maskCtx.strokeStyle = 'rgba(255,255,255,.3)'
		_maskCtx.moveTo(state.clipX, state.clipY + state.clipHeight)
		_maskCtx.lineTo(state.clipX + state.clipWidth, state.clipY + state.clipHeight)
		_maskCtx.lineTo(state.clipX + state.clipWidth, state.clipY)
		_maskCtx.lineTo(state.clipX, state.clipY)
		_maskCtx.lineTo(state.clipX, state.clipY + state.clipHeight)
		_maskCtx.stroke()
		// y虚线
		_maskCtx.beginPath()
		_maskCtx.moveTo(state.clipX, state.clipY + state.clipHeight / 3)
		_maskCtx.lineTo(state.clipX + state.clipWidth, state.clipY + state.clipHeight / 3)
		
		_maskCtx.stroke()
		
		_maskCtx.beginPath()
		_maskCtx.moveTo(state.clipX, state.clipY + state.clipHeight / 3 * 2)
		_maskCtx.lineTo(state.clipX + state.clipWidth, state.clipY + state.clipHeight / 3 * 2)
		_maskCtx.stroke()
		// x虚线
		_maskCtx.beginPath()
		_maskCtx.moveTo(state.clipX + state.clipWidth / 3, state.clipY)
		_maskCtx.lineTo(state.clipX + state.clipWidth / 3, state.clipY + state.clipHeight)
		_maskCtx.stroke()
		
		_maskCtx.beginPath()
		_maskCtx.moveTo(state.clipX + state.clipWidth / 3 * 2, state.clipY)
		_maskCtx.lineTo(state.clipX + state.clipWidth / 3 * 2, state.clipY + state.clipHeight)
		_maskCtx.stroke()
		
		// 左上角
		const edgeLength = 20
		const edgeWidth = 3
		_maskCtx.lineWidth = 3
		_maskCtx.strokeStyle = 'rgba(255,255,255,1)'
		// #ifdef APP-IOS
		_maskCtx.setLineDash([0, 0.0001])//ios 不能为0
		// #endif
		// #ifdef APP-ANDROID
		_maskCtx.setLineDash([0, 0])
		// #endif
		_maskCtx.beginPath()
		_maskCtx.moveTo(state.clipX, state.clipY + edgeLength)
		_maskCtx.lineTo(state.clipX, state.clipY)
		_maskCtx.lineTo(state.clipX + edgeLength, state.clipY)
		_maskCtx.stroke()
		// 右上角
		_maskCtx.beginPath()
		_maskCtx.moveTo(state.clipX + state.clipWidth - edgeLength, state.clipY)
		_maskCtx.lineTo(state.clipX + state.clipWidth, state.clipY)
		_maskCtx.lineTo(state.clipX + state.clipWidth, state.clipY + edgeLength)
		_maskCtx.stroke()
		// 右下角
		_maskCtx.beginPath()
		_maskCtx.moveTo(state.clipX + state.clipWidth, state.clipY + state.clipHeight - edgeLength)
		_maskCtx.lineTo(state.clipX + state.clipWidth, state.clipY + state.clipHeight)
		_maskCtx.lineTo(state.clipX + state.clipWidth - edgeLength, state.clipY + state.clipHeight)
		_maskCtx.stroke()
		// 左下角
		_maskCtx.beginPath()
		_maskCtx.moveTo(state.clipX + edgeLength, state.clipY + state.clipHeight)
		_maskCtx.lineTo(state.clipX, state.clipY + state.clipHeight)
		_maskCtx.lineTo(state.clipX, state.clipY + state.clipHeight - edgeLength)
		_maskCtx.stroke()
		
		_maskCtx.update()
	}
	
	watchEffect(()=>{
		if(clipperMaskRef.value == null) return;
		if(maskCtx == null) {
			setTimeout(()=>{
				drawMaskClip()
			},100)
		} 
		drawMaskClip()
		
		// clipperRef.value!.style.setProperty('width', state.clipWidth + 'px')
		// clipperRef.value!.style.setProperty('height', state.clipHeight + 'px')
		// clipperRef.value!.style.setProperty('transition-property', state.animation ? '': 'background')
		// clipperRef.value!.style.setProperty('left', state.clipX + 'px')
		// clipperRef.value!.style.setProperty('top', state.clipY + 'px')
		// clipperRef.value!.style.setProperty('z-index', 10)
	})
	// #endif
	
	
	watch(():string|null => state.image, (src: string|null)=>{
		if(src == null) return
		state.imageInit = false
		uni.showLoading({
			title: '请稍候...',
			mask: true
		});
		uni.getImageInfo({
			src,
			success(res) {
				uni.hideLoading()
				if(['right', 'left'].includes(res.orientation ?? '')){
					imgComputeSize(res.height, res.width)
				} else {
					imgComputeSize(res.width, res.height)
				}
				_image = res.path;
				if(props.isLimitMove) {
					imgMarginDetectionScale(state.scale)
				}
			},
			fail(err) {
				uni.hideLoading()
			}
		})
	}, {immediate: true})
	
	// watch(():boolean => state.animation ,(value: boolean) => {
	// 	clearTimeout(animationTimer);
	// 	if(value) {
	// 		animationTimer = setTimeout(() => {
	// 			state.animation = false;
	// 		}, 260);
	// 	}
	// })
	watch(clipBoxSizes, (_clipBoxSizes: ClipBoxSizes)=> {
		state.clipWidth = clamp(clipBoxSizes.value.width, clipBoxSizes.value.minWidth, clipBoxSizes.value.maxWidth)
		state.clipHeight = clamp(clipBoxSizes.value.height, clipBoxSizes.value.minHeight, clipBoxSizes.value.maxHeight)
		calcClipSize()
	})
	watch(():number=> state.angle, (angle: number)=>{
		state.animation = true//state.imageInit;
		moveStop()
		if(props.isLimitMove && angle % 90 != 0) {
			state.angle = Math.round(angle / 90) * 90
		}
		imgMarginDetectionScale(state.scale)
	})
	watch(():boolean => props.isLimitMove, (limit: boolean)=>{
		state.animation = true//state.imageInit;
		moveStop()
		if(limit && state.angle % 90 != 0) {
			state.angle = Math.round(state.angle / 90) * 90
		}
		imgMarginDetectionScale(state.scale)
	})
	watch(():number[] => [state.clipX, state.clipY], (_:number[])=> {
		cutDetectionPosition()
	})
	
	
	onMounted(() => {
		nextTick(()=>{
			let res = uni.getWindowInfo();
			windowHeight = res.windowHeight
			windowWidth = res.windowWidth
			pixelRatio = res.pixelRatio;
			// state.image = props.imageUrl
			setClipInfo()
			setClipCenter()
			calcClipSize()
			cutDetectionPosition()
			
			watchEffect(()=>{
				state.image = props.imageUrl;
			})
		})
	})
</script>

<style lang="scss">
	@import './index';
</style>