<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="onSliderChange"
			@reset="onSliderReset"
		/>
	</view>
</template>

<script setup>
import { computed, getCurrentInstance, onMounted, onUnmounted, 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 },
	// 新增：引擎接口（由父组件从 useLenticularCraftTiltPreview / useLenticularPreview 等注入）
	// Function 类型 prop 必须用 factory 返回默认函数，避免所有实例共享同一引用
	simulateTilt:             { type: Function, default: () => () => {} },
	simulateTiltFromNormalized:{ type: Function, default: () => () => {} },
	simulateTiltReset:        { type: Function, default: () => () => {} },
	// 新增：自动控制
	autoEnabled:  { type: Boolean, default: true },
	// auto 周期：sin 波一个完整循环的毫秒数。10000 = 10s 一周，再慢会感觉拖沓/卡帧
	autoPeriodMs: { type: Number,  default: 15000 },
	idleResumeMs: { type: Number,  default: 1500 },
})

// 保留 @simulate 事件以兼容其他不传引擎 props 的消费者
// （他们 listen @simulate 自己驱动引擎；本组件内已用 props.simulateTilt* 直接驱动）
// 新增 @mode-change：控制模式切换时通知父组件（auto ↔ manual）
const emit = defineEmits(['simulate', 'modeChange'])

/** 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)

// 内部滑块值（auto / 触摸 / 滑块拖动 / 复位 都在这里写）
const sliderValue = ref(0.5)

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()
		}
	})
}

// ====================================================================
//  自动控制状态机（auto ↔ manual + idle 恢复）
// ====================================================================

const controlMode = ref('auto')            // 'auto' | 'manual'
const lastAutoWriteTime = ref(0)          // auto 最近一次写滑块值的时间戳（v-model 回声时间窗检测用）
let idleAccumMs = 0                       // manual 模式下空闲累计时长
let autoPhase = 0                         // sin 相位
let lastTickTime = null                   // 上一次 tick 的时间戳
let rafId = null                          // rAF 句柄

// uniapp 小程序运行时（app-service.js）下，performance 和 requestAnimationFrame 都不存在
// 用 setTimeout 递归调度（60fps ≈ 16ms/帧），跨平台稳
// 时间戳统一用 Date.now()（不依赖 performance）
const nextFrame = (cb) => setTimeout(cb, 16)
const cancelFrame = (id) => clearTimeout(id)

/**
 * auto tick：rAF 循环的唯一调度点
 *  - auto 模式：按 dt 推进 sin 相位，写滑块 + 引擎
 *  - manual 模式：累计空闲时长，达到 idleResumeMs 时 snap 相位到当前滑块并切回 auto
 */
function autoTick(t) {
	if (!props.autoEnabled) return
	if (lastTickTime == null) lastTickTime = t
	const dt = t - lastTickTime
	lastTickTime = t

	if (controlMode.value !== 'auto') {
		idleAccumMs += dt
		if (idleAccumMs >= props.idleResumeMs) {
			const targetSin = Math.max(-1, Math.min(1, 2 * sliderValue.value - 1))
			autoPhase = Math.asin(targetSin)
			lastTickTime = null
			idleAccumMs = 0
			controlMode.value = 'auto'
			emit('modeChange', 'auto')
		}
		return
	}

	idleAccumMs = 0
	autoPhase += (dt / props.autoPeriodMs) * 2 * Math.PI
	if (Math.abs(autoPhase) > 100 * 2 * Math.PI) {
		autoPhase = autoPhase % (2 * Math.PI)
	}
	// 用 sin 波 + 宽摆幅（AMP=0.5）让 auto 真正触到 A=100% / B=100% 状态
	//   sin 在极值点速度=0，会"停留"在端点，让用户看清 A=100% / B=100%
	//   配合 blendBaseScale=5 + 线性权重，端点附近叠化也很明显，不是硬切
	const AUTO_SWING_AMP = 0.3  // 满摆幅，v ∈ [0, 1]，覆盖完整 0/100% 状态
	const sinVal = Math.sin(autoPhase)
	const v = 0.5 + sinVal * AUTO_SWING_AMP
	sliderValue.value = v
	lastAutoWriteTime.value = Date.now()
	// 走归一化版本（兼容两种消费者：
	//   - 传 simulateTiltFromNormalized（离散 snap）：直接 snap
	//   - 只传 simulate（连续）：simulateTiltFromNormalized 默认是 no-op，
	//     这种场景下消费者需要额外传 simulate，但我们让 auto 优先尝试归一化接口）
	const x = v * 2 - 1
	props.simulateTiltFromNormalized?.(x, 0)
}

/** 启动 rAF 循环（幂等）。
 * 时间戳统一用 Date.now()，避免 uniapp 真机 performance 缺失导致的 ReferenceError。
 */
function startAutoLoop() {
	if (rafId != null) return
	const tickFn = () => {
		autoTick(Date.now())
		rafId = nextFrame(tickFn)
	}
	rafId = nextFrame(tickFn)
}

/** 停止 rAF 循环 */
function stopAutoLoop() {
	if (rafId != null) {
		cancelFrame(rafId)
		rafId = null
	}
}

/** 用户接管 → 暂停 auto + 重置空闲计时器 */
function pauseAutoAndScheduleResume() {
	if (controlMode.value !== 'manual') {
		controlMode.value = 'manual'
		emit('modeChange', 'manual')
	}
	idleAccumMs = 0
}

// ====================================================================
//  触摸 / 滑块 / 复位：组件内部直接调引擎函数（通过 props 注入）
// ====================================================================

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
	handleTouch(Math.max(-1, Math.min(1, nx)), Math.max(-1, Math.min(1, ny)))
}

function handleTouch(x, y) {
	pauseAutoAndScheduleResume()
	sliderValue.value = Math.max(0, Math.min(1, (x + 1) / 2))
	props.simulateTiltFromNormalized?.(x, y)
	// 向后兼容：emit 给其他不传引擎 props 的消费者
	emit('simulate', x, y)
}

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
	// 不发 (0,0)，由 auto / 引擎自身状态决定
}

/**
 * 滑块变化：
 *   - auto 自己写滑块触发的 v-model/原生 change 回声（时间窗 100ms 内）→ 不暂停 auto
 *   - 用户拖动 → 暂停 auto + 驱动引擎
 */
function onSliderChange(v) {
	sliderValue.value = v
	const isAutoEcho = Date.now() - lastAutoWriteTime.value < 100
	if (!isAutoEcho) {
		pauseAutoAndScheduleResume()
		const dx = (v - 0.5) * 90
		props.simulateTilt?.(dx, 0)
	}
}

/** 复位按钮：暂停 auto + 滑块回 0.5 + 引擎归中 */
function onSliderReset() {
	pauseAutoAndScheduleResume()
	sliderValue.value = 0.5
	props.simulateTiltReset?.()
}

onMounted(() => {
	setTimeout(() => {
		void refreshRect()
	}, 0)
	if (props.autoEnabled) {
		startAutoLoop()
	}
})

onUnmounted(() => {
	stopAutoLoop()
})
</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>
