<template>
	<view class="cl-select-seat" :style="{ width: width + 'px', height: height + 'px' }">
		<view
			class="cl-select-seat__index"
			:style="{
				transform: `translateY(${translateY}px)`
			}"
		>
			<view
				class="cl-select-seat__index-item"
				v-for="i in rows"
				:key="i"
				:style="{
					height: seatSize * scale + 'px',
					marginTop: i == 1 ? 0 : seatGap * scale + 'px'
				}"
			>
				<cl-text color="white" :size="10">{{ i }}</cl-text>
			</view>
		</view>

		<canvas
			id="seatCanvas"
			class="cl-select-seat__canvas"
			@touchstart.stop.prevent="onTouchStart"
			@touchmove.stop.prevent="onTouchMove"
			@touchend.stop.prevent="onTouchEnd"
		></canvas>
	</view>
</template>

<script setup lang="ts">
import { assign, getColor, getDevicePixelRatio, isDark, isH5 } from "@/.cool";
import { computed, getCurrentInstance, onMounted, ref, watch } from "vue";
import { getIcon } from "../cl-icon/utils";
import type { PropType } from "vue";
import type { ClSelectSeatItem, ClSelectSeatValue } from "../../types";

defineOptions({
	name: "cl-select-seat"
});

type TouchPoint = {
	x: number;
	y: number;
	identifier: number;
};

const props = defineProps({
	modelValue: {
		type: Array as PropType<ClSelectSeatValue[]>,
		default: () => []
	},
	rows: {
		type: Number,
		default: 0
	},
	cols: {
		type: Number,
		default: 0
	},
	seatGap: {
		type: Number,
		default: 8
	},
	borderRadius: {
		type: Number,
		default: 8
	},
	borderWidth: {
		type: Number,
		default: 1
	},
	minScale: {
		type: Number,
		default: 1
	},
	maxScale: {
		type: Number,
		default: 3
	},
	color: {
		type: String,
		default: () => getColor("surface-300")
	},
	darkColor: {
		type: String,
		default: () => getColor("surface-500")
	},
	bgColor: {
		type: String,
		default: () => "#ffffff"
	},
	darkBgColor: {
		type: String,
		default: () => getColor("surface-800")
	},
	borderColor: {
		type: String,
		default: () => getColor("surface-200")
	},
	darkBorderColor: {
		type: String,
		default: () => getColor("surface-600")
	},
	selectedBgColor: {
		type: String,
		default: () => getColor("primary-500")
	},
	selectedColor: {
		type: String,
		default: () => "#ffffff"
	},
	selectedIcon: {
		type: String,
		default: "check-line"
	},
	selectedImage: {
		type: String,
		default: ""
	},
	image: {
		type: String,
		default: ""
	},
	width: {
		type: Number,
		default: 0
	},
	height: {
		type: Number,
		default: 0
	},
	seats: {
		type: Array as PropType<ClSelectSeatItem[]>,
		default: () => []
	}
});

const emit = defineEmits(["update:modelValue", "seatClick", "move"]);

const { proxy } = getCurrentInstance()!;

// 画布渲染上下文
let renderingContext: CanvasRenderingContext2D | null = null;
let canvasContext: CanvasContext | null = null;
// 画布偏移顶部
let offsetTop = 0;
// 画布偏移左侧
let offsetLeft = 0;
// 左侧边距
let leftPadding = 0;

// 座位数据
let seats: ClSelectSeatItem[] = [];

// 图片缓存
const imageCache = new Map<string, Image>();

// 画布变换参数
const scale = ref(1);
const translateX = ref(0);
const translateY = ref(0);

// 触摸状态
let touchPoints: TouchPoint[] = [];
let lastDistance = 0;
let lastCenterX = 0;
let lastCenterY = 0;
// 是否发生过拖动或缩放，用于防止误触选座
let hasMoved = false;
// 起始触摸点，用于判断是否发生拖动
let startTouchX = 0;
let startTouchY = 0;
// 拖动阈值（像素）
const moveThreshold = 10;

// 根据视图大小计算座位大小
const seatSize = computed(() => {
	const availableWidth =
		props.width - (props.cols - 1) * props.seatGap - props.borderWidth * props.cols;
	const seatWidth = availableWidth / props.cols;

	const availableHeight =
		props.height - (props.rows - 1) * props.seatGap - props.borderWidth * props.rows;
	const seatHeight = availableHeight / props.rows;

	return Math.min(seatWidth, seatHeight);
});

// 是否选中
function isSelected(row: number, col: number): boolean {
	return props.modelValue.some((item) => item.row == row && item.col == col);
}

// 初始化座位数据
function initSeats() {
	seats = [];

	if (props.seats.length > 0) {
		props.seats.forEach((e) => {
			seats.push({
				row: e.row,
				col: e.col,
				disabled: e.disabled,
				empty: e.empty,
				bgColor: e.bgColor,
				borderColor: e.borderColor,
				selectedBgColor: e.selectedBgColor,
				selectedColor: e.selectedColor,
				selectedIcon: e.selectedIcon,
				selectedImage: e.selectedImage,
				icon: e.icon,
				image: e.image,
				color: e.color
			} as ClSelectSeatItem);
		});
	} else {
		for (let row = 0; row < props.rows; row++) {
			for (let col = 0; col < props.cols; col++) {
				seats.push({
					row,
					col
				} as ClSelectSeatItem);
			}
		}
	}
}

// 加载图片
function loadImage(src: string): Promise<Image> {
	return new Promise((resolve, reject) => {
		if (imageCache.has(src)) {
			resolve(imageCache.get(src)!);
			return;
		}

		// 创建图片
		let img: Image;

		// 微信小程序环境创建图片
		// #ifdef MP-WEIXIN || APP-HARMONY
		img = canvasContext!.createImage();
		// #endif

		// 其他环境创建图片
		// #ifndef MP-WEIXIN || APP-HARMONY
		img = new Image();
		// #endif

		img.src = src;
		img.onload = () => {
			imageCache.set(src, img);
			resolve(img);
		};
	});
}

// 预加载所有图片
async function preloadImages() {
	const imagesToLoad: string[] = [];

	// 收集所有需要加载的图片
	if (props.image != "") imagesToLoad.push(props.image);
	if (props.selectedImage != "") imagesToLoad.push(props.selectedImage);

	seats.forEach((seat) => {
		if (seat.image != null) imagesToLoad.push(seat.image);
		if (seat.selectedImage != null) imagesToLoad.push(seat.selectedImage);
	});

	// 去重并加载
	const uniqueImages = [...new Set(imagesToLoad)];
	await Promise.all(uniqueImages.map((src) => loadImage(src).catch(() => {})));
}

// 居中显示
function centerView() {
	if (renderingContext == null) return;

	const contentWidth = props.cols * (seatSize.value + props.seatGap) - props.seatGap;
	const contentHeight = props.rows * (seatSize.value + props.seatGap) - props.seatGap;

	translateX.value = (renderingContext!.canvas.offsetWidth - contentWidth) / 2;
	translateY.value = (renderingContext!.canvas.offsetHeight - contentHeight) / 2;

	leftPadding = translateX.value;
}

// 限制平移范围
function constrainTranslate() {
	if (renderingContext == null) return;

	// 计算内容区（座位区域）宽高
	const contentWidth = props.cols * (seatSize.value + props.seatGap) - props.seatGap;
	const contentHeight = props.rows * (seatSize.value + props.seatGap) - props.seatGap;
	// 获取画布的显示区域宽高
	const viewWidth = renderingContext!.canvas.offsetWidth;
	const viewHeight = renderingContext!.canvas.offsetHeight;

	// 计算缩放后的实际宽高
	const scaledWidth = contentWidth * scale.value;
	const scaledHeight = contentHeight * scale.value;

	// 允许的最大空白比例（视图的 20%）
	const marginRatio = 0.2;
	const marginX = viewWidth * marginRatio;
	const marginY = viewHeight * marginRatio;

	// 水平方向边界限制：内容左边缘最多在视图左侧 20% 处，右边缘最多在视图右侧 80% 处
	const minTranslateX = viewWidth * (1 - marginRatio) - scaledWidth;
	const maxTranslateX = marginX;
	translateX.value = Math.max(minTranslateX, Math.min(maxTranslateX, translateX.value));

	// 垂直方向边界限制：内容上边缘最多在视图顶部 20% 处，下边缘最多在视图底部 80% 处
	const minTranslateY = viewHeight * (1 - marginRatio) - scaledHeight;
	const maxTranslateY = marginY;
	translateY.value = Math.max(minTranslateY, Math.min(maxTranslateY, translateY.value));
}

// 绘制圆角矩形
function drawRoundRect(
	ctx: CanvasRenderingContext2D,
	x: number,
	y: number,
	width: number,
	height: number,
	radius: number
) {
	ctx.beginPath();
	ctx.moveTo(x + radius, y);
	ctx.lineTo(x + width - radius, y);
	ctx.arc(x + width - radius, y + radius, radius, Math.PI * 1.5, Math.PI * 2);
	ctx.lineTo(x + width, y + height - radius);
	ctx.arc(x + width - radius, y + height - radius, radius, 0, Math.PI * 0.5);
	ctx.lineTo(x + radius, y + height);
	ctx.arc(x + radius, y + height - radius, radius, Math.PI * 0.5, Math.PI);
	ctx.lineTo(x, y + radius);
	ctx.arc(x + radius, y + radius, radius, Math.PI, Math.PI * 1.5);
	ctx.closePath();
}

// 绘制单个座位
function drawSeat(seat: ClSelectSeatItem) {
	if (renderingContext == null) return;

	// 如果是空位，不渲染但保留位置
	if (seat.empty == true) return;

	const x = seat.col * (seatSize.value + props.seatGap);
	const y = seat.row * (seatSize.value + props.seatGap);

	// 计算图标中心位置（使用整数避免亚像素渲染问题）
	const centerX = Math.round(x + seatSize.value / 2);
	const centerY = Math.round(y + seatSize.value / 2);
	const fontSize = Math.round(seatSize.value * 0.6);

	if (isSelected(seat.row, seat.col)) {
		// 优先使用图片，否则使用背景+图标
		const selectedImageSrc = seat.selectedImage ?? props.selectedImage;
		if (selectedImageSrc != "" && imageCache.has(selectedImageSrc)) {
			// 使用圆角裁剪绘制图片
			renderingContext!.save();
			drawRoundRect(
				renderingContext!,
				x,
				y,
				seatSize.value,
				seatSize.value,
				props.borderRadius
			);
			renderingContext!.clip();
			const img = imageCache.get(selectedImageSrc)!;
			renderingContext!.drawImage(img, x, y, seatSize.value, seatSize.value);
			renderingContext!.restore();
		} else {
			// 绘制选中背景
			renderingContext!.fillStyle = seat.selectedBgColor ?? props.selectedBgColor;
			drawRoundRect(
				renderingContext!,
				x,
				y,
				seatSize.value,
				seatSize.value,
				props.borderRadius
			);
			renderingContext!.fill();

			// 绘制选中图标
			const { text, font } = getIcon(seat.selectedIcon ?? props.selectedIcon);
			if (text != "") {
				renderingContext!.fillStyle = seat.selectedColor ?? props.selectedColor;
				renderingContext!.font = `${fontSize}px ${font}`;
				renderingContext!.textAlign = "center";
				renderingContext!.textBaseline = "middle";
				renderingContext!.fillText(text, centerX, centerY);
			}
		}
	} else {
		// 优先使用图片，否则使用背景+边框+图标
		const imageSrc = seat.image ?? props.image;
		if (imageSrc != "" && imageCache.has(imageSrc)) {
			// 使用圆角裁剪绘制图片
			renderingContext!.save();
			drawRoundRect(
				renderingContext!,
				x,
				y,
				seatSize.value,
				seatSize.value,
				props.borderRadius
			);
			renderingContext!.clip();
			const img = imageCache.get(imageSrc)!;
			renderingContext!.drawImage(img, x, y, seatSize.value, seatSize.value);
			renderingContext!.restore();
		} else {
			// 绘制未选中背景
			const bgColor = seat.bgColor ?? (isDark.value ? props.darkBgColor : props.bgColor);
			renderingContext!.fillStyle = bgColor;
			drawRoundRect(
				renderingContext!,
				x,
				y,
				seatSize.value,
				seatSize.value,
				props.borderRadius
			);
			renderingContext!.fill();

			// 绘制边框
			renderingContext!.strokeStyle =
				seat.borderColor ?? (isDark.value ? props.darkBorderColor : props.borderColor);
			renderingContext!.lineWidth = props.borderWidth;
			drawRoundRect(
				renderingContext!,
				x,
				y,
				seatSize.value,
				seatSize.value,
				props.borderRadius
			);
			renderingContext!.stroke();

			// 绘制默认图标
			if (seat.icon != null) {
				const { text, font } = getIcon(seat.icon);
				if (text != "") {
					renderingContext!.fillStyle =
						seat.color ?? (isDark.value ? props.darkColor : props.color);
					renderingContext!.font = `${fontSize}px ${font}`;
					renderingContext!.textAlign = "center";
					renderingContext!.textBaseline = "middle";
					renderingContext!.fillText(text, centerX, centerY);
				}
			}
		}
	}
}

// 绘制画布
function draw() {
	if (renderingContext == null) return;

	// 清空画布
	renderingContext!.save();
	renderingContext!.setTransform(1, 0, 0, 1, 0, 0);
	renderingContext!.clearRect(
		0,
		0,
		renderingContext!.canvas.width,
		renderingContext!.canvas.height
	);
	renderingContext!.restore();

	// 应用变换
	renderingContext!.save();
	renderingContext!.translate(translateX.value, translateY.value);
	renderingContext!.scale(scale.value, scale.value);

	// 绘制座位
	seats.forEach((seat) => {
		drawSeat(seat);
	});

	renderingContext!.restore();
}

// 转换触摸点数据
function getTouches(touches: UniTouch[]): TouchPoint[] {
	const result: TouchPoint[] = [];
	for (let i = 0; i < touches.length; i++) {
		const touch = touches[i];
		result.push({
			x: touch.clientX,
			y: touch.clientY,
			identifier: touch.identifier
		} as TouchPoint);
	}
	return result;
}

// 更新画布偏移量
function updateOffset() {
	uni.createSelectorQuery()
		.in(proxy)
		.select("#seatCanvas")
		.boundingClientRect((rect) => {
			offsetTop = (rect as NodeInfo).top ?? 0;
			offsetLeft = (rect as NodeInfo).left ?? 0;
		})
		.exec();
}

// 计算两点距离
function getTouchDistance(p1: TouchPoint, p2: TouchPoint): number {
	const dx = p2.x - p1.x;
	const dy = p2.y - p1.y;
	return Math.sqrt(dx * dx + dy * dy);
}

// 计算两点中心
function getTouchCenter(p1: TouchPoint, p2: TouchPoint): TouchPoint {
	return {
		x: (p1.x + p2.x) / 2,
		y: (p1.y + p2.y) / 2,
		identifier: 0
	};
}

// 根据坐标获取座位
function getSeatAtPoint(screenX: number, screenY: number): ClSelectSeatItem | null {
	// 转换为相对坐标
	const relativeX = screenX - offsetLeft;
	const relativeY = screenY - offsetTop - (isH5() ? 45 : 0);

	// 转换为画布坐标
	const canvasX = (relativeX - translateX.value) / scale.value;
	const canvasY = (relativeY - translateY.value) / scale.value;

	// 计算行列
	const col = Math.floor(canvasX / (seatSize.value + props.seatGap));
	const row = Math.floor(canvasY / (seatSize.value + props.seatGap));

	// 检查有效性
	if (row >= 0 && row < props.rows && col >= 0 && col < props.cols) {
		const localX = canvasX - col * (seatSize.value + props.seatGap);
		const localY = canvasY - row * (seatSize.value + props.seatGap);

		// 检查是否在座位内（排除间隙）
		if (localX >= 0 && localX <= seatSize.value && localY >= 0 && localY <= seatSize.value) {
			const index = row * props.cols + col;
			return seats[index];
		}
	}

	return null;
}

// 设置座位
function setSeat(row: number, col: number, data: UTSJSONObject) {
	const index = row * props.cols + col;
	if (index >= 0 && index < seats.length) {
		assign(seats[index], data);
		draw();
	}
}

// 获取所有座位
function getSeats(): ClSelectSeatItem[] {
	return seats;
}

// 初始化 Canvas
function initCanvas() {
	uni.createCanvasContextAsync({
		id: "seatCanvas",
		component: proxy,
		success: (context: CanvasContext) => {
			canvasContext = context;
			renderingContext = context.getContext("2d")!;

			// 适配高清屏
			const dpr = getDevicePixelRatio();
			renderingContext!.canvas.width = renderingContext!.canvas.offsetWidth * dpr;
			renderingContext!.canvas.height = renderingContext!.canvas.offsetHeight * dpr;
			renderingContext!.scale(dpr, dpr);

			initSeats();
			centerView();

			// 预加载图片后再绘制
			preloadImages().finally(() => {
				draw();
				updateOffset();
			});
		}
	});
}

// 触摸开始
function onTouchStart(e: UniTouchEvent) {
	updateOffset();

	touchPoints = getTouches(e.touches);
	hasMoved = false;

	// 记录起始触摸点
	if (touchPoints.length == 1) {
		startTouchX = touchPoints[0].x;
		startTouchY = touchPoints[0].y;
	}

	// 记录双指初始状态
	if (touchPoints.length == 2) {
		hasMoved = true; // 双指操作直接标记为已移动
		lastDistance = getTouchDistance(touchPoints[0], touchPoints[1]);
		const center = getTouchCenter(touchPoints[0], touchPoints[1]);
		lastCenterX = center.x;
		lastCenterY = center.y;
	}
}

// 触摸移动
function onTouchMove(e: UniTouchEvent) {
	const currentTouches = getTouches(e.touches);

	// 双指缩放
	if (currentTouches.length == 2 && touchPoints.length == 2) {
		hasMoved = true;
		const currentDistance = getTouchDistance(currentTouches[0], currentTouches[1]);
		const currentCenter = getTouchCenter(currentTouches[0], currentTouches[1]);

		// 计算缩放
		const scaleChange = currentDistance / lastDistance;
		const newScale = Math.max(
			props.minScale,
			Math.min(props.maxScale, scale.value * scaleChange)
		);

		// 以触摸中心为基准缩放
		const scaleDiff = newScale - scale.value;
		translateX.value -= (currentCenter.x - translateX.value) * (scaleDiff / scale.value);
		translateY.value -= (currentCenter.y - translateY.value) * (scaleDiff / scale.value);

		scale.value = newScale;
		lastDistance = currentDistance;

		// 计算平移
		const dx = currentCenter.x - lastCenterX;
		const dy = currentCenter.y - lastCenterY;
		translateX.value += dx;
		translateY.value += dy;

		lastCenterX = currentCenter.x;
		lastCenterY = currentCenter.y;
	} else if (currentTouches.length == 1 && touchPoints.length == 1) {
		// 单指拖动
		const dx = currentTouches[0].x - touchPoints[0].x;
		const dy = currentTouches[0].y - touchPoints[0].y;

		translateX.value += dx;
		translateY.value += dy;

		// 判断是否超过拖动阈值
		const totalDx = currentTouches[0].x - startTouchX;
		const totalDy = currentTouches[0].y - startTouchY;
		if (Math.abs(totalDx) > moveThreshold || Math.abs(totalDy) > moveThreshold) {
			hasMoved = true;
		}
	}

	// 限制平移范围
	constrainTranslate();

	// 绘制
	draw();

	// 触发移动事件
	emit("move", {
		translateX: translateX.value,
		translateY: translateY.value,
		scale: scale.value,
		screenTranslateX: translateX.value - leftPadding + (props.width * (scale.value - 1)) / 2
	});

	// 更新触摸点
	touchPoints = currentTouches;
}

// 触摸结束
function onTouchEnd(e: UniTouchEvent) {
	const changedTouches = getTouches(e.changedTouches);

	// 单击选座（未发生拖动或缩放时才触发）
	if (changedTouches.length == 1 && touchPoints.length == 1 && !hasMoved) {
		const touch = changedTouches[0];
		const seat = getSeatAtPoint(touch.x, touch.y);

		if (seat != null && seat.disabled != true && seat.empty != true) {
			let value: ClSelectSeatValue[] = [];

			if (isSelected(seat.row, seat.col)) {
				value = props.modelValue.filter(
					(item) => !(item.row == seat.row && item.col == seat.col)
				);
			} else {
				value = [
					...props.modelValue,
					{ row: seat.row, col: seat.col } as ClSelectSeatValue
				];
			}

			emit("update:modelValue", value);
			emit("seatClick", seat);
		}
	}

	touchPoints = getTouches(e.touches);

	// 所有手指抬起后重置状态
	if (touchPoints.length == 0) {
		hasMoved = false;
	}
}

// 监听选中变化
watch(
	computed<ClSelectSeatValue[]>(() => props.modelValue),
	() => {
		draw();
	},
	{ deep: true }
);

// 监听暗色模式变化
watch(
	isDark,
	() => {
		draw();
	},
	{ deep: true }
);

// 监听座位数据变化
watch(
	computed<ClSelectSeatItem[]>(() => props.seats),
	() => {
		initSeats();
		draw();
	},
	{ deep: true }
);

onMounted(() => {
	initCanvas();
});

defineExpose({
	setSeat,
	getSeats,
	draw
});
</script>

<style lang="scss" scoped>
.cl-select-seat {
	@apply relative;

	&__canvas {
		@apply h-full w-full;
	}

	&__index {
		@apply absolute top-0 left-1 z-10 rounded-xl;
		background-color: rgba(0, 0, 0, 0.4);

		&-item {
			@apply flex flex-col items-center justify-center;
			width: 14px;
		}
	}
}
</style>
