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

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

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

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

interface Particle {
    x: number
    y: number
    vx: number
    vy: number
    size: number
    color: string
    shape: ParticleShape
    rotation: number
    rotationSpeed: number
    life: number
    gravity: number
}

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

interface CrushItemConfig {
    colors: string[]
    shapes: ParticleShape[]
    particles: number
    speedMin: number
    speedMax: number
    sizeMin: number
    sizeMax: number
    gravity: number
    shockwaveColor: string
}

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

const CRUSH_ITEMS: Record<CrushItemType, CrushItemConfig> = {
    egg: {
        colors: ['#FFEB3B', '#FFF59D', '#FFFFFF', '#FFC107'],
        shapes: ['circle', 'circle', 'circle'],
        particles: 18,
        speedMin: 3,
        speedMax: 5.5,
        sizeMin: 4,
        sizeMax: 9,
        gravity: 0.32,
        shockwaveColor: 'rgba(255, 235, 59, 0.4)',
    },
    walnut: {
        colors: ['#5D4037', '#795548', '#8D6E63', '#3E2723'],
        shapes: ['square', 'triangle', 'rect'],
        particles: 20,
        speedMin: 2.5,
        speedMax: 4.5,
        sizeMin: 4,
        sizeMax: 8,
        gravity: 0.4,
        shockwaveColor: 'rgba(141, 110, 99, 0.5)',
    },
    can: {
        colors: ['#90A4AE', '#CFD8DC', '#B0BEC5', '#78909C', '#ECEFF1'],
        shapes: ['rect', 'rect', 'triangle'],
        particles: 22,
        speedMin: 4,
        speedMax: 6.5,
        sizeMin: 3,
        sizeMax: 10,
        gravity: 0.35,
        shockwaveColor: 'rgba(176, 190, 197, 0.6)',
    },
    balloon: {
        colors: ['#FF5252', '#FF4081', '#E040FB', '#7C4DFF', '#536DFE', '#448AFF', '#FFEB3B', '#69F0AE'],
        shapes: ['circle', 'triangle'],
        particles: 24,
        speedMin: 4.5,
        speedMax: 7,
        sizeMin: 3,
        sizeMax: 6,
        gravity: 0.12,
        shockwaveColor: 'rgba(255, 64, 129, 0.5)',
    },
}

const PHYSICS = {
    AIR_RESISTANCE: 0.985,
    LIFE_DECAY: 0.02,
    SHOCKWAVE_DECAY: 0.04,
}

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

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 particles = ref<Particle[]>([])
const shockwaves = ref<Shockwave[]>([])

const instance = getCurrentInstance()

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) {
    if (!ctx.value || !canvasNode.value) return

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

    const centerX = canvasWidth.value / 2
    const centerY = canvasHeight.value / 2

    // 1. 生成双层冲击波
    shockwaves.value.push({
        x: centerX,
        y: centerY,
        radius: 10,
        maxRadius: Math.min(canvasWidth.value, canvasHeight.value) * 0.6,
        color: item.shockwaveColor,
        life: 1.0,
    })
    shockwaves.value.push({
        x: centerX,
        y: centerY,
        radius: 4,
        maxRadius: Math.min(canvasWidth.value, canvasHeight.value) * 0.4,
        color: item.shockwaveColor,
        life: 1.0,
    })

    // 2. 生成粒子(径向扩散)
    const particleCount = item.particles
    for (let i = 0; i < particleCount; i++) {
        const angle = (Math.PI * 2 * i) / particleCount + (Math.random() - 0.5) * 0.6
        const speed = item.speedMin + Math.random() * (item.speedMax - item.speedMin)
        const size = item.sizeMin + Math.random() * (item.sizeMax - item.sizeMin)
        const color = item.colors[Math.floor(Math.random() * item.colors.length)]
        const shape = item.shapes[Math.floor(Math.random() * item.shapes.length)]

        particles.value.push({
            x: centerX,
            y: centerY,
            vx: Math.cos(angle) * speed,
            vy: Math.sin(angle) * speed,
            size,
            color,
            shape,
            rotation: Math.random() * Math.PI * 2,
            rotationSpeed: (Math.random() - 0.5) * 0.3,
            life: 1.0,
            gravity: item.gravity,
        })
    }

    ensureRenderLoop()
}

// ============================================================
// 渲染循环(使用 requestAnimationFrame,更准确高效)
// ============================================================

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

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

    // 更新粒子物理
    for (let i = particles.value.length - 1; i >= 0; i--) {
        const p = particles.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.rotation += p.rotationSpeed
        p.life -= PHYSICS.LIFE_DECAY

        const outOfBounds =
            p.x < -50 || p.x > canvasWidth.value + 50 ||
            p.y < -50 || p.y > canvasHeight.value + 50
        if (p.life <= 0 || outOfBounds) {
            particles.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.15
        sw.life -= PHYSICS.SHOCKWAVE_DECAY
        if (sw.life <= 0) {
            shockwaves.value.splice(i, 1)
        }
    }

    draw()

    // 决定是否继续
    if (particles.value.length > 0 || shockwaves.value.length > 0) {
        // canvas 2d 模式下使用 canvas.requestAnimationFrame
        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. 绘制冲击波(底层)
    shockwaves.value.forEach(sw => {
        c.beginPath()
        c.arc(sw.x, sw.y, sw.radius, 0, Math.PI * 2)
        c.strokeStyle = applyAlphaToRgba(sw.color, sw.life)
        c.lineWidth = 3 * sw.life
        c.stroke()
    })

    // 2. 绘制粒子(上层)
    particles.value.forEach(p => {
        const alpha = p.life
        const currentSize = p.size * (0.6 + p.life * 0.4)
        c.fillStyle = hexToRgba(p.color, alpha)
        drawParticleShape(c, p, currentSize)
    })
}

function drawParticleShape(c: any, p: Particle, 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.5, -size * 0.5, size * 3, 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 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
    particles.value = []
    shockwaves.value = []
}

defineExpose({
    triggerCrush,
})
</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>
