/**
 * 文件功能：聊天室彩带庆典特效
 *
 * 通过大量彩纸碎片与飘带在空中散落、翻转，形成明显的庆典氛围，
 * 适合活动开始、中奖提示和管理员公告等场景。
 */

const ConfettiEffect = (() => {
    class Piece {
        constructor(w, h) {
            this.w = w;
            this.h = h;
            this.reset();
        }

        /**
         * 初始化彩带碎片参数。
         */
        reset() {
            this.x = this.w * (0.15 + Math.random() * 0.7);
            this.y = -Math.random() * this.h * 0.2;
            this.vx = Math.random() * 4.4 - 2.2;
            this.vy = Math.random() * 1.6 + 0.8;
            this.gravity = Math.random() * 0.03 + 0.025;
            this.rot = Math.random() * Math.PI * 2;
            this.rotSpeed = (Math.random() - 0.5) * 0.16;
            this.width = Math.random() * 10 + 6;
            this.height = Math.random() * 18 + 8;
            this.wave = Math.random() * Math.PI * 2;
            this.waveSpeed = Math.random() * 0.06 + 0.03;
            this.alpha = Math.random() * 0.2 + 0.75;
            this.color = ["#ef4444", "#f59e0b", "#22c55e", "#3b82f6", "#8b5cf6", "#ec4899"][
                Math.floor(Math.random() * 6)
            ];
            this.isRibbon = Math.random() > 0.72;
        }

        /**
         * 更新碎片运动状态。
         */
        update() {
            this.wave += this.waveSpeed;
            this.vy += this.gravity;
            this.vx *= 0.995;
            this.x += this.vx + Math.sin(this.wave) * 0.65;
            this.y += this.vy;
            this.rot += this.rotSpeed;
        }

        /**
         * 判断彩带是否仍在画布内。
         */
        get alive() {
            return this.y < this.h + 60;
        }

        /**
         * 绘制单个彩带碎片。
         *
         * @param {CanvasRenderingContext2D} ctx
         */
        draw(ctx) {
            const scaleX = Math.max(0.18, Math.abs(Math.cos(this.rot)));

            ctx.save();
            ctx.translate(this.x, this.y);
            ctx.rotate(this.rot);
            ctx.scale(scaleX, 1);
            ctx.globalAlpha = this.alpha;
            ctx.fillStyle = this.color;
            ctx.shadowColor = `${this.color}66`;
            ctx.shadowBlur = 6;

            if (this.isRibbon) {
                ctx.fillRect(-this.width * 0.2, -this.height / 2, this.width * 0.4, this.height);
            } else {
                ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height);
            }

            ctx.restore();
        }
    }

    /**
     * 启动彩带庆典特效。
     *
     * @param {HTMLCanvasElement} canvas
     * @param {Function} onEnd
     */
    function start(canvas, onEnd) {
        const ctx = canvas.getContext("2d");
        const w = canvas.width;
        const h = canvas.height;
        const DURATION = 7800;
        const isMobile = window.matchMedia?.("(max-width: 640px)")?.matches || window.innerWidth <= 640;
        const initialPieceCount = isMobile ? 56 : 90;
        const spawnPieceCount = isMobile ? 6 : 10;
        let pieces = Array.from({ length: initialPieceCount }, () => new Piece(w, h));
        const startTime = performance.now();
        let lastSpawnAt = startTime;
        let animId = null;
        let finished = false;

        /**
         * 统一结束彩纸动画，手动取消时只清理不回调。
         *
         * @param {boolean} canceled 是否为手动取消
         */
        function finish(canceled) {
            if (finished) {
                return;
            }

            finished = true;
            if (animId) {
                cancelAnimationFrame(animId);
            }
            pieces = [];
            ctx.clearRect(0, 0, w, h);
            if (!canceled) {
                onEnd();
            }
        }

        function animate(now) {
            ctx.clearRect(0, 0, w, h);

            pieces = pieces.filter((piece) => {
                piece.update();
                piece.draw(ctx);
                return piece.alive;
            });

            if (now - startTime < DURATION * 0.9 && now - lastSpawnAt >= 120) {
                pieces.push(...Array.from({ length: spawnPieceCount }, () => new Piece(w, h)));
                lastSpawnAt = now;
            }

            if (now - startTime < DURATION) {
                animId = requestAnimationFrame(animate);
            } else {
                finish(false);
            }
        }

        animId = requestAnimationFrame(animate);
        return {
            cancel() {
                finish(true);
            },
        };
    }

    return { start };
})();

window.ConfettiEffect = ConfettiEffect;
