<template>
	<view class="laser-preview-root">
		<canvas
			v-if="webglSupport"
			id="laserPreviewWebgl"
			type="webgl"
			class="laser-preview-canvas"
			:disable-scroll="true"
			:style="{ width: exportCssW + 'px', height: exportCssH + 'px' }"
			:width="exportCssW"
			:height="exportCssH"
		/>
		<view v-else class="laser-preview-fallback">
			<image
				v-if="variantPath || fallbackPath"
				class="laser-preview-fallback-img"
				:src="variantPath || fallbackPath"
				mode="aspectFill"
			/>
			<text v-else class="laser-preview-fallback-text">无预览</text>
			<!-- 兜底时也保留镭射扫光感(CSS 动画,与 LaserVariantPyramid 一致) -->
			<view v-if="variantPath || fallbackPath" class="laser-preview-fallback-shimmer" />
			<view v-if="variantPath || fallbackPath" class="laser-preview-fallback-edge" />
		</view>
	</view>
</template>

<script setup>
import { getCurrentInstance, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { createLaserPreviewWebgl, loadTextureImage, BEAM_STYLE_HUE } from '@/utils/laser-card/laserPreviewWebgl.js'
import { resolveGratingConfig } from '@/utils/laser-card/laserGrating.js'

const props = defineProps({
  /** 抠图 PNG（用于闪粉 alpha 计算） */
  cutoutPath: { type: String, default: '' },
  /** 已生成的激光卡图（WebGL 直接渲染这张） */
  variantPath: { type: String, default: '' },
  /** WebGL 不可用时的兜底图 */
  fallbackPath: { type: String, default: '' },
  preset: { type: Object, default: () => ({}) },
  /** 当前选中的卡索引（用于 resolveGratingConfig 匹配导出参数） */
  variantIndex: { type: Number, default: 0 },
  paused: { type: Boolean, default: false },
})

const exportCssW = 360
const exportCssH = 480

const webglSupport = ref(true)

const pageProxy = getCurrentInstance()?.proxy

let _canvasNode = null
let _webglR = null
let _photoRect = null
let _rafId = null
let _engineReady = false
let _startTime = 0

function computeCover(iw, ih, cw, ch) {
	const w = Number(iw) || 1
	const h = Number(ih) || 1
	const scale = Math.max(cw / w, ch / h)
	const dw = w * scale
	const dh = h * scale
	return { dx: (cw - dw) / 2, dy: (ch - dh) / 2, dw, dh }
}

function scheduleFrame(cb) {
	if (typeof requestAnimationFrame === 'function') return requestAnimationFrame(cb)
	return setTimeout(cb, 33)
}

function cancelFrame(id) {
	if (id == null) return
	try {
		if (typeof cancelAnimationFrame === 'function') cancelAnimationFrame(id)
		else clearTimeout(id)
	} catch {
		/* noop */
	}
}

const scheduleLoop = () => {
	cancelFrame(_rafId)
	if (_startTime === 0) _startTime = performance.now()
	const loop = () => {
		if (!_engineReady) {
			_rafId = scheduleFrame(loop)
			return
		}
		if (!props.paused && _webglR && _photoRect) {
			const elapsed = performance.now() - _startTime
			renderFrame(elapsed)
		}
		_rafId = scheduleFrame(loop)
	}
	_rafId = scheduleFrame(loop)
}

function stopLoop() {
	cancelFrame(_rafId)
	_rafId = null
}

function _resetEngine() {
	_engineReady = false
	_photoRect = null
	_startTime = 0
	if (_webglR) {
		try { _webglR.destroy() } catch { /* noop */ }
		_webglR = null
	}
}

async function _initWebglEngine() {
	if (_webglR) return true
	try {
		return await new Promise((resolve) => {
			const q = pageProxy ? uni.createSelectorQuery().in(pageProxy) : uni.createSelectorQuery()
			q.select('#laserPreviewWebgl')
				.fields({ node: true, size: true })
				.exec((res) => {
					const row = res && res[0]
					if (!row || !row.node) {
						resolve(false)
						return
					}
					_canvasNode = row.node

					// 像素比：与 LaserCardStudio 保持一致的上限范围
					let pr = 1
					try {
						const sys = uni.getSystemInfoSync()
						pr = Math.min(2.5, Math.max(1, Number(sys.pixelRatio) || 1))
					} catch {
						pr = 1
					}

					const bw = Math.max(2, Math.floor(exportCssW * pr))
					const bh = Math.max(2, Math.floor(exportCssH * pr))

					try {
						_canvasNode.width = bw
						_canvasNode.height = bh
					} catch {
						resolve(false)
						return
					}

					const gl =
						_canvasNode.getContext('webgl', {
							alpha: true,
							antialias: true,
							preserveDrawingBuffer: false,
							stencil: false,
							depth: false,
						}) || _canvasNode.getContext('experimental-webgl')

					if (!gl) {
						webglSupport.value = false
						resolve(false)
						return
					}

					try {
						_webglR = createLaserPreviewWebgl(gl, { width: exportCssW, height: exportCssH })
						resolve(true)
					} catch (e3) {
						console.warn('[LaserPreviewCanvas] createLaserPreviewWebgl failed', e3)
						webglSupport.value = false
						resolve(false)
					}
				})
		})
	} catch (e) {
		console.warn('[LaserPreviewCanvas] init webgl failed', e)
		webglSupport.value = false
		return false
	}
}

async function _uploadTexturesAndRects() {
	if (!_webglR || !_canvasNode) return
	// cutoutPath 用于闪粉 alpha 计算（抠图 PNG 是透明底的）
	const cutoutSrc = String(props.cutoutPath || '').trim()
	if (!cutoutSrc) return

	try {
		const imgInfo = await new Promise((resolve) => {
			uni.getImageInfo({ src: cutoutSrc, success: resolve, fail: () => resolve(null) })
		})
		if (imgInfo) {
			_photoRect = computeCover(imgInfo.width, imgInfo.height, exportCssW, exportCssH)
		}
	} catch { /* noop */ }

	try {
		const photoImg = await loadTextureImage(cutoutSrc, _canvasNode)
		_webglR.uploadPhoto(photoImg)
	} catch {
		_webglR.uploadPhoto(null)
	}

	_engineReady = true
	if (_photoRect) {
		_startTime = performance.now()
		renderFrame(0)
	}
}

function renderFrame(timeMs) {
	if (!_webglR || !_photoRect) return
	const preset = props.preset || {}
	const gratingConfig = resolveGratingConfig(preset, props.variantIndex)
	const beamEffectId = String(preset?.beam || 'prism')
	const styleHue = typeof BEAM_STYLE_HUE[beamEffectId] === 'number' ? BEAM_STYLE_HUE[beamEffectId] : BEAM_STYLE_HUE.prism
	const frostIntensity = Number(preset?.frostIntensity) || 18
	const flowSpeed = Number(preset?.flowSpeed || 1)
	const cw = exportCssW
	const ch = exportCssH
	const cornerRadiusPx = Math.min(32, cw * 0.065, ch * 0.048)

	// 根据 _photoRect（抠图 PNG 的 cover layout）计算人物中心 UV 和边缘环形参数
	const pr = _photoRect
	const personEdgeX = (pr.dx + pr.dw / 2) / cw
	const personEdgeY = (pr.dy + pr.dh / 2) / ch
	const personEdgeInner = Math.min(pr.dw, pr.dh) / cw * 0.40
	const personEdgeOuter = Math.min(pr.dw, pr.dh) / cw * 0.72

	_webglR.render(timeMs, {
		photoRect: _photoRect,
		gratingConfig,
		cornerRadiusPx,
		styleHue,
		animSpeed: 1,
		frostIntensity,
		flowSpeed,
		personEdgeX,
		personEdgeY,
		personEdgeInner,
		personEdgeOuter,
		canvasW: cw,
		canvasH: ch,
	})
}

watch(
	() => [props.cutoutPath, props.variantPath, props.preset, props.paused],
	async ([cutout, variant, preset, paused]) => {
		if (!cutout && !variant) return
		if (!webglSupport.value) return
		if (!preset) return
		_resetEngine()
		stopLoop()
		const ok = await _initWebglEngine()
		if (!ok) return
		await _uploadTexturesAndRects()
		if (!paused) scheduleLoop()
	},
	{ immediate: true, deep: true }
)

onMounted(async () => {
	// 初次挂载尝试初始化；后续由 watch 处理重新渲染
	if (!props.paused) scheduleLoop()
})

onBeforeUnmount(() => {
	stopLoop()
	try {
		_webglR?.destroy?.()
	} catch {
		/* noop */
	}
	_webglR = null
	_canvasNode = null
})
</script>

<style scoped>
.laser-preview-root {
	width: 100%;
	height: 100%;
	display: flex;
	align-items: center;
	justify-content: center;
}

.laser-preview-canvas {
	border-radius: 24rpx;
}

.laser-preview-fallback {
	position: relative;
	width: 360px;
	height: 480px;
	border-radius: 24rpx;
	overflow: hidden;
	background: rgba(255, 255, 255, 0.06);
	display: flex;
	align-items: center;
	justify-content: center;
}

.laser-preview-fallback-img {
	width: 100%;
	height: 100%;
}

.laser-preview-fallback-text {
	color: rgba(255, 255, 255, 0.7);
	font-size: 28rpx;
}

/* 兜底场景下的镭射扫光(WebGL 不可用时仍能看到动态光感) */
.laser-preview-fallback-shimmer {
	position: absolute;
	inset: 0;
	border-radius: 24rpx;
	background: linear-gradient(
		115deg,
		transparent 32%,
		rgba(255, 255, 255, 0.18) 46%,
		rgba(255, 255, 255, 0.32) 50%,
		rgba(255, 255, 255, 0.18) 54%,
		transparent 68%
	);
	background-size: 220% 220%;
	background-position: -120% -120%;
	animation: laser-preview-shimmer-sweep 8s ease-in-out infinite;
	mix-blend-mode: screen;
	pointer-events: none;
}

/* 兜底场景下的彩虹描边 */
.laser-preview-fallback-edge {
	position: absolute;
	inset: 0;
	border-radius: 24rpx;
	pointer-events: none;
	box-shadow:
		inset 0 0 8rpx rgba(200, 220, 240, 0.22),
		inset 0 0 18rpx rgba(220, 230, 240, 0.12);
	mix-blend-mode: screen;
}

@keyframes laser-preview-shimmer-sweep {
	0% {
		background-position: -120% -120%;
	}
	55% {
		background-position: -120% -120%;
	}
	100% {
		background-position: 220% 220%;
	}
}
</style>

