import * as bus from '../../../core/bus.js';
import * as scene from '../../../core/scene.js';
import * as audio from '../../../core/audio.js';
import * as store from '../../../core/store.js';
import { t } from '../../../core/i18n.js';
import { createCanvas, clear } from '../../../core/canvas.js';
import * as rules from '../logic/rules.js';
import * as juice from '../../../core/juice.js';
import { getPiecePosition, getHomeBasePosition, SAFE_SQUARES, HOME_COLUMNS, SHARED_PATH } from '../logic/board-map.js';
import * as emoteSystem from '../../chess/components/emotes.js';
import * as mp from '../../../core/multiplayer.js';
import { emoji, getAsset, getColor } from '../../../core/theme.js';
import * as matchLive from '../../../core/match-live.js';
import * as net from '../../../core/net.js';
import * as modal from '../../../core/modal.js';

let game, validMoves, ctx, canvas, boardSize, cellSize;
let diceAnimating = false;

// 60fps animation system
let boardCanvas = null; // offscreen cached static board
let boardDirty = true;  // flag to redraw static board
let animFrame = null;   // rAF handle
let pieceAnims = new Map(); // pieceId -> {fromX,fromY,toX,toY,t,duration}

// Order: Red(BL), Green(TL), Yellow(TR), Blue(BR) — standard Ludo layout
const DEFAULT_COLORS = ['#E53935', '#43A047', '#FDD835', '#1E88E5'];
const DEFAULT_COLORS_LIGHT = ['#EF9A9A', '#A5D6A7', '#FFF59D', '#90CAF9'];
let COLORS = DEFAULT_COLORS;
let COLORS_LIGHT = DEFAULT_COLORS_LIGHT;
const PAWN_SLOTS = ['ludo_pawn_red', 'ludo_pawn_green', 'ludo_pawn_yellow', 'ludo_pawn_blue'];
const pawnImages = [null, null, null, null];
let PLAYER_NAMES = ['أنت', 'Bot 1', 'Bot 2', 'Bot 3'];

function lightenColor(hex, amount) {
  const num = parseInt(hex.replace('#', ''), 16);
  const r = Math.min(255, ((num >> 16) & 0xFF) + Math.round(255 * amount));
  const g = Math.min(255, ((num >> 8) & 0xFF) + Math.round(255 * amount));
  const b = Math.min(255, (num & 0xFF) + Math.round(255 * amount));
  return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`;
}
let livePoller = null;
let myPlayerIndex = 0;
let matchId = null;
let isHost = false;
let turnTimer = null;
let turnTimerStart = 0;
const TURN_TIMEOUT = 15000;

function renderPanel(p) {
  return `
    <div class="pp" id="pp-${p.i}" style="--pc:${p.color};">
      <div class="pp-turn-arrow">▶</div>
      <div class="pp-avatar" style="width:30px;height:30px;border-radius:50%;background:linear-gradient(135deg,#2a2a4a,#1a1a3a);border:2.5px solid ${p.color};display:flex;align-items:center;justify-content:center;font-size:14px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.3);">
        ${p.avatar}
      </div>
      <div style="display:flex;flex-direction:column;">
        <span class="pp-name" style="font-size:11px;font-weight:700;color:#f8fafc;">${p.name}</span>
        ${p.level ? `<span style="font-size:9px;color:#64748b;">${p.level}</span>` : ''}
      </div>
      <div class="pp-dice" id="dice-${p.i}"></div>
    </div>
  `;
}

export function mountGame(el, params) {
  const { mode = 'bot', numPlayers = 4 } = params;
  scene.enterGameMode();

  myPlayerIndex = params.playerIndex || 0;
  matchId = params.matchId || null;
  isHost = myPlayerIndex === 0;

  if (mode === 'live' && params.players) {
    PLAYER_NAMES = params.players.map((p, i) => {
      if (i === myPlayerIndex) return 'أنت';
      if (p.startsWith('bot')) return 'Bot ' + p.split('_')[1];
      return 'لاعب ' + (i + 1);
    });
  } else {
    PLAYER_NAMES = ['أنت', 'Bot 1', 'Bot 2', 'Bot 3'];
  }

  game = rules.createGame(numPlayers);
  game.mode = mode;
  game.startTime = Date.now();
  game.turboMode = false;
  game.turnCount = 0;
  game.humanPlayers = PLAYER_NAMES.map((n, i) => n.startsWith('Bot') ? -1 : i).filter(i => i >= 0);
  validMoves = [];
  diceAnimating = false;
  lastSyncTurnCount = 0;
  if (livePoller) { clearInterval(livePoller); livePoller = null; }

  const player = store.get('player') || {};
  const panels = [0,1,2,3].map(i => {
    const isMe = i === myPlayerIndex;
    const isBot = PLAYER_NAMES[i].startsWith('Bot');
    const avatar = isMe && player.avatar_url ? `<img src="${player.avatar_url}" style="width:100%;height:100%;object-fit:cover;border-radius:50%;">` : isBot ? '🤖' : '👤';
    const name = isMe ? (player.display_name || player.username || 'أنت') : PLAYER_NAMES[i];
    const level = isMe ? `Lv.${player.level || 1}` : (isBot ? '' : '');
    return { i, avatar, name, level, color: COLORS[i] };
  });

  el.innerHTML = `
    <div style="display:flex;flex-direction:column;height:100%;background:linear-gradient(180deg,#0d0d1a 0%,#1a1a2e 50%,#0d0d1a 100%);justify-content:flex-end;">
      <div style="display:flex;justify-content:space-between;padding:8px 12px;background:rgba(15,15,30,0.9);border-bottom:1px solid rgba(255,255,255,0.04);direction:ltr;">
        ${renderPanel(panels[1])}
        ${renderPanel(panels[2])}
      </div>
      <div id="ludo-wrap" style="flex:1;display:flex;align-items:center;justify-content:center;padding:6px;min-height:0;"></div>
      <div style="display:flex;justify-content:space-between;padding:8px 12px;background:rgba(15,15,30,0.9);border-top:1px solid rgba(255,255,255,0.04);direction:ltr;">
        ${renderPanel(panels[0])}
        ${renderPanel(panels[3])}
      </div>
      <div id="turn-timer-bar" style="height:4px;background:rgba(255,255,255,0.05);position:relative;overflow:hidden;"><div id="turn-timer-fill" style="position:absolute;inset:0;background:linear-gradient(90deg,#4ade80,#E4AC38,#EF4444);transform:scaleX(1);transform-origin:left;transition:none;"></div></div>
      <div id="dice-area" style="display:flex;align-items:center;gap:12px;padding:14px 16px;background:linear-gradient(180deg,#12122a,#0a0a1a);border-top:1px solid rgba(228,172,56,0.15);justify-content:center;padding-bottom:max(14px, env(safe-area-inset-bottom, 0px));position:relative;overflow:hidden;">
        <div style="position:absolute;inset:0;background:radial-gradient(ellipse at 50% 0%,rgba(228,172,56,0.06) 0%,transparent 70%);pointer-events:none;"></div>
        <button class="btn btn-secondary" id="exit-btn" style="min-height:44px;min-width:44px;padding:0;font-size:13px;color:#EF4444;border-radius:50%;background:rgba(239,68,68,0.08);border:1px solid rgba(239,68,68,0.2);">✕</button>
        <div id="dice-box" style="width:56px;height:56px;background:linear-gradient(145deg,#ffffff,#f0ede8);border-radius:12px;display:grid;grid-template:repeat(3,1fr)/repeat(3,1fr);padding:7px;box-shadow:0 4px 12px rgba(0,0,0,0.4),inset 0 2px 0 rgba(255,255,255,0.9),0 0 0 2px rgba(228,172,56,0.15);transition:transform 0.15s cubic-bezier(0.34,1.56,0.64,1);">
        </div>
        <button class="btn btn-primary" id="roll-btn" style="font-size:15px;padding:14px 32px;min-height:52px;border-radius:14px;background:linear-gradient(135deg,#E4AC38,#F59E0B);box-shadow:0 4px 14px rgba(228,172,56,0.3);font-weight:800;" disabled>ارمِ النرد</button>
      </div>
    </div>
    <style>
      .pp{display:flex;align-items:center;gap:6px;padding:6px 10px;border-radius:10px;border:2px solid transparent;transition:all 0.3s cubic-bezier(0.34,1.56,0.64,1);position:relative;}
      .pp.active{border-color:var(--pc);background:rgba(255,255,255,0.06);animation:panelPulse 2s ease-in-out infinite;box-shadow:0 0 12px color-mix(in srgb, var(--pc) 20%, transparent);}
      .pp-turn-arrow{position:absolute;left:-2px;top:50%;transform:translateY(-50%) scale(0);font-size:10px;color:var(--pc);transition:transform 0.3s cubic-bezier(0.34,1.56,0.64,1);filter:drop-shadow(0 0 4px var(--pc));}
      .pp.active .pp-turn-arrow{transform:translateY(-50%) scale(1);animation:arrowBounce 1s ease-in-out infinite;}
      .pp-name{transition:color 0.3s,text-shadow 0.3s;}
      .pp.active .pp-name{color:#fff !important;text-shadow:0 0 8px color-mix(in srgb, var(--pc) 50%, transparent);}
      .pp-dice{width:28px;height:28px;background:linear-gradient(145deg,#ffffff,#f0ede8);border-radius:6px;display:grid;grid-template:repeat(3,1fr)/repeat(3,1fr);padding:3px;box-shadow:0 2px 6px rgba(0,0,0,0.3);transition:all 0.2s cubic-bezier(0.34,1.56,0.64,1);opacity:0;transform:scale(0);margin-left:auto;}
      .pp-dice.visible{opacity:1;transform:scale(1);}
      .pp-dice.rolling{animation:diceShake 0.08s infinite alternate;opacity:1;transform:scale(1);}
      .pp-dice.landed{opacity:1;transform:scale(1.15);box-shadow:0 0 12px rgba(228,172,56,0.5);}
      .pp.active .pp-avatar{animation:avatarBounce 2s ease-in-out infinite;box-shadow:0 0 12px color-mix(in srgb, var(--pc) 40%, transparent);}
      @keyframes panelPulse{0%,100%{box-shadow:0 0 8px 0 transparent;}50%{box-shadow:0 0 16px 3px color-mix(in srgb, var(--pc) 25%, transparent);}}
      @keyframes avatarBounce{0%,100%{transform:scale(1);}50%{transform:scale(1.1);}}
      @keyframes arrowBounce{0%,100%{transform:translateY(-50%) scale(1) translateX(0);}50%{transform:translateY(-50%) scale(1) translateX(3px);}}
      @keyframes diceShake{from{transform:rotate(-15deg) scale(0.85);}to{transform:rotate(15deg) scale(0.85);}}
      @keyframes dicePopIn{0%{transform:scale(0) rotate(-30deg);opacity:0;}60%{transform:scale(1.3) rotate(5deg);opacity:1;}100%{transform:scale(1) rotate(0deg);opacity:1;}}
      @keyframes diceSix{0%,100%{box-shadow:0 0 8px rgba(228,172,56,0.4);}50%{box-shadow:0 0 20px rgba(228,172,56,0.9);}}
      @keyframes rollBtnPulse{0%,100%{box-shadow:0 4px 14px rgba(228,172,56,0.3);}50%{box-shadow:0 4px 24px rgba(228,172,56,0.6);}}
      #roll-btn:not([disabled]){animation:rollBtnPulse 2s ease-in-out infinite;}
    </style>
  `;

  const wrap = el.querySelector('#ludo-wrap');
  const availableH = wrap.clientHeight || 300;
  const availableW = wrap.clientWidth || 360;
  boardSize = Math.min(availableW - 8, availableH - 8, 420);
  cellSize = boardSize / 15;
  const { canvas: c, ctx: cx } = createCanvas(wrap, boardSize, boardSize);
  c.style.cssText = 'border-radius:6px;box-shadow:0 4px 16px rgba(0,0,0,0.4);max-width:100%;max-height:100%;';
  canvas = c; ctx = cx;

  // Create offscreen canvas for static board cache
  boardCanvas = document.createElement('canvas');
  boardCanvas.width = boardSize;
  boardCanvas.height = boardSize;
  boardDirty = true;
  pieceAnims = new Map();

  // Load themed pawn colors
  COLORS = [
    getColor('ludo_red', DEFAULT_COLORS[0]),
    getColor('ludo_green', DEFAULT_COLORS[1]),
    getColor('ludo_yellow', DEFAULT_COLORS[2]),
    getColor('ludo_blue', DEFAULT_COLORS[3]),
  ];
  COLORS_LIGHT = COLORS.map(c => lightenColor(c, 0.4));

  // Preload custom pawn sprites (non-blocking)
  PAWN_SLOTS.forEach((slot, i) => {
    const url = getAsset(slot);
    if (url) {
      const img = new Image();
      img.src = url;
      img.onload = () => { pawnImages[i] = img; };
    } else {
      pawnImages[i] = null;
    }
  });

  renderDiceFace(el.querySelector('#dice-box'), 1);
  for (let i = 0; i < 4; i++) {
    const md = el.querySelector(`#dice-${i}`);
    if (md) { renderMiniDice(md, 1); md.className = 'pp-dice'; }
  }
  drawBoard();
  startRenderLoop();
  startHeartbeat();
  updatePanels(el);
  el.querySelector('#roll-btn').addEventListener('click', () => handleRoll(el));
  el.querySelector('#exit-btn').addEventListener('click', () => handleExit(el));

  // Emotes + multiplayer
  const emoteWrap = el.querySelector('#ludo-wrap');
  emoteSystem.create(emoteWrap, (emote) => {
    audio.play('notification');
    // Animate from MY panel position to center
    const myPanel = el.querySelector(`#pp-${myPlayerIndex}`);
    emoteSystem.showReceived(emoteWrap, emote.emoji, myPanel);
    if (game.mode === 'live' && matchId) {
      mp.sendEmote(matchId, 'ludo', emote.key);
    }
  });

  // Live mode setup — full multiplayer integration
  if (mode === 'live' && matchId) {
    matchLive.start(matchId, 'ludo', {
      onMove: (data) => { mp.updateConnectionStatus(true); },
      onGameEnd: () => { if (!game.gameOver) { game.gameOver = true; scene.exitGameMode(); scene.replace('ludo-result', { result: 'loss' }); } }
    });
    mp.startDisconnectWatch(matchId, 'ludo', 60000);
    mp.onEmoteReceived((emote) => {
      // Animate from OPPONENT's panel position to center
      // Find which player sent it (not me)
      const senderIdx = emote.from === store.get('auth.userId') ? myPlayerIndex : (myPlayerIndex === 0 ? 1 : 0);
      const oppPanel = el.querySelector(`#pp-${senderIdx}`);
      emoteSystem.showReceived(emoteWrap, emote.key === 'gg' ? emoji('handshake', '🤝', 24) : emote.key === 'good_move' ? '👏' : '😮', oppPanel);
      audio.play('notification');
    });

    // Fetch opponent profiles and update player panels
    if (params.players) {
      params.players.forEach((pId, i) => {
        if (i === myPlayerIndex || pId.startsWith('bot')) return;
        mp.fetchOpponentProfile(pId).then(profile => {
          if (profile && !profile.error) {
            const panel = el.querySelector(`#pp-${i}`);
            if (panel) {
              const nameEl = panel.querySelector('span');
              if (nameEl) nameEl.textContent = profile.display_name || profile.username || 'لاعب';
              // Make panel tappable for friend add
              panel.style.cursor = 'pointer';
              panel.addEventListener('click', () => {
                audio.play('click');
                showOpponentPopup(el, profile);
              });
            }
          }
        });
      });
    }

    // If not my turn at start, begin polling
    if (!isMyTurn()) handleNonPlayerTurn(el);
  }

  bus.emit('game:started', { gameKey: 'ludo', mode });
}

function renderDiceFace(diceBox, value) {
  const diceUrl = getAsset('emoji_dice_' + value);
  if (diceUrl) {
    diceBox.innerHTML = `<img src="${diceUrl}" style="width:100%;height:100%;object-fit:contain;" draggable="false">`;
    diceBox.style.padding = '0';
    diceBox.style.display = 'flex';
    diceBox.style.gridTemplate = 'none';
    return;
  }
  diceBox.style.padding = '6px';
  diceBox.style.display = 'grid';
  diceBox.style.gridTemplate = 'repeat(3,1fr)/repeat(3,1fr)';
  const dots = {
    1: [0,0,0,0,1,0,0,0,0],
    2: [0,0,1,0,0,0,1,0,0],
    3: [0,0,1,0,1,0,1,0,0],
    4: [1,0,1,0,0,0,1,0,1],
    5: [1,0,1,0,1,0,1,0,1],
    6: [1,0,1,1,0,1,1,0,1],
  };
  const pattern = dots[value] || dots[1];
  diceBox.innerHTML = pattern.map(d =>
    `<div style="width:10px;height:10px;border-radius:50%;background:${d ? '#1a1a2e' : 'transparent'};margin:auto;transition:transform 0.1s;${d ? 'box-shadow:0 1px 2px rgba(0,0,0,0.3);' : ''}"></div>`
  ).join('');
}

function isMyTurn() {
  return game.currentPlayer === myPlayerIndex;
}

async function handleRoll(el) {
  if (diceAnimating || game.rolled || game.gameOver || !isMyTurn() || game.players[myPlayerIndex].finished) return;
  clearTurnTimer();
  diceAnimating = true;
  updatePanels(el);

  const dice = await animateDice(el, myPlayerIndex);
  game.diceValue = dice;
  game.rolled = true;
  diceAnimating = false;

  // Star burst on 6
  if (dice === 6) {
    const rect = el.querySelector('#dice-box').getBoundingClientRect();
    juice.starBurst(rect.left + 31, rect.top + 31, 8);
  }

  validMoves = rules.getValidMoves(game, myPlayerIndex, dice);
  if (validMoves.length === 0) {
    setTimeout(() => {
      game.rolled = false;
      rules.nextTurn(game);
      if (game.mode === 'live') syncLudoState(dice);
      updatePanels(el);
      drawBoard();
      if (!isMyTurn()) handleNonPlayerTurn(el);
    }, 800);
  } else if (validMoves.length === 1) {
    highlightMovablePieces(validMoves);
    setTimeout(() => doMove(el, validMoves[0]), 600);
  } else {
    highlightMovablePieces(validMoves);
    waitForPieceSelection(el, validMoves);
    startTurnTimer(el, () => {
      // Auto-pick best move on timeout
      if (selectionListener) { canvas.removeEventListener('click', selectionListener); selectionListener = null; }
      highlightedPieces = [];
      const best = rules.getBotMove(game, myPlayerIndex, dice);
      if (best) doMove(el, best);
    });
  }
}

function doMove(el, move) {
  animateMove(el, move);
}

function handleNonPlayerTurn(el) {
  if (game.mode === 'live') {
    const currentPlayerStr = PLAYER_NAMES[game.currentPlayer];
    if (currentPlayerStr && currentPlayerStr.startsWith('Bot') && isHost) {
      // Host runs bots locally and syncs result to server
      setTimeout(() => botLoop(el), 400);
    } else {
      // Non-host (or waiting for human opponent): poll for state updates
      startLudoPolling(el);
    }
  } else {
    // Single player — all non-player turns are bots
    setTimeout(() => botLoop(el), 400);
  }
}

async function botLoop(el) {
  if (game.gameOver || isMyTurn() || game.players[game.currentPlayer].finished) return;

  // === BOT PERSONALITY PROFILES ===
  const personalities = {
    1: { name: 'aggressive', thinkMin: 500, thinkMax: 1000, preferCapture: true },
    2: { name: 'casual', thinkMin: 1500, thinkMax: 3000, suboptimal: true },
    3: { name: 'strategic', thinkMin: 1000, thinkMax: 2000, optimal: true },
  };
  const personality = personalities[game.currentPlayer] || personalities[1];

  // === BOT HUMANIZATION: delays that mimic real player ===
  const turboMul = game.turboMode ? 0.4 : 1;
  const thinkDelay = (personality.thinkMin + Math.random() * (personality.thinkMax - personality.thinkMin)) * turboMul;

  // Show "thinking" indicator on the bot's player panel
  const botPanel = el.querySelector(`#pp-${game.currentPlayer}`);
  const botPanelSpan = botPanel ? botPanel.querySelector('span') : null;
  const originalPanelText = botPanelSpan ? botPanelSpan.textContent : '';
  if (botPanelSpan) botPanelSpan.textContent = 'يفكر...';

  await new Promise(r => setTimeout(r, thinkDelay));
  if (game.gameOver || isMyTurn()) { if (botPanelSpan) botPanelSpan.textContent = originalPanelText; return; }

  // 2. Roll dice with SAME animation as player
  if (botPanelSpan) botPanelSpan.textContent = originalPanelText;
  const dice = await animateDice(el, game.currentPlayer);

  // 3. "Deciding" which piece to move — personality-driven delay
  const decideDelay = (personality.thinkMin * 0.5 + Math.random() * (personality.thinkMax - personality.thinkMin) * 0.5) * turboMul;

  // Show thinking indicator again while deciding
  if (botPanelSpan) botPanelSpan.textContent = 'يفكر...';
  await new Promise(r => setTimeout(r, decideDelay));
  if (botPanelSpan) botPanelSpan.textContent = originalPanelText;
  if (game.gameOver || isMyTurn()) return;

  // 4. Execute move with step animation
  game.diceValue = dice;
  const move = rules.getBotMove(game, game.currentPlayer, dice);
  if (!move) {
    // No valid moves — just pass turn
    if (game.gameOver) {
      if (game.mode === 'live' && isHost) syncLudoState(dice);
      endGame(el);
      return;
    }
    rules.nextTurn(game);
    updatePanels(el);
    drawBoard();
    if (game.mode === 'live' && isHost) syncLudoState(dice);
    if (!isMyTurn()) handleNonPlayerTurn(el);
    return;
  }

  // Animate bot move step by step
  const pIdx = parseInt(move.pieceId.split('-')[0]);
  const pieceIdx = parseInt(move.pieceId.split('-')[1]);
  const piece = game.players[pIdx].pieces[pieceIdx];
  const fromPos = piece.pos;
  const toPos = move.to;

  if (move.type !== 'enter' && fromPos >= 0 && toPos > fromPos && toPos - fromPos <= 6) {
    const hopDuration = game.turboMode ? 120 : 200;
    const bounceHeight = cellSize * 0.6;
    for (let i = 1; i <= toPos - fromPos; i++) {
      piece.pos = fromPos + i;
      await tween(hopDuration, t => {
        const arc = -4 * bounceHeight * t * (t - 1);
        const squash = t > 0.8 ? (t - 0.8) * 5 * 2 : 0;
        piece._bounceOffset = -arc + squash;
      });
      piece._bounceOffset = 0;
      audio.play('move', 'game');
    }
    piece.pos = fromPos;
  }

  rules.applyMove(game, game.currentPlayer, move);

  if (move.type === 'capture') {
    const capturedMe = move._capturedPlayers && move._capturedPlayers.includes(myPlayerIndex);
    if (capturedMe) {
      audio.play('lose', 'game');
      juice.hapticError();
      juice.shake(el, 8, 400);
      juice.screenFlash('rgba(229,57,53,0.25)', 500);
    } else {
      audio.play('capture', 'game');
      juice.shake(el, 3, 150);
    }
  } else if (move.type === 'finish') {
    audio.play('notification');
    const boardRect = canvas.getBoundingClientRect();
    fireworkBurst(boardRect.left + boardRect.width / 2, boardRect.top + boardRect.height / 2, COLORS[game.currentPlayer]);
  }

  if (move.type === 'capture' && Math.random() > 0.5) {
    const emoteWrap = el.querySelector('#ludo-wrap');
    setTimeout(() => {
      const botEmotes = ['😂', '💪', '🎉', '😎'];
      const botPanel2 = el.querySelector(`#pp-${game.currentPlayer}`);
      emoteSystem.showReceived(emoteWrap, botEmotes[Math.floor(Math.random() * botEmotes.length)], botPanel2);
    }, 300);
  }

  if (game.gameOver) {
    if (game.mode === 'live' && isHost) syncLudoState(dice);
    endGame(el);
    return;
  }
  rules.nextTurn(game);
  updatePanels(el);
  drawBoard();

  if (game.mode === 'live' && isHost) syncLudoState(dice);

  if (!isMyTurn()) handleNonPlayerTurn(el);
}

// === LIVE SYNC ===
let lastSyncTurnCount = 0;

function syncLudoState(diceRolled = null) {
  if (!matchId) return;
  const turnCount = (game.turnCount || 0) + 1;
  game.turnCount = turnCount;
  const positions = game.players.map(p => ({
    pos: p.pieces.map(pc => pc.pos),
    finished: p.finished
  }));
  const payload = {
    action: 'move',
    match_id: matchId,
    positions,
    current_turn: game.currentPlayer,
    dice_value: game.diceValue,
    game_state: JSON.stringify({ turn_count: turnCount, dice_rolled: diceRolled }),
  };
  if (game.gameOver) {
    payload.status = 'completed';
    payload.winners = game.winners;
  }
  net.post('ludo-match.php', payload).catch(() => {});
}

function startLudoPolling(el) {
  if (livePoller) return;
  livePoller = setInterval(async () => {
    if (game.gameOver) { clearInterval(livePoller); livePoller = null; return; }
    if (isMyTurn()) { clearInterval(livePoller); livePoller = null; return; }
    try {
      const data = await net.post('ludo-match.php', { action: 'get', match_id: matchId });
      if (!data || data.error) return;

      // Server detected turn timeout — handle bot replacement
      if (data._turn_timed_out) {
        const timedOutPlayer = data._timeout_player;
        if (data._replace_with_bot && timedOutPlayer != null) {
          PLAYER_NAMES[timedOutPlayer] = 'Bot ' + timedOutPlayer;
        }
      }

      // Parse game_state to get turn_count
      let gs = {};
      try { gs = typeof data.game_state === 'string' ? JSON.parse(data.game_state) : (data.game_state || {}); } catch(e) {}
      const remoteTurnCount = gs.turn_count || 0;

      // Only apply if server state has advanced
      if (remoteTurnCount <= lastSyncTurnCount) return;
      lastSyncTurnCount = remoteTurnCount;

      // Apply remote state — positions, turn, dice
      const positions = typeof data.positions === 'string' ? JSON.parse(data.positions) : data.positions;
      if (positions) {
        positions.forEach((p, pIdx) => {
          if (p.pos) p.pos.forEach((pos, pieceIdx) => { game.players[pIdx].pieces[pieceIdx].pos = pos; });
          if (p.finished !== undefined) game.players[pIdx].finished = p.finished;
        });
      }
      game.currentPlayer = data.current_turn;
      game.diceValue = data.dice_value;
      game.turnCount = remoteTurnCount;

      // Show opponent's dice result briefly
      if (gs.dice_rolled != null) {
        const mainDice = el.querySelector('#dice-box');
        if (mainDice) renderDiceFace(mainDice, gs.dice_rolled);
      }

      // Check if game ended
      if (data.status === 'completed' && !game.gameOver) {
        game.gameOver = true;
        clearInterval(livePoller); livePoller = null;
        endGame(el);
        return;
      }

      updatePanels(el);
      drawBoard();

      // If it's now my turn, stop polling
      if (isMyTurn()) {
        clearInterval(livePoller); livePoller = null;
        audio.play('notification');
      } else {
        // If it's a bot's turn and I'm host, run it
        const currentPlayerStr = PLAYER_NAMES[game.currentPlayer];
        if (currentPlayerStr && currentPlayerStr.startsWith('Bot') && isHost) {
          clearInterval(livePoller); livePoller = null;
          setTimeout(() => botLoop(el), 400);
        }
      }
    } catch (e) {}
  }, 1500);
}

// Heartbeat: ping server every 10s to track connection state
function startHeartbeat() {
  if (!matchId || game.mode !== 'live') return;
  const hb = setInterval(() => {
    if (game.gameOver) { clearInterval(hb); return; }
    net.post('ludo-match.php', { action: 'heartbeat', match_id: matchId }).catch(() => {});
  }, 10000);
}

// ===== OPPONENT POPUP =====
function showOpponentPopup(el, profile) {
  const existing = document.getElementById('ludo-opp-popup');
  if (existing) { existing.remove(); return; }

  const popup = document.createElement('div');
  popup.id = 'ludo-opp-popup';
  popup.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:#1a1a2e;border:1px solid rgba(255,255,255,0.1);border-radius:16px;padding:20px;z-index:200;box-shadow:0 12px 40px rgba(0,0,0,0.7);text-align:center;min-width:200px;';
  popup.innerHTML = `
    <div style="width:48px;height:48px;border-radius:50%;background:#2a2a4a;margin:0 auto 8px;display:flex;align-items:center;justify-content:center;font-size:22px;">
      ${profile.avatar_url ? `<img src="${profile.avatar_url}" style="width:100%;height:100%;border-radius:50%;object-fit:cover;">` : emoji('person', '👤', 14)}
    </div>
    <div style="font-size:15px;font-weight:700;color:#f8fafc;margin-bottom:4px;">${profile.display_name || profile.username}</div>
    <div style="font-size:11px;color:#64748b;margin-bottom:14px;">Level ${profile.level || 1}</div>
    <div style="display:flex;gap:8px;justify-content:center;">
      <button id="opp-add-friend" style="padding:8px 16px;background:#2563EB;border:none;border-radius:8px;color:#fff;font-size:12px;font-weight:600;cursor:pointer;">➕ صديق</button>
      <button id="opp-close" style="padding:8px 16px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.08);border-radius:8px;color:#94a3b8;font-size:12px;cursor:pointer;">إغلاق</button>
    </div>
  `;
  document.body.appendChild(popup);

  popup.querySelector('#opp-close').addEventListener('click', () => popup.remove());
  popup.querySelector('#opp-add-friend').addEventListener('click', async () => {
    await mp.addFriendFromGame(profile.id);
    popup.querySelector('#opp-add-friend').textContent = '✓ تم';
    popup.querySelector('#opp-add-friend').style.background = '#34D399';
    juice.hapticLight();
    setTimeout(() => popup.remove(), 1000);
  });

  // Close on outside click
  setTimeout(() => {
    document.addEventListener('click', function close(e) {
      if (!popup.contains(e.target)) { popup.remove(); document.removeEventListener('click', close); }
    });
  }, 200);
}

// ===== PIECE SELECTION + ANIMATION =====
let highlightedPieces = [];
let selectionListener = null;

function highlightMovablePieces(moves) {
  highlightedPieces = moves.map(m => m.pieceId);
  drawBoard(); // Redraw with highlights
}

function waitForPieceSelection(el, moves) {
  // Listen for canvas tap to select a piece
  const handler = (e) => {
    const rect = canvas.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;
    const scaleX = boardSize / rect.width;
    const scaleY = boardSize / rect.height;
    const cx = x * scaleX;
    const cy = y * scaleY;

    // Find which movable piece was tapped
    for (const move of moves) {
      const pIdx = parseInt(move.pieceId.split('-')[0]);
      const pieceIdx = parseInt(move.pieceId.split('-')[1]);
      const piece = game.players[pIdx].pieces[pieceIdx];
      let pos;
      if (piece.pos === -1) pos = getHomeBasePosition(pIdx, pieceIdx, cellSize);
      else pos = getPiecePosition(piece.pos, pIdx, cellSize);
      if (!pos) continue;

      const dist = Math.sqrt((cx - pos.x) ** 2 + (cy - pos.y) ** 2);
      if (dist < cellSize * 0.6) {
        // Selected this piece!
        canvas.removeEventListener('click', handler);
        selectionListener = null;
        highlightedPieces = [];
        audio.play('click');
        juice.hapticLight();
        animateMove(el, move);
        return;
      }
    }
  };

  selectionListener = handler;
  canvas.addEventListener('click', handler);
}

// Smooth 60fps tween helper — runs a function with t=[0,1] each frame
function tween(duration, fn) {
  return new Promise(resolve => {
    const start = performance.now();
    function tick() {
      const elapsed = performance.now() - start;
      const t = Math.min(elapsed / duration, 1);
      fn(t);
      if (t < 1) requestAnimationFrame(tick);
      else resolve();
    }
    requestAnimationFrame(tick);
  });
}

// Easing functions
function easeOutCubic(t) { return 1 - Math.pow(1 - t, 3); }
function easeOutBounce(t) {
  if (t < 1/2.75) return 7.5625*t*t;
  if (t < 2/2.75) { t -= 1.5/2.75; return 7.5625*t*t + 0.75; }
  if (t < 2.5/2.75) { t -= 2.25/2.75; return 7.5625*t*t + 0.9375; }
  t -= 2.625/2.75; return 7.5625*t*t + 0.984375;
}

async function animateMove(el, move) {
  const pIdx = parseInt(move.pieceId.split('-')[0]);
  const pieceIdx = parseInt(move.pieceId.split('-')[1]);
  const piece = game.players[pIdx].pieces[pieceIdx];
  const fromPos = piece.pos;
  const toPos = move.to;

  if (move.type === 'enter') {
    rules.applyMove(game, game.currentPlayer, move);
    audio.play('move', 'game');
    juice.hapticMedium();
    // Smooth bounce-in from home
    await tween(300, t => {
      const et = easeOutBounce(t);
      piece._bounceOffset = -cellSize * 0.7 * (1 - et);
    });
    piece._bounceOffset = 0;
    // Landing burst
    const pos = getPiecePosition(0, pIdx, cellSize);
    if (pos) {
      const rect = canvas.getBoundingClientRect();
      const sx = rect.left + (pos.x / boardSize) * rect.width;
      const sy = rect.top + (pos.y / boardSize) * rect.height;
      juice.burst?.(sx, sy, { count: 6, colors: [COLORS[pIdx], '#fff'], size: 4, spread: 30, duration: 400 });
    }
    afterMove(el, move);
    return;
  }

  const steps = toPos - fromPos;
  if (steps <= 0 || steps > 6) {
    rules.applyMove(game, game.currentPlayer, move);
    afterMove(el, move);
    return;
  }

  // Smooth hop per step — each hop is a parabolic arc at 60fps
  const hopDuration = game.turboMode ? 120 : 200;
  const bounceHeight = cellSize * 0.6;
  for (let i = 1; i <= steps; i++) {
    piece.pos = fromPos + i;
    await tween(hopDuration, t => {
      // Parabolic arc: goes up then down
      const arc = -4 * bounceHeight * t * (t - 1);
      // Add slight squash on landing (last 20% of animation)
      const squash = t > 0.8 ? (t - 0.8) * 5 * 2 : 0;
      piece._bounceOffset = -arc + squash;
    });
    piece._bounceOffset = 0;
    audio.play('move', 'game');
  }

  // Landing effect on final square
  if (steps >= 3) {
    const pos = getPiecePosition(toPos, pIdx, cellSize);
    if (pos) {
      const rect = canvas.getBoundingClientRect();
      const sx = rect.left + (pos.x / boardSize) * rect.width;
      const sy = rect.top + (pos.y / boardSize) * rect.height;
      juice.burst?.(sx, sy, { count: 5, colors: [COLORS[pIdx], '#fff'], size: 4, spread: 25, duration: 350 });
    }
    juice.hapticLight();
  }

  // Apply via rules for capture logic
  piece.pos = fromPos;
  rules.applyMove(game, game.currentPlayer, move);
  afterMove(el, move);
}


function fireworkBurst(x, y, color) {
  const lighten = (hex, pct) => {
    const num = parseInt(hex.replace('#', ''), 16);
    const r = Math.min(255, ((num >> 16) & 0xFF) + pct);
    const g = Math.min(255, ((num >> 8) & 0xFF) + pct);
    const b = Math.min(255, (num & 0xFF) + pct);
    return `rgb(${r},${g},${b})`;
  };
  const colors = [color, lighten(color, 80), lighten(color, 140), '#fff'];
  // Wave 1 — fast tight burst
  juice.burst(x, y, { count: 16, colors, size: 7, spread: 90, duration: 700 });
  // Wave 2 — delayed wider burst
  setTimeout(() => juice.burst(x, y, { count: 20, colors, size: 9, spread: 160, duration: 1000, type: 'star' }), 200);
  // Wave 3 — sparkle trail
  setTimeout(() => juice.burst(x, y, { count: 10, colors: [color, '#fff'], size: 5, spread: 200, duration: 1200 }), 450);
}

function afterMove(el, move) {
  game.rolled = false;
  const mover = game.currentPlayer;

  if (move.type === 'capture') {
    if (mover === myPlayerIndex) {
      // I captured someone — celebrate!
      audio.play('capture', 'reward');
      juice.hapticSuccess();
      juice.confetti(window.innerWidth / 2, window.innerHeight / 2, 30);
      juice.starBurst(window.innerWidth / 2, window.innerHeight / 2, 10);
      juice.screenFlash('rgba(76,175,80,0.2)', 400);
    } else {
      // A bot/opponent captured — check if MY pawn was the victim
      const capturedMe = move._capturedPlayers && move._capturedPlayers.includes(myPlayerIndex);
      if (capturedMe) {
        audio.play('lose', 'game');
        juice.hapticError();
        juice.shake(el, 8, 400);
        juice.screenFlash('rgba(229,57,53,0.25)', 500);
      } else {
        audio.play('capture', 'game');
        juice.shake(el, 3, 150);
      }
    }
  } else if (move.type === 'finish') {
    if (mover === myPlayerIndex) {
      audio.play('win', 'reward');
      juice.hapticSuccess();
      const boardRect = canvas.getBoundingClientRect();
      fireworkBurst(boardRect.left + boardRect.width / 2, boardRect.top + boardRect.height / 2, COLORS[mover]);
      juice.screenFlash(COLORS[mover] + '22', 500);
    } else {
      audio.play('notification');
    }
    const panel = el.querySelector(`#pp-${mover}`);
    if (panel) panel.animate([{ background: 'rgba(76,175,80,0.3)' }, { background: 'transparent' }], { duration: 600 });
  }

  if (game.gameOver) {
    if (game.mode === 'live') syncLudoState(game.diceValue);
    endGame(el);
    return;
  }
  rules.nextTurn(game);
  updatePanels(el);
  drawBoard();

  if (game.mode === 'live') syncLudoState(game.diceValue);

  if (!isMyTurn()) handleNonPlayerTurn(el);
}
// ===== END PIECE SELECTION + ANIMATION =====

function startRenderLoop() {
  if (animFrame) return;
  const loop = () => {
    renderFrame();
    animFrame = requestAnimationFrame(loop);
  };
  animFrame = requestAnimationFrame(loop);
}

function stopRenderLoop() {
  if (animFrame) { cancelAnimationFrame(animFrame); animFrame = null; }
}

function renderFrame() {
  const cs = cellSize;
  if (!ctx || !boardCanvas) return;

  // Draw cached static board
  if (boardDirty) {
    drawStaticBoard();
    boardDirty = false;
  }
  clear(ctx, boardSize, boardSize);
  ctx.drawImage(boardCanvas, 0, 0);

  // === Ambient corner glow (animated, lightweight) ===
  if (game && !game.gameOver) {
    const cp = game.currentPlayer;
    const corners = [[0,boardSize],[0,0],[boardSize,0],[boardSize,boardSize]];
    const [gx, gy] = corners[cp];
    const ambGrad = ctx.createRadialGradient(gx, gy, 0, gx, gy, cs*5);
    ambGrad.addColorStop(0, COLORS[cp] + '20');
    ambGrad.addColorStop(1, 'transparent');
    ctx.fillStyle = ambGrad;
    ctx.fillRect(0, 0, boardSize, boardSize);
  }

  // Draw move preview ghosts
  if (highlightedPieces.length > 0 && validMoves && validMoves.length > 0) {
    const now = performance.now();
    const pulse = 0.2 + Math.sin(now * 0.004) * 0.1;
    validMoves.forEach(move => {
      const pIdx = parseInt(move.pieceId.split('-')[0]);
      const destPos = getPiecePosition(move.to, pIdx, cs);
      if (destPos) {
        const r = cs * 0.42;
        ctx.beginPath();
        ctx.arc(destPos.x, destPos.y, r, 0, Math.PI * 2);
        ctx.globalAlpha = pulse;
        ctx.fillStyle = COLORS[pIdx];
        ctx.fill();
        ctx.globalAlpha = 0.6;
        ctx.setLineDash([4, 3]);
        ctx.strokeStyle = COLORS[pIdx];
        ctx.lineWidth = 1.5;
        ctx.stroke();
        ctx.setLineDash([]);
        ctx.globalAlpha = 1.0;
      }
    });
  }

  // Draw pieces with smooth interpolation
  drawPieces(cs);
}

function drawBoard() {
  boardDirty = true;
}

function drawStaticBoard() {
  const cs = cellSize;
  const sctx = boardCanvas.getContext('2d');
  sctx.clearRect(0, 0, boardSize, boardSize);

  // Background
  sctx.fillStyle = '#FAFAFA';
  sctx.fillRect(0, 0, boardSize, boardSize);

  // Home zones
  [[0,0,9],[1,0,0],[2,9,0],[3,9,9]].forEach(([p,c,r]) => {
    sctx.fillStyle = COLORS[p];
    sctx.fillRect(c*cs, r*cs, 6*cs, 6*cs);
    sctx.fillStyle = '#FAFAFA';
    const ins = cs*0.85;
    sctx.fillRect(c*cs+ins, r*cs+ins, 6*cs-ins*2, 6*cs-ins*2);
    sctx.strokeStyle = COLORS[p];
    sctx.lineWidth = 2.5;
    sctx.strokeRect(c*cs+ins+3, r*cs+ins+3, 6*cs-ins*2-6, 6*cs-ins*2-6);
  });

  // Cross paths
  sctx.fillStyle = '#fff';
  sctx.fillRect(6*cs,0,3*cs,6*cs);
  sctx.fillRect(6*cs,9*cs,3*cs,6*cs);
  sctx.fillRect(0,6*cs,6*cs,3*cs);
  sctx.fillRect(9*cs,6*cs,6*cs,3*cs);
  sctx.fillRect(6*cs,6*cs,3*cs,3*cs);

  // Grid lines
  sctx.strokeStyle = '#ddd';
  sctx.lineWidth = 0.5;
  for (let i = 0; i <= 15; i++) {
    if (i >= 6 && i <= 9) { sctx.beginPath(); sctx.moveTo(i*cs,0); sctx.lineTo(i*cs,boardSize); sctx.stroke(); sctx.beginPath(); sctx.moveTo(0,i*cs); sctx.lineTo(boardSize,i*cs); sctx.stroke(); }
  }
  for (let i = 0; i <= 6; i++) {
    sctx.beginPath(); sctx.moveTo(6*cs,i*cs); sctx.lineTo(9*cs,i*cs); sctx.stroke();
    sctx.beginPath(); sctx.moveTo(6*cs,(i+9)*cs); sctx.lineTo(9*cs,(i+9)*cs); sctx.stroke();
    sctx.beginPath(); sctx.moveTo(i*cs,6*cs); sctx.lineTo(i*cs,9*cs); sctx.stroke();
    sctx.beginPath(); sctx.moveTo((i+9)*cs,6*cs); sctx.lineTo((i+9)*cs,9*cs); sctx.stroke();
  }

  // Home columns
  for (let p = 0; p < 4; p++) HOME_COLUMNS[p].forEach(([col,row]) => { sctx.fillStyle = COLORS_LIGHT[p]; sctx.fillRect(col*cs+1, row*cs+1, cs-2, cs-2); });

  // Safe squares
  SAFE_SQUARES.forEach(idx => { const [col,row] = SHARED_PATH[idx]; sctx.fillStyle = '#FFF9C4'; sctx.fillRect(col*cs+1,row*cs+1,cs-2,cs-2); sctx.fillStyle = '#F9A825'; sctx.font = `${cs*0.45}px sans-serif`; sctx.textAlign = 'center'; sctx.textBaseline = 'middle'; sctx.fillText('★', col*cs+cs/2, row*cs+cs/2); });

  // Center triangles
  const cx = 7.5*cs, cy = 7.5*cs, hs = 1.5*cs;
  sctx.fillStyle = COLORS[0]; sctx.beginPath();
  sctx.moveTo(cx-hs, cy+hs); sctx.lineTo(cx+hs, cy+hs); sctx.lineTo(cx, cy); sctx.closePath(); sctx.fill();
  sctx.fillStyle = COLORS[1]; sctx.beginPath();
  sctx.moveTo(cx-hs, cy-hs); sctx.lineTo(cx-hs, cy+hs); sctx.lineTo(cx, cy); sctx.closePath(); sctx.fill();
  sctx.fillStyle = COLORS[2]; sctx.beginPath();
  sctx.moveTo(cx-hs, cy-hs); sctx.lineTo(cx+hs, cy-hs); sctx.lineTo(cx, cy); sctx.closePath(); sctx.fill();
  sctx.fillStyle = COLORS[3]; sctx.beginPath();
  sctx.moveTo(cx+hs, cy-hs); sctx.lineTo(cx+hs, cy+hs); sctx.lineTo(cx, cy); sctx.closePath(); sctx.fill();
  sctx.strokeStyle = '#fff'; sctx.lineWidth = 1.5;
  sctx.strokeRect(cx-hs, cy-hs, hs*2, hs*2);

  // Start position squares
  const startColors = [[6,13,'#FFCDD2'],[1,6,'#C8E6C9'],[8,1,'#FFF9C4'],[13,8,'#BBDEFB']];
  startColors.forEach(([col,row,color]) => { sctx.fillStyle = color; sctx.fillRect(col*cs+1, row*cs+1, cs-2, cs-2); });
}

function drawPieces(cs) {
  // Group pieces by their cell position to handle stacking
  const cellOccupants = new Map();
  game.players.forEach((player, pIdx) => {
    player.pieces.forEach((piece, pieceIdx) => {
      if (piece.finished) return;
      let pos;
      if (piece.pos === -1) pos = getHomeBasePosition(pIdx, pieceIdx, cs);
      else pos = getPiecePosition(piece.pos, pIdx, cs);
      if (!pos) return;
      const key = `${Math.round(pos.x)},${Math.round(pos.y)}`;
      if (!cellOccupants.has(key)) cellOccupants.set(key, []);
      cellOccupants.get(key).push({ pIdx, pieceIdx, piece, pos });
    });
  });

  // Grid offsets for 1-4 pawns sharing a cell
  const STACK_OFFSETS = [
    [[0, 0]],
    [[-0.35, -0.3], [0.35, 0.3]],
    [[-0.35, -0.35], [0.35, -0.35], [0, 0.35]],
    [[-0.35, -0.35], [0.35, -0.35], [-0.35, 0.35], [0.35, 0.35]],
  ];

  cellOccupants.forEach((occupants) => {
    const count = occupants.length;
    const offsets = STACK_OFFSETS[Math.min(count, 4) - 1];
    const scale = count === 1 ? 1 : count === 2 ? 0.82 : 0.7;

    occupants.forEach((occ, idx) => {
      const { pIdx, pieceIdx, piece, pos } = occ;
      const pieceId = `${pIdx}-${pieceIdx}`;
      const isHighlighted = highlightedPieces.includes(pieceId);
      const r = cs * 0.42 * scale;
      const offset = offsets[idx % offsets.length];
      const ox = pos.x + offset[0] * cs;
      const oy = pos.y + offset[1] * cs;

      const bounceY = piece._bounceOffset || 0;
      const drawY = oy + bounceY;

      // Highlight ring (same for sprite or canvas pawn)
      if (isHighlighted) {
        ctx.beginPath(); ctx.arc(ox, drawY, r + 3, 0, Math.PI*2);
        ctx.strokeStyle = '#E4AC38'; ctx.lineWidth = 2.5; ctx.stroke();
        ctx.beginPath(); ctx.arc(ox, drawY, r + 6, 0, Math.PI*2);
        ctx.strokeStyle = 'rgba(228,172,56,0.3)'; ctx.lineWidth = 1.5; ctx.stroke();
      }
      // Shadow
      ctx.beginPath(); ctx.arc(ox, oy + 2, r, 0, Math.PI*2); ctx.fillStyle = 'rgba(0,0,0,0.2)'; ctx.fill();

      const spriteImg = pawnImages[pIdx];
      if (spriteImg) {
        const size = r * 2;
        ctx.drawImage(spriteImg, ox - size/2, drawY - size/2, size, size);
      } else {
        // Fallback: canvas-drawn pawn
        ctx.beginPath(); ctx.arc(ox, drawY - r*0.15, r*0.65, 0, Math.PI*2); ctx.fillStyle = COLORS[pIdx]; ctx.fill();
        ctx.beginPath(); ctx.ellipse(ox, drawY + r*0.4, r*0.8, r*0.4, 0, 0, Math.PI*2); ctx.fillStyle = COLORS[pIdx]; ctx.fill();
        ctx.beginPath(); ctx.arc(ox, drawY - r*0.15, r*0.65, 0, Math.PI*2); ctx.strokeStyle = '#fff'; ctx.lineWidth = 1.5; ctx.stroke();
        ctx.beginPath(); ctx.ellipse(ox, drawY + r*0.4, r*0.8, r*0.4, 0, 0, Math.PI*2); ctx.strokeStyle = '#fff'; ctx.lineWidth = 1; ctx.stroke();
        ctx.beginPath(); ctx.arc(ox - r*0.2, drawY - r*0.35, r*0.18, 0, Math.PI*2); ctx.fillStyle = 'rgba(255,255,255,0.5)'; ctx.fill();
      }
    });
  });
}

function checkTurboMode(el) {
  if (game.turboMode) return;
  const elapsed = (Date.now() - game.startTime) / 60000;
  if (elapsed >= 12) {
    game.turboMode = true;
    const banner = document.createElement('div');
    banner.style.cssText = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:rgba(228,172,56,0.95);color:#000;padding:10px 24px;border-radius:12px;font-size:14px;font-weight:700;z-index:100;';
    banner.textContent = '⚡ وضع السرعة!';
    el.querySelector('#ludo-wrap')?.appendChild(banner);
    banner.animate([{opacity:0,transform:'translate(-50%,-50%) scale(0.5)'},{opacity:1,transform:'translate(-50%,-50%) scale(1)'},{opacity:0,transform:'translate(-50%,-50%) translateY(-20px)'}], {duration:2000}).onfinish = () => banner.remove();
    juice.hapticHeavy();
  }
}

function updatePanels(el) {
  checkTurboMode(el);
  for (let i = 0; i < 4; i++) {
    const p = el.querySelector(`#pp-${i}`);
    if (p) {
      const wasActive = p.classList.contains('active');
      const isNowActive = i === game.currentPlayer;
      p.classList.toggle('active', isNowActive);
      if (game.players[i].finished) p.style.opacity = '0.5';
      if (wasActive && !isNowActive) {
        const md = el.querySelector(`#dice-${i}`);
        if (md) { setTimeout(() => { md.className = 'pp-dice'; md.style.animation = ''; }, 1200); }
      }
    }
  }
  const diceArea = el.querySelector('#dice-area');
  const btn = el.querySelector('#roll-btn');
  const diceBox = el.querySelector('#dice-box');
  const timerBar = el.querySelector('#turn-timer-bar');
  const myTurn = isMyTurn() && !game.players[myPlayerIndex].finished && !game.gameOver;
  if (diceArea) {
    diceArea.style.opacity = myTurn ? '1' : '0.3';
    diceArea.style.pointerEvents = myTurn ? 'auto' : 'none';
  }
  if (diceBox) diceBox.style.visibility = myTurn || diceAnimating ? 'visible' : 'hidden';
  if (btn) {
    const canRoll = myTurn && !game.rolled && !diceAnimating;
    btn.disabled = !canRoll;
    btn.style.opacity = canRoll ? '1' : '0.4';
    btn.style.visibility = myTurn ? 'visible' : 'hidden';
    if (canRoll && !turnTimer) {
      startTurnTimer(el, () => handleRoll(el));
    }
  }
  if (timerBar) timerBar.style.opacity = myTurn ? '1' : '0.3';
  if (!myTurn) clearTurnTimer();
}

function startTurnTimer(el, onTimeout) {
  clearTurnTimer();
  turnTimerStart = performance.now();
  const fill = el.querySelector('#turn-timer-fill');
  if (fill) { fill.style.transition = 'none'; fill.style.transform = 'scaleX(1)'; }
  const timerBar = el.querySelector('#turn-timer-bar');
  if (timerBar) timerBar.style.opacity = '1';

  function tick() {
    const elapsed = performance.now() - turnTimerStart;
    const pct = Math.max(0, 1 - elapsed / TURN_TIMEOUT);
    if (fill) fill.style.transform = `scaleX(${pct})`;
    if (pct <= 0) {
      clearTurnTimer();
      onTimeout();
      return;
    }
    turnTimer = requestAnimationFrame(tick);
  }
  turnTimer = requestAnimationFrame(tick);
}

function clearTurnTimer() {
  if (turnTimer) { cancelAnimationFrame(turnTimer); turnTimer = null; }
}

function animateDice(el, playerIdx) {
  return new Promise(resolve => {
    const mainDice = el.querySelector('#dice-box');
    const miniDice = el.querySelector(`#dice-${playerIdx}`);
    const isMe = playerIdx === myPlayerIndex;

    // Show mini dice in rolling state
    if (miniDice) {
      miniDice.className = 'pp-dice rolling';
      renderMiniDice(miniDice, 1);
    }

    // Show main dice for everyone (not just self)
    if (mainDice) {
      mainDice.style.visibility = 'visible';
      mainDice.style.opacity = '1';
    }

    let count = 0;
    const maxFrames = game.turboMode ? 8 : 14;
    const shakeAnim = setInterval(() => {
      const randVal = Math.floor(Math.random() * 6) + 1;
      renderDiceFace(mainDice, randVal);
      if (miniDice) renderMiniDice(miniDice, randVal);
      const rx = (Math.random() - 0.5) * 16;
      const ry = (Math.random() - 0.5) * 16;
      const rot = (Math.random() - 0.5) * 30;
      mainDice.style.transform = `translate(${rx}px,${ry}px) rotate(${rot}deg) scale(0.9)`;
      count++;
      if (count > maxFrames) {
        clearInterval(shakeAnim);
        const dice = rules.rollDice(game, game.currentPlayer);
        game.diceValue = dice;
        renderDiceFace(mainDice, dice);
        if (miniDice) renderMiniDice(miniDice, dice);

        // Pop-in landing on main dice
        mainDice.style.transform = 'scale(1.3)';
        setTimeout(() => { mainDice.style.transform = 'scale(1)'; }, 180);

        // Mini dice lands with pop
        if (miniDice) {
          miniDice.className = 'pp-dice landed';
          miniDice.style.animation = 'dicePopIn 0.35s cubic-bezier(0.34,1.56,0.64,1)';
          if (dice === 6) miniDice.style.animation += ', diceSix 1s ease-in-out infinite';
        }

        audio.play('dice', 'game');
        juice.hapticHeavy();
        juice.shake(el.querySelector(isMe ? '#dice-area' : `#pp-${playerIdx}`), 4, 200);

        if (dice === 6) {
          mainDice.style.boxShadow = '0 0 20px #E4AC38, 0 4px 14px rgba(0,0,0,0.4)';
          setTimeout(() => { mainDice.style.boxShadow = '0 4px 14px rgba(0,0,0,0.4),inset 0 1px 0 rgba(255,255,255,0.8)'; }, 800);
          juice.hapticSuccess();
          // Flash the player panel gold on a 6
          const panel = el.querySelector(`#pp-${playerIdx}`);
          if (panel) panel.animate([{background:'rgba(228,172,56,0.3)'},{background:'transparent'}], {duration:600});
        }

        resolve(dice);
      }
    }, 55);
  });
}

function renderMiniDice(miniDice, value) {
  const dots = { 1:[0,0,0,0,1,0,0,0,0], 2:[0,0,1,0,0,0,1,0,0], 3:[0,0,1,0,1,0,1,0,0], 4:[1,0,1,0,0,0,1,0,1], 5:[1,0,1,0,1,0,1,0,1], 6:[1,0,1,1,0,1,1,0,1] };
  const pattern = dots[value] || dots[1];
  miniDice.style.display = 'grid';
  miniDice.style.gridTemplate = 'repeat(3,1fr)/repeat(3,1fr)';
  miniDice.style.padding = '3px';
  miniDice.innerHTML = pattern.map(d =>
    `<div style="width:5px;height:5px;border-radius:50%;background:${d ? '#1a1a2e' : 'transparent'};margin:auto;${d ? 'box-shadow:0 1px 1px rgba(0,0,0,0.2);' : ''}"></div>`
  ).join('');
}

async function handleExit(el) {
  if (game.gameOver) return;
  const confirmed = await modal.confirm('هل تريد الخروج من المباراة؟', {
    title: 'مغادرة',
    icon: '🚪',
    confirmText: 'نعم، اخرج',
    cancelText: 'ابقَ',
    danger: true
  });
  if (!confirmed) return;

  game.gameOver = true;
  clearTurnTimer();
  stopRenderLoop();
  audio.play('click');

  if (game.mode === 'live' && matchId) {
    net.post('ludo-match.php', { action: 'leave', match_id: matchId, player_index: myPlayerIndex }).catch(() => {});
    mp.stopDisconnectWatch();
    if (livePoller) { clearInterval(livePoller); livePoller = null; }
  }

  scene.exitGameMode();
  scene.replace('ludo-result', { result: 'loss', resigned: true });
  bus.emit('game:ended', { gameKey: 'ludo', result: 'loss', resigned: true });
}

function endGame(el) {
  game.gameOver = true;
  clearTurnTimer();
  stopRenderLoop();
  if (matchId) matchLive.session?.destroy?.();

  const myRank = game.winners.indexOf(myPlayerIndex);
  const isLoser = myRank === 3 || myRank === -1;
  const result = isLoser ? 'loss' : 'win';
  const place = myRank >= 0 ? myRank + 1 : 4;

  // Build ordered player IDs for rewards (1st → last)
  const players = game.humanPlayers ? PLAYER_NAMES : ['bot_0','bot_1','bot_2','bot_3'];
  const orderedWinners = game.winners.map(idx => {
    const name = PLAYER_NAMES[idx];
    if (name && name.startsWith('Bot')) return 'bot_' + idx;
    return store.get('player')?.id || 'unknown';
  });

  // Call server for rewards (fire and forget for bots, await for live)
  if (matchId || game.mode === 'live') {
    net.post('ludo-match.php', {
      action: 'complete',
      match_id: matchId,
      winners: orderedWinners,
      reason: 'normal'
    }).then(data => {
      if (data?.rewards) bus.emit('rewards:received', data.rewards);
    }).catch(() => {});
  }

  if (!isLoser) {
    juice.confetti(window.innerWidth / 2, window.innerHeight / 3, 50);
    juice.starBurst(window.innerWidth / 2, window.innerHeight / 3, 15);
    juice.hapticSuccess();
    audio.play('win', 'reward');
    juice.screenFlash('rgba(76,175,80,0.12)', 600);
    if (place === 1) {
      fireworkBurst(window.innerWidth / 2, window.innerHeight / 3, COLORS[myPlayerIndex]);
    }
  } else {
    audio.play('lose', 'game');
    juice.hapticError();
    juice.screenFlash('rgba(229,57,53,0.12)', 500);
  }

  setTimeout(() => {
    scene.exitGameMode();
    scene.replace('ludo-result', { result, place, winners: game.winners });
    bus.emit('game:ended', { gameKey: 'ludo', result, place });
  }, 1500);
}
