// Match Live — glue between match-session (logic) and match-ui (visuals)
// Games import this ONE module to get full multiplayer edge case handling

import * as session from './match-session.js';
import * as ui from './match-ui.js';
import * as mp from './multiplayer.js';
import * as store from './store.js';

/**
 * When the opponent was last seen, from the heartbeat the server records in
 * game_state. Returns 0 when there is nothing to go on.
 */
function opponentHeartbeatAt(data) {
  if (!data || !data.game_state) return 0;
  try {
    const gs = typeof data.game_state === 'string' ? JSON.parse(data.game_state) : data.game_state;
    if (!gs || typeof gs !== 'object') return 0;

    // The server stamps one flat `hb_<playerId>` key per player every 10s.
    // Take the most recent one that is not ours.
    const mine = 'hb_' + myPlayerId();
    let latest = 0;
    for (const [key, ts] of Object.entries(gs)) {
      if (!key.startsWith('hb_') || key === mine) continue;
      const v = typeof ts === 'number' ? ts : Date.parse(ts);
      if (Number.isFinite(v) && v > latest) latest = v;
    }
    return latest;
  } catch (e) { return 0; }
}

function myPlayerId() {
  try { return store.get('auth.userId'); } catch (e) { return null; }
}

export function start(matchId, gameType, options = {}) {
  const { onMove, onGameEnd } = options;

  let lastMoveCount = 0;
  let lastOpponentSeenAt = 0;
  let connectionLost = false;

  const sess = session.create(matchId, gameType, {
    onOpponentMove: (data) => {
      ui.hideOpponentThinking();

      if (connectionLost) {
        connectionLost = false;
        ui.showConnectionRestored();
      }

      // Only *evidence of the opponent* counts as opponent activity.
      //
      // This used to call session.markOpponentActive() unconditionally on every
      // poll. Since we poll every 2s regardless of what the opponent is doing,
      // the "last seen" timestamp never aged and disconnect and abandon
      // detection could never fire — a player whose opponent walked away waited
      // forever, which stalls a whole tournament round.
      const moveCount = data.move_count || data.current_turn || 0;
      if (moveCount > lastMoveCount) {
        lastMoveCount = moveCount;
        session.markOpponentActive();
      }
      if (opponentHeartbeatAt(data) > lastOpponentSeenAt) {
        lastOpponentSeenAt = opponentHeartbeatAt(data);
        session.markOpponentActive();
      }

      onMove?.(data);

      if (data.status === 'completed') {
        onGameEnd?.(data);
      }
    },

    onOpponentDisconnect: () => {
      ui.showOpponentDisconnect();
    },

    onOpponentReconnect: () => {
      ui.showOpponentReconnect();
    },

    onOpponentAbandon: () => {
      // Auto-win handled by UI (shows trophy + navigates back)
    },

    onConnectionLost: () => {
      connectionLost = true;
      ui.showConnectionLost();
    },

    onConnectionRestored: () => {
      if (connectionLost) {
        connectionLost = false;
        ui.showConnectionRestored();
      }
    }
  });

  return {
    session: sess,
    markOpponentActive: () => session.markOpponentActive(),
    showThinking: () => ui.showOpponentThinking(),
    hideThinking: () => ui.hideOpponentThinking(),
    showReconnecting: () => ui.showReconnecting(),
    toast: (msg, color) => ui.toast(msg, color),
    cleanup: () => { session.destroy(); ui.cleanup(); }
  };
}

export function recover() {
  const match = session.getRecoverableMatch();
  if (match) {
    ui.showReconnecting();
    return match;
  }
  return null;
}

export { session, ui };
