// Match Session Manager — handles ALL multiplayer edge cases
// Used by Chess, Ludo, Domino for consistent connection handling

import * as store from './store.js';
import * as net from './net.js';
import * as bus from './bus.js';

const PING_INTERVAL = 10000;      // Ping server every 10s to show "online"
const DISCONNECT_THRESHOLD = 30000; // Consider opponent disconnected after 30s
const ABANDON_THRESHOLD = 60000;    // Auto-win after 60s disconnect
const POLL_INTERVAL = 2000;         // Poll for opponent moves every 2s
const RECONNECT_GRACE = 5000;       // Wait 5s on page load before assuming fresh start
const RECOVERY_WINDOW_MS = 4 * 60 * 60 * 1000;  // rejoin a match for up to 4h

let currentSession = null;

export function create(matchId, gameType, options = {}) {
  destroy(); // Clean up any existing session

  currentSession = {
    matchId,
    gameType,               // 'chess' | 'ludo' | 'domino'
    myUserId: store.get('auth.userId'),
    lastServerPing: Date.now(),
    lastOpponentActivity: Date.now(),
    pollTimer: null,
    pingTimer: null,
    disconnectTimer: null,
    isActive: true,
    isBackground: false,
    onOpponentMove: options.onOpponentMove || null,
    onOpponentDisconnect: options.onOpponentDisconnect || null,
    onOpponentReconnect: options.onOpponentReconnect || null,
    onOpponentAbandon: options.onOpponentAbandon || null,
    onConnectionLost: options.onConnectionLost || null,
    onConnectionRestored: options.onConnectionRestored || null,
    opponentDisconnected: false,
    abandonReported: false,   // latched separately from opponentDisconnected
    connectionLost: false  // Track whether we are currently in a disconnected state
  };

  // Save match ID to localStorage for tab refresh recovery
  localStorage.setItem('el3ab_active_match', JSON.stringify({
    matchId, gameType, timestamp: Date.now()
  }));

  startPolling();
  startPinging();
  startDisconnectWatch();
  setupVisibilityHandler();

  return currentSession;
}

export function destroy() {
  if (!currentSession) return;
  currentSession.isActive = false;
  clearInterval(currentSession.pollTimer);
  clearInterval(currentSession.pingTimer);
  clearInterval(currentSession.disconnectTimer);
  if (currentSession._visibilityHandler) {
    document.removeEventListener('visibilitychange', currentSession._visibilityHandler);
  }
  localStorage.removeItem('el3ab_active_match');
  currentSession = null;
  pollInFlight = false;
}

// Check if player has an active match to resume (after tab refresh)
export function getRecoverableMatch() {
  try {
    const saved = localStorage.getItem('el3ab_active_match');
    if (!saved) return null;
    const data = JSON.parse(saved);
    // Recovery window has to outlast a real game. Five minutes meant a
    // classical or long rapid tournament game could not be rejoined after a
    // refresh. The server is asked whether the match is still live anyway, so a
    // generous window here costs nothing.
    if (Date.now() - data.timestamp > RECOVERY_WINDOW_MS) {
      localStorage.removeItem('el3ab_active_match');
      return null;
    }
    return data;
  } catch (e) { return null; }
}

// Record that opponent made an action (keeps disconnect timer alive)
export function markOpponentActive() {
  if (!currentSession) return;
  currentSession.lastOpponentActivity = Date.now();
  currentSession.abandonReported = false;
  if (currentSession.opponentDisconnected) {
    currentSession.opponentDisconnected = false;
    currentSession.onOpponentReconnect?.();
  }
}

// ===== POLLING =====
let pollInFlight = false;

function startPolling() {
  if (!currentSession) return;
  currentSession.pollTimer = setInterval(async () => {
    if (!currentSession || !currentSession.isActive) return;
    if (currentSession.isBackground) return;
    if (pollInFlight) return;

    pollInFlight = true;
    try {
      const endpoint = currentSession.gameType === 'ludo' ? 'ludo-match.php' : currentSession.gameType === 'domino' ? 'domino-match.php' : currentSession.gameType === 'backgammon' ? 'backgammon-match.php' : 'game.php';
      const data = await net.post(endpoint, {
        action: 'get',
        match_id: currentSession.matchId
      });

      if (!data || data.error) return;

      currentSession.lastServerPing = Date.now();
      // Only fire onConnectionRestored if we previously fired onConnectionLost
      if (currentSession.connectionLost) {
        currentSession.connectionLost = false;
        currentSession.onConnectionRestored?.();
      }

      if (data.status === 'abandoned') {
        currentSession.onOpponentAbandon?.();
        return;
      }

      currentSession.onOpponentMove?.(data);

    } catch (e) {
      if (!currentSession) return;
      if (Date.now() - currentSession.lastServerPing > 10000 && !currentSession.connectionLost) {
        currentSession.connectionLost = true;
        currentSession.onConnectionLost?.();
      }
    } finally {
      pollInFlight = false;
    }
  }, POLL_INTERVAL);
}

// ===== HEARTBEAT (let server know we're still connected) =====
function startPinging() {
  if (!currentSession) return;
  currentSession.pingTimer = setInterval(async () => {
    if (!currentSession || !currentSession.isActive) return;
    try {
      const endpoint = currentSession.gameType === 'ludo' ? 'ludo-match.php' : currentSession.gameType === 'domino' ? 'domino-match.php' : currentSession.gameType === 'backgammon' ? 'backgammon-match.php' : 'game.php';
      await net.post(endpoint, {
        action: 'heartbeat',
        match_id: currentSession.matchId
      });
    } catch (e) {}
  }, PING_INTERVAL);
}

// ===== DISCONNECT DETECTION =====
function startDisconnectWatch() {
  if (!currentSession) return;
  currentSession.disconnectTimer = setInterval(() => {
    if (!currentSession || !currentSession.isActive) return;

    const elapsed = Date.now() - currentSession.lastOpponentActivity;

    // These were `else if` on a shared `!opponentDisconnected` guard, so the
    // 30s branch set the flag and the 60s abandon branch could then never run.
    // They are independent conditions with independent latches.
    if (elapsed > DISCONNECT_THRESHOLD && !currentSession.opponentDisconnected) {
      currentSession.opponentDisconnected = true;
      currentSession.onOpponentDisconnect?.();
    }

    if (elapsed > ABANDON_THRESHOLD && !currentSession.abandonReported) {
      currentSession.abandonReported = true;
      currentSession.onOpponentAbandon?.();
    }
  }, 5000);
}

// ===== TAB VISIBILITY (handles tab switch/minimize) =====
function setupVisibilityHandler() {
  const handler = () => {
    if (!currentSession) return;

    if (document.hidden) {
      currentSession.isBackground = true;
    } else {
      currentSession.isBackground = false;
      currentSession.lastServerPing = Date.now();

      if (currentSession.onOpponentMove) {
        const endpoint = currentSession.gameType === 'ludo' ? 'ludo-match.php' : currentSession.gameType === 'domino' ? 'domino-match.php' : currentSession.gameType === 'backgammon' ? 'backgammon-match.php' : 'game.php';
        net.post(endpoint, { action: 'get', match_id: currentSession.matchId })
          .then(data => {
            if (data && !data.error && currentSession) {
              // Do NOT markOpponentActive() here: coming back to our own tab
              // says nothing about whether the opponent is still there.
              // onOpponentMove decides that from the data itself.
              currentSession.onOpponentMove(data);
            }
          })
          .catch(() => {});
      }
    }
  };
  currentSession._visibilityHandler = handler;
  document.addEventListener('visibilitychange', handler);
}

// ===== UTILITY =====
export function isConnected() {
  if (!currentSession) return false;
  return (Date.now() - currentSession.lastServerPing) < 15000;
}

export function isOpponentConnected() {
  if (!currentSession) return false;
  return !currentSession.opponentDisconnected;
}

export function getMatchId() {
  return currentSession?.matchId || null;
}

export function getSession() {
  return currentSession;
}
