let instance;

/**
 * 全局状态管理器
 * 负责管理伙伴泡泡游戏的状态，包括帧数、分数、泡泡网格、飞行泡泡等
 */
export default class DataBus {
  frame = 0;
  score = 0;
  isGameOver = false;

  /** 本局游戏时长（秒），0 表示无限制 */
  gameDuration = 0;
  /** 游戏开始时间戳（ms），用于计算剩余时间 */
  gameStartTime = 0;
  /** 玩家昵称（多人模式下使用） */
  nickname = '';

  /** 房间 ID：用作初始布局的随机种子，相同 ID 的玩家布局一致 */
  roomId = Math.floor(Math.random() * 900000) + 100000;

  /** @type {import('./bubble/bubbleGrid').default|null} 泡泡网格实例 */
  bubbleGrid = null;

  /** @type {import('./bubble/bubble').default|null} 当前待发射泡泡 */
  currentBubble = null;

  /** @type {import('./bubble/bubble').default|null} 下一颗待发射泡泡 */
  nextBubble = null;

  /** @type {Array} 飞行中的泡泡列表 */
  fireBubbles = [];

  /** @type {Array} 正在播放的爆炸特效列表 */
  explosions = [];

  /** 已发射次数计数器，每 PUSH_INTERVAL 次触发顶部下推 */
  shotCount = 0;

  constructor() {
    if (instance) return instance;
    instance = this;
  }

  reset() {
    this.frame = 0;
    this.score = 0;
    this.isGameOver = false;

    // 显式释放旧 BubbleGrid 中所有 Bubble 的 Image 引用，帮助 GC 回收 native 内存
    if (this.bubbleGrid && this.bubbleGrid.grid) {
      for (const row of this.bubbleGrid.grid) {
        if (!row) continue;
        for (let i = 0; i < row.length; i++) {
          if (row[i] && row[i].img) row[i].img = null;
          row[i] = null;
        }
      }
      this.bubbleGrid.grid = null;
    }
    this.bubbleGrid = null;

    // 释放飞行泡泡
    for (const fb of this.fireBubbles) {
      if (fb && fb.img) fb.img = null;
    }
    this.fireBubbles.length = 0;

    // 释放爆炸粒子
    for (const e of this.explosions) {
      if (e) { e.particles = null; e.rings = null; }
    }
    this.explosions.length = 0;

    this.currentBubble = null;
    this.nextBubble = null;
    this.gameStartTime = 0;
    this.shotCount = 0;
    // roomId / gameDuration 保留不重置
  }

  gameOver() {
    this.isGameOver = true;
  }

  /**
   * 将当前游戏状态序列化为可传输的紧凑 JSON 对象。
   * 用于通过 WebSocket 将状态实时上报给服务端/大屏。
   *
   * grid 使用二维数组：每格存颜色编号（1-6），0 表示空格。
   * explosions 只序列化爆炸起始位置与颜色，由接收方自行播放特效，不传递粒子运行时数据。
   *
   * @returns {{
   *   frame: number,
   *   score: number,
   *   isGameOver: boolean,
   *   grid: number[][],
   *   pushAnimOffsetY: number,
   *   fireBubbles: Array<{x:number,y:number,color:number,vx:number,vy:number}>,
   *   explosions: Array<{x:number,y:number,colorHex:string}>
   * }}
   */
  serialize() {
    const gridObj = this.bubbleGrid;

    // 二维网格：行 × 列，值为颜色编号，0 = 空
    const grid = gridObj
      ? gridObj.grid.map(rowArr =>
          rowArr ? rowArr.map(b => (b ? b.color : 0)) : []
        )
      : [];

    const pushAnimOffsetY = gridObj
      ? parseFloat(gridObj._pushAnimOffsetY.toFixed(2))
      : 0;

    const fireBubbles = this.fireBubbles.map(fb => ({
      x: Math.round(fb.x),
      y: Math.round(fb.y),
      color: fb.color,
      vx: parseFloat(fb.vx.toFixed(2)),
      vy: parseFloat(fb.vy.toFixed(2)),
    }));

    return {
      frame: this.frame,
      score: this.score,
      isGameOver: this.isGameOver,
      grid,
      pushAnimOffsetY,
      fireBubbles,
    };
  }
}
