/* global React, window */
// ============================================================
// PandaKit · 客户端 canvas 图层合成（Sprint 2.J 大改，2026-05-10）
//
// 改动：
//   - 删 lofi 滤镜（applyLofi / quantize / pixelate）
//   - 删 glow bg 染色（fillBg 简化为纯色）
//   - 新增 luminance-to-alpha：shell 黑变 opaque/灰变半透明/白变透明
//     → 修复"轮廓被背景色串色"（边缘抗锯齿灰像素和彩色 bg 混色）
//     → panda 内白也透出 bg 色（视觉更自然，不再是死白）
//   - 新增 accessories 配饰渲染（在 panda layer 之上画 SVG path）
//
// 渲染管线 drawComposite:
//   1. fillBg                     纯色背景
//   2. pc = shell + pmask clip + luminance-to-alpha   panda 黑廓+面区透明，可受 bg 染色
//   3. drawFaceLayer onto pc      face 用 face mask clip 贴到脸区
//   4. drawImage(pc) 到主 canvas
//   5. drawAccessories            汗滴/腮红/泪水等画在主 canvas 上（可超出脸区）
//   6. drawCaption                底部白条+中文
// ============================================================

// 4 个 meme 标准字体（user：之前 6 个有 3 个难读已删）
const PANDA_FONTS = {
	sans:   '"Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif',  // 思源黑 Bold（meme 标准）
	yahei:  '"Microsoft YaHei", "PingFang SC", "Noto Sans SC", sans-serif',  // 系统兜底
	qingke: '"ZCOOL QingKe HuangYou", "Noto Sans SC", sans-serif',           // 厚重萌系
	kuaile: '"ZCOOL KuaiLe", "Noto Sans SC", sans-serif',                    // 圆润可爱
}

// --- helpers -------------------------------------------------

// _imageCache 由 PandaCanvas 检查 cache hit 同步画（避免 Promise.all microtask 帧延迟）
const _imageCache = new Map()

// idle preload 全部 shell + face PNG → 后续 random 时图都在 cache 走 fast path 同步画
// 调用约束：first draw 完成后调用一次（panda.jsx PandaCanvas 内部触发）
let _preloadStarted = false
function preloadAllAssets() {
	if (_preloadStarted) return
	_preloadStarted = true
	const shells = window.SHELLS || []
	const faces = window.FACES || []
	const items = []
	for (const s of shells) {
		items.push('shells/' + s.file)
		items.push('shells/' + s.mask)
		items.push('shells/' + s.pmask)
	}
	for (const f of faces) {
		items.push('faces/' + f.file)
	}
	const idle = window.requestIdleCallback || ((cb) => setTimeout(cb, 100))
	let i = 0
	const BATCH = 4   // 4 个并发 PNG fetch（HTTP/2 multiplex 友好）
	function next() {
		if (i >= items.length) return
		const batch = items.slice(i, i + BATCH).filter((s) => !_imageCache.has(s))
		i += BATCH
		if (batch.length === 0) { idle(next); return }
		Promise.allSettled(batch.map(loadImage)).finally(() => idle(next))
	}
	idle(next)
}

function loadImage(src) {
	if (_imageCache.has(src)) return Promise.resolve(_imageCache.get(src))
	return new Promise((resolve, reject) => {
		const img = new Image()
		img.crossOrigin = 'anonymous'
		img.onload = () => { _imageCache.set(src, img); resolve(img) }
		img.onerror = () => reject(new Error(`load fail: ${src}`))
		img.src = src
	})
}

function fillBg(ctx, w, h, bg) {
	ctx.fillStyle = bg || '#FFFFFF'
	ctx.fillRect(0, 0, w, h)
}

// 只处理"灰色 + 暗"的像素（panda 黑廓抗锯齿），**保留白色 + 彩色**
// - 灰且暗 (luma<200, R≈G≈B): RGB→黑 + alpha 按 luma 渐变（黑实心，灰半透明）
// - 白色 (luma>=200): 不动
// - 彩色 (R/G/B 差异大): 不动 — 修复"彩色 shell 被 shellEdgeToAlpha 强制变黑"的 bug
//   （之前 lxhws2 蓝身体被误伤变成黑色 → 用户以为 lofi 滤镜还在）
function shellEdgeToAlpha(ctx, w, h) {
	const data = ctx.getImageData(0, 0, w, h)
	const arr = data.data
	for (let i = 0; i < arr.length; i += 4) {
		if (arr[i + 3] === 0) continue
		const r = arr[i], g = arr[i + 1], b = arr[i + 2]
		const luma = 0.299 * r + 0.587 * g + 0.114 * b
		const maxC = Math.max(r, g, b)
		const minC = Math.min(r, g, b)
		const isGray = maxC - minC < 30   // 极差 < 30 才算灰色（区分 panda 黑廓 vs 彩色衣服/物品）
		if (luma < 200 && isGray) {
			// 灰色 + 暗 → panda 黑廓抗锯齿：RGB 强制黑 + alpha 渐变
			arr[i] = 0; arr[i + 1] = 0; arr[i + 2] = 0
			arr[i + 3] = Math.min(255, Math.round(255 - luma * 0.85))
		}
		// 白色 + 彩色像素都保留原色（彩色 panda 身体 / 拿的彩色道具都保持原色）
	}
	ctx.putImageData(data, 0, 0)
}

// panda 层 cache（cache by shell.src + size）— shell + pmask + shellEdgeToAlpha 是最重的步骤
// 1024×1024 全像素 loop ~50-150ms，重复同 shell 时直接 clone cache canvas（GPU drawImage <1ms）
// random 时换 shell 仍 cache miss 但只一次；同 shell 改 face/text/rotation cache hit 0ms
const _pandaLayerCache = new Map()
function getPandaLayer(shellImg, pmaskImg, W, H) {
	const key = shellImg.src + '|' + W + 'x' + H
	let cached = _pandaLayerCache.get(key)
	if (!cached) {
		cached = document.createElement('canvas')
		cached.width = W; cached.height = H
		const ctx = cached.getContext('2d')
		ctx.imageSmoothingEnabled = true
		ctx.imageSmoothingQuality = 'high'
		ctx.drawImage(shellImg, 0, 0, W, H)
		ctx.globalCompositeOperation = 'destination-in'
		ctx.drawImage(pmaskImg, 0, 0, W, H)
		ctx.globalCompositeOperation = 'source-over'
		shellEdgeToAlpha(ctx, W, H)
		_pandaLayerCache.set(key, cached)
	}
	// clone 一份给 caller（caller 会画 face 上去，不能污染 cache）
	const out = document.createElement('canvas')
	out.width = W; out.height = H
	out.getContext('2d').drawImage(cached, 0, 0)
	return out
}

// 算 shell 白色像素 mask（cache by shell.src）— 让 face 只在 shell 白色像素显示
// 修"墨镜款 face 覆盖墨镜" / "举牌款 face 盖到牌子"等问题：shell 黑色保留，face 不画上去
const _shellWhiteMaskCache = new Map()
function getShellWhiteMask(shellImg, W, H) {
	const key = shellImg.src + '|' + W + 'x' + H
	if (_shellWhiteMaskCache.has(key)) return _shellWhiteMaskCache.get(key)
	const c = document.createElement('canvas')
	c.width = W; c.height = H
	const ctx = c.getContext('2d')
	ctx.imageSmoothingEnabled = true
	ctx.drawImage(shellImg, 0, 0, W, H)
	const data = ctx.getImageData(0, 0, W, H)
	const arr = data.data
	for (let i = 0; i < arr.length; i += 4) {
		const r = arr[i], g = arr[i + 1], b = arr[i + 2]
		const isWhite = r > 200 && g > 200 && b > 200
		arr[i] = arr[i + 1] = arr[i + 2] = 255
		arr[i + 3] = isWhite ? 255 : 0
	}
	ctx.putImageData(data, 0, 0)
	_shellWhiteMaskCache.set(key, c)
	return c
}

// face layer：face 缩放 + 旋转 + 翻转，按 content_center 对齐到 shell 脸区中心
function drawFaceLayer({ W, H, shellImg, faceImg, maskImg, bbox, ratio, faceFill, rotation, flipX, flipY, contentCenter }) {
	const fc = document.createElement('canvas')
	fc.width = W; fc.height = H
	const fctx = fc.getContext('2d')
	fctx.imageSmoothingEnabled = true
	fctx.imageSmoothingQuality = 'high'

	const [x1, y1, x2, y2] = bbox
	const bw = (x2 - x1) * ratio
	const bh = (y2 - y1) * ratio
	const cx = ((x1 + x2) / 2) * ratio
	const cy = ((y1 + y2) / 2) * ratio

	const fScale = Math.min(bw / faceImg.naturalWidth, bh / faceImg.naturalHeight) * faceFill
	const fw = faceImg.naturalWidth * fScale
	const fh = faceImg.naturalHeight * fScale
	const ccX = (contentCenter ? contentCenter[0] : faceImg.naturalWidth / 2) * fScale
	const ccY = (contentCenter ? contentCenter[1] : faceImg.naturalHeight / 2) * fScale

	fctx.fillStyle = '#FFFFFF'
	fctx.fillRect(0, 0, W, H)

	fctx.save()
	fctx.translate(cx, cy)
	if (rotation) fctx.rotate((rotation * Math.PI) / 180)
	if (flipX || flipY) fctx.scale(flipX ? -1 : 1, flipY ? -1 : 1)
	fctx.drawImage(faceImg, -ccX, -ccY, fw, fh)
	fctx.restore()

	// 双重 clip：
	// 1) face mask 椭圆 — face 只在 yaml.bbox 内显示
	// 2) shell white mask — face 只在 shell 白色像素显示（黑廓/墨镜/身体保留 shell 原色）
	fctx.globalCompositeOperation = 'destination-in'
	fctx.drawImage(maskImg, 0, 0, W, H)
	if (shellImg) {
		const wmask = getShellWhiteMask(shellImg, W, H)
		fctx.drawImage(wmask, 0, 0)
	}
	fctx.globalCompositeOperation = 'source-over'
	return fc
}

// 配饰已删（用户：太丑）— 保留空函数避免破坏 drawComposite 的调用

// 底部加文字
function drawCaption(ctx, text, x, y, w, h, fontKey = 'kuaile') {
	ctx.fillStyle = '#FFFFFF'
	ctx.fillRect(x, y, w, h)
	if (!text) return
	const family = PANDA_FONTS[fontKey] || PANDA_FONTS.sans
	let fs = Math.floor(h * 0.55)
	ctx.fillStyle = '#1A1714'
	ctx.textAlign = 'center'
	ctx.textBaseline = 'middle'

	const wrap = (s, fontSize) => {
		ctx.font = `700 ${fontSize}px ${family}`
		const maxW = w * 0.92
		const lines = []
		const paragraphs = s.split('\n')
		for (const p of paragraphs) {
			let cur = ''
			for (const ch of p) {
				const test = cur + ch
				if (ctx.measureText(test).width > maxW && cur) { lines.push(cur); cur = ch }
				else cur = test
			}
			if (cur) lines.push(cur)
			else if (paragraphs.length > 1) lines.push('')
		}
		return lines
	}
	let lines = wrap(text, fs)
	while (lines.length * fs * 1.25 > h * 0.95 && fs > 18) {
		fs = Math.floor(fs * 0.88)
		lines = wrap(text, fs)
	}
	ctx.font = `700 ${fs}px ${family}`
	const lineH = fs * 1.2
	const totalH = lineH * lines.length
	const startY = y + (h - totalH) / 2 + lineH / 2
	for (let i = 0; i < lines.length; i++) {
		ctx.fillText(lines[i], x + w / 2, startY + i * lineH)
	}
}

function drawComposite({
	canvas, shellImg, maskImg, pmaskImg, faceImg,
	bbox, pandaBbox, text, font, bg,
	faceRotation = 0, faceFlipX = false, faceFlipY = false,
	contentCenter = null,
	accessories = [], accessoryImages = {},
	size, faceFill = 0.92,
}) {
	const origW = shellImg.naturalWidth || size
	const origH = shellImg.naturalHeight || size
	const aspect = origH / origW
	let W, H
	if (aspect <= 1) { W = size; H = Math.round(size * aspect) }
	else { H = size; W = Math.round(size / aspect) }
	// panda 实际像素的底部 y（按 size 比例）— 裁去 shell 底部空白让 caption 紧贴
	const ratio0 = W / origW
	const pandaBottomY = pandaBbox
		? Math.min(H, Math.ceil((pandaBbox[3] + 8) * ratio0))   // +8px 留点 margin
		: H
	const captionH = text ? Math.floor(W * 0.22) : 0
	const wantW = W
	const wantH = pandaBottomY + captionH
	// 只在 size 真变化时才 set canvas.width/height —— 否则会 clear canvas 引起白闪
	if (canvas.width !== wantW || canvas.height !== wantH) {
		canvas.width = wantW
		canvas.height = wantH
	}
	const ctx = canvas.getContext('2d')
	ctx.imageSmoothingEnabled = true
	ctx.imageSmoothingQuality = 'high'

	// 1. bg
	fillBg(ctx, W, H + captionH, bg)

	// 2. panda layer (cached by shell.src + size — shellEdgeToAlpha 不重做)
	const pc = getPandaLayer(shellImg, pmaskImg, W, H)
	const pctx = pc.getContext('2d')

	// 3. face layer (shellImg 让 face 只在 shell 白色像素显示，墨镜/黑廓不被覆盖)
	const ratio = W / origW
	const fc = drawFaceLayer({
		W, H, shellImg, faceImg, maskImg, bbox, ratio, faceFill,
		rotation: faceRotation, flipX: faceFlipX, flipY: faceFlipY,
		contentCenter,
	})
	pctx.drawImage(fc, 0, 0)

	// 4. composite pc onto bg
	ctx.drawImage(pc, 0, 0)

	// 5. accessories — 已删

	// 6. caption — 紧贴 panda 实际底部（pandaBottomY），不再 H 底（避免横图大空白）
	if (text) drawCaption(ctx, text, 0, pandaBottomY, W, captionH, font)
}

// --- React component ---------------------------------------

const PandaCanvas = ({
	shell, face, text = '', font = 'kuaile', bg = '#FFFFFF',
	faceRotation = 0, faceFlipX = false, faceFlipY = false,
	accessories = [],
	size = 320, exportSize = 1024,
	style, className, onCanvasReady,
}) => {
	const canvasRef = React.useRef(null)
	const [loading, setLoading] = React.useState(true)
	const [err, setErr] = React.useState(null)
	const drawnOnceRef = React.useRef(false)
	const inFlightRef = React.useRef(0)

	const accKey = (accessories || []).join('|')

	React.useEffect(() => {
		if (!shell || !face) { setLoading(false); return }
		let cancelled = false
		const reqId = ++inFlightRef.current
		// 只首次画之前显示 loading 蒙版；rotation/text 等微调时 keep canvas 可见 → 不闪
		if (!drawnOnceRef.current) setLoading(true)
		setErr(null)
		const srcs = [
			'shells/' + shell.file,
			'shells/' + shell.mask,
			'shells/' + shell.pmask,
			'faces/' + face.file,
		]
		// fast path：所有图片都在 cache 时同步画（避免 microtask 引起的延迟帧）
		const allCached = srcs.every((s) => _imageCache.has(s))
		const drawWith = ([shellImg, maskImg, pmaskImg, faceImg]) => {
			if (cancelled || reqId !== inFlightRef.current || !canvasRef.current) return
			drawComposite({
				canvas: canvasRef.current,
				shellImg, maskImg, pmaskImg, faceImg,
				bbox: shell.bbox,
				pandaBbox: shell.panda_bbox,
				text, font, bg,
				faceRotation, faceFlipX, faceFlipY,
				contentCenter: face.content_center,
				size: exportSize,
			})
			const wasFirst = !drawnOnceRef.current
			drawnOnceRef.current = true
			if (loading) setLoading(false)
			if (onCanvasReady) onCanvasReady(canvasRef.current)
			// 首张画完后启动 idle preload 全部资源（只跑一次）
			if (wasFirst) {
				const idle = window.requestIdleCallback || ((cb) => setTimeout(cb, 500))
				idle(() => preloadAllAssets())
			}
		}
		if (allCached) {
			drawWith(srcs.map((s) => _imageCache.get(s)))
		} else {
			Promise.all(srcs.map(loadImage)).then(drawWith).catch((e) => {
				if (cancelled || reqId !== inFlightRef.current) return
				console.error('[PandaCanvas]', e)
				setErr(e.message || 'load failed')
				setLoading(false)
			})
		}
		return () => { cancelled = true }
	}, [shell?.id, face?.id, text, font, bg, faceRotation, faceFlipX, faceFlipY, exportSize, face?.content_center?.[0], face?.content_center?.[1], accKey])

	return (
		<div
			className={className}
			style={{
				position: 'relative',
				width: '100%',
				height: '100%',
				display: 'flex',
				alignItems: 'center',
				justifyContent: 'center',
				background: 'transparent',
				...style,
			}}
		>
			<canvas
				ref={canvasRef}
				style={{
					display: 'block',
					maxWidth: '100%',
					maxHeight: '100%',
					width: 'auto',
					height: 'auto',
					borderRadius: 'var(--r-md)',
				}}
			/>
			{loading && (
				<div style={{
					position: 'absolute', inset: 0, display: 'flex',
					alignItems: 'center', justifyContent: 'center',
					background: 'rgba(255,255,255,0.5)',
					borderRadius: 'var(--r-md)',
					fontSize: 12, color: 'var(--text-soft)',
				}}>载入中…</div>
			)}
			{err && (
				<div style={{
					position: 'absolute', inset: 0, display: 'flex',
					alignItems: 'center', justifyContent: 'center',
					fontSize: 12, color: 'var(--text-soft)',
					background: '#FFF5F5', borderRadius: 'var(--r-md)',
				}}>{err}</div>
			)}
		</div>
	)
}

// ============================================================
// PandaGlyph 顶部 logo
// ============================================================
const PandaGlyph = ({ size = 28, className }) => (
	<svg viewBox="0 0 40 40" width={size} height={size} className={className} aria-hidden="true">
		<circle cx="11" cy="9" r="5" fill="#1F1B17" />
		<circle cx="29" cy="9" r="5" fill="#1F1B17" />
		<circle cx="20" cy="22" r="13" fill="#FCFAF6" stroke="#1F1B17" strokeWidth="1.6" />
		<ellipse cx="14" cy="20" rx="3" ry="4" fill="#1F1B17" transform="rotate(-12 14 20)" />
		<ellipse cx="26" cy="20" rx="3" ry="4" fill="#1F1B17" transform="rotate(12 26 20)" />
		<circle cx="14" cy="20" r="1.2" fill="#FCFAF6" />
		<circle cx="26" cy="20" r="1.2" fill="#FCFAF6" />
		<ellipse cx="20" cy="26" rx="2" ry="1.4" fill="#1F1B17" />
		<path d="M16 29 Q20 32 24 29" stroke="#1F1B17" strokeWidth="1.4" fill="none" strokeLinecap="round" />
	</svg>
)

const PandaSVG = ({ size = 56 }) => <PandaGlyph size={Math.min(size, 56)} />

window.PandaCanvas = PandaCanvas
window.PandaGlyph = PandaGlyph
window.PandaSVG = PandaSVG
// 不覆盖 window.DEFAULT_TRAITS — 保持 data.jsx 设的真实值（panda.jsx 加载在 data.jsx 之后）
window.PANDA_FONTS = PANDA_FONTS
window.drawComposite = drawComposite
window.fillBg = fillBg
window.loadImage = loadImage
window.preloadAllAssets = preloadAllAssets
