/**
 * 文件功能：聊天室流星特效
 *
 * 在透明 Canvas 上绘制夜空光点和多枚斜向掠过的流星，
 * 通过长尾渐变与随机节奏制造快速划空的视觉效果。
 */

const MeteorsEffect = (() => {
    class Meteor {
        constructor(w, h) {
            this.w = w;
            this.h = h;
            this.reset(true);
        }

        /**
         * 重置单颗流星的出发状态。
         *
         * @param {boolean} initial 是否首次初始化
         */
        reset(initial = false) {
            this.x = initial ? Math.random() * this.w : this.w + Math.random() * 160;
            this.y = initial ? Math.random() * this.h * 0.45 : Math.random() * this.h * 0.52;
            this.vx = -(12 + Math.random() * 7);
            this.vy = 4.4 + Math.random() * 2.6;
            this.length = 170 + Math.random() * 170;
            this.alpha = 0;
            this.maxAlpha = Math.random() * 0.28 + 0.72;
            this.delay = Math.random() * 1500;
            this.birth = performance.now();
            this.life = 1800 + Math.random() * 1000;
            this.width = Math.random() * 2.4 + 1.8;
            this.tint = [
                [255, 255, 255],
                [191, 219, 254],
                [125, 211, 252],
                [253, 224, 71],
            ][Math.floor(Math.random() * 4)];
            this.active = false;
        }

        /**
         * 更新流星位置。
         *
         * @param {number} now
         */
        update(now) {
            if (!this.active) {
                if (now - this.birth >= this.delay) {
                    this.active = true;
                } else {
                    return;
                }
            }

            this.x += this.vx;
            this.y += this.vy;

            const progress = (now - this.birth - this.delay) / this.life;
            if (progress < 0.2) {
                this.alpha = this.maxAlpha * (progress / 0.2);
            } else if (progress > 0.78) {
                this.alpha = this.maxAlpha * Math.max(0, (1 - progress) / 0.22);
            } else {
                this.alpha = this.maxAlpha;
            }

            if (progress >= 1 || this.x < -this.length || this.y > this.h + this.length) {
                this.reset(false);
            }
        }

        /**
         * 绘制流星主体和尾迹。
         *
         * @param {CanvasRenderingContext2D} ctx
         */
        draw(ctx) {
            if (!this.active || this.alpha <= 0.01) {
                return;
            }

            const tailX = this.x - this.vx * 9;
            const tailY = this.y - this.vy * 9;
            const [r, g, b] = this.tint;
            const gradient = ctx.createLinearGradient(this.x, this.y, tailX, tailY);
            gradient.addColorStop(0, `rgba(255,255,255,${Math.min(1, this.alpha + 0.12)})`);
            gradient.addColorStop(0.18, `rgba(${r},${g},${b},${this.alpha})`);
            gradient.addColorStop(0.62, `rgba(${r},${g},${b},${this.alpha * 0.42})`);
            gradient.addColorStop(1, `rgba(${r},${g},${b},0)`);

            ctx.save();
            ctx.strokeStyle = gradient;
            ctx.lineWidth = this.width;
            ctx.lineCap = "round";
            ctx.shadowColor = `rgba(${r},${g},${b},0.95)`;
            ctx.shadowBlur = 18;
            ctx.beginPath();
            ctx.moveTo(this.x, this.y);
            ctx.lineTo(tailX, tailY);
            ctx.stroke();

            ctx.strokeStyle = `rgba(255,255,255,${this.alpha * 0.65})`;
            ctx.lineWidth = this.width * 0.42;
            ctx.beginPath();
            ctx.moveTo(this.x, this.y);
            ctx.lineTo(this.x - this.vx * 2.2, this.y - this.vy * 2.2);
            ctx.stroke();

            ctx.fillStyle = `rgba(255,255,255,${Math.min(1, this.alpha + 0.18)})`;
            ctx.beginPath();
            ctx.arc(this.x, this.y, this.width * 1.55, 0, Math.PI * 2);
            ctx.fill();

            ctx.fillStyle = `rgba(${r},${g},${b},${this.alpha * 0.55})`;
            ctx.beginPath();
            ctx.arc(this.x, this.y, this.width * 3.2, 0, Math.PI * 2);
            ctx.fill();
            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 = 9000;
        const stars = Array.from({ length: 48 }, () => ({
            x: Math.random() * w,
            y: Math.random() * h * 0.62,
            r: Math.random() * 1.9 + 0.6,
            alpha: Math.random() * 0.42 + 0.2,
        }));
        const meteors = Array.from({ length: 14 }, () => new Meteor(w, h));
        const startTime = performance.now();
        let animId = null;
        let finished = false;

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

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

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

            stars.forEach((star) => {
                ctx.save();
                ctx.fillStyle = `rgba(248,250,252,${star.alpha})`;
                ctx.shadowColor = "rgba(255,255,255,0.6)";
                ctx.shadowBlur = 7;
                ctx.beginPath();
                ctx.arc(star.x, star.y, star.r, 0, Math.PI * 2);
                ctx.fill();
                ctx.restore();
            });

            meteors.forEach((meteor) => {
                meteor.update(now);
                meteor.draw(ctx);
            });

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

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

    return { start };
})();

window.MeteorsEffect = MeteorsEffect;
