import * as bus from '../../../core/bus.js';
import * as scene from '../../../core/scene.js';
import * as audio from '../../../core/audio.js';
import * as juice from '../../../core/juice.js';
import * as net from '../../../core/net.js';
import * as store from '../../../core/store.js';
import * as matchLive from '../../../core/match-live.js';
import * as mp from '../../../core/multiplayer.js';
import { t } from '../../../core/i18n.js';
import { emoji } from '../../../core/theme.js';
import * as modal from '../../../core/modal.js';
import * as rules from '../logic/rules.js';
import * as bot from '../logic/bot.js';
import { DominoBoard } from '../canvas/board.js';
import { DominoHand } from '../components/hand.js';
import { DominoDrag } from '../components/drag.js';

let state, board, hand, drag, liveSession;
let botTimeout = null;
let autoPassTimeout = null;
let drawInFlight = false;

export function mountGame(el, params) {
  const { mode = 'bot', matchId, playerIndex = 0, players, botLevel = 'intermediate', targetScore } = params;
  scene.enterGameMode();

  state = createInitialState(mode, matchId, playerIndex, botLevel, players, targetScore);

  if (mode !== 'live') {
    dealNewRound();
  }

  el.innerHTML = buildLayout(mode);

  const boardContainer = el.querySelector('#domino-board');
  board = new DominoBoard(boardContainer, {
    onEndpointTap: (hit) => handleEndpointTap(el, hit)
  });
  board.setChain(state.chain);

  const handContainer = el.querySelector('#domino-hand-area');
  hand = new DominoHand(handContainer, {
    onTileSelect: (tile) => handleTileSelect(el, tile),
    onDragStart: (tile, info) => handleDragStart(el, tile, info)
  });

  drag = new DominoDrag({
    board,
    onPlace: (tile, end) => executePlacement(el, tile, end),
    onCancel: () => { hand.clearSelection(); state.selectedTile = null; },
    getEnds: () => ({ leftEnd: state.leftEnd, rightEnd: state.rightEnd })
  });

  injectStyles(el);

  updateUI(el);
  refreshHand();

  el.querySelector('#btn-draw').addEventListener('click', () => drawFromBoneyard(el));
  el.querySelector('#btn-pass').addEventListener('click', () => passTurn(el));
  el.querySelector('#btn-resign')?.addEventListener('click', () => confirmResign(el));
  el.querySelector('#btn-emote')?.addEventListener('click', () => showEmoteMenu(el));

  if (mode === 'bot') {
    createServerRecord();
    if (state.currentPlayer !== state.myPlayerIndex) {
      scheduleBotTurn(el);
    }
  } else if (mode === 'live' && matchId) {
    setupLiveMultiplayer(el, matchId);
  }

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

function createInitialState(mode, matchId, playerIndex, botLevel, players, targetScore) {
  return {
    mode, matchId, myPlayerIndex: playerIndex,
    players: players || [],
    hands: [[], []], boneyard: [], chain: [],
    leftEnd: null, rightEnd: null,
    currentPlayer: 0, numPlayers: 2,
    matchScores: [0, 0], roundNumber: 1, targetScore: targetScore || 100,
    selectedTile: null, gameOver: false, matchOver: false,
    moveCount: 0, lastSyncMoveCount: 0,
    botPersonality: bot.getPersonality(botLevel),
    botThinking: false,
    lastEmoteHandled: 0,
    lastDrawOfferHandled: 0,
  };
}

function dealNewRound() {
  const allTiles = rules.shuffle(rules.createDominoSet());
  const { hands, boneyard } = rules.dealHands(allTiles, state.numPlayers);
  state.hands = hands;
  state.boneyard = boneyard;
  state.chain = [];
  state.leftEnd = null;
  state.rightEnd = null;
  state.gameOver = false;
  state.selectedTile = null;
  state.tensionReset = true;
}

function buildLayout(mode) {
  const isLive = mode === 'live';
  return `
    <div id="domino-wrap" class="dg-wrap">
      <!-- Opponent zone (top) -->
      <div id="domino-opp-bar" class="dg-opp-bar">
        <div class="dg-opp-left">
          <div id="opp-avatar" class="dg-avatar">${isLive ? emoji('person', '👤', 17) : emoji('robot', '🤖', 17)}</div>
          <div class="dg-opp-info">
            <div id="opp-name" class="dg-opp-name">${isLive ? t('common.opponent') : t('game.bot')}</div>
            <div class="dg-opp-meta">
              <span id="opp-count" class="dg-opp-count">${t('common.pieces', { n: 7 })}</span>
              <span id="bot-thinking" class="dg-thinking">${t('game.thinking')}<span class="dg-dots"></span></span>
            </div>
          </div>
        </div>
        <div class="dg-opp-right">
          ${isLive ? '<div id="conn-dot" class="dg-conn-dot"></div>' : ''}
          <div id="boneyard-count" class="dg-boneyard-badge">
            <span class="dg-boneyard-icon">${emoji('boneyard', '📦', 13)}</span>
            <span id="boneyard-num">14</span>
          </div>
        </div>
      </div>

      <!-- Opponent back-tiles (visual only) -->
      <div id="opp-hand-visual" class="dg-opp-hand"></div>

      <!-- Emote display -->
      <div id="emote-display" class="dg-emote-area"></div>

      <!-- Canvas board -->
      <div id="domino-board" class="dg-board"></div>

      <!-- Score + Turn status ribbon -->
      <div class="dg-status-ribbon">
        <div class="dg-score-section">
          <div class="dg-score-me">
            <span class="dg-score-label">${t('common.you')}</span>
            <span class="dg-score-value" id="my-score">0</span>
          </div>
          <div class="dg-score-divider"></div>
          <div class="dg-score-target">
            <span id="round-num">1</span>/<span class="dg-target-val">${state.targetScore}</span>
          </div>
          <div class="dg-score-divider"></div>
          <div class="dg-score-opp">
            <span class="dg-score-label">${t('common.opponent')}</span>
            <span class="dg-score-value" id="opp-score">0</span>
          </div>
        </div>
        <div id="turn-status" class="dg-turn-status">${t('game.your_turn')}</div>
      </div>

      <!-- Player hand -->
      <div id="domino-hand-area" class="dg-hand-area"></div>

      <!-- Controls -->
      <div id="domino-controls" class="dg-controls">
        <button class="dg-ctrl-btn dg-btn-resign" id="btn-resign">${t('game.resign')}</button>
        <button class="dg-ctrl-btn dg-btn-emote" id="btn-emote">${emoji('smiley', '😄', 18)}</button>
        <button class="dg-ctrl-btn dg-btn-draw" id="btn-draw">${t('domino.draw_boneyard')}</button>
        <button class="dg-ctrl-btn dg-btn-pass" id="btn-pass" style="display:none;">${t('domino.pass')}</button>
      </div>

    </div>
  `;
}

function injectStyles(el) {
  const style = document.createElement('style');
  style.textContent = (hand?.getStyle() || '') + (drag?.getStyle() || '') + `
    /* ═══ Layout ═══ */
    .dg-wrap {
      display:flex;flex-direction:column;height:100%;
      background:linear-gradient(180deg,#060a10 0%,var(--bg-inset) 50%,#0c1828 100%);
      position:relative;overflow:hidden;
    }

    /* ═══ Opponent Bar ═══ */
    .dg-opp-bar {
      display:flex;align-items:center;justify-content:space-between;
      padding:10px 14px;
      background:rgba(12,18,30,0.92);
      border-bottom:1px solid rgba(255,255,255,0.04);
      z-index:5;
    }
    .dg-opp-left { display:flex;align-items:center;gap:10px; }
    .dg-avatar {
      width:38px;height:38px;border-radius:50%;
      background:linear-gradient(135deg,#1a2844,#0f1a30);
      border:2px solid rgba(228,172,56,0.25);
      display:flex;align-items:center;justify-content:center;font-size:17px;
      box-shadow:0 2px 8px rgba(0,0,0,0.3);
    }
    .dg-opp-info { display:flex;flex-direction:column;gap:2px; }
    .dg-opp-name { font-size:14px;font-weight:700;color:var(--text-primary); }
    .dg-opp-meta { display:flex;align-items:center;gap:6px; }
    .dg-opp-count { font-size:11px;color:var(--text-secondary); }
    .dg-thinking { font-size:11px;color:var(--gold);display:none; }
    .dg-dots::after { content:'...';animation:dotsAnim 1.4s steps(3) infinite; }
    .dg-opp-right { display:flex;align-items:center;gap:8px; }
    .dg-conn-dot { width:8px;height:8px;border-radius:50%;background:var(--green-light);box-shadow:0 0 6px rgba(74,222,128,0.5); }
    .dg-boneyard-badge {
      display:flex;align-items:center;gap:5px;
      font-size:12px;font-weight:700;color:var(--gold);
      background:rgba(228,172,56,0.06);
      padding:5px 10px;border-radius:10px;
      border:1px solid rgba(228,172,56,0.1);
    }
    .dg-boneyard-icon { font-size:13px; }

    /* ═══ Opponent back-tiles ═══ */
    .dg-opp-hand {
      display:flex;align-items:center;justify-content:center;gap:3px;
      padding:6px 12px;min-height:32px;
      overflow:hidden;
    }
    .dg-opp-tile {
      width:16px;height:26px;
      background:linear-gradient(135deg,#2a3a5a,#1a2844);
      border:1px solid rgba(255,255,255,0.08);
      border-radius:3px;
      box-shadow:0 1px 3px rgba(0,0,0,0.3);
      transition:transform 0.3s cubic-bezier(0.34,1.56,0.64,1), opacity 0.3s;
    }

    /* ═══ Board ═══ */
    .dg-board { flex:1;min-height:0;position:relative; }

    /* ═══ Status Ribbon ═══ */
    .dg-status-ribbon {
      display:flex;align-items:center;justify-content:space-between;
      padding:8px 14px;
      background:rgba(12,18,30,0.92);
      border-top:1px solid rgba(255,255,255,0.03);
      border-bottom:1px solid rgba(255,255,255,0.03);
    }
    .dg-score-section {
      display:flex;align-items:center;gap:10px;
    }
    .dg-score-me, .dg-score-opp {
      display:flex;flex-direction:column;align-items:center;gap:1px;
    }
    .dg-score-label { font-size:10px;color:var(--text-muted);font-weight:500; }
    .dg-score-me .dg-score-value { font-size:16px;font-weight:800;color:var(--green-light); }
    .dg-score-opp .dg-score-value { font-size:16px;font-weight:800;color:var(--text-secondary); }
    .dg-score-divider { width:1px;height:24px;background:var(--border); }
    .dg-score-target {
      font-size:11px;color:var(--text-muted);
      display:flex;align-items:center;gap:2px;
    }
    .dg-target-val { color:var(--gold);font-weight:700; }
    .dg-turn-status {
      font-size:13px;font-weight:700;color:var(--gold);
      padding:4px 12px;border-radius:8px;
      background:rgba(228,172,56,0.06);
      border:1px solid rgba(228,172,56,0.1);
      transition:all 0.3s;
    }
    .dg-turn-status.dg-waiting {
      color:var(--text-muted);background:transparent;border-color:transparent;
    }

    /* ═══ Hand area ═══ */
    .dg-hand-area {
      /* Styled by DominoHand component */
    }

    /* ═══ Controls ═══ */
    .dg-controls {
      display:flex;gap:6px;
      padding:8px 12px;
      padding-bottom:calc(8px + env(safe-area-inset-bottom, 0px));
      background:rgba(8,14,24,0.95);
      border-top:1px solid rgba(255,255,255,0.03);
    }
    .dg-ctrl-btn {
      min-height:40px;border-radius:12px;border:none;
      font-size:12px;font-weight:600;cursor:pointer;
      transition:transform 0.15s cubic-bezier(0.34,1.56,0.64,1), opacity 0.2s;
    }
    .dg-ctrl-btn:active { transform:scale(0.93); }
    .dg-btn-resign {
      flex:0;padding:0 12px;
      background:rgba(239,68,68,0.06);border:1px solid rgba(239,68,68,0.12);
      color:var(--red-soft);font-size:11px;
    }
    .dg-btn-emote {
      flex:0;padding:0 14px;
      background:rgba(255,255,255,0.03);border:1px solid var(--border);
      font-size:18px;
    }
    .dg-btn-draw {
      flex:1;
      background:rgba(228,172,56,0.08);border:1px solid rgba(228,172,56,0.18);
      color:var(--gold);font-size:13px;font-weight:700;
    }
    .dg-btn-pass {
      flex:0;padding:0 16px;
      background:rgba(251,191,36,0.06);border:1px solid rgba(251,191,36,0.12);
      color:#fde68a;font-size:12px;
    }

    /* ═══ Emotes ═══ */
    .dg-emote-area {
      position:absolute;top:90px;left:50%;transform:translateX(-50%);
      z-index:50;pointer-events:none;
    }
    .emote-bubble {
      animation: emoteFloat 2.2s cubic-bezier(0.34,1.56,0.64,1) forwards;
      position:absolute;left:50%;font-size:40px;
    }

    /* ═══ Animations ═══ */
    @keyframes fadeIn { from{opacity:0;transform:translateY(8px)} to{opacity:1;transform:translateY(0)} }
    @keyframes emoteFloat {
      0%{transform:translateX(-50%) scale(0);opacity:0}
      15%{transform:translateX(-50%) scale(1.3);opacity:1}
      75%{opacity:1}
      100%{transform:translateX(-50%) translateY(-30px) scale(0.8);opacity:0}
    }
    @keyframes drawPulse {
      0%,100%{box-shadow:0 0 4px rgba(228,172,56,0.1);border-color:rgba(228,172,56,0.18)}
      50%{box-shadow:0 0 16px rgba(228,172,56,0.35);border-color:rgba(228,172,56,0.5)}
    }
    @keyframes dotsAnim { 0%{content:'.'} 33%{content:'..'} 66%{content:'...'} }
    @keyframes oppTileRemove {
      to { transform:translateY(10px) scale(0.6);opacity:0; }
    }
  `;
  el.appendChild(style);
}

// ═══════════════════════════════════════
// LIVE MULTIPLAYER
// ═══════════════════════════════════════

function setupLiveMultiplayer(el, matchId) {
  liveSession = matchLive.start(matchId, 'domino', {
    onMove: (data) => handleLivePollData(el, data),
    onGameEnd: (data) => {
      if (!state.gameOver && !state.matchOver) {
        endMatch(el, 'loss', 'abandon');
      }
    }
  });

  fetchOpponentProfile(el);
  startHeartbeat();

  if (state.myPlayerIndex === 0) {
    dealAndSyncToServer(el, matchId);
  } else {
    fetchInitialState(el, matchId);
  }
}

let heartbeatTimer = null;
function startHeartbeat() {
  if (heartbeatTimer) clearInterval(heartbeatTimer);
  heartbeatTimer = setInterval(() => {
    if (!state.matchId || state.matchOver) { clearInterval(heartbeatTimer); return; }
    net.post('domino-match.php', { action: 'heartbeat', match_id: state.matchId }).catch(() => {});
  }, 10000);
}

async function dealAndSyncToServer(el, matchId, retryCount = 0) {
  dealNewRound();
  board.setChain(state.chain);
  updateUI(el);
  refreshHand();

  try {
    await net.post('domino-match.php', {
      action: 'move',
      match_id: matchId,
      board: state.chain,
      boneyard: state.boneyard,
      hands: JSON.stringify({ '0': state.hands[0], '1': state.hands[1] }),
      current_turn: state.currentPlayer,
      game_state: JSON.stringify({
        move_count: 0,
        round: 1,
        left_end: state.leftEnd,
        right_end: state.rightEnd,
        scores: { '0': 0, '1': 0 },
        dealt: true,
      })
    });
  } catch (e) {
    console.error('[domino] deal sync failed:', e);
    if (retryCount < 1) {
      bus.emit('toast', { text: t('game.sync_error_retrying') || 'Sync error — retrying...', duration: 2000 });
      setTimeout(() => dealAndSyncToServer(el, matchId, retryCount + 1), 2000);
    } else {
      bus.emit('toast', { text: t('game.sync_failed') || 'Connection failed — returning to room.', duration: 3000 });
      setTimeout(() => {
        scene.exitGameMode();
        scene.replace('domino-room', { mode: 'menu' });
      }, 1500);
    }
  }
}

async function fetchOpponentProfile(el) {
  if (!state.matchId) return;
  try {
    const data = await net.post('domino-match.php', { action: 'get', match_id: state.matchId });
    if (data?.players) {
      const myId = store.get('auth.userId');
      const players = typeof data.players === 'string' ? JSON.parse(data.players) : data.players;
      const oppId = players.find(p => p !== myId);
      if (oppId) {
        try {
          const profile = await net.post('profile.php', { action: 'get', user_id: oppId });
          if (profile?.display_name) {
            const nameEl = el.querySelector('#opp-name');
            if (nameEl) nameEl.textContent = profile.display_name;
          }
        } catch (e) {}
      }
    }
  } catch (e) {}
}

async function fetchInitialState(el, matchId, retries = 0) {
  try {
    const data = await net.post('domino-match.php', { action: 'get', match_id: matchId });

    // If hands not dealt yet (host hasn't synced), retry
    if ((!data?.my_hand || data.my_hand.length === 0) && retries < 10) {
      setTimeout(() => fetchInitialState(el, matchId, retries + 1), 1000);
      return;
    }

    if (data && data.board) {
      const board_data = typeof data.board === 'string' ? JSON.parse(data.board) : data.board;
      if (board_data.length > 0) {
        state.chain = board_data;
        const ends = rules.getEnds(state.chain);
        state.leftEnd = ends.leftEnd;
        state.rightEnd = ends.rightEnd;
      }
    }
    if (data?.my_hand && data.my_hand.length > 0) {
      state.hands[state.myPlayerIndex] = data.my_hand;
    }
    if (data?.game_state) {
      const gs = typeof data.game_state === 'string' ? JSON.parse(data.game_state) : data.game_state;
      if (gs.scores) state.matchScores = [gs.scores['0'] || 0, gs.scores['1'] || 0];
      if (gs.round) state.roundNumber = gs.round;
      if (gs.move_count) { state.moveCount = gs.move_count; state.lastSyncMoveCount = gs.move_count; }
    }
    if (data?.current_turn !== undefined) state.currentPlayer = data.current_turn;
    if (data?.opponent_count !== undefined) {
      const oppIdx = 1 - state.myPlayerIndex;
      state.hands[oppIdx] = new Array(data.opponent_count).fill({ left: 0, right: 0, id: 'hidden' });
    }
    const boneyard = data?.boneyard ? (typeof data.boneyard === 'string' ? JSON.parse(data.boneyard) : data.boneyard) : [];
    state.boneyard = boneyard;

    board.setChain(state.chain);
    updateUI(el);
    refreshHand();
  } catch (e) {}
}

function handleLivePollData(el, data) {
  if (!data || data.error || state.matchOver) return;

  mp.updateConnectionStatus?.(true);
  const connDot = el.querySelector('#conn-dot');
  if (connDot) connDot.style.background = 'var(--green-light)';

  // Handle server-side turn timeout
  if (data._turn_timed_out) {
    const timedOutPlayer = data._timeout_player;
    if (timedOutPlayer !== state.myPlayerIndex) {
      // Opponent timed out — auto-pass their turn
      state.currentPlayer = state.myPlayerIndex;
      updateUI(el);
      refreshHand();
    }
  }

  const gs = data.game_state ? (typeof data.game_state === 'string' ? JSON.parse(data.game_state) : data.game_state) : {};
  const myId = store.get('auth.userId');

  checkEmote(el, gs, myId);

  if (data.status === 'completed' && !state.matchOver) {
    const resigned = gs.resigned_by;
    if (resigned && resigned !== myId) {
      endMatch(el, 'win', 'resign');
    } else if (resigned === myId) {
      // Already handled locally
    } else {
      const winnerId = data.winner_id;
      const result = winnerId === myId ? 'win' : 'loss';
      endMatch(el, result, 'completed');
    }
    return;
  }

  const remoteMoveCount = gs.move_count || 0;
  if (remoteMoveCount > state.lastSyncMoveCount) {
    state.lastSyncMoveCount = remoteMoveCount;
    state.moveCount = remoteMoveCount;

    const lastMove = gs.last_move;
    if (lastMove && lastMove.player !== state.myPlayerIndex) {
      applyOpponentMove(el, data, gs, lastMove);
    }
  }

  if (data.current_turn !== undefined) {
    state.currentPlayer = data.current_turn;
  }

  if (data.opponent_count !== undefined) {
    const oppIdx = 1 - state.myPlayerIndex;
    const oppEl = el.querySelector('#opp-count');
    if (oppEl) oppEl.textContent = t('common.pieces', { n: data.opponent_count });
  }

  if (gs.scores) {
    state.matchScores = [gs.scores['0'] || 0, gs.scores['1'] || 0];
  }
  if (gs.round && gs.round > state.roundNumber) {
    state.roundNumber = gs.round;
    handleRemoteNewRound(el, data, gs);
  }

  updateUI(el);
  refreshHand();
}

function applyOpponentMove(el, data, gs, lastMove) {
  const boardData = data.board ? (typeof data.board === 'string' ? JSON.parse(data.board) : data.board) : state.chain;
  state.chain = boardData;

  if (gs.left_end !== undefined) state.leftEnd = gs.left_end;
  if (gs.right_end !== undefined) state.rightEnd = gs.right_end;

  const boneyard = data.boneyard ? (typeof data.boneyard === 'string' ? JSON.parse(data.boneyard) : data.boneyard) : state.boneyard;
  state.boneyard = boneyard;

  board.animatePlacement({ left: 0, right: 0, id: lastMove.tile_id || 'opp' }, lastMove.end || 'right', () => {
    board.setChain(state.chain);
  }, 'opponent');

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

  if (data.my_hand) {
    state.hands[state.myPlayerIndex] = data.my_hand;
  }
}

function handleRemoteNewRound(el, data, gs) {
  state.gameOver = false;
  if (data.my_hand) state.hands[state.myPlayerIndex] = data.my_hand;

  const boardData = data.board ? (typeof data.board === 'string' ? JSON.parse(data.board) : data.board) : [];
  state.chain = boardData;
  const ends = rules.getEnds(state.chain);
  state.leftEnd = ends.leftEnd;
  state.rightEnd = ends.rightEnd;

  const boneyard = data.boneyard ? (typeof data.boneyard === 'string' ? JSON.parse(data.boneyard) : data.boneyard) : [];
  state.boneyard = boneyard;

  board.setChain(state.chain);
}

async function syncMoveToServer(tile, end) {
  if (state.mode !== 'live' || !state.matchId) return;
  try {
    await net.post('domino-match.php', {
      action: 'move',
      match_id: state.matchId,
      board: state.chain,
      boneyard: state.boneyard,
      hands: JSON.stringify({ [state.myPlayerIndex]: state.hands[state.myPlayerIndex] }),
      current_turn: state.currentPlayer,
      game_state: JSON.stringify({
        move_count: state.moveCount,
        last_move: { player: state.myPlayerIndex, tile_id: tile.id, end, t: Date.now() },
        left_end: state.leftEnd,
        right_end: state.rightEnd,
        scores: { '0': state.matchScores[0], '1': state.matchScores[1] },
        round: state.roundNumber,
      })
    });
  } catch (e) {}
}

async function syncDrawToServer() {
  if (state.mode !== 'live' || !state.matchId) return;
  try {
    await net.post('domino-match.php', {
      action: 'move',
      match_id: state.matchId,
      boneyard: state.boneyard,
      hands: JSON.stringify({ [state.myPlayerIndex]: state.hands[state.myPlayerIndex] }),
      current_turn: state.currentPlayer,
      game_state: JSON.stringify({
        move_count: state.moveCount,
        left_end: state.leftEnd,
        right_end: state.rightEnd,
      })
    });
  } catch (e) {}
}

async function syncPassToServer() {
  if (state.mode !== 'live' || !state.matchId) return;
  const pendingMoveCount = state.moveCount + 1;
  try {
    await net.post('domino-match.php', {
      action: 'move',
      match_id: state.matchId,
      current_turn: state.currentPlayer,
      game_state: JSON.stringify({
        move_count: pendingMoveCount,
        last_move: { player: state.myPlayerIndex, action: 'pass', t: Date.now() },
        left_end: state.leftEnd,
        right_end: state.rightEnd,
      })
    });
    // Only increment after successful server response
    state.moveCount = pendingMoveCount;
  } catch (e) {
    console.warn('[domino] syncPassToServer failed, moveCount not incremented:', e);
  }
}

async function syncRoundEndToServer(winnerIdx, roundPoints) {
  if (state.mode !== 'live' || !state.matchId) return;
  try {
    await net.post('domino-match.php', {
      action: 'move',
      match_id: state.matchId,
      board: state.chain,
      current_turn: state.currentPlayer,
      game_state: JSON.stringify({
        move_count: state.moveCount,
        scores: { '0': state.matchScores[0], '1': state.matchScores[1] },
        round: state.roundNumber,
        round_ended: true,
        round_winner: winnerIdx,
        round_points: roundPoints,
      })
    });
  } catch (e) {}
}

// ═══════════════════════════════════════
// EMOTES
// ═══════════════════════════════════════

function checkEmote(el, gs, myId) {
  if (!gs.emote) return;
  const emote = gs.emote;
  if (emote.from === myId) return;
  if (emote.t <= state.lastEmoteHandled) return;
  if (Date.now() - emote.t > 15000) return;

  state.lastEmoteHandled = emote.t;
  showEmoteBubble(el, emote.key);
}

function showEmoteBubble(el, emoteKey) {
  const emotes = { laugh: '😂', think: '🤔', wow: '😮', angry: '😡', gg: '👏', love: '❤️', fire: '🔥', cry: '😢' };
  const display = el.querySelector('#emote-display');
  if (!display) return;

  const bubble = document.createElement('div');
  bubble.className = 'emote-bubble';
  bubble.textContent = emotes[emoteKey] || '😄';
  display.appendChild(bubble);
  audio.play('notification');

  setTimeout(() => bubble.remove(), 2000);
}

function showEmoteMenu(el) {
  const emotes = [
    { key: 'laugh', icon: '😂' }, { key: 'think', icon: '🤔' },
    { key: 'wow', icon: '😮' }, { key: 'angry', icon: '😡' },
    { key: 'gg', icon: '👏' }, { key: 'love', icon: '❤️' },
    { key: 'fire', icon: '🔥' }, { key: 'cry', icon: '😢' }
  ];

  const existing = el.querySelector('#emote-menu');
  if (existing) { existing.remove(); return; }

  const menu = document.createElement('div');
  menu.id = 'emote-menu';
  menu.style.cssText = `
    position:absolute;bottom:110px;left:50%;transform:translateX(-50%);
    display:flex;gap:6px;padding:10px 14px;background:var(--bg-card);
    border-radius:16px;border:1px solid rgba(228,172,56,0.15);
    box-shadow:0 8px 24px rgba(0,0,0,0.6);z-index:60;
    animation:fadeIn 0.2s ease;
  `;
  menu.innerHTML = emotes.map(e => `
    <button data-emote="${e.key}" style="font-size:24px;background:none;border:none;cursor:pointer;padding:4px;border-radius:8px;transition:transform 0.1s;">
      ${e.icon}
    </button>
  `).join('');

  const wrap = el.querySelector('#domino-wrap');
  wrap.appendChild(menu);

  menu.querySelectorAll('button').forEach(btn => {
    btn.addEventListener('click', () => {
      const key = btn.dataset.emote;
      sendEmote(key);
      showEmoteBubble(el, key);
      menu.remove();
    });
  });

  setTimeout(() => menu.remove(), 5000);
}

async function sendEmote(key) {
  if (!state.matchId) return;
  const myId = store.get('auth.userId');
  try {
    await net.post('domino-match.php', {
      action: 'move',
      match_id: state.matchId,
      game_state: JSON.stringify({ emote: { key, from: myId, t: Date.now() } })
    });
  } catch (e) {}
}

// ═══════════════════════════════════════
// INTERACTION HANDLERS
// ═══════════════════════════════════════

function handleTileSelect(el, tile) {
  if (state.currentPlayer !== state.myPlayerIndex || state.gameOver) return;

  const playEnd = rules.determinePlayEnd(tile, state.leftEnd, state.rightEnd);
  if (!playEnd) return;

  // Deselect if same tile tapped again
  if (state.selectedTile?.id === tile.id) {
    state.selectedTile = null;
    hand.clearSelection();
    board.clearActiveEndpoint();
    updateUI(el);
    return;
  }

  // Select the tile and highlight valid placement locations
  state.selectedTile = tile;
  hand.setSelected(tile.id);

  // First tile on empty board → show center endpoint
  if (state.chain.length === 0) {
    board.setActiveEndpoint('both');
  } else {
    board.setActiveEndpoint(playEnd);
  }

  updateUI(el);
}

function handleEndpointTap(el, hit) {
  if (!state.selectedTile) return;
  const tile = state.selectedTile;

  // First tile on empty board — place on any tap
  if (state.chain.length === 0) {
    executePlacement(el, tile, 'right');
    return;
  }

  const canPlayOnEnd = tile.left === hit.value || tile.right === hit.value;
  if (canPlayOnEnd) {
    executePlacement(el, tile, hit.end);
  }
}

function handleDragStart(el, tile, info) {
  if (state.currentPlayer !== state.myPlayerIndex || state.gameOver) return;
  state.selectedTile = tile;
  hand.setSelected(tile.id);
  drag.start(tile, info);
}

// ═══════════════════════════════════════
// GAME LOGIC
// ═══════════════════════════════════════

function executePlacement(el, tile, end) {
  if (state.gameOver) return;

  // Validate tile actually exists in the player's hand
  const myHand = state.hands[state.myPlayerIndex];
  const handTile = myHand.find(t => t.id === tile.id);
  if (!handTile) return;

  // Use the hand's copy to prevent stale reference bugs
  const actualEnd = state.chain.length === 0 ? 'right' : end;
  const result = rules.placeTile(state.chain, handTile, actualEnd);
  state.chain = result.chain;
  state.leftEnd = result.leftEnd;
  state.rightEnd = result.rightEnd;

  state.hands[state.myPlayerIndex] = myHand.filter(t => t.id !== handTile.id);
  state.selectedTile = null;
  state.moveCount++;

  board.clearActiveEndpoint();
  hand.clearSelection();
  board.animatePlacement(tile, actualEnd, () => {
    board.setChain(state.chain);
  });

  audio.play('place', 'game');
  juice.hapticMedium?.();

  // Last tile fanfare
  if (state.hands[state.myPlayerIndex].length === 0) {
    juice.confetti(window.innerWidth / 2, window.innerHeight / 2, 20);
    juice.hapticSuccess?.();
  }

  syncMoveToServer(tile, actualEnd);

  if (checkRoundEnd(el)) return;
  nextTurn(el);
}

async function drawFromBoneyard(el) {
  if (state.drawing || drawInFlight) return;
  if (state.currentPlayer !== state.myPlayerIndex) return;
  if (state.boneyard.length === 0) return;
  if (state.gameOver) return;
  if (rules.hasValidMove(state.hands[state.myPlayerIndex], state.leftEnd, state.rightEnd)) return;

  state.drawing = true;
  drawInFlight = true;

  while (state.boneyard.length > 0 && !rules.hasValidMove(state.hands[state.myPlayerIndex], state.leftEnd, state.rightEnd)) {
    const tile = state.boneyard.pop();
    state.hands[state.myPlayerIndex].push(tile);
    audio.play('click');
    juice.hapticLight?.();
    updateUI(el);
    refreshHand();
    await new Promise(r => setTimeout(r, 400));
  }

  try {
    await syncDrawToServer();
  } finally {
    drawInFlight = false;
  }
  state.drawing = false;

  if (!rules.hasValidMove(state.hands[state.myPlayerIndex], state.leftEnd, state.rightEnd) && state.boneyard.length === 0) {
    passTurn(el);
  }
}

function passTurn(el) {
  if (state.currentPlayer !== state.myPlayerIndex) return;
  if (state.boneyard.length > 0) return;
  if (rules.hasValidMove(state.hands[state.myPlayerIndex], state.leftEnd, state.rightEnd)) return;

  syncPassToServer();
  nextTurn(el);
}

function nextTurn(el) {
  state.currentPlayer = (state.currentPlayer + 1) % state.numPlayers;
  updateUI(el);
  refreshHand();

  if (state.mode === 'bot' && state.currentPlayer !== state.myPlayerIndex) {
    scheduleBotTurn(el);
  }
}

function checkRoundEnd(el) {
  if (state.hands[state.currentPlayer]?.length === 0) {
    endRound(el, state.currentPlayer);
    return true;
  }
  if (rules.checkRoundEnd(state.hands, state.boneyard, state.chain)) {
    const { winner } = rules.getRoundWinner(state.hands);
    endRound(el, winner);
    return true;
  }
  return false;
}

function endRound(el, winnerIdx) {
  state.gameOver = true;
  const roundPoints = rules.scoreRound(state.hands, winnerIdx);
  state.matchScores[winnerIdx] += roundPoints;

  const isMyWin = winnerIdx === state.myPlayerIndex;
  audio.play(isMyWin ? 'win' : 'lose', 'game');

  if (isMyWin) {
    juice.hapticSuccess?.();
    const boardEl = el.querySelector('#domino-board');
    if (boardEl) {
      const rect = boardEl.getBoundingClientRect();
      juice.confetti?.(rect.left + rect.width / 2, rect.top + rect.height / 2, 20);
    }
  }

  syncRoundEndToServer(winnerIdx, roundPoints);
  updateUI(el);

  if (state.matchScores[0] >= state.targetScore || state.matchScores[1] >= state.targetScore) {
    const matchWinner = state.matchScores[0] >= state.targetScore ? 0 : 1;
    const result = matchWinner === state.myPlayerIndex ? 'win' : 'loss';
    endMatch(el, result, 'score');
  } else {
    showRoundOverlay(el, winnerIdx, roundPoints);
  }
}

function showRoundOverlay(el, winnerIdx, points) {
  const isMyWin = winnerIdx === state.myPlayerIndex;
  const overlay = document.createElement('div');
  overlay.id = 'round-overlay';
  overlay.style.cssText = `
    position:absolute;inset:0;z-index:100;display:flex;flex-direction:column;
    align-items:center;justify-content:center;gap:12px;
    background:rgba(0,0,0,0.85);animation:fadeIn 0.3s ease;
  `;
  overlay.innerHTML = `
    <div style="font-size:48px;">${isMyWin ? emoji('trophy', '🏆', 48) : emoji('skull', '💀', 48)}</div>
    <div style="font-size:20px;font-weight:800;color:${isMyWin ? 'var(--green-light)' : 'var(--red-soft)'};">
      ${isMyWin ? t('game.round_won') : t('game.round_lost')}
    </div>
    <div style="font-size:14px;color:var(--text-secondary);">${t('game.points', { n: points })}</div>
    <div style="display:flex;gap:16px;margin-top:8px;">
      <div style="text-align:center;">
        <div style="font-size:24px;font-weight:800;color:var(--green-light);">${state.matchScores[state.myPlayerIndex]}</div>
        <div style="font-size:11px;color:#6ee7b7;">${t('common.you')}</div>
      </div>
      <div style="font-size:20px;color:var(--text-dim);align-self:center;">—</div>
      <div style="text-align:center;">
        <div style="font-size:24px;font-weight:800;color:var(--red-soft);">${state.matchScores[1 - state.myPlayerIndex]}</div>
        <div style="font-size:11px;color:var(--red-soft);">${t('common.opponent')}</div>
      </div>
    </div>
    <div style="font-size:12px;color:var(--text-muted);margin-top:8px;">${t('game.round_next')}</div>
  `;

  const wrap = el.querySelector('#domino-wrap');
  wrap.appendChild(overlay);

  setTimeout(() => {
    overlay.remove();
    startNewRound(el);
  }, 2500);
}

function startNewRound(el) {
  state.roundNumber++;
  state.currentPlayer = state.matchScores[0] <= state.matchScores[1] ? 0 : 1;

  if (state.mode === 'live') {
    if (state.myPlayerIndex === 0) {
      dealNewRound();
      syncNewRoundToServer();
      board.setChain(state.chain);
      updateUI(el);
      refreshHand();
    }
    // Guest will receive the new round data via poll
  } else {
    dealNewRound();
    board.setChain(state.chain);
    updateUI(el);
    refreshHand();

    if (state.currentPlayer !== state.myPlayerIndex) {
      scheduleBotTurn(el);
    }
  }
}

async function syncNewRoundToServer() {
  if (!state.matchId) return;
  try {
    await net.post('domino-match.php', {
      action: 'move',
      match_id: state.matchId,
      board: state.chain,
      boneyard: state.boneyard,
      hands: JSON.stringify({ '0': state.hands[0], '1': state.hands[1] }),
      current_turn: state.currentPlayer,
      game_state: JSON.stringify({
        move_count: state.moveCount,
        scores: { '0': state.matchScores[0], '1': state.matchScores[1] },
        round: state.roundNumber,
        left_end: state.leftEnd,
        right_end: state.rightEnd,
        dealt: true,
      })
    });
  } catch (e) {}
}

function endMatch(el, result, reason) {
  if (state.matchOver) return;
  state.matchOver = true;
  state.gameOver = true;

  liveSession?.cleanup?.();


  if (result === 'win') {
    audio.play('win', 'reward');
    juice.hapticSuccess?.();
    const boardEl = el.querySelector('#domino-board');
    if (boardEl) {
      const rect = boardEl.getBoundingClientRect();
      juice.confetti?.(rect.left + rect.width / 2, rect.top + rect.height / 2, 40);
    }
  } else {
    audio.play('lose', 'game');
  }

  if (state.matchId && state.mode === 'live') {
    const myId = store.get('auth.userId');
    const players = Array.isArray(state.players) && state.players.length > 0 ? state.players : null;
    const oppId = (players && players.find(p => p !== myId))
      || state.opponentId
      || store.get(`match.${state.matchId}.opponentId`)
      || '';
    if (myId && oppId) {
      const winners = result === 'win' ? [myId, oppId] : [oppId, myId];
      net.post('domino-match.php', {
        action: 'complete',
        match_id: state.matchId,
        winners,
        reason
      }).catch(() => {});
    } else {
      console.warn('[domino] endMatch: cannot determine opponent ID, skipping complete call');
    }
  }

  if (heartbeatTimer) { clearInterval(heartbeatTimer); heartbeatTimer = null; }

  setTimeout(() => {
    scene.exitGameMode();
    scene.replace('domino-result', {
      result, reason, mode: state.mode,
      myScore: state.matchScores[state.myPlayerIndex],
      oppScore: state.matchScores[1 - state.myPlayerIndex],
      rounds: state.roundNumber,
      matchId: state.matchId
    });
    bus.emit('game:ended', { gameKey: 'domino', result, mode: state.mode });
  }, 1200);
}

// ═══════════════════════════════════════
// BOT
// ═══════════════════════════════════════

function scheduleBotTurn(el) {
  state.botThinking = true;
  const thinkEl = el.querySelector('#bot-thinking');
  if (thinkEl) thinkEl.style.display = 'inline';

  const delay = bot.getThinkDelay(state.botPersonality);
  botTimeout = setTimeout(() => executeBotTurn(el), delay);
}

function executeBotTurn(el) {
  if (state.gameOver || state.matchOver) return;
  state.botThinking = false;
  const thinkEl = el.querySelector('#bot-thinking');
  if (thinkEl) thinkEl.style.display = 'none';

  const botIdx = state.currentPlayer;
  const botHand = state.hands[botIdx];

  if (!rules.hasValidMove(botHand, state.leftEnd, state.rightEnd)) {
    if (state.boneyard.length > 0) {
      botHand.push(state.boneyard.pop());
      audio.play('click');
      updateUI(el);
      botTimeout = setTimeout(() => executeBotTurn(el), 400);
      return;
    }
    if (checkRoundEnd(el)) return;
    nextTurn(el);
    return;
  }

  const oppCount = state.hands[state.myPlayerIndex].length;
  const move = bot.pickMove(botHand, state.leftEnd, state.rightEnd, state.chain, oppCount, state.botPersonality);
  if (!move) { nextTurn(el); return; }

  const actualEnd = state.chain.length === 0 ? 'right' : (move.end === 'first' ? 'right' : move.end);
  const result = rules.placeTile(state.chain, move.tile, actualEnd);
  state.chain = result.chain;
  state.leftEnd = result.leftEnd;
  state.rightEnd = result.rightEnd;
  state.hands[botIdx] = botHand.filter(t => t.id !== move.tile.id);
  state.moveCount++;

  board.animatePlacement(move.tile, actualEnd, () => {
    board.setChain(state.chain);
  }, 'opponent');
  audio.play('place', 'game');

  if (bot.shouldEmote(state.botPersonality)) {
    setTimeout(() => showEmoteBubble(el, bot.getRandomEmote()), 300);
  }

  updateUI(el);

  if (checkRoundEnd(el)) return;
  nextTurn(el);
}

// ═══════════════════════════════════════
// UI
// ═══════════════════════════════════════

function updateUI(el) {
  const isMyTurn = state.currentPlayer === state.myPlayerIndex;
  const turnEl = el.querySelector('#turn-status');
  if (turnEl) {
    if (state.gameOver) {
      turnEl.textContent = t('game.round_end');
      turnEl.className = 'dg-turn-status dg-waiting';
    } else if (isMyTurn && state.selectedTile) {
      turnEl.textContent = t('game.choose_placement');
      turnEl.className = 'dg-turn-status';
    } else if (isMyTurn) {
      turnEl.textContent = t('game.your_turn');
      turnEl.className = 'dg-turn-status';
    } else {
      turnEl.textContent = t('game.opponent_plays');
      turnEl.className = 'dg-turn-status dg-waiting';
    }
  }

  const oppCountEl = el.querySelector('#opp-count');
  const oppIdx = 1 - state.myPlayerIndex;
  const oppHandLen = state.hands[oppIdx]?.length || 0;
  if (oppCountEl) {
    oppCountEl.textContent = t('common.pieces', { n: oppHandLen });
  }

  // Reset tension on new round
  if (state.tensionReset) {
    const oppBar = el.querySelector('#domino-opp-bar');
    if (oppBar) { delete oppBar.dataset.tensionShown; oppBar.style.borderBottom = ''; }
    state.tensionReset = false;
  }

  // Endgame tension alert
  if (oppHandLen > 0 && oppHandLen <= 2) {
    const oppBar = el.querySelector('#domino-opp-bar');
    if (oppBar && !oppBar.dataset.tensionShown) {
      oppBar.dataset.tensionShown = '1';
      oppBar.style.borderBottom = '2px solid var(--error)';
      oppBar.animate([{background:'rgba(239,68,68,0.15)'},{background:'#0f0f1e'}], {duration:800});
      juice.hapticLight();
      const alert = document.createElement('div');
      alert.style.cssText = 'position:absolute;top:56px;left:50%;transform:translateX(-50%);background:rgba(239,68,68,0.9);color:#fff;padding:6px 16px;border-radius:8px;font-size:12px;font-weight:700;z-index:50;';
      alert.textContent = oppHandLen === 1 ? t('game.one_piece_warning') : t('game.two_piece_warning');
      el.querySelector('#domino-wrap')?.appendChild(alert);
      alert.animate([{opacity:0,transform:'translateX(-50%) translateY(-10px)'},{opacity:1,transform:'translateX(-50%) translateY(0)'},{opacity:0,transform:'translateX(-50%) translateY(-10px)'}], {duration:2500}).onfinish = () => alert.remove();
    }
  }

  const boneNumEl = el.querySelector('#boneyard-num');
  if (boneNumEl) boneNumEl.textContent = state.boneyard.length;


  const myScoreEl = el.querySelector('#my-score');
  const oppScoreEl = el.querySelector('#opp-score');
  if (myScoreEl) myScoreEl.textContent = state.matchScores[state.myPlayerIndex];
  if (oppScoreEl) oppScoreEl.textContent = state.matchScores[1 - state.myPlayerIndex];

  const roundEl = el.querySelector('#round-num');
  if (roundEl) roundEl.textContent = state.roundNumber;

  const drawBtn = el.querySelector('#btn-draw');
  const passBtn = el.querySelector('#btn-pass');
  const myHand = state.hands[state.myPlayerIndex];
  const hasMove = rules.hasValidMove(myHand, state.leftEnd, state.rightEnd);

  if (isMyTurn && !hasMove && state.boneyard.length > 0 && !state.gameOver) {
    if (drawBtn) { drawBtn.style.display = ''; drawBtn.style.animation = 'drawPulse 1.5s ease-in-out infinite'; }
    if (passBtn) passBtn.style.display = 'none';
  } else if (isMyTurn && !hasMove && state.boneyard.length === 0 && !state.gameOver) {
    if (drawBtn) drawBtn.style.display = 'none';
    if (passBtn) passBtn.style.display = '';
    if (!autoPassTimeout) {
      autoPassTimeout = setTimeout(() => { autoPassTimeout = null; passTurn(el); }, 1500);
    }
  } else {
    if (autoPassTimeout) { clearTimeout(autoPassTimeout); autoPassTimeout = null; }
    if (drawBtn) { drawBtn.style.display = ''; drawBtn.style.animation = ''; }
    if (passBtn) passBtn.style.display = 'none';
  }

  // Opponent back-tiles visual
  const oppHandVisual = el.querySelector('#opp-hand-visual');
  if (oppHandVisual) {
    const currentCount = oppHandVisual.children.length;
    if (currentCount !== oppHandLen) {
      if (oppHandLen < currentCount && currentCount > 0) {
        // Animate removal of last tile
        const last = oppHandVisual.lastElementChild;
        if (last) {
          last.style.animation = 'oppTileRemove 0.3s ease forwards';
          setTimeout(() => {
            oppHandVisual.innerHTML = '';
            for (let i = 0; i < oppHandLen; i++) {
              const tile = document.createElement('div');
              tile.className = 'dg-opp-tile';
              oppHandVisual.appendChild(tile);
            }
          }, 300);
        }
      } else {
        oppHandVisual.innerHTML = '';
        for (let i = 0; i < oppHandLen; i++) {
          const tile = document.createElement('div');
          tile.className = 'dg-opp-tile';
          tile.style.animationDelay = `${i * 30}ms`;
          oppHandVisual.appendChild(tile);
        }
      }
    }
  }

  // Bot thinking indicator
  const thinkingEl = el.querySelector('#bot-thinking');
  if (thinkingEl) {
    thinkingEl.style.display = (!isMyTurn && !state.gameOver && state.mode === 'bot') ? 'inline' : 'none';
  }
}

function refreshHand() {
  const myHand = state.hands[state.myPlayerIndex];
  const validIds = new Set();

  if (state.currentPlayer === state.myPlayerIndex && !state.gameOver) {
    const moves = state.chain.length === 0
      ? myHand.map(t => ({ tile: t }))
      : rules.getValidMoves(myHand, state.leftEnd, state.rightEnd);
    moves.forEach(m => validIds.add(m.tile.id));
  }

  hand.setTiles(myHand, validIds);
  hand.setDisabled(state.currentPlayer !== state.myPlayerIndex || state.gameOver);
}

async function confirmResign(el) {
  if (state.gameOver || state.matchOver) return;
  const confirmed = await modal.confirm(t('game.resign_confirm'), {
    title: t('game.resign'),
    icon: '🏳️',
    confirmText: t('game.resign_yes'),
    cancelText: t('game.resign_no'),
    danger: true
  });
  if (!confirmed) return;

  state.matchOver = true;
  state.gameOver = true;

  if (state.matchId) {
    net.post('domino-match.php', { action: 'resign', match_id: state.matchId }).catch(() => {});
  }

  audio.play('lose', 'game');
  setTimeout(() => {
    scene.exitGameMode();
    scene.replace('domino-result', {
      result: 'loss', mode: state.mode, resigned: true,
      myScore: state.matchScores[state.myPlayerIndex],
      oppScore: state.matchScores[1 - state.myPlayerIndex],
      rounds: state.roundNumber, matchId: state.matchId
    });
    bus.emit('game:ended', { gameKey: 'domino', result: 'loss', mode: state.mode });
  }, 500);
}

// ═══════════════════════════════════════
// SERVER
// ═══════════════════════════════════════

async function createServerRecord() {
  try {
    const data = await net.post('domino-match.php', {
      action: 'start',
      mode: 'bot',
      bot_level: state.botPersonality.strategy
    });
    if (data?.id) state.matchId = data.id;
  } catch (e) {}
}

// ═══════════════════════════════════════
// CLEANUP
// ═══════════════════════════════════════

export function unmountGame() {
  if (botTimeout) { clearTimeout(botTimeout); botTimeout = null; }
  if (autoPassTimeout) { clearTimeout(autoPassTimeout); autoPassTimeout = null; }
  liveSession?.cleanup?.();

  board?.destroy();
  hand?.destroy();
  drag?.destroy();
  state = null;
  liveSession = null;
  drawInFlight = false;
}
