import * as scene from '../../../core/scene.js';
import * as audio from '../../../core/audio.js';
import * as bus from '../../../core/bus.js';
import * as store from '../../../core/store.js';
import * as net from '../../../core/net.js';
import * as matchSession from '../../../core/match-session.js';
import * as modal from '../../../core/modal.js';
import { t } from '../../../core/i18n.js';
import { emoji } from '../../../core/theme.js';
import { createGame, rollDice, getValidMoves, applyMove, nextTurn, hasWon, getPipCount, WHITE, BLACK, serializeState } from '../logic/rules.js';
import { getBotMove } from '../logic/bot.js';
import { createMatch, startNewGame, endGame, canUseCube, getMatchState } from '../logic/match.js';
import { createCube, canDouble, offerDouble, acceptDouble, declineDouble, shouldBotDouble, shouldBotAccept } from '../logic/doubling.js';
import { drawBoard, hitTest } from '../canvas/board-renderer.js';
import { drawDice, createRollAnimation } from '../canvas/dice-renderer.js';
import { createMoveAnimation } from '../canvas/move-animator.js';
import * as reactions from '../../../core/reactions.js';

let canvas, ctx, layout;
let game, match;
let selectedPiece = null;
let highlights = [];
let isRolling = false;
let isAnimating = false;
let myColor = WHITE;
let params = {};
let rafId = null;
let diceRollFrame = 0;
let diceRollTotal = 24;
let particles = [];
let animatingChecker = null;
let lastFrameTime = 0;
let container = null;
let logicalW = 360, logicalH = 400;

export function mountGame(el, p) {
  params = p || {};
  const { mode = 'bot', variant = 'sheshbesh', matchLength = 3, difficulty = 'medium', useCube = true } = params;

  myColor = mode === 'live' ? (params.color === 'black' ? BLACK : WHITE) : WHITE;
  match = createMatch(matchLength, variant);
  if (!useCube) match.cube = null;

  const player = store.get('player') || {};
  const myName = player.display_name || player.username || t('common.you');
  const myAvatar = player.avatar_url
    ? `<img src="${player.avatar_url}" style="width:100%;height:100%;object-fit:cover;border-radius:50%;">`
    : emoji('person', '👤', 18);
  const myLevel = player.level || 1;

  const rawOppName = mode === 'bot' ? t('game.bot') : (params.opponentName || t('common.opponent'));
  const oppName = rawOppName.replace(/[<>"&]/g, c => ({'<':'&lt;','>':'&gt;','"':'&quot;','&':'&amp;'}[c]));
  const oppAvatarUrl = params.opponentAvatar ? String(params.opponentAvatar).replace(/[<>"]/g, '') : '';
  const oppAvatar = oppAvatarUrl
    ? `<img src="${oppAvatarUrl}" style="width:100%;height:100%;object-fit:cover;border-radius:50%;">`
    : (mode === 'bot' ? emoji('robot', '🤖', 18) : emoji('person', '👤', 18));
  const oppLevel = params.opponentLevel || (mode === 'bot' ? '—' : 1);

  const cubeVal = match.cube ? match.cube.value : 1;

  el.innerHTML = `
    <div class="bgg-container">
      <div class="bgg-top-bar">
        <div class="bgg-player-card bgg-opp-card" id="opp-card">
          <div class="bgg-avatar bgg-avatar-opp">${oppAvatar}</div>
          <div class="bgg-player-info">
            <div class="bgg-name" id="name-top">${oppName}</div>
            <div class="bgg-meta"><span class="bgg-level">Lv.${oppLevel}</span><span class="bgg-pips" id="pips-top">167</span></div>
          </div>
          <div class="bgg-score" id="score-top">0</div>
        </div>
        <div class="bgg-match-info">
          <span class="bgg-cube-display" id="cube-display">×${cubeVal}</span>
          <span class="bgg-match-length">${matchLength} ${t('game.points', { n: '' }).trim()}</span>
        </div>
      </div>

      <div class="bgg-board-area">
        <canvas id="bg-canvas"></canvas>
        <div class="bgg-turn-badge" id="turn-indicator">${t('game.your_turn')}</div>
      </div>

      <div class="bgg-controls">
        <button class="bgg-action-btn bgg-quit" id="btn-quit">✕</button>
        <button class="bgg-action-btn bgg-emote-btn" id="btn-emote">${emoji('sparkles', '✨', 18)}</button>
        <button class="bgg-roll-btn" id="btn-roll">${emoji('game_die', '🎲', 20)} ${t('game.roll_dice')}</button>
        <button class="bgg-cube-btn" id="btn-double" style="display:none;">×2</button>
      </div>

      <div class="bgg-double-offer" id="double-offer" style="display:none;">
        <div class="bgg-double-icon">×<span id="double-val">2</span></div>
        <p>${t('backgammon.opponent_doubles')}</p>
        <div class="bgg-double-actions">
          <button class="bgg-decline-btn" id="btn-decline-double">${t('backgammon.decline')}</button>
          <button class="bgg-accept-btn" id="btn-accept-double">${t('backgammon.accept')}</button>
        </div>
      </div>

      <div class="bgg-bottom-bar">
        <div class="bgg-player-card bgg-my-card" id="my-card">
          <div class="bgg-avatar bgg-avatar-me">${myAvatar}</div>
          <div class="bgg-player-info">
            <div class="bgg-name" id="name-bottom">${myName}</div>
            <div class="bgg-meta"><span class="bgg-level">Lv.${myLevel}</span><span class="bgg-pips" id="pips-bottom">167</span></div>
          </div>
          <div class="bgg-score bgg-my-score" id="score-bottom">0</div>
        </div>
      </div>

    </div>
    ${getStyles()}
  `;

  container = el.querySelector('.bgg-container');
  canvas = el.querySelector('#bg-canvas');
  ctx = canvas.getContext('2d');

  resizeCanvas();
  startNewGameRound();

  canvas.addEventListener('click', onCanvasClick);
  canvas.addEventListener('touchstart', onCanvasTouch, { passive: false });
  el.querySelector('#btn-roll')?.addEventListener('click', onRollClick);
  el.querySelector('#btn-double')?.addEventListener('click', onDoubleClick);
  el.querySelector('#btn-accept-double')?.addEventListener('click', onAcceptDouble);
  el.querySelector('#btn-decline-double')?.addEventListener('click', onDeclineDouble);
  el.querySelector('#btn-quit')?.addEventListener('click', onQuit);
  window.addEventListener('resize', resizeCanvas);

  if (mode === 'live') {
    matchSession.create(params.matchId, 'backgammon', {
      onOpponentMove: handleServerState,
      onOpponentDisconnect: () => bus.emit('toast', { text: t('game.opponent_disconnected') }),
      onOpponentAbandon: () => endMatchWithWin()
    });
  }

  setupEmotePanel(el);
  startLoop();
}

export function unmountGame() {
  reactions.stop();
  if (rafId) { cancelAnimationFrame(rafId); rafId = null; }
  window.removeEventListener('resize', resizeCanvas);
  matchSession.destroy();
  container = null; canvas = null; ctx = null;
}

// ── Resize ──
function resizeCanvas() {
  if (!canvas) return;
  const area = canvas.parentElement;
  const w = Math.min(area.clientWidth - 8, 400);
  const h = Math.round(w * 1.1);
  logicalW = w;
  logicalH = h;
  canvas.width = w;
  canvas.height = h;
  canvas.style.width = w + 'px';
  canvas.style.height = h + 'px';
}

// ── Game Loop ──
function startLoop() {
  lastFrameTime = performance.now();
  function loop(now) {
    const dt = Math.min((now - lastFrameTime) / 1000, 0.05);
    lastFrameTime = now;
    updateParticles(dt);
    draw();
    rafId = requestAnimationFrame(loop);
  }
  rafId = requestAnimationFrame(loop);
}

function updateParticles(dt) {
  for (let i = particles.length - 1; i >= 0; i--) {
    const p = particles[i];
    p.x += p.vx * dt;
    p.y += p.vy * dt;
    p.vy += 250 * dt;
    p.alpha -= 2 * dt;
    if (p.alpha <= 0) particles.splice(i, 1);
  }
}

function draw() {
  if (!ctx || !game) return;

  layout = drawBoard(ctx, logicalW, logicalH, game.state, {
    highlights,
    selectedPoint: selectedPiece?.from ?? null,
    particles
  });

  // Dice on board
  if (game.dice && !isRolling) {
    const allDice = game.dice[0] === game.dice[1]
      ? [game.dice[0], game.dice[0], game.dice[0], game.dice[0]]
      : game.dice;
    const totalW = allDice.length * 38 + (allDice.length - 1) * 10;
    const diceX = logicalW / 2 - totalW / 2;
    const diceY = logicalH / 2 - 19;
    drawDice(ctx, diceX, diceY, allDice, { used: getUsedDiceIndices() });
  }

  if (isRolling) {
    const fake = [Math.ceil(Math.random() * 6), Math.ceil(Math.random() * 6)];
    drawDice(ctx, logicalW / 2 - 44, logicalH / 2 - 19, fake, {
      rolling: true, frame: diceRollFrame, totalFrames: diceRollTotal
    });
  }

  // Animated checker in flight
  if (animatingChecker) {
    const { x, y, type } = animatingChecker;
    const r = layout?.checkerR || 14;
    ctx.save();
    // shadow
    ctx.beginPath();
    ctx.arc(x + 1, y + 3, r, 0, Math.PI * 2);
    ctx.fillStyle = 'rgba(0,0,0,0.5)';
    ctx.fill();
    // checker
    ctx.beginPath();
    ctx.arc(x, y, r, 0, Math.PI * 2);
    ctx.fillStyle = type === WHITE ? '#F5E6D3' : '#2A2A2A';
    ctx.fill();
    ctx.strokeStyle = type === WHITE ? '#A08060' : '#555';
    ctx.lineWidth = 2;
    ctx.stroke();
    // glow
    ctx.beginPath();
    ctx.arc(x, y, r + 3, 0, Math.PI * 2);
    ctx.strokeStyle = 'rgba(255,215,0,0.6)';
    ctx.lineWidth = 2;
    ctx.stroke();
    ctx.restore();
  }
}

// ── Game Logic ──
function startNewGameRound() {
  game = startNewGame(match);
  selectedPiece = null;
  highlights = [];
  isRolling = false;
  isAnimating = false;
  animatingChecker = null;
  particles = [];
  updateUI();

  if (game.turn !== myColor && params.mode === 'bot') {
    setTimeout(doBotTurn, 800);
  }
}

function getUsedDiceIndices() {
  if (!game.dice) return [];
  const used = [];
  const allDice = game.dice[0] === game.dice[1]
    ? [game.dice[0], game.dice[0], game.dice[0], game.dice[0]]
    : [...game.dice];
  for (const played of (game.movesPlayed || [])) {
    for (let j = 0; j < allDice.length; j++) {
      if (!used.includes(j) && allDice[j] === played) { used.push(j); break; }
    }
  }
  return used;
}

function updateUI() {
  if (!game || !container) return;
  const topColor = myColor === WHITE ? BLACK : WHITE;
  const isMyTurn = game.turn === myColor;

  setText('#pips-top', getPipCount(game.state, topColor, game.variant));
  setText('#pips-bottom', getPipCount(game.state, myColor, game.variant));
  setText('#score-top', String(match.scores[topColor]));
  setText('#score-bottom', String(match.scores[myColor]));

  // Cube display
  const cubeEl = container.querySelector('#cube-display');
  if (cubeEl && match.cube) cubeEl.textContent = `×${match.cube.value}`;

  // Active turn highlight on cards
  const oppCard = container.querySelector('#opp-card');
  const myCard = container.querySelector('#my-card');
  if (oppCard) oppCard.classList.toggle('bgg-active-turn', !isMyTurn);
  if (myCard) myCard.classList.toggle('bgg-active-turn', isMyTurn);

  // Roll button
  const rollBtn = container.querySelector('#btn-roll');
  rollBtn.style.display = (isMyTurn && !game.dice && !isAnimating && !isRolling) ? '' : 'none';

  // Double button
  const dblBtn = container.querySelector('#btn-double');
  if (match.cube && canUseCube(match) && isMyTurn && !game.dice && canDouble(match.cube, myColor)) {
    dblBtn.style.display = '';
  } else {
    dblBtn.style.display = 'none';
  }

  // Turn indicator
  const ti = container.querySelector('#turn-indicator');
  if (ti) {
    ti.textContent = isMyTurn ? t('game.your_turn') : t('game.opponent_turn');
    ti.className = `bgg-turn-badge ${isMyTurn ? 'bgg-turn-mine' : 'bgg-turn-opp'}`;
  }
}

function setText(sel, text) {
  const el = container?.querySelector(sel);
  if (el) el.textContent = text;
}

// ── Input ──
function onCanvasClick(e) {
  if (isAnimating || isRolling) return;
  if (game.turn !== myColor) return;
  if (!game.dice) return;

  const rect = canvas.getBoundingClientRect();
  const x = (e.clientX - rect.left) * (logicalW / rect.width);
  const y = (e.clientY - rect.top) * (logicalH / rect.height);
  handleTap(x, y);
}

function onCanvasTouch(e) {
  if (isAnimating || isRolling) return;
  if (game.turn !== myColor) return;
  if (!game.dice) return;

  e.preventDefault();
  const t = e.touches[0];
  const rect = canvas.getBoundingClientRect();
  const x = (t.clientX - rect.left) * (logicalW / rect.width);
  const y = (t.clientY - rect.top) * (logicalH / rect.height);
  handleTap(x, y);
}

function handleTap(x, y) {
  const hit = hitTest(x, y, layout, game.state);
  if (!hit) { clearSelection(); return; }

  // Tap destination
  if (selectedPiece && hit.type === 'point' && highlights.includes(hit.index)) {
    executeMove(hit.index); return;
  }
  if (selectedPiece && hit.type === 'bearoff' && highlights.includes('off')) {
    executeMove('off'); return;
  }

  // Select piece
  if (hit.type === 'point') selectPoint(hit.index);
  else if (hit.type === 'bar' && hit.player === game.turn) selectBar();
  else clearSelection();
}

function selectPoint(idx) {
  const pieces = game.state.points[idx];
  if (!pieces.length || pieces[pieces.length - 1].type !== game.turn) { clearSelection(); return; }

  const piece = pieces[pieces.length - 1];
  const moves = getValidMoves(game).filter(m => m.piece.id === piece.id);
  if (!moves.length) { clearSelection(); return; }

  selectedPiece = { id: piece.id, from: idx, moves };
  highlights = moves.map(m => m.to === 'off' ? 'off' : m.to);
  audio.play('click');
}

function selectBar() {
  const moves = getValidMoves(game).filter(m => m.from === 'bar' || m.from === null);
  if (!moves.length) return;
  selectedPiece = { id: moves[0].piece.id, from: 'bar', moves };
  highlights = moves.map(m => m.to);
  audio.play('click');
}

function clearSelection() { selectedPiece = null; highlights = []; }

function executeMove(dest) {
  if (!selectedPiece) return;
  const move = selectedPiece.moves.find(m => m.to === dest);
  if (!move) return;

  isAnimating = true;
  const fromPt = selectedPiece.from;
  const pieceType = game.turn;

  audio.play('sfx_bg_piece_move', 'game');

  createMoveAnimation(fromPt, dest, layout,
    (frame) => { animatingChecker = { x: frame.x, y: frame.y, type: pieceType }; },
    () => {
      animatingChecker = null;
      const actions = applyMove(game, move.piece.id, move.steps);

      if (actions?.some(a => a.type === 'hit')) {
        audio.play('sfx_bg_piece_hit', 'game');
        spawnParticles(logicalW / 2, logicalH / 2, ['#EF4444', '#F97316', '#FBBF24']);
      }
      if (actions?.some(a => a.type === 'bear')) {
        audio.play('sfx_bg_bear_off', 'game');
        spawnParticles(logicalW - 20, logicalH / 2, ['#10B981', '#34D399', '#FFD700']);
      }

      clearSelection();
      isAnimating = false;
      updateUI();

      if (game.isOver) { handleGameOver(); return; }
      if (game.movesLeft.length === 0) { endTurn(); return; }
      const remaining = getValidMoves(game);
      if (remaining.length === 0) endTurn();
    }
  );
}

function endTurn() {
  nextTurn(game);
  updateUI();
  if (params.mode === 'bot' && game.turn !== myColor) setTimeout(doBotTurn, 600);
  else if (params.mode === 'live') syncToServer();
}

// ── Dice ──
function onRollClick() {
  if (game.dice || isRolling || isAnimating) return;
  if (game.turn !== myColor) return;

  isRolling = true;
  diceRollFrame = 0;
  audio.play('sfx_bg_dice_roll', 'game');
  updateUI();

  createRollAnimation(
    (frame, total) => { diceRollFrame = frame; diceRollTotal = total; },
    () => {
      isRolling = false;
      rollDice(game);

      if (game.movesLeft.length === 0) {
        bus.emit('toast', { text: t('backgammon.no_moves') });
        setTimeout(endTurn, 1000);
        return;
      }
      updateUI();

      // Auto-play single forced move
      const moves = getValidMoves(game);
      if (moves.length === 1 && game.movesLeft.length === 1) {
        setTimeout(() => {
          selectedPiece = { id: moves[0].piece.id, from: moves[0].from, moves };
          executeMove(moves[0].to);
        }, 400);
      }
    }
  );
}

// ── Doubling ──
function onDoubleClick() {
  if (!match.cube || !canDouble(match.cube, myColor) || game.dice) return;
  audio.play('sfx_bg_double', 'game');

  if (params.mode === 'bot') {
    offerDouble(match.cube, myColor);
    const myPips = getPipCount(game.state, myColor, game.variant);
    const oppPips = getPipCount(game.state, myColor === WHITE ? BLACK : WHITE, game.variant);
    if (shouldBotAccept(match.cube, oppPips, myPips, params.difficulty)) {
      acceptDouble(match.cube);
      bus.emit('toast', { text: t('backgammon.bot_accepted', { n: match.cube.value }) });
    } else {
      declineDouble(match.cube);
      bus.emit('toast', { text: t('backgammon.bot_declined') });
      showGameResult(endGame(match, myColor, 1));
      return;
    }
    updateUI();
  } else if (params.mode === 'live') {
    offerDouble(match.cube, myColor);
    syncToServer();
  }
}

function onAcceptDouble() {
  if (!match.cube) return;
  acceptDouble(match.cube);
  audio.play('click');
  container.querySelector('#double-offer').style.display = 'none';
  updateUI();
  if (params.mode === 'live') syncToServer();
}

function onDeclineDouble() {
  if (!match.cube) return;
  declineDouble(match.cube);
  container.querySelector('#double-offer').style.display = 'none';
  showGameResult(endGame(match, game.turn, 1));
}

// ── Bot ──
function doBotTurn() {
  if (game.turn === myColor || game.isOver) return;

  // Doubling consideration
  if (match.cube && canUseCube(match) && canDouble(match.cube, game.turn) && !game.dice) {
    const botPips = getPipCount(game.state, game.turn, game.variant);
    const myPips = getPipCount(game.state, myColor, game.variant);
    if (shouldBotDouble(match.cube, botPips, myPips, params.difficulty)) {
      offerDouble(match.cube, game.turn);
      audio.play('sfx_bg_double', 'game');
      container.querySelector('#double-offer').style.display = '';
      container.querySelector('#double-val').textContent = match.cube.value * 2;
      return;
    }
  }

  // Roll
  isRolling = true;
  diceRollFrame = 0;
  audio.play('sfx_bg_dice_roll', 'game');

  createRollAnimation(
    (frame, total) => { diceRollFrame = frame; diceRollTotal = total; },
    () => {
      isRolling = false;
      rollDice(game);
      if (game.movesLeft.length === 0) { setTimeout(endTurn, 500); return; }
      botPlayMoves();
    }
  );
}

function botPlayMoves() {
  if (game.movesLeft.length === 0 || game.isOver) {
    if (!game.isOver) endTurn(); else handleGameOver();
    return;
  }
  const move = getBotMove(game, params.difficulty);
  if (!move) { endTurn(); return; }

  isAnimating = true;
  createMoveAnimation(move.from, move.to, layout,
    (frame) => { animatingChecker = { x: frame.x, y: frame.y, type: game.turn }; },
    () => {
      animatingChecker = null;
      audio.play('sfx_bg_piece_move', 'game');
      const actions = applyMove(game, move.piece.id, move.steps);
      if (actions?.some(a => a.type === 'hit')) {
        audio.play('sfx_bg_piece_hit', 'game');
        spawnParticles(logicalW / 2, logicalH / 2, ['#EF4444', '#F97316']);
      }
      isAnimating = false;
      updateUI();
      if (game.isOver) { handleGameOver(); return; }
      setTimeout(botPlayMoves, 300);
    }
  );
}

// ── Game Over ──
function handleGameOver() {
  const isMyWin = game.winner === myColor;
  audio.play(isMyWin ? 'sfx_bg_win_game' : 'lose', 'game');
  if (isMyWin) spawnParticles(logicalW / 2, logicalH * 0.3, ['#FFD700', '#10B981', '#8B5CF6'], 20);
  showGameResult(endGame(match, game.winner, game.score || 1));
}

function showGameResult(result) {
  updateUI();
  if (result.matchOver) {
    audio.play(result.winner === myColor ? 'sfx_bg_win_match' : 'lose', 'game');
    setTimeout(() => {
      if (params.mode === 'live') syncComplete(result);
      scene.exitGameMode();
      scene.replace('backgammon-result', {
        winner: result.winner === myColor ? 'you' : 'opponent',
        scores: result.scores, matchLength: match.length, mode: params.mode
      });
    }, 1800);
  } else {
    const who = result.winner === myColor ? t('game.round_won') : t('game.round_lost');
    bus.emit('toast', { text: `${who} (+${result.stake})` });
    setTimeout(startNewGameRound, 2000);
  }
}

function endMatchWithWin() {
  scene.exitGameMode();
  scene.replace('backgammon-result', {
    winner: 'you', scores: match.scores, matchLength: match.length,
    mode: params.mode, reason: 'abandon'
  });
}

// ── Particles ──
function spawnParticles(x, y, colors, count = 10) {
  for (let i = 0; i < count; i++) {
    const angle = (Math.PI * 2 * i) / count;
    const speed = 60 + Math.random() * 100;
    particles.push({
      x, y,
      vx: Math.cos(angle) * speed,
      vy: Math.sin(angle) * speed - 80,
      alpha: 1,
      size: 2 + Math.random() * 3,
      color: colors[i % colors.length]
    });
  }
}

// ── Multiplayer ──
function syncToServer() {
  if (params.mode !== 'live') return;
  net.post('backgammon-match.php', {
    action: 'move', match_id: params.matchId,
    game_state: serializeState(game), match_state: getMatchState(match), cube: match.cube
  }).catch(() => {});
}

function syncComplete(result) {
  net.post('backgammon-match.php', {
    action: 'complete', match_id: params.matchId,
    winner: result.winner === myColor ? store.get('auth.userId') : 'opponent', reason: 'normal'
  }).catch(() => {});
}

function handleServerState(data) {
  if (!data || data.error) return;
  matchSession.markOpponentActive();
  if (data.game_state) {
    const gs = data.game_state;
    // Only merge authoritative server fields — preserve local-only state
    // (selectedPiece, highlights, isRolling, isAnimating, animatingChecker, particles)
    if (gs.state) game.state = gs.state;
    if (gs.dice) game.dice = gs.dice;
    if (gs.movesLeft) game.movesLeft = gs.movesLeft;
    if (gs.movesPlayed) game.movesPlayed = gs.movesPlayed;
    if (gs.turn !== undefined) game.turn = gs.turn;
    if (gs.turnNumber !== undefined) game.turnNumber = gs.turnNumber;
    if (gs.isOver !== undefined) game.isOver = gs.isOver;
    if (gs.winner !== undefined) game.winner = gs.winner;
    if (gs.score !== undefined) game.score = gs.score;
    if (data.match_state) match.scores = data.match_state.scores || match.scores;
    if (data.cube) match.cube = data.cube;

    clearSelection();
    updateUI();

    if (game.isOver) { handleGameOver(); return; }
    if (game.turn === myColor && game.movesLeft && game.movesLeft.length === 0 && game.dice) {
      // Opponent finished their turn, now it's ours
      game.dice = null;
    }
  }
}

// ── Emotes ──
function setupEmotePanel(el) {
  // Backgammon used to post reactions to `backgammon-match.php action:'emote'`,
  // which wrote a `last_emote` column that NO client ever read — so the opponent
  // never saw anything. (That table does not exist in the database either.)
  // Reactions now use the shared module and the shared channel.
  reactions.start({
    matchId: params.matchId,
    gameKey: 'backgammon',
    myId: store.get('auth.userId'),
    live: params.mode === 'live',
  });
  reactions.seat(store.get('auth.userId'), el.querySelector('#my-card'), {
    name: store.get('player.display_name') || store.get('player.username') || t('common.you'),
    avatarUrl: store.get('player.avatar_url'),
    side: 'bottom',
  });
  reactions.seat('bgg-opponent', el.querySelector('#opp-card'), {
    name: params.mode === 'live' ? t('common.opponent') : t('game.bot'),
    side: 'top',
  });
  reactions.mountPanel(document.body, el.querySelector('#btn-emote'));
}

function showBubble(text) {
  if (!container) return;
  const b = document.createElement('div');
  b.className = 'bgg-bubble';
  b.textContent = text;
  container.appendChild(b);
  setTimeout(() => b.remove(), 2200);
}

async function onQuit() {
  audio.play('click');
  if (params.mode === 'live') {
    const confirmed = await modal.confirm(t('game.leave_confirm'), {
      title: t('game.leave_title'), confirmText: t('game.leave'), cancelText: t('game.stay'), danger: true
    });
    if (!confirmed) return;
    net.post('backgammon-match.php', { action: 'leave', match_id: params.matchId }).catch(() => {});
    matchSession.destroy();
  }
  scene.exitGameMode();
  scene.replace('backgammon-room', { mode: 'menu' });
}

// ── Styles ──
function getStyles() {
  return `<style>
.bgg-container {
  display:flex;flex-direction:column;height:100%;
  background:linear-gradient(180deg,#060a12 0%,#0c1424 40%,#0a1018 100%);
  color:var(--text-primary);overflow:hidden;
  font-family:var(--font-ar,'IBM Plex Sans Arabic',sans-serif);
  position:relative;
}

/* ── Player Bars ── */
.bgg-top-bar {
  display:flex;align-items:center;padding:10px 14px;gap:8px;
  background:linear-gradient(180deg,rgba(0,0,0,0.6),rgba(0,0,0,0.3));
  border-bottom:1px solid rgba(255,255,255,0.04);
}
.bgg-bottom-bar {
  display:flex;align-items:center;padding:10px 14px;gap:8px;
  background:linear-gradient(0deg,rgba(0,0,0,0.6),rgba(0,0,0,0.3));
  border-top:1px solid rgba(255,255,255,0.04);
  padding-bottom:max(10px, env(safe-area-inset-bottom, 0px));
}
.bgg-player-card {
  display:flex;align-items:center;gap:10px;flex:1;
  padding:6px 10px;border-radius:12px;
  background:rgba(255,255,255,0.02);
  border:1.5px solid transparent;
  transition:border-color 0.3s,box-shadow 0.3s;
}
.bgg-player-card.bgg-active-turn {
  border-color:rgba(52,211,153,0.4);
  box-shadow:0 0 12px rgba(52,211,153,0.1);
}
.bgg-avatar {
  width:36px;height:36px;border-radius:50%;
  display:flex;align-items:center;justify-content:center;
  background:linear-gradient(135deg,#1a2540,#0e1830);
  font-size:16px;overflow:hidden;flex-shrink:0;
  border:2px solid rgba(255,255,255,0.1);
  box-shadow:0 2px 8px rgba(0,0,0,0.3);
}
.bgg-avatar-me { border-color:rgba(228,172,56,0.5); }
.bgg-avatar-opp { border-color:rgba(139,92,246,0.4); }
.bgg-player-info { flex:1;min-width:0; }
.bgg-name { font-size:13px;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis; }
.bgg-meta { display:flex;align-items:center;gap:8px;margin-top:1px; }
.bgg-level { font-size:var(--fs-xs);color:var(--text-muted);font-weight:600; }
.bgg-pips { font-size:var(--fs-xs);color:var(--text-secondary);font-weight:500; }
.bgg-pips::before { content:'⬡ ';opacity:0.5; }
.bgg-score {
  font-size:20px;font-weight:800;color:var(--text-dim);min-width:24px;text-align:center;
  font-variant-numeric:tabular-nums;
}
.bgg-my-score { color:var(--gold,#E4AC38); }
.bgg-match-info {
  display:flex;flex-direction:column;align-items:center;gap:2px;
}
.bgg-cube-display {
  font-size:14px;font-weight:800;color:var(--purple-light);
  background:rgba(139,92,246,0.12);border:1px solid rgba(139,92,246,0.25);
  border-radius:6px;padding:2px 8px;
}
.bgg-match-length { font-size:var(--fs-xs);color:var(--text-muted); }

/* ── Board ── */
.bgg-board-area {
  flex:1;display:flex;align-items:center;justify-content:center;
  padding:4px 6px;position:relative;min-height:0;
}
#bg-canvas {
  border-radius:8px;touch-action:none;cursor:pointer;
  box-shadow:0 4px 24px rgba(0,0,0,0.5),0 0 0 1px rgba(255,255,255,0.04);
}
.bgg-turn-badge {
  position:absolute;top:6px;left:50%;transform:translateX(-50%);
  font-size:var(--fs-xs);font-weight:700;padding:3px 10px;border-radius:20px;
  backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
  pointer-events:none;transition:all 0.3s;opacity:0.9;
}
.bgg-turn-mine { background:rgba(16,185,129,0.25);color:var(--success);border:1px solid rgba(52,211,153,0.3); }
.bgg-turn-opp { background:rgba(239,68,68,0.15);color:var(--red-light);border:1px solid rgba(239,68,68,0.2); }

/* ── Controls ── */
.bgg-controls {
  display:flex;align-items:center;justify-content:center;gap:10px;
  padding:10px 14px;
  background:linear-gradient(180deg,rgba(0,0,0,0.2),rgba(0,0,0,0.4));
  min-height:56px;
  transition:min-height 0.2s;
}
.bgg-roll-btn {
  display:flex;align-items:center;gap:6px;
  padding:13px 32px;border-radius:14px;border:none;
  background:linear-gradient(135deg,var(--gold),var(--gold-dark));
  color:#fff;font-size:15px;font-weight:800;cursor:pointer;
  box-shadow:0 4px 16px rgba(228,172,56,0.35),inset 0 1px 0 rgba(255,255,255,0.2);
  transition:transform 0.12s var(--ease-spring,cubic-bezier(0.34,1.56,0.64,1)),box-shadow 0.15s;
}
.bgg-roll-btn:active { transform:scale(0.92);box-shadow:0 2px 8px rgba(228,172,56,0.3); }
.bgg-cube-btn {
  padding:10px 16px;min-height:var(--tap-min);border-radius:10px;border:none;
  background:rgba(139,92,246,0.12);border:1.5px solid rgba(139,92,246,0.35);
  color:var(--purple-light);font-size:15px;font-weight:800;cursor:pointer;
  transition:transform 0.12s;
}
.bgg-cube-btn:active { transform:scale(0.9); }
.bgg-action-btn {
  width:var(--tap-min);height:var(--tap-min);border-radius:50%;border:none;
  background:rgba(255,255,255,0.04);color:var(--text-secondary);
  font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;
  border:1px solid var(--border);
  transition:background 0.15s;
}
.bgg-action-btn:active { background:rgba(255,255,255,0.1); }
.bgg-quit { color:var(--error);border-color:rgba(239,68,68,0.15); }
.bgg-emote-btn { color:var(--text-primary); }

/* ── Doubling Offer ── */
.bgg-double-offer {
  position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
  background:rgba(10,15,25,0.96);border:1.5px solid rgba(228,172,56,0.4);
  border-radius:20px;padding:24px 28px;text-align:center;z-index:20;
  backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
  box-shadow:0 16px 48px rgba(0,0,0,0.6);
}
.bgg-double-icon {
  font-size:28px;font-weight:900;color:var(--gold);margin-bottom:8px;
}
.bgg-double-offer p { margin:0 0 16px;font-size:14px;color:var(--text-light); }
.bgg-double-actions { display:flex;gap:10px;justify-content:center; }
.bgg-accept-btn,.bgg-decline-btn {
  padding:11px 24px;border-radius:12px;border:none;
  font-weight:700;font-size:14px;cursor:pointer;
  transition:transform 0.12s;
}
.bgg-accept-btn { background:linear-gradient(135deg,var(--emerald),#059669);color:#fff;box-shadow:0 3px 12px rgba(16,185,129,0.3); }
.bgg-decline-btn { background:rgba(239,68,68,0.15);color:var(--red-light);border:1.5px solid rgba(239,68,68,0.3); }
.bgg-accept-btn:active,.bgg-decline-btn:active { transform:scale(0.93); }

/* ── Emotes ── */
.bgg-emote-panel {
  position:absolute;bottom:72px;left:50%;transform:translateX(-50%);
  background:rgba(10,15,25,0.96);border:1px solid rgba(255,255,255,0.08);
  border-radius:16px;padding:14px;z-index:20;
  backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
  box-shadow:0 12px 40px rgba(0,0,0,0.5);
}
.bgg-emotes { display:flex;gap:6px;flex-wrap:wrap;margin-bottom:8px; }
.bgg-emote-btn {
  width:40px;height:40px;border-radius:10px;border:none;
  background:rgba(255,255,255,0.04);font-size:22px;cursor:pointer;
  transition:transform 0.1s,background 0.15s;
}
.bgg-emote-btn:active { transform:scale(0.88);background:rgba(255,255,255,0.12); }
.bgg-phrases { display:flex;flex-direction:column;gap:4px; }
.bgg-phrase-btn {
  padding:7px 12px;border-radius:8px;border:none;
  background:rgba(255,255,255,0.03);color:var(--text-secondary);
  font-size:12px;cursor:pointer;text-align:right;
  transition:background 0.15s;
}
.bgg-phrase-btn:active { background:rgba(255,255,255,0.08); }

/* ── Bubble ── */
.bgg-bubble {
  position:absolute;bottom:100px;left:50%;transform:translateX(-50%);
  font-size:36px;pointer-events:none;
  animation:bubblePop 2.2s var(--ease-out,cubic-bezier(0.16,1,0.3,1)) forwards;
}
@keyframes bubblePop {
  0%{opacity:1;transform:translateX(-50%) translateY(0) scale(1)}
  100%{opacity:0;transform:translateX(-50%) translateY(-50px) scale(1.3)}
}
</style>`;
}
