<template>
    <view class="crush-canvas-container">
        <canvas
            type="2d"
            id="crush"
            class="crush-canvas"
        ></canvas>
    </view>
</template>

<script setup lang="ts">
import { ref, watch, onMounted, onBeforeUnmount, getCurrentInstance } from 'vue'

// ============================================================
// 类型定义
// ============================================================

type CrushItemType = 'egg' | 'walnut' | 'can' | 'balloon'
type ParticleShape = 'circle' | 'square' | 'triangle' | 'rect'

// 碎片(主体飞溅物)
interface Debris {
    x: number
    y: number
    vx: number
    vy: number
    size: number
    color: string
    shape: ParticleShape
    rotation: number
    rotationSpeed: number
    life: number
    decay: number
    gravity: number
    glow: boolean
    swing: number // 水平摇摆幅度(confetti 彩纸用),0 表示不摇摆
    age: number
}

// 火花(细小高速亮点,带拖尾)
interface Spark {
    x: number
    y: number
    px: number // 上一帧位置(拖尾起点)
    py: number
    vx: number
    vy: number
    size: number
    color: string
    life: number
    decay: number
    gravity: number
}

// 闪烁星光(四角星高光)
interface Sparkle {
    x: number
    y: number
    size: number
    rotation: number
    spin: number
    color: string
    life: number
    decay: number
}

// 中心爆闪
interface Flash {
    x: number
    y: number
    radius: number
    maxRadius: number
    color: string
    life: number
    decay: number
}

interface Shockwave {
    x: number
    y: number
    radius: number
    maxRadius: number
    color: string
    width: number
    life: number
    decay: number
}

interface CrushItemConfig {
    colors: string[]        // 碎片主体色
    sparkColors: string[]   // 火花/高光色(偏亮)
    shapes: ParticleShape[]
    debrisCount: number
    sparkCount: number
    sparkleCount: number
    speedMin: number
    speedMax: number
    sizeMin: number
    sizeMax: number
    gravity: number
    flashColor: string      // 中心爆闪核心色
    shockwaveColor: string
    confetti?: boolean      // balloon: 碎片改为彩纸飘落
}

// ============================================================
// 物品差异化配置
// ============================================================

const CRUSH_ITEMS: Record<CrushItemType, CrushItemConfig> = {
    egg: {
        colors: ['#FFEB3B', '#FFF59D', '#FFFFFF', '#FFD54F', '#FFC107'],
        sparkColors: ['#FFFFFF', '#FFF9C4', '#FFEE58'],
        shapes: ['circle', 'circle', 'circle'],
        debrisCount: 20,
        sparkCount: 16,
        sparkleCount: 5,
        speedMin: 3,
        speedMax: 6,
        sizeMin: 4,
        sizeMax: 10,
        gravity: 0.34,
        flashColor: '#FFFDE7',
        shockwaveColor: 'rgba(255, 224, 130, 0.85)',
    },
    walnut: {
        colors: ['#5D4037', '#795548', '#8D6E63', '#A1887F', '#3E2723'],
        sparkColors: ['#FFCC80', '#FFB74D', '#FFE0B2'],
        shapes: ['square', 'triangle', 'rect'],
        debrisCount: 22,
        sparkCount: 18,
        sparkleCount: 5,
        speedMin: 2.8,
        speedMax: 5,
        sizeMin: 4,
        sizeMax: 9,
        gravity: 0.42,
        flashColor: '#FFE0B2',
        shockwaveColor: 'rgba(255, 167, 38, 0.8)',
    },
    can: {
        colors: ['#90A4AE', '#CFD8DC', '#B0BEC5', '#78909C', '#ECEFF1', '#607D8B'],
        sparkColors: ['#FFFFFF', '#E1F5FE', '#B3E5FC'],
        shapes: ['rect', 'rect', 'triangle', 'square'],
        debrisCount: 24,
        sparkCount: 20,
        sparkleCount: 6,
        speedMin: 4,
        speedMax: 7,
        sizeMin: 3,
        sizeMax: 11,
        gravity: 0.36,
        flashColor: '#FFFFFF',
        shockwaveColor: 'rgba(207, 216, 220, 0.9)',
    },
    balloon: {
        colors: ['#FF5252', '#FF4081', '#E040FB', '#7C4DFF', '#536DFE', '#448AFF', '#FFEB3B', '#69F0AE', '#FF6E40'],
        sparkColors: ['#FFFFFF', '#FF80AB', '#82B1FF', '#FFFF8D'],
        shapes: ['rect', 'rect', 'square'],
        debrisCount: 28,
        sparkCount: 18,
        sparkleCount: 8,
        speedMin: 4,
        speedMax: 7.5,
        sizeMin: 4,
        sizeMax: 8,
        gravity: 0.14,
        flashColor: '#FCE4EC',
        shockwaveColor: 'rgba(255, 64, 129, 0.85)',
        confetti: true,
    },
}

const PHYSICS = {
    AIR_RESISTANCE: 0.985,
    SPARK_RESISTANCE: 0.92,
    DEBRIS_DECAY: 0.018,
    SPARK_DECAY: 0.035,
    SPARKLE_DECAY: 0.045,
    FLASH_DECAY: 0.11,
    SHOCKWAVE_DECAY: 0.045,
}

// ============================================================
// 状态
// ============================================================

const canvasNode = ref<any>(null)
const ctx = ref<any>(null)
const canvasWidth = ref<number>(240)
const canvasHeight = ref<number>(240)
const dpr = ref<number>(1)

const rafId = ref<number | null>(null)
const renderRunning = ref<boolean>(false)

const debrisList = ref<Debris[]>([])
const sparkList = ref<Spark[]>([])
const sparkleList = ref<Sparkle[]>([])
const flashList = ref<Flash[]>([])
const shockwaves = ref<Shockwave[]>([])

const instance = getCurrentInstance()

// 由父级 prop 信号驱动触发特效(避免跨组件 ref 调用, 小程序端更稳)
const props = defineProps<{
    crushSignal?: { type: CrushItemType; nonce: number; intensity?: number } | null
}>()

watch(
    () => props.crushSignal,
    (sig) => {
        if (sig) triggerCrush(sig.type, sig.intensity ?? 1)
    },
)

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

onBeforeUnmount(() => {
    cleanup()
})

// ============================================================
// 初始化 Canvas 2D
// ============================================================

function initCanvas() {
    setTimeout(() => {
        if (!instance) return

        const query = uni.createSelectorQuery().in(instance)
        query.select('#crush')
            .fields({ node: true, size: true } as any)
            .exec((res: any[]) => {
                if (!res || !res[0] || !res[0].node) {
                    // 降级:如果获取不到 node(非微信平台),不渲染
                    console.warn('[crush-canvas] canvas 2d not supported on this platform')
                    return
                }

                const canvas = res[0].node
                const width = res[0].width
                const height = res[0].height

                // 获取设备像素比
                const systemInfo = uni.getSystemInfoSync()
                const pixelRatio = systemInfo.pixelRatio || 1

                // 设置 canvas 物理像素尺寸(高清)
                canvas.width = width * pixelRatio
                canvas.height = height * pixelRatio

                // 获取 2D 上下文
                const context = canvas.getContext('2d')
                context.scale(pixelRatio, pixelRatio)

                // 保存状态
                canvasNode.value = canvas
                ctx.value = context
                canvasWidth.value = width
                canvasHeight.value = height
                dpr.value = pixelRatio
            })
    }, 200)
}

// ============================================================
// 触发捏碎特效
// ============================================================

function triggerCrush(itemType: CrushItemType, intensity = 1) {
    if (!ctx.value || !canvasNode.value) return

    const item = CRUSH_ITEMS[itemType]
    if (!item) return

    const scale = Math.max(0.45, Math.min(1.15, intensity))
    const centerX = canvasWidth.value / 2
    const centerY = canvasHeight.value / 2
    const maxR = Math.min(canvasWidth.value, canvasHeight.value)

    // 1. 中心爆闪
    flashList.value.push({
        x: centerX,
        y: centerY,
        radius: maxR * 0.12 * scale,
        maxRadius: maxR * 0.62 * scale,
        color: item.flashColor,
        life: 1.0,
        decay: PHYSICS.FLASH_DECAY,
    })

    // 2. 双层冲击波（低强度时只保留一层）
    shockwaves.value.push({
        x: centerX, y: centerY,
        radius: 12, maxRadius: maxR * 0.62 * scale,
        color: item.shockwaveColor, width: 5,
        life: 1.0, decay: PHYSICS.SHOCKWAVE_DECAY,
    })
    if (scale >= 0.8) {
        shockwaves.value.push({
            x: centerX, y: centerY,
            radius: 4, maxRadius: maxR * 0.42 * scale,
            color: item.shockwaveColor, width: 3,
            life: 1.0, decay: PHYSICS.SHOCKWAVE_DECAY * 1.3,
        })
    }

    const debrisCount = Math.max(8, Math.round(item.debrisCount * scale))
    const sparkCount = Math.max(6, Math.round(item.sparkCount * scale))
    const sparkleCount = Math.max(2, Math.round(item.sparkleCount * scale))

    // 3. 碎片(主体飞溅)
    for (let i = 0; i < debrisCount; i++) {
        const angle = (Math.PI * 2 * i) / debrisCount + (Math.random() - 0.5) * 0.7
        const speed = (item.speedMin + Math.random() * (item.speedMax - item.speedMin)) * (0.85 + scale * 0.15)
        const size = (item.sizeMin + Math.random() * (item.sizeMax - item.sizeMin)) * scale
        const color = item.colors[Math.floor(Math.random() * item.colors.length)]
        const shape = item.shapes[Math.floor(Math.random() * item.shapes.length)]

        const confetti = !!item.confetti
        const vy0 = confetti
            ? Math.sin(angle) * speed - 2.5
            : Math.sin(angle) * speed

        debrisList.value.push({
            x: centerX,
            y: centerY,
            vx: Math.cos(angle) * speed,
            vy: vy0,
            size,
            color,
            shape,
            rotation: Math.random() * Math.PI * 2,
            rotationSpeed: (Math.random() - 0.5) * (confetti ? 0.5 : 0.3),
            life: 1.0,
            decay: PHYSICS.DEBRIS_DECAY * (confetti ? 0.7 : 1) * (scale < 0.7 ? 1.25 : 1),
            gravity: item.gravity,
            glow: !confetti,
            swing: confetti ? 0.6 + Math.random() * 1.2 : 0,
            age: Math.random() * Math.PI * 2,
        })
    }

    // 4. 火花
    for (let i = 0; i < sparkCount; i++) {
        const angle = Math.random() * Math.PI * 2
        const speed = item.speedMax * (0.9 + Math.random() * 0.8) * scale
        const color = item.sparkColors[Math.floor(Math.random() * item.sparkColors.length)]
        sparkList.value.push({
            x: centerX,
            y: centerY,
            px: centerX,
            py: centerY,
            vx: Math.cos(angle) * speed,
            vy: Math.sin(angle) * speed,
            size: 1.5 + Math.random() * 2,
            color,
            life: 1.0,
            decay: PHYSICS.SPARK_DECAY * (0.8 + Math.random() * 0.6),
            gravity: item.gravity * 0.3,
        })
    }

    // 5. 闪烁星光
    for (let i = 0; i < sparkleCount; i++) {
        const r = maxR * (0.08 + Math.random() * 0.32) * scale
        const a = Math.random() * Math.PI * 2
        const color = item.sparkColors[Math.floor(Math.random() * item.sparkColors.length)]
        sparkleList.value.push({
            x: centerX + Math.cos(a) * r,
            y: centerY + Math.sin(a) * r,
            size: 10 + Math.random() * 16,
            rotation: Math.random() * Math.PI,
            spin: (Math.random() - 0.5) * 0.16,
            color,
            life: 1.0 + Math.random() * 0.4,
            decay: PHYSICS.SPARKLE_DECAY * (0.8 + Math.random() * 0.5),
        })
    }

    ensureRenderLoop()
}

// ============================================================
// 渲染循环
// ============================================================

function ensureRenderLoop() {
    if (renderRunning.value) return
    renderRunning.value = true
    renderLoop()
}

function renderLoop() {
    if (!canvasNode.value || !ctx.value) {
        renderRunning.value = false
        return
    }

    const w = canvasWidth.value
    const h = canvasHeight.value

    // 碎片
    for (let i = debrisList.value.length - 1; i >= 0; i--) {
        const p = debrisList.value[i]
        p.vy += p.gravity
        p.vx *= PHYSICS.AIR_RESISTANCE
        p.vy *= PHYSICS.AIR_RESISTANCE
        p.x += p.vx
        p.y += p.vy
        p.age += 0.2
        if (p.swing > 0) p.x += Math.sin(p.age) * p.swing // 彩纸摇摆
        p.rotation += p.rotationSpeed
        p.life -= p.decay
        if (p.life <= 0 || p.x < -60 || p.x > w + 60 || p.y > h + 60) {
            debrisList.value.splice(i, 1)
        }
    }

    // 火花
    for (let i = sparkList.value.length - 1; i >= 0; i--) {
        const s = sparkList.value[i]
        s.px = s.x
        s.py = s.y
        s.vy += s.gravity
        s.vx *= PHYSICS.SPARK_RESISTANCE
        s.vy *= PHYSICS.SPARK_RESISTANCE
        s.x += s.vx
        s.y += s.vy
        s.life -= s.decay
        if (s.life <= 0) sparkList.value.splice(i, 1)
    }

    // 闪烁星光
    for (let i = sparkleList.value.length - 1; i >= 0; i--) {
        const sp = sparkleList.value[i]
        sp.rotation += sp.spin
        sp.life -= sp.decay
        if (sp.life <= 0) sparkleList.value.splice(i, 1)
    }

    // 中心爆闪
    for (let i = flashList.value.length - 1; i >= 0; i--) {
        const f = flashList.value[i]
        f.radius += (f.maxRadius - f.radius) * 0.35
        f.life -= f.decay
        if (f.life <= 0) flashList.value.splice(i, 1)
    }

    // 冲击波
    for (let i = shockwaves.value.length - 1; i >= 0; i--) {
        const sw = shockwaves.value[i]
        sw.radius += (sw.maxRadius - sw.radius) * 0.16
        sw.life -= sw.decay
        if (sw.life <= 0) shockwaves.value.splice(i, 1)
    }

    draw()

    const alive =
        debrisList.value.length > 0 ||
        sparkList.value.length > 0 ||
        sparkleList.value.length > 0 ||
        flashList.value.length > 0 ||
        shockwaves.value.length > 0

    if (alive) {
        rafId.value = canvasNode.value.requestAnimationFrame(renderLoop) as unknown as number
    } else {
        rafId.value = null
        renderRunning.value = false
    }
}

// ============================================================
// 绘制
// ============================================================

function draw() {
    const c = ctx.value
    const w = canvasWidth.value
    const h = canvasHeight.value

    c.clearRect(0, 0, w, h)

    // —— 1. 冲击波(底层) ——
    c.globalCompositeOperation = 'source-over'
    shockwaves.value.forEach(sw => {
        c.beginPath()
        c.arc(sw.x, sw.y, sw.radius, 0, Math.PI * 2)
        c.strokeStyle = applyAlphaToRgba(sw.color, Math.min(1, sw.life))
        c.lineWidth = sw.width * sw.life
        c.stroke()
    })

    // —— 2. 中心爆闪(叠加发光) ——
    c.globalCompositeOperation = 'lighter'
    flashList.value.forEach(f => {
        const grd = c.createRadialGradient(f.x, f.y, 0, f.x, f.y, f.radius)
        const a = Math.min(1, f.life)
        grd.addColorStop(0, hexToRgba(f.color, 0.95 * a))
        grd.addColorStop(0.4, hexToRgba(f.color, 0.5 * a))
        grd.addColorStop(1, hexToRgba(f.color, 0))
        c.fillStyle = grd
        c.beginPath()
        c.arc(f.x, f.y, f.radius, 0, Math.PI * 2)
        c.fill()
    })

    // —— 3. 碎片(实体,带辉光) ——
    c.globalCompositeOperation = 'source-over'
    debrisList.value.forEach(p => {
        const alpha = Math.min(1, p.life)
        const currentSize = p.size * (0.65 + p.life * 0.35)

        // 辉光: 同色低透明大圆衬底
        if (p.glow) {
            c.fillStyle = hexToRgba(p.color, alpha * 0.22)
            c.beginPath()
            c.arc(p.x, p.y, currentSize * 1.9, 0, Math.PI * 2)
            c.fill()
        }

        c.fillStyle = hexToRgba(p.color, alpha)
        drawDebrisShape(c, p, currentSize)
    })

    // —— 4. 火花(拖尾 + 亮点,叠加发光) ——
    c.globalCompositeOperation = 'lighter'
    sparkList.value.forEach(s => {
        const a = Math.min(1, s.life)
        // 拖尾线
        c.strokeStyle = hexToRgba(s.color, a * 0.8)
        c.lineWidth = s.size * a
        c.lineCap = 'round'
        c.beginPath()
        c.moveTo(s.px, s.py)
        c.lineTo(s.x, s.y)
        c.stroke()
        // 头部亮点
        c.fillStyle = hexToRgba(s.color, a)
        c.beginPath()
        c.arc(s.x, s.y, s.size * a, 0, Math.PI * 2)
        c.fill()
    })

    // —— 5. 闪烁星光(四角星,叠加发光) ——
    sparkleList.value.forEach(sp => {
        // life 在 1→0, 用 sin 做"先亮后灭"的缩放
        const t = Math.max(0, Math.min(1, sp.life))
        const scale = Math.sin(Math.min(1, sp.life) * Math.PI) // 0→1→0
        if (scale <= 0.02) return
        const a = t
        drawStar(c, sp.x, sp.y, sp.size * scale, sp.size * scale * 0.32, sp.rotation, hexToRgba(sp.color, a))
    })

    c.globalCompositeOperation = 'source-over'
}

function drawDebrisShape(c: any, p: Debris, size: number) {
    switch (p.shape) {
        case 'circle':
            c.beginPath()
            c.arc(p.x, p.y, size, 0, Math.PI * 2)
            c.fill()
            break
        case 'square':
            c.save()
            c.translate(p.x, p.y)
            c.rotate(p.rotation)
            c.fillRect(-size, -size, size * 2, size * 2)
            c.restore()
            break
        case 'rect':
            c.save()
            c.translate(p.x, p.y)
            c.rotate(p.rotation)
            c.fillRect(-size * 1.6, -size * 0.5, size * 3.2, size)
            c.restore()
            break
        case 'triangle':
            c.save()
            c.translate(p.x, p.y)
            c.rotate(p.rotation)
            c.beginPath()
            c.moveTo(0, -size)
            c.lineTo(size, size)
            c.lineTo(-size, size)
            c.closePath()
            c.fill()
            c.restore()
            break
    }
}

// 四角星(尖锐十字星),用于高光闪烁
function drawStar(c: any, cx: number, cy: number, outerR: number, innerR: number, rotation: number, fill: string) {
    const points = 4
    c.save()
    c.translate(cx, cy)
    c.rotate(rotation)
    c.beginPath()
    for (let i = 0; i < points * 2; i++) {
        const r = i % 2 === 0 ? outerR : innerR
        const a = (Math.PI * i) / points
        const x = Math.cos(a) * r
        const y = Math.sin(a) * r
        if (i === 0) c.moveTo(x, y)
        else c.lineTo(x, y)
    }
    c.closePath()
    c.fillStyle = fill
    c.fill()
    c.restore()
}

// ============================================================
// 工具函数
// ============================================================

function hexToRgba(hex: string, alpha: number): string {
    const r = parseInt(hex.slice(1, 3), 16)
    const g = parseInt(hex.slice(3, 5), 16)
    const b = parseInt(hex.slice(5, 7), 16)
    return `rgba(${r}, ${g}, ${b}, ${alpha})`
}

function applyAlphaToRgba(rgba: string, alphaMultiplier: number): string {
    const match = rgba.match(/rgba?\(([^)]+)\)/)
    if (!match) return rgba

    const parts = match[1].split(',').map(s => s.trim())
    const r = parts[0]
    const g = parts[1]
    const b = parts[2]
    const a = parts[3] ? parseFloat(parts[3]) : 1
    return `rgba(${r}, ${g}, ${b}, ${a * alphaMultiplier})`
}

// ============================================================
// 清理
// ============================================================

function cleanup() {
    if (rafId.value !== null && canvasNode.value) {
        try {
            canvasNode.value.cancelAnimationFrame(rafId.value)
        } catch (_) {}
        rafId.value = null
    }
    renderRunning.value = false
    debrisList.value = []
    sparkList.value = []
    sparkleList.value = []
    flashList.value = []
    shockwaves.value = []
}

</script>

<style scoped>
.crush-canvas-container {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: 10;
}

.crush-canvas {
    display: block;
    width: 100%;
    height: 100%;
    background: transparent;
}
</style>
