import { SCREEN_WIDTH, SCREEN_HEIGHT, SAFE_AREA_TOP, UI_BASE } from '../render';
import GameSocket from '../network/socket';

const TEAM_NAME_MAP = {
  A: '深耕队',
  B: '致远队',
};

function getTeamDisplayName(team) {
  return TEAM_NAME_MAP[team] || team;
}

/**
 * 等待房间页面
 *
 * 展示内容：
 *   - 房间号（3位数）
 *   - 已加入 / 总人数 进度条
 *   - 玩家槽位（头像占位格）
 *   - 分享提示文案
 *
 * Socket 事件：
 *   发送 room:create → 服务端创建房间
 *   监听 room:playerJoined → 更新已加入人数
 *   监听 room:allReady → 所有人到齐，触发 onStart
 */
export default class WaitingRoomScreen {
  /**
   * @param {object} opts
   * @param {string}   opts.roomId       3位字符串房间号，如 "042"
   * @param {number}   opts.totalSeats   总座位数（创建方传入；加入方由服务端返回后更新）
   * @param {number}   [opts.playerCount] 单边人数，仅 role='create' 时使用
   * @param {'create'|'join'} opts.role  身份：创建方 or 加入方
   * @param {'A'|'B'} [opts.team]        所属队伍，创建方默认'A'，加入方需传入
   * @param {Function} opts.onStart      所有人到齐后的回调
   * @param {Function} opts.onCancel     取消/返回回调
   * @param {Function} [opts.onPlayersUpdate] 玩家列表更新回调
   */
  constructor({ roomId, totalSeats, playerCount, role = 'create', team, onStart, onCancel, onPlayersUpdate, onTeamFull }) {
    this.roomId      = roomId;
    this.totalSeats  = totalSeats;
    this.playerCount = playerCount || totalSeats / 2;
    this.role        = role;
    this.team        = team || 'A';  // 创建方默认A队，加入方传入
    this.visible     = true;

    this.onStart  = onStart;
    this.onCancel = onCancel;
    this.onPlayersUpdate = onPlayersUpdate;
    this.onTeamFull = onTeamFull;

    // 玩家列表，包含每个玩家的信息 { playerId, nickname, team }
    this.players = [];
    this.joinedCount = role === 'create' ? 1 : 0;
    this.myPlayerId = 1;

    this._frame = 0;

    // 倒计时相关
    this.isCountingDown = false;
    this.countdownValue = 3;

    this._initLayout();
    this._touchHandler = this._onTouch.bind(this);
    wx.onTouchStart(this._touchHandler);

    // ── Socket ──────────────────────────────────────────────
    this._onPlayerJoined = this._handlePlayerJoined.bind(this);
    this._onAllReady     = this._handleAllReady.bind(this);
    this._onCountdown    = this._handleCountdown.bind(this);
    this._onGameStart    = this._handleGameStart.bind(this);
    this._onJoined       = this._handleJoined.bind(this);
    this._onError        = this._handleError.bind(this);

    GameSocket.on('room:playerJoined', this._onPlayerJoined);
    GameSocket.on('room:allReady',     this._onAllReady);
    GameSocket.on('room:countdown',    this._onCountdown);
    GameSocket.on('room:gameStart',    this._onGameStart);
    GameSocket.on('room:joined',       this._onJoined);
    GameSocket.on('error',             this._onError);

    if (role === 'create') {
      // 创建方自己先加入 players 列表
      this.players.push({
        playerId: 1,
        nickname: GameGlobal.databus.nickname || '玩家',
        team: 'A'
      });
      // 连接成功后发 room:create
      GameSocket.connect(undefined, () => {
        GameSocket.emit('room:create', {
          roomId:       String(roomId),
          totalSeats:   this.totalSeats,
          gameDuration: GameGlobal.databus.gameDuration || 0,
          nickname:     GameGlobal.databus.nickname || '玩家',
          team:         'A',  // 创建方默认A队
        });
      });
    } else {
      // 加入方：连接成功后发 room:join
      GameSocket.connect(undefined, () => {
        GameSocket.emit('room:join', {
          roomId:   String(roomId),
          nickname: GameGlobal.databus.nickname || '玩家',
          team:     this.team,  // 加入方传入选择的队伍
        });
      });
    }
  }

  // ── Socket 事件处理 ──────────────────────────────────────

  _handleJoined({ joinedCount, totalSeats, players, myPlayerId, team }) {
    // 加入方收到服务端确认，更新真实人数和玩家列表
    if (joinedCount !== undefined) this.joinedCount = joinedCount;
    if (totalSeats  !== undefined) this.totalSeats  = totalSeats;
    if (players) this.players = players;
    if (myPlayerId) this.myPlayerId = myPlayerId;
    // 服务端可能因队伍满员调整了队伍，同步更新
    if (team) this.team = team;
    console.log(`[WaitingRoom] 加入成功 ${this.joinedCount}/${this.totalSeats} team=${team}`, players);
    // 回调通知玩家列表更新（含队伍信息）
    if (this.onPlayersUpdate) {
      this.onPlayersUpdate(this.players, this.myPlayerId, team);
    }
  }

  _handlePlayerJoined({ joinedCount, totalSeats, players }) {
    this.joinedCount = joinedCount;
    this.totalSeats  = totalSeats;
    if (players) this.players = players;
    console.log(`[WaitingRoom] 人数更新 ${joinedCount}/${totalSeats}`, players);
    // 回调通知玩家列表更新
    if (this.onPlayersUpdate) {
      this.onPlayersUpdate(this.players, this.myPlayerId);
    }
  }

  _handleAllReady({ sessionId, gameDuration }) {
    console.log('[WaitingRoom] 所有人到齐，等待服务端倒计时...', 'sessionId:', sessionId, 'gameDuration:', gameDuration);
    if (!this.visible) return;
    if (this.isCountingDown) return; // 防重复
    // 服务端下发的 gameDuration 写入 databus，供游戏倒计时使用
    if (gameDuration > 0) {
      GameGlobal.databus.gameDuration = gameDuration;
    }
    // 标记进入倒计时状态，具体数值由 room:countdown 事件驱动
    this.isCountingDown = true;
  }

  _handleCountdown({ value }) {
    if (!this.visible) return;
    console.log('[WaitingRoom] 服务端倒计时:', value);
    this.isCountingDown = true;
    this.countdownValue = value;
  }

  _handleGameStart() {
    console.log('[WaitingRoom] 服务端通知游戏开始');
    if (!this.visible) return;
    this.isCountingDown = false;
    this.visible = false;
    this._cleanup();
    this.onStart && this.onStart();
  }

  _handleError({ message, code }) {
    console.error('[WaitingRoom] socket 错误:', message, code);

    if (code === 'TEAM_FULL' && this.onTeamFull) {
      // 队伍已满，提示后跳回选队页面重新选择
      wx.showToast({ title: message || '该队伍已满', icon: 'error', duration: 3000 });
      GameGlobal.setTimeout(() => {
        this.visible = false;
        this._cleanup();
        GameSocket.disconnect();
        this.onTeamFull();
      }, 3000);
      return;
    }

    wx.showToast({ title: message || '网络错误', icon: 'error', duration: 3000 });
  }

  _initLayout() {
    const cx = SCREEN_WIDTH / 2;
    this._cx = cx;

    // 取消按钮（左上角）
    this._cancelBtn = { x: 16, y: SAFE_AREA_TOP + 12, w: 72, h: 36, r: 18 };
  }

  _cleanup() {
    wx.offTouchStart(this._touchHandler);
    GameSocket.off('room:playerJoined', this._onPlayerJoined);
    GameSocket.off('room:allReady',     this._onAllReady);
    GameSocket.off('room:countdown',    this._onCountdown);
    GameSocket.off('room:gameStart',    this._onGameStart);
    GameSocket.off('room:joined',       this._onJoined);
    GameSocket.off('error',             this._onError);
  }

  destroy() {
    this._cleanup();
  }

  _onTouch(e) {
    if (!this.visible) return;
    const { clientX: x, clientY: y } = e.touches[0];
    const b = this._cancelBtn;
    if (x >= b.x && x <= b.x + b.w && y >= b.y && y <= b.y + b.h) {
      this.visible = false;
      this._cleanup();
      // 断开 socket，避免继续监听
      GameSocket.disconnect();
      this.onCancel && this.onCancel();
    }
  }

  update() {
    this._frame++;
  }

  render(ctx) {
    if (!this.visible) return;
    this.update();

    this._drawBg(ctx);
    this._drawTitle(ctx);
    this._drawRoomId(ctx);
    this._drawSlots(ctx);
    this._drawProgress(ctx);
    this._drawHint(ctx);
    this._drawCancelBtn(ctx);

    // 倒计时显示
    if (this.isCountingDown) {
      this._drawCountdown(ctx);
    }
  }

  // ─── 背景 ─────────────────────────────────────────────────

  _drawBg(ctx) {
    const g = ctx.createLinearGradient(0, 0, 0, SCREEN_HEIGHT);
    g.addColorStop(0, '#1a0f2e');
    g.addColorStop(0.5, '#251240');
    g.addColorStop(1, '#1a0f2e');
    ctx.fillStyle = g;
    ctx.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

    // 星点装饰
    ctx.fillStyle = 'rgba(139,92,246,0.09)';
    for (let i = 0; i < 24; i++) {
      ctx.beginPath();
      ctx.arc((i * 79) % SCREEN_WIDTH, (i * 53 + 60) % SCREEN_HEIGHT, 1.5 + (i % 3), 0, Math.PI * 2);
      ctx.fill();
    }
  }

  // ─── 顶部标题 ─────────────────────────────────────────────

  _drawTitle(ctx) {
    const cx = this._cx;
    const titleY = SCREEN_HEIGHT * 0.15;

    ctx.textAlign    = 'center';
    ctx.textBaseline = 'middle';

    ctx.font = `bold ${UI_BASE * 0.035}px Arial`;
    ctx.fillStyle = 'rgba(167,139,250,0.65)';
    ctx.fillText('WAITING ROOM', cx, titleY - SCREEN_HEIGHT * 0.04);

    const sz = UI_BASE * 0.09;
    ctx.font = `bold ${sz}px Arial`;
    ctx.fillStyle = 'rgba(0,0,0,0.4)';
    ctx.fillText('等待加入', cx + 2, titleY + 2);

    const tg = ctx.createLinearGradient(cx - 80, titleY - 20, cx + 80, titleY + 20);
    tg.addColorStop(0, '#C4B5FD');
    tg.addColorStop(0.5, '#A78BFA');
    tg.addColorStop(1, '#8B5CF6');
    ctx.fillStyle = tg;
    ctx.fillText('等待加入', cx, titleY);
  }

  // ─── 房间号展示卡片 ───────────────────────────────────────

  _drawRoomId(ctx) {
    const cx    = this._cx;
    const cardW = UI_BASE * 0.72;
    const cardH = SCREEN_HEIGHT * 0.17;
    const cardX = cx - cardW / 2;
    const cardY = SCREEN_HEIGHT * 0.26;

    // 卡片背景
    ctx.save();
    ctx.shadowColor   = 'rgba(139,92,246,0.4)';
    ctx.shadowBlur    = 24;
    ctx.shadowOffsetY = 6;
    const bg = ctx.createLinearGradient(cardX, cardY, cardX, cardY + cardH);
    bg.addColorStop(0, 'rgba(80,45,155,0.9)');
    bg.addColorStop(1, 'rgba(45,22,95,0.95)');
    ctx.fillStyle = bg;
    this._rr(ctx, cardX, cardY, cardW, cardH, 20);
    ctx.fill();
    ctx.shadowBlur = 0;
    ctx.strokeStyle = 'rgba(139,92,246,0.5)';
    ctx.lineWidth = 1.5;
    this._rr(ctx, cardX, cardY, cardW, cardH, 20);
    ctx.stroke();
    ctx.restore();

    // 顶部高光条
    ctx.save();
    this._rr(ctx, cardX, cardY, cardW, cardH, 20);
    ctx.clip();
    const hl = ctx.createLinearGradient(cardX, cardY, cardX + cardW, cardY);
    hl.addColorStop(0,   'rgba(167,139,250,0.0)');
    hl.addColorStop(0.5, 'rgba(196,181,253,0.18)');
    hl.addColorStop(1,   'rgba(167,139,250,0.0)');
    ctx.fillStyle = hl;
    ctx.fillRect(cardX, cardY, cardW, 4);
    ctx.restore();

    // 标签
    ctx.textAlign    = 'center';
    ctx.textBaseline = 'middle';
    ctx.font = `bold ${UI_BASE * 0.032}px Arial`;
    ctx.fillStyle = 'rgba(196,181,253,0.6)';
    ctx.fillText('房 间 号', cx, cardY + cardH * 0.28);

    // 房间号数字 —— 金色大字 + 脉冲光晕
    const pulse = 0.7 + 0.3 * Math.sin(this._frame * 0.05);
    ctx.save();
    ctx.shadowColor = `rgba(251,191,36,${0.3 * pulse})`;
    ctx.shadowBlur  = 18 * pulse;
    const ng = ctx.createLinearGradient(cx - 40, 0, cx + 40, 0);
    ng.addColorStop(0,   '#FDE68A');
    ng.addColorStop(0.5, '#FCD34D');
    ng.addColorStop(1,   '#F59E0B');
    ctx.fillStyle = ng;
    ctx.font = `bold ${UI_BASE * 0.18}px Arial`;
    ctx.fillText(String(this.roomId), cx, cardY + cardH * 0.65);
    ctx.restore();
  }

  // ─── 玩家槽位 ─────────────────────────────────────────────

  _drawSlots(ctx) {
    const cx       = this._cx;
    const total    = this.totalSeats;
    const slotR    = UI_BASE * 0.075;
    const gap      = UI_BASE * 0.04;
    const rowY     = SCREEN_HEIGHT * 0.56;
    const teamBadgeH = SCREEN_HEIGHT * 0.035;

    // 每边人数
    const teamSize = total / 2;

    // 分离A队和B队玩家
    const teamAPlayers = this.players.filter(p => p.team === 'A');
    const teamBPlayers = this.players.filter(p => p.team === 'B');

    // 计算A队（左边）起始位置
    const teamAW = teamSize * slotR * 2 + (teamSize - 1) * gap;
    const teamAStartX = cx - UI_BASE * 0.08 - teamAW + slotR;

    // 计算B队（右边）起始位置
    const teamBW = teamSize * slotR * 2 + (teamSize - 1) * gap;
    const teamBStartX = cx + UI_BASE * 0.08 + slotR;

    // 只显示玩家自己所在队伍的标识
    if (this.team === 'A') {
      this._drawTeamBadgeAboveSlots(ctx, teamAStartX + teamAW / 2 - slotR, rowY - slotR - teamBadgeH - 10, 'A');
    } else {
      this._drawTeamBadgeAboveSlots(ctx, teamBStartX + teamBW / 2 - slotR, rowY - slotR - teamBadgeH - 10, 'B');
    }

    // 绘制A队槽位（左边）- 根据实际玩家列表渲染
    for (let i = 0; i < teamSize; i++) {
      const slotX = teamAStartX + i * (slotR * 2 + gap);
      const player = teamAPlayers[i];
      const filled = !!player;
      const isMe = player && player.playerId === this.myPlayerId;
      this._drawSlot(ctx, slotX, rowY, slotR, filled, false, isMe);
    }

    // 绘制VS标记（中间）
    ctx.save();
    ctx.textAlign    = 'center';
    ctx.textBaseline = 'middle';
    ctx.font = `bold ${UI_BASE * 0.045}px Arial`;
    ctx.fillStyle = 'rgba(253,230,138,0.8)';
    ctx.fillText('VS', cx, rowY);
    ctx.restore();

    // 绘制B队槽位（右边）- 根据实际玩家列表渲染
    for (let i = 0; i < teamSize; i++) {
      const slotX = teamBStartX + i * (slotR * 2 + gap);
      const player = teamBPlayers[i];
      const filled = !!player;
      const isMe = player && player.playerId === this.myPlayerId;
      this._drawSlot(ctx, slotX, rowY, slotR, filled, false, isMe);
    }

    // 已加入文字
    ctx.textAlign    = 'center';
    ctx.textBaseline = 'middle';
    ctx.font = `${UI_BASE * 0.03}px Arial`;
    ctx.fillStyle = 'rgba(196,181,253,0.55)';
    ctx.fillText(`${this.players.length} 位已加入`, cx, rowY + slotR + 22);
  }

  /**
   * 在队伍槽位上方绘制队伍标识
   */
  _drawTeamBadgeAboveSlots(ctx, centerX, y, team) {
    const isTeamA = team === 'A';
    const teamColor = isTeamA ? '#8B5CF6' : '#EC4899';
    const teamLabel = getTeamDisplayName(team);

    ctx.save();

    const w = UI_BASE * 0.16;
    const h = SCREEN_HEIGHT * 0.035;
    const x = centerX - w / 2;
    const r = h / 2;

    // 背景
    ctx.fillStyle = teamColor + '30';
    ctx.strokeStyle = teamColor;
    ctx.lineWidth = 1.5;
    this._rr(ctx, x, y, w, h, r);
    ctx.fill();
    ctx.stroke();

    // 文字
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.font = `bold ${UI_BASE * 0.026}px Arial`;
    ctx.fillStyle = teamColor;
    ctx.fillText(teamLabel, centerX, y + h / 2);

    ctx.restore();
  }

  /** 单个玩家槽位 */
  _drawSlot(ctx, cx, cy, r, filled, isNext, isMe = false) {
    ctx.save();

    // 外发光（已填充 or 是当前玩家）
    if (filled) {
      if (isMe) {
        // 当前玩家高亮显示
        ctx.shadowColor = '#FCD34D';
        ctx.shadowBlur  = 20;
      } else {
        ctx.shadowColor = 'rgba(139,92,246,0.6)';
        ctx.shadowBlur  = 14;
      }
    } else if (isNext) {
      const pulse = 0.5 + 0.5 * Math.abs(Math.sin(this._frame * 0.06));
      ctx.shadowColor = `rgba(139,92,246,${0.3 * pulse})`;
      ctx.shadowBlur  = 10 * pulse;
    }

    // 背景圆
    const bg = ctx.createRadialGradient(cx, cy - r * 0.1, 0, cx, cy, r);
    if (filled) {
      if (isMe) {
        // 当前玩家用金色高亮
        bg.addColorStop(0, '#FDE68A');
        bg.addColorStop(1, '#F59E0B');
      } else {
        bg.addColorStop(0, '#9F7AEA');
        bg.addColorStop(1, '#6D28D9');
      }
    } else {
      bg.addColorStop(0, 'rgba(60,35,100,0.7)');
      bg.addColorStop(1, 'rgba(35,18,65,0.7)');
    }
    ctx.fillStyle = bg;
    ctx.beginPath();
    ctx.arc(cx, cy, r, 0, Math.PI * 2);
    ctx.fill();
    ctx.shadowBlur = 0;

    // 边框
    if (isMe) {
      ctx.strokeStyle = '#FCD34D';
      ctx.lineWidth = 3;
    } else {
      ctx.strokeStyle = filled
        ? 'rgba(196,181,253,0.7)'
        : isNext
          ? `rgba(139,92,246,${0.4 + 0.4 * Math.abs(Math.sin(this._frame * 0.06))})`
          : 'rgba(100,80,140,0.35)';
      ctx.lineWidth = filled ? 2 : 1.5;
    }
    ctx.beginPath();
    ctx.arc(cx, cy, r, 0, Math.PI * 2);
    ctx.stroke();

    // 图标
    ctx.textAlign    = 'center';
    ctx.textBaseline = 'middle';
    if (filled) {
      // 人形图标
      ctx.fillStyle = isMe ? '#FFFFFF' : 'rgba(255,255,255,0.9)';
      ctx.font = `${r * 1.1}px Arial`;
      ctx.fillText('👤', cx, cy);
    } else {
      // 问号
      ctx.fillStyle = isNext
        ? `rgba(196,181,253,${0.5 + 0.4 * Math.abs(Math.sin(this._frame * 0.06))})`
        : 'rgba(120,100,160,0.4)';
      ctx.font = `bold ${r * 0.9}px Arial`;
      ctx.fillText('?', cx, cy + 1);
    }

    ctx.restore();
  }

  // ─── 进度条 ───────────────────────────────────────────────

  _drawProgress(ctx) {
    const cx      = this._cx;
    const barW    = UI_BASE * 0.72;
    const barH    = 10;
    const barX    = cx - barW / 2;
    const barY    = SCREEN_HEIGHT * 0.695;
    const r       = barH / 2;
    const ratio   = this.joinedCount / this.totalSeats;

    // 轨道背景
    ctx.save();
    ctx.fillStyle = 'rgba(60,35,100,0.6)';
    this._rr(ctx, barX, barY, barW, barH, r);
    ctx.fill();

    // 填充进度
    const fillW = Math.max(barH, barW * ratio); // 最小宽度等于高度（圆形末端）
    const pg = ctx.createLinearGradient(barX, barY, barX + fillW, barY);
    pg.addColorStop(0, '#A78BFA');
    pg.addColorStop(1, '#7C3AED');
    ctx.fillStyle = pg;
    this._rr(ctx, barX, barY, fillW, barH, r);
    ctx.fill();

    // 进度条末端光点
    const dotX = barX + fillW;
    ctx.shadowColor = 'rgba(167,139,250,0.8)';
    ctx.shadowBlur  = 8;
    ctx.fillStyle   = '#EDE9FE';
    ctx.beginPath();
    ctx.arc(dotX, barY + r, r + 1, 0, Math.PI * 2);
    ctx.fill();
    ctx.shadowBlur = 0;

    ctx.restore();

    // 进度文字  joined / total
    ctx.textAlign    = 'center';
    ctx.textBaseline = 'middle';
    const textY = barY + barH + 20;

    // 数字
    ctx.font = `bold ${UI_BASE * 0.058}px Arial`;
    const numGrad = ctx.createLinearGradient(cx - 30, textY, cx + 30, textY);
    numGrad.addColorStop(0, '#FDE68A');
    numGrad.addColorStop(1, '#F59E0B');
    ctx.fillStyle = numGrad;
    ctx.fillText(`${this.joinedCount} / ${this.totalSeats}`, cx, textY);

    // 小标注
    ctx.font = `${UI_BASE * 0.028}px Arial`;
    ctx.fillStyle = 'rgba(180,160,220,0.5)';
    ctx.fillText('等待其他玩家加入…', cx, textY + 24);
  }

  // ─── 分享提示 ─────────────────────────────────────────────

  _drawHint(ctx) {
    const cx = this._cx;
    const hintY = SCREEN_HEIGHT * 0.8;

    // 分割线
    ctx.save();
    const lg = ctx.createLinearGradient(cx - 100, hintY - 12, cx + 100, hintY - 12);
    lg.addColorStop(0,   'rgba(139,92,246,0)');
    lg.addColorStop(0.5, 'rgba(139,92,246,0.3)');
    lg.addColorStop(1,   'rgba(139,92,246,0)');
    ctx.strokeStyle = lg;
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.moveTo(cx - 110, hintY - 12);
    ctx.lineTo(cx + 110, hintY - 12);
    ctx.stroke();
    ctx.restore();

    ctx.textAlign    = 'center';
    ctx.textBaseline = 'middle';
    ctx.font = `${UI_BASE * 0.03}px Arial`;
    ctx.fillStyle = 'rgba(167,139,250,0.6)';
    ctx.fillText('将房间号告知其他玩家，邀请加入！', cx, hintY);
  }

  // ─── 取消按钮 ─────────────────────────────────────────────

  _drawCancelBtn(ctx) {
    const { x, y, w, h, r } = this._cancelBtn;

    ctx.save();

    // 按钮背景
    ctx.fillStyle = 'rgba(60, 40, 100, 0.6)';
    this._rr(ctx, x, y, w, h, r);
    ctx.fill();

    // 边框
    ctx.strokeStyle = 'rgba(167, 139, 250, 0.5)';
    ctx.lineWidth = 1.5;
    this._rr(ctx, x, y, w, h, r);
    ctx.stroke();

    // 文字
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.font = `bold ${UI_BASE * 0.035}px Arial`;
    ctx.fillStyle = 'rgba(221, 214, 254, 0.9)';
    ctx.fillText('< 返回', x + w / 2, y + h / 2);

    ctx.restore();
  }

  // ─── 倒计时显示 ───────────────────────────────────────────

  _drawCountdown(ctx) {
    const cx = this._cx;
    const cy = SCREEN_HEIGHT * 0.5;

    // 半透明遮罩
    ctx.save();
    ctx.fillStyle = 'rgba(0,0,0,0.6)';
    ctx.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
    ctx.restore();

    // 倒计时数字
    ctx.save();
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';

    // 脉冲效果
    const pulse = 1 + 0.1 * Math.sin(this._frame * 0.15);
    const fontSize = UI_BASE * 0.35 * pulse;

    ctx.font = `bold ${fontSize}px Arial`;
    ctx.shadowColor = 'rgba(251,191,36,0.8)';
    ctx.shadowBlur = 30;

    const numGrad = ctx.createLinearGradient(cx - 60, cy - 40, cx + 60, cy + 40);
    numGrad.addColorStop(0, '#FDE68A');
    numGrad.addColorStop(0.5, '#FCD34D');
    numGrad.addColorStop(1, '#F59E0B');
    ctx.fillStyle = numGrad;

    ctx.fillText(String(this.countdownValue), cx, cy);
    ctx.shadowBlur = 0;

    // "即将开始" 文字
    ctx.font = `bold ${UI_BASE * 0.06}px Arial`;
    ctx.fillStyle = 'rgba(196,181,253,0.9)';
    ctx.fillText('游戏即将开始', cx, cy + SCREEN_HEIGHT * 0.15);

    ctx.restore();
  }

  // ─── 工具 ─────────────────────────────────────────────────

  _rr(ctx, x, y, w, h, r) {
    ctx.beginPath();
    ctx.moveTo(x + r, y);
    ctx.lineTo(x + w - r, y);
    ctx.quadraticCurveTo(x + w, y, x + w, y + r);
    ctx.lineTo(x + w, y + h - r);
    ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
    ctx.lineTo(x + r, y + h);
    ctx.quadraticCurveTo(x, y + h, x, y + h - r);
    ctx.lineTo(x, y + r);
    ctx.quadraticCurveTo(x, y, x + r, y);
    ctx.closePath();
  }
}
