<template>
	<view class="card-container">
		<view
			:id="cardId"
			class="card-frame"
			@touchstart.stop="onFrameTouchStart"
			@touchmove.stop.prevent="onFrameTouchMove"
			@touchend.stop="onFrameTouchEnd"
			@touchcancel.stop="onFrameTouchEnd"
		>
			<view class="card-body" :style="cardRotateStyle">
				<view
					v-for="layer in layers"
					:key="layer.id"
					class="card-layer"
					:style="getLayerStyle(layer)"
				>
					<image
						v-if="layer.src"
						class="card-layer-img"
						:src="layer.src"
						mode="aspectFill"
						@error="onImageError"
						@load="onImageLoad"
					/>
					<template v-else>
						<view
							v-for="(dot, i) in layer.dots || []"
							:key="i"
							class="card-layer-dot"
							:style="getDotStyle(dot)"
						/>
					</template>
				</view>

				<view class="lenticular-shimmer" :style="shimmerStyle" />
				<view class="lenticular-tint" />
				<view class="glass-rim" />
				<view class="vignette" />
			</view>

			<!-- <view v-if="showHint && tiltHintText" class="tilt-hint">
				<text class="tilt-hint-icon">↻</text>
				<text class="tilt-hint-text">{{ tiltHintText }}</text>
				<text v-if="approximatePreview" class="tilt-hint-sub">叠化近似预览（倾斜或拖动体验光栅效果）</text>
			</view> -->
		</view>

		<ManualTiltSlider
			v-if="showSlider"
			:value="sliderValue"
			@change="v => emit('update:sliderValue', v)"
			@reset="emit('sliderReset')"
		/>
	</view>
</template>

<script setup>
import { computed, getCurrentInstance, onMounted, ref, watch } from 'vue'
import ManualTiltSlider from './ManualTiltSlider.vue'

const props = defineProps({
	layers: { type: Array, required: true },
	transforms: { type: Object, required: true },
	gyroSource: { type: String, default: 'simulation' },
	approximatePreview: { type: Boolean, default: true },
	skipBuiltInTouch: { type: Boolean, default: false },
	tiltHintText: { type: String, default: '倾斜手机预览' },
	/** 条纹高光中线不透明度 0~1，略提可增强光栅「栅感」 */
	shimmerMidOpacity: { type: Number, default: 0.1 },
	// 新增：手动滑块接入（spec §3.3）
	showSlider:  { type: Boolean, default: false },
	sliderValue: { type: Number,  default: 0.5 },
})

const emit = defineEmits(['simulate', 'update:sliderValue', 'sliderReset'])

/** Vue3 + uni：createSelectorQuery().in() 必须传组件 public 实例（proxy），传 internal instance 会运行时报错导致整页白屏 */
const pageProxy = getCurrentInstance()?.proxy

const showHint = ref(true)
const cardId = `lcard-${Math.random().toString(36).slice(2, 9)}`
const cardRect = ref(null)

function onImageError(e) {
	// console.log('[LenticularCard] image error:', e)
}

function onImageLoad(e) {
	// console.log('[LenticularCard] image loaded')
}

const MOTION_HIDE_PX = 0.45

/** 仅在画面因倾斜产生可见位移后隐藏提示（非陀螺仪连接即隐藏） */
function shouldHideTiltHint() {
	return Object.values(props.transforms || {}).some((t) => Math.abs(t.x) > MOTION_HIDE_PX)
}

watch(
	() => props.transforms,
	() => {
		if (shouldHideTiltHint()) showHint.value = false
	},
	{ deep: true }
)

const tiltVisualX = computed(() => {
	let num = 0
	let den = 0
	for (const layer of props.layers) {
		const t = props.transforms[layer.id]
		if (!t) continue
		const w = Math.max(0.06, t.opacity)
		num += t.x * w
		den += w
	}
	return den > 1e-6 ? num / den : 0
})

const cardRotateStyle = computed(() => {
	const x = tiltVisualX.value
	const rotateY = (x / 120) * 12
	return {
		transform: `perspective(1000px) rotateY(${rotateY}deg) rotateX(0deg)`,
	}
})

const shimmerStyle = computed(() => {
	const x = tiltVisualX.value
	const angle = 135 + (x / 120) * 60
	const a = Math.max(0, Math.min(0.35, Number(props.shimmerMidOpacity) || 0.1))
	return {
		background: `linear-gradient(${angle}deg, transparent 30%, rgba(221,183,255,${a}) 50%, transparent 70%)`,
	}
})

function getLayerStyle(layer) {
	const t = props.transforms[layer.id]
	const baseOpacity = t ? t.opacity : layer.opacity
	const x = t ? t.x : 0
	// 不在行内样式写 mix-blend-mode：部分小程序 / WebView 对非 normal 支持差，可能引发渲染异常
	return {
		opacity: baseOpacity,
		transform: `translate3d(${x}px, 0, 0)`,
		background: layer.background || 'transparent',
	}
}

function getDotStyle(dot) {
	const color = dot.color || '#dae2fd'
	return {
		left: `${dot.x}%`,
		top: `${dot.y}%`,
		width: `${dot.r * 2}px`,
		height: `${dot.r * 2}px`,
		background: color,
		opacity: dot.opacity != null ? dot.opacity : 1,
		boxShadow: `0 0 ${dot.r * 3}px ${color}`,
	}
}

function refreshRect() {
	return new Promise((resolve) => {
		try {
			const query = pageProxy ? uni.createSelectorQuery().in(pageProxy) : uni.createSelectorQuery()
			query
				.select(`#${cardId}`)
				.boundingClientRect((rect) => {
					if (rect && rect.width) {
						cardRect.value = {
							left: rect.left,
							top: rect.top,
							width: rect.width,
							height: rect.height,
						}
					}
					resolve()
				})
				.exec()
		} catch (e) {
			console.warn('[LenticularCard] refreshRect failed', e)
			resolve()
		}
	})
}

function dispatch(clientX, clientY) {
	const rect = cardRect.value
	if (!rect || !rect.width || !rect.height) {
		void refreshRect()
		return
	}
	const cx = rect.left + rect.width / 2
	const cy = rect.top + rect.height / 2
	const nx = ((clientX - cx) / (rect.width / 2)) * 1.1
	const ny = ((clientY - cy) / (rect.height / 2)) * 1.1
	emit(
		'simulate',
		Math.max(-1, Math.min(1, nx)),
		Math.max(-1, Math.min(1, ny))
	)
}

function onTouchMove(e) {
	if (props.skipBuiltInTouch) return
	const touch = e.touches && e.touches[0] ? e.touches[0] : e.changedTouches && e.changedTouches[0] ? e.changedTouches[0] : null
	if (!touch) return
	dispatch(touch.clientX, touch.clientY)
}

function onFrameTouchStart(e) {
	if (props.skipBuiltInTouch) return
	onTouchMove(e)
}

function onFrameTouchMove(e) {
	if (props.skipBuiltInTouch) return
	onTouchMove(e)
}

function onFrameTouchEnd() {
	if (props.skipBuiltInTouch) return
	// 修改前：emit('simulate', 0, 0)  ← 与滑块数据驱动冲突
	// 修改后：触摸结束不再强制发 (0,0)，由调用方决定
	// （spec §4.1 修复方案）
}

onMounted(() => {
	setTimeout(() => {
		void refreshRect()
	}, 0)
})
</script>

<style scoped>
.card-container {
	width: 100%;
	height: 100%;
	display: flex;
	align-items: center;
	justify-content: center;
	perspective: 1000px;
	position: relative;
}

.card-frame {
	position: relative;
	height: 100%;
	width: 100%;
	overflow: visible;
}

.card-body {
	position: absolute;
	inset: 0;
	border-radius: 24px;
	overflow: hidden;
	transform-style: preserve-3d;
	box-shadow: 0 20px 50px rgba(0, 0, 0, 0.55);
	border: 1px solid rgba(255, 255, 255, 0.18);
	background-color: #060e20;
	will-change: transform;
}

.card-layer {
	position: absolute;
	/* top: -6%;
	left: -6%; */
	width: 100%;
	height: 100%;
	will-change: transform, opacity;
	background-size: cover;
	background-position: center;
}

.card-layer-img {
	width: 100%;
	height: 100%;
}

.card-layer-dot {
	position: absolute;
	border-radius: 50%;
	transform: translate(-50%, -50%);
}

.lenticular-shimmer {
	position: absolute;
	inset: 0;
	pointer-events: none;
	opacity: 0.85;
}

.lenticular-tint {
	position: absolute;
	inset: 0;
	pointer-events: none;
	background: linear-gradient(
		135deg,
		rgba(221, 183, 255, 0.1) 0%,
		transparent 35%,
		transparent 65%,
		rgba(76, 215, 246, 0.1) 100%
	);
	opacity: 0.6;
}

.glass-rim {
	position: absolute;
	inset: 0;
	border-radius: 24px;
	border-top: 1px solid rgba(221, 183, 255, 0.42);
	box-shadow: inset 0 0 40px rgba(255, 255, 255, 0.05);
	pointer-events: none;
}

.vignette {
	position: absolute;
	inset: 0;
	background: linear-gradient(to top, rgba(6, 14, 32, 0.82), transparent 50%);
	pointer-events: none;
}

.tilt-hint {
	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 8px;
	background: rgba(45, 52, 73, 0.6);
	backdrop-filter: blur(12px);
	padding: 14px 22px;
	border-radius: 16px;
	border: 1px solid rgba(255, 255, 255, 0.1);
	pointer-events: none;
}

.tilt-hint-icon {
	font-size: 28px;
	line-height: 1;
	color: #ddb7ff;
}

.tilt-hint-text {
	font-size: 13px;
	font-weight: 600;
	letter-spacing: 0.08em;
	color: #dae2fd;
}

.tilt-hint-sub {
	font-size: 10px;
	font-weight: 500;
	letter-spacing: 0.04em;
	color: rgba(207, 194, 214, 0.75);
	text-align: center;
	max-width: 200px;
	line-height: 1.35;
}
</style>
