/**
 * 文件功能：聊天室烟花特效（真实感版本）
 *
 * 完整模拟真实烟花流程：
 *  1. 火箭从底部带着尾迹飞向目标高度
 *  2. 到达位置后爆炸，产生 3 种形态：球形/柳叶/星形
 *  3. 每颗粒子带历史轨迹尾巴，随重力下坠并消散
 *
 * 全程透明 Canvas，不遮挡聊天背景。
 */

const FireworksEffect = (() => {
    // ─── 火箭类 ──────────────────────────────────────────
    class Rocket {
        /**
         * @param {number}  x        发射 x 位置
         * @param {number}  targetY  爆炸目标高度（距顶部比例）
         * @param {string}  color    爆炸颜色
         * @param {string}  type     爆炸类型：sphere / willow / ring
         */
        constructor(x, targetY, color, type, canvasHeight, drift = 0) {
            this.x = x;
            this.y = canvasHeight; // 从画布底部出发
            this.targetY = targetY;
            this.color = color;
            this.type = type;
            this.vx = drift;

            // 根据飞行距离动态计算初始速度，保证必然到达目标高度
            // 等比级数求和：total = vy / (1 - 0.98) = vy × 50
            // 加 10%~20% 余量，使火箭略微超过目标再触发爆炸（更真实）
            const dist = canvasHeight - targetY;
            this.vy = -(dist / 50) * (1.1 + Math.random() * 0.15);

            this.trail = []; // 尾迹历史坐标
            this.exploded = false;
            this.done = false;
        }

        /** 更新火箭位置，到达目标高度后标记为已爆炸 */
        update() {
            this.trail.push({ x: this.x, y: this.y });
            if (this.trail.length > 12) {
                this.trail.shift();
            }

            this.x += this.vx;
            this.y += this.vy;
            this.vx *= 0.992;
            this.vy *= 0.98; // 轻微减速（仿真阻力）
            if (this.y <= this.targetY) {
                this.exploded = true;
                this.done = true;
            }
        }

        /** 绘制火箭（白色头部 + 橙色尾迹） */
        draw(ctx) {
            // 尾迹（由新到旧逐渐变淡）
            for (let i = 0; i < this.trail.length; i++) {
                const a = (i / this.trail.length) * 0.6;
                ctx.save();
                ctx.globalAlpha = a;
                ctx.fillStyle = "#ffaa44";
                ctx.shadowColor = "#ff6600";
                ctx.shadowBlur = 6;
                ctx.beginPath();
                ctx.arc(this.trail[i].x, this.trail[i].y, 2, 0, Math.PI * 2);
                ctx.fill();
                ctx.restore();
            }
            // 火箭头部（亮白色光点）
            ctx.save();
            ctx.fillStyle = "#ffffff";
            ctx.shadowColor = "#ffcc88";
            ctx.shadowBlur = 10;
            ctx.beginPath();
            ctx.arc(this.x, this.y, 3, 0, Math.PI * 2);
            ctx.fill();
            ctx.restore();
        }
    }

    // ─── 爆炸粒子类 ──────────────────────────────────────
    class Particle {
        /**
         * @param {number}  x      爆炸中心 x
         * @param {number}  y      爆炸中心 y
         * @param {string}  color  粒子颜色
         * @param {string}  type   爆炸类型
         * @param {number}  angle  发射角度（ring 类型用）
         */
        constructor(x, y, color, type, angle, options = {}) {
            this.x = x;
            this.y = y;
            this.color = color;
            this.trail = [];
            this.innerColor = options.innerColor ?? "#ffffff";
            this.trailLimit = options.trailLimit ?? 8;
            this.drag = options.drag ?? 0.985;
            this.radiusScale = options.radiusScale ?? 1;

            let speed;
            if (type === "ring") {
                // 环形：均匀角度，固定速度
                speed = 5.8 + Math.random() * 2.2;
                this.vx = Math.cos(angle) * speed;
                this.vy = Math.sin(angle) * speed;
                this.gravity = 0.06;
                this.decay = 0.012;
                this.radius = 2.2 * this.radiusScale;
            } else if (type === "willow") {
                // 柳叶：慢速，在空中下垂
                speed = Math.random() * 3.8 + 1.4;
                const a = Math.random() * Math.PI * 2;
                this.vx = Math.cos(a) * speed;
                this.vy = Math.sin(a) * speed - 2.4; // 初速稍微向上
                this.gravity = 0.072;
                this.decay = 0.0075; // 衰减慢，拖出长尾
                this.radius = 1.7 * this.radiusScale;
            } else {
                // sphere：标准球形爆炸
                speed = Math.random() * 6.8 + 2.4;
                const a = Math.random() * Math.PI * 2;
                this.vx = Math.cos(a) * speed;
                this.vy = Math.sin(a) * speed;
                this.gravity = 0.095;
                this.decay = 0.0135;
                this.radius = (Math.random() * 2.2 + 1.7) * this.radiusScale;
            }

            this.alpha = 1;
            // 部分粒子有闪烁效果
            this.sparkle = Math.random() > 0.45;
            this.frame = 0;
        }

        /** 更新粒子物理状态 */
        update() {
            this.frame++;
            // 保存轨迹历史（尾迹长度由透明度控制）
            this.trail.push({ x: this.x, y: this.y });
            if (this.trail.length > this.trailLimit) {
                this.trail.shift();
            }

            this.vy += this.gravity;
            this.x += this.vx;
            this.y += this.vy;
            this.vx *= this.drag;
            this.vy *= this.drag;

            // 闪烁：每隔几帧透明度轻微抖动
            if (this.sparkle && this.frame % 4 === 0) {
                this.alpha = Math.max(
                    0,
                    this.alpha - this.decay * (0.5 + Math.random()),
                );
            } else {
                this.alpha -= this.decay;
            }
        }

        /** 绘制粒子及其运动轨迹尾迹 */
        draw(ctx) {
            // 绘制尾迹
            for (let i = 0; i < this.trail.length; i++) {
                const a = (i / this.trail.length) * this.alpha * 0.45;
                ctx.save();
                ctx.globalAlpha = a;
                ctx.fillStyle = this.color;
                ctx.beginPath();
                ctx.arc(
                    this.trail[i].x,
                    this.trail[i].y,
                    this.radius * 0.55,
                    0,
                    Math.PI * 2,
                );
                ctx.fill();
                ctx.restore();
            }
            // 绘制粒子主体（带发光）
            ctx.save();
            ctx.globalAlpha = Math.max(0, this.alpha);
            ctx.fillStyle = this.color;
            ctx.shadowColor = this.color;
            ctx.shadowBlur = 8;
            ctx.beginPath();
            ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
            ctx.fill();

            // 用白色内核强化“炸点”质感，避免颜色过闷。
            ctx.fillStyle = this.innerColor;
            ctx.globalAlpha = Math.max(0, this.alpha * 0.35);
            ctx.beginPath();
            ctx.arc(this.x, this.y, this.radius * 0.4, 0, Math.PI * 2);
            ctx.fill();
            ctx.restore();
        }

        get alive() {
            return this.alpha > 0.01;
        }
    }

    // ─── 爆炸光晕类 ──────────────────────────────────────
    class Halo {
        /**
         * @param {number} x 爆炸中心 x
         * @param {number} y 爆炸中心 y
         * @param {string} color 爆炸主色
         * @param {number} radius 最大光晕半径
         */
        constructor(x, y, color, radius) {
            this.x = x;
            this.y = y;
            this.color = color;
            this.radius = radius * 0.32;
            this.maxRadius = radius;
            this.alpha = 0.34;
        }

        /** 更新光晕扩散与淡出 */
        update() {
            this.radius += (this.maxRadius - this.radius) * 0.16 + 1.8;
            this.alpha *= 0.88;
        }

        /** 绘制爆炸余辉，让烟花更有层次和氛围 */
        draw(ctx) {
            // 使用发光圆替代每帧渐变重建，尽量保留余辉质感同时降低爆炸高峰的绘制成本。
            ctx.save();
            ctx.globalCompositeOperation = "lighter";
            ctx.globalAlpha = this.alpha;
            ctx.fillStyle = this.color;
            ctx.shadowColor = this.color;
            ctx.shadowBlur = this.radius * 0.45;
            ctx.beginPath();
            ctx.arc(this.x, this.y, this.radius * 0.62, 0, Math.PI * 2);
            ctx.fill();
            ctx.restore();
        }

        get alive() {
            return this.alpha > 0.02;
        }
    }

    // ─── 预定义颜色 / 类型 ───────────────────────────────
    const COLORS = [
        "#ff2200",
        "#ff7700",
        "#ffcc00",
        "#00dd44",
        "#cc00ff",
        "#ff0088",
        "#00bbff",
        "#ff4488",
        "#ffaa00",
    ];
    const TYPES = ["sphere", "willow", "ring"];
    const FLASH_COLORS = ["#ffffff", "#ffe3a3", "#ffd4f0", "#cfe9ff"];

    /**
     * 随机取数组中的一个元素
     *
     * @param {Array} items 候选数组
     * @returns {*}
     */
    function _pick(items) {
        return items[Math.floor(Math.random() * items.length)];
    }

    /**
     * 为画布做渐隐，而不是硬清屏。
     *
     * 这里使用 destination-out 只擦除旧像素，不会给聊天室背景额外盖一层黑幕。
     *
     * @param {CanvasRenderingContext2D} ctx
     * @param {number} w
     * @param {number} h
     */
    function _fadeFrame(ctx, w, h) {
        ctx.save();
        ctx.globalCompositeOperation = "destination-out";
        ctx.fillStyle = "rgba(0, 0, 0, 0.24)";
        ctx.fillRect(0, 0, w, h);
        ctx.restore();
    }

    /**
     * 生成一批爆炸粒子
     *
     * @param  {number}  x      爆炸中心 x
     * @param  {number}  y      爆炸中心 y
     * @param  {string}  color  颜色
     * @param  {string}  type   爆炸类型
     * @param  {number}  density 粒子密度倍率
     * @returns {Particle[]}
     */
    function _burst(x, y, color, type, density = 1) {
        const particles = [];
        const baseCount = type === "ring" ? 120 : type === "willow" ? 170 : 145;
        const count = Math.round(baseCount * density);
        const accentColor = _pick(FLASH_COLORS);

        if (type === "ring") {
            // 环形：均匀分布
            for (let i = 0; i < count; i++) {
                const angle = ((Math.PI * 2) / count) * i;
                particles.push(new Particle(x, y, color, type, angle));
            }
        } else {
            for (let i = 0; i < count; i++) {
                particles.push(new Particle(x, y, color, type, 0));
            }

            // 核心补一圈亮色星火，让爆点更饱满。
            for (let i = 0; i < 18; i++) {
                const p = new Particle(x, y, accentColor, "sphere", 0, {
                    trailLimit: 4,
                    drag: 0.978,
                    radiusScale: 0.7,
                });
                p.decay *= 1.6;
                p.radius *= 0.7;
                particles.push(p);
            }
        }

        // 再补一层高亮碎火，提升烟花“炸开”的亮度与体积感。
        const sparkleCount = Math.max(10, Math.round(count * 0.12));
        for (let i = 0; i < sparkleCount; i++) {
            const sparkle = new Particle(x, y, accentColor, "sphere", 0, {
                trailLimit: 3,
                drag: 0.972,
                radiusScale: 0.58,
                innerColor: "#ffffff",
            });
            sparkle.decay *= 1.9;
            sparkle.gravity *= 0.8;
            particles.push(sparkle);
        }

        return particles;
    }

    /**
     * 批量把新增粒子追加到现有数组，避免频繁 concat 产生新数组。
     *
     * @param {Particle[]} target
     * @param {Particle[]} incoming
     */
    function _appendParticles(target, incoming) {
        for (let i = 0; i < incoming.length; i++) {
            target.push(incoming[i]);
        }
    }

    /**
     * 在粒子预算内追加粒子，避免主爆炸阶段瞬间超量。
     *
     * @param {Particle[]} target
     * @param {Particle[]} incoming
     * @param {number} budget
     */
    function _appendParticlesWithinBudget(target, incoming, budget) {
        const remaining = Math.max(0, budget - target.length);
        if (remaining <= 0) {
            return;
        }

        _appendParticles(target, incoming.slice(0, remaining));
    }

    /**
     * 统一发射一枚火箭。
     *
     * @param {Rocket[]} rockets
     * @param {number} w
     * @param {number} h
     * @param {string[]} colors
     * @param {Function} getX
     * @param {Function} getTargetY
     */
    function _launchRocket(rockets, w, h, colors, getX, getTargetY) {
        const x = getX(w);
        const ty = getTargetY(h);
        const color = _pick(colors);
        const type = _pick(TYPES);
        const drift = (Math.random() - 0.5) * 0.9;
        rockets.push(new Rocket(x, ty, color, type, h, drift));
    }

    /**
     * 通用烟花演出引擎。
     *
     * @param {HTMLCanvasElement} canvas
     * @param {Function} onEnd
     * @param {object} config
     */
    function _runShow(canvas, onEnd, config) {
        const ctx = canvas.getContext("2d");
        const w = canvas.width;
        const h = canvas.height;
        const isMobile = window.matchMedia?.("(max-width: 640px)")?.matches || window.innerWidth <= 640;
        const mobileScale = isMobile ? 0.72 : 1;
        const duration = config.duration;
        const hardStopAt = duration + 2600;
        const peakParticleBudget = Math.round((config.peakParticleBudget ?? 1650) * mobileScale);
        const maxLaunches = Math.max(8, Math.round(config.maxLaunches * mobileScale));
        const particleDensity = config.particleDensity * mobileScale;
        const secondaryDensity = config.secondaryDensity * mobileScale;

        let rockets = [];
        let particles = [];
        let halos = [];
        let scheduledBursts = [];
        let animId = null;
        let launchCnt = 0;
        let finished = false;
        const timers = [];

        const launchInterval = setInterval(() => {
            if (launchCnt >= maxLaunches) {
                clearInterval(launchInterval);
                return;
            }

            const batchSize = config.getBatchSize(launchCnt);
            for (let i = 0; i < batchSize && launchCnt < maxLaunches; i++) {
                _launchRocket(
                    rockets,
                    w,
                    h,
                    config.colors,
                    config.getLaunchX,
                    config.getTargetY,
                );
                launchCnt++;
            }
        }, config.launchEvery);

        // 开场礼炮先把气氛撑起来，避免一开始太空。
        if (typeof config.openingVolley === "function") {
            timers.push(setTimeout(() => {
                config.openingVolley(rockets, w, h);
            }, 120));
        }

        const startTime = performance.now();

        function animate(now) {
            _fadeFrame(ctx, w, h);

            halos = halos.filter((halo) => halo.alive);
            halos.forEach((halo) => {
                halo.update();
                halo.draw(ctx);
            });

            for (let i = scheduledBursts.length - 1; i >= 0; i--) {
                if (scheduledBursts[i].triggerAt <= now) {
                    const burst = scheduledBursts[i];
                    _appendParticlesWithinBudget(
                        particles,
                        _burst(burst.x, burst.y, burst.color, burst.type, burst.density),
                        peakParticleBudget,
                    );
                    halos.push(new Halo(burst.x, burst.y, burst.color, burst.haloRadius));
                    scheduledBursts.splice(i, 1);
                }
            }

            for (let i = rockets.length - 1; i >= 0; i--) {
                const rocket = rockets[i];
                if (rocket.done) {
                    _appendParticlesWithinBudget(
                        particles,
                        _burst(
                            rocket.x,
                            rocket.y,
                            rocket.color,
                            rocket.type,
                            particleDensity,
                        ),
                        peakParticleBudget,
                    );
                    halos.push(new Halo(rocket.x, rocket.y, rocket.color, config.primaryHaloRadius));

                    // 粒子高峰时先保主爆炸观感，压掉一部分二次爆裂来避免卡顿。
                    if (
                        particles.length < peakParticleBudget
                        && Math.random() < config.secondaryBurstChance
                    ) {
                        scheduledBursts.push({
                            triggerAt: now + 90 + Math.random() * 140,
                            x: rocket.x + (Math.random() - 0.5) * 34,
                            y: rocket.y + (Math.random() - 0.5) * 26,
                            color: _pick(config.colors),
                            type: Math.random() > 0.5 ? "sphere" : "ring",
                            density: secondaryDensity,
                            haloRadius: config.secondaryHaloRadius,
                        });
                    }

                    rockets.splice(i, 1);
                } else {
                    rocket.update();
                    rocket.draw(ctx);
                }
            }

            particles = particles.filter((particle) => particle.alive);
            particles.forEach((particle) => {
                particle.update();
                particle.draw(ctx);
            });

            const elapsed = now - startTime;
            const shouldContinue = elapsed < duration
                || rockets.length > 0
                || particles.length > 0
                || scheduledBursts.length > 0;

            if (shouldContinue && elapsed < hardStopAt) {
                animId = requestAnimationFrame(animate);
            } else {
                finish(false);
            }
        }

        animId = requestAnimationFrame(animate);

        /**
         * 统一结束烟花演出，取消时不再回调管理器。
         *
         * @param {boolean} canceled 是否为手动取消
         */
        function finish(canceled) {
            if (finished) {
                return;
            }

            finished = true;
            clearInterval(launchInterval);
            timers.forEach((timer) => clearTimeout(timer));
            if (animId) {
                cancelAnimationFrame(animId);
            }
            rockets = [];
            particles = [];
            halos = [];
            scheduledBursts = [];
            ctx.clearRect(0, 0, w, h);

            if (!canceled) {
                onEnd();
            }
        }

        return {
            cancel() {
                finish(true);
            },
        };
    }

    /**
     * 启动烟花特效（普通版）
     *
     * @param  {HTMLCanvasElement}  canvas   全屏 Canvas
     * @param  {Function}           onEnd    特效结束回调
     */
    function start(canvas, onEnd) {
        return _runShow(canvas, onEnd, {
            duration: 10500,
            launchEvery: 340,
            maxLaunches: 24,
            particleDensity: 1.08,
            peakParticleBudget: 1500,
            secondaryDensity: 0.42,
            primaryHaloRadius: 150,
            secondaryHaloRadius: 84,
            secondaryBurstChance: 0.54,
            colors: COLORS,
            getBatchSize(launchCnt) {
                return launchCnt % 5 === 0 ? 2 : 1;
            },
            getLaunchX(width) {
                return width * (0.1 + Math.random() * 0.8);
            },
            getTargetY(height) {
                return height * (0.08 + Math.random() * 0.42);
            },
            openingVolley(rockets, width, height) {
                [0.18, 0.5, 0.82].forEach((ratio) => {
                    rockets.push(new Rocket(
                        width * ratio,
                        height * (0.12 + Math.random() * 0.12),
                        _pick(COLORS),
                        "sphere",
                        height,
                        (Math.random() - 0.5) * 0.6,
                    ));
                });
            },
        });
    }

    /**
     * 启动婚礼加倍烟花特效（双侧轮流发射，粒子增倍，持续更久）
     *
     * @param  {HTMLCanvasElement}  canvas   全屏 Canvas
     * @param  {Function}           onEnd    特效结束回调
     */
    function startDouble(canvas, onEnd) {
        // 婚礼专属浪漫色组（增加金色/粉色）
        const WEDDING_COLORS = [
            "#ff2266",
            "#ff66aa",
            "#ff99cc", // 粉红系
            "#ffcc00",
            "#ffdd44",
            "#fff066", // 金黄系
            "#cc44ff",
            "#ff44cc",
            "#aa00ff", // 紫色系
            "#ff4400",
            "#ff8800",
            "#00ddff", // 其他
        ];

        return _runShow(canvas, onEnd, {
            duration: 12400,
            launchEvery: 280,
            maxLaunches: 34,
            particleDensity: 1.3,
            peakParticleBudget: 1850,
            secondaryDensity: 0.56,
            primaryHaloRadius: 176,
            secondaryHaloRadius: 96,
            secondaryBurstChance: 0.72,
            colors: WEDDING_COLORS,
            getBatchSize(launchCnt) {
                return launchCnt % 4 === 0 ? 2 : 1;
            },
            getLaunchX(width) {
                const fromLeft = Math.random() > 0.5;
                return fromLeft
                    ? width * (0.04 + Math.random() * 0.38)
                    : width * (0.58 + Math.random() * 0.38);
            },
            getTargetY(height) {
                return height * (0.05 + Math.random() * 0.38);
            },
            openingVolley(rockets, width, height) {
                [0.12, 0.32, 0.5, 0.68, 0.88].forEach((ratio, index) => {
                    rockets.push(new Rocket(
                        width * ratio,
                        height * (index % 2 === 0 ? 0.1 : 0.16),
                        _pick(WEDDING_COLORS),
                        index % 2 === 0 ? "sphere" : "ring",
                        height,
                        (Math.random() - 0.5) * 0.7,
                    ));
                });
            },
        });
    }

    return { start, startDouble };
})();

window.FireworksEffect = FireworksEffect;
