// Pure mapping from a game view (for the viewer) to a status/result badge: a label key
// and a place-based emoji. Used by the lobby lists.

import type { GameView } from './model';
import type { MessageKey } from './i18n/catalog';

export interface ResultBadge {
  key: MessageKey;
  emoji: string;
}

/** placeBadge maps a 1-based finishing rank to its label + medal (2nd is a defeat in a duel). */
function placeBadge(rank: number, players: number): ResultBadge {
  if (rank === 2) return players === 2 ? { key: 'result.defeat', emoji: '🥈' } : { key: 'result.place2', emoji: '🥈' };
  if (rank === 3) return { key: 'result.place3', emoji: '🥉' };
  return { key: 'result.place4', emoji: '🏅' };
}

export function resultBadge(game: GameView, myIds: readonly string[]): ResultBadge {
  const me = game.seats.find((s) => myIds.includes(s.accountId));

  if (game.status === 'active' || game.status === 'open') {
    return game.toMove === me?.seat
      ? { key: 'result.yourMove', emoji: '🟢' }
      : { key: 'result.oppMove', emoji: '⏳' };
  }

  if (me?.isWinner) return { key: 'result.victory', emoji: '🏆' };

  // No single winner: a tie for the lead, a full draw, or an aborted game. An abort or an all-level
  // finish is a draw; a tie for the lead is a SHARED victory for the top scorers, and a placed finish
  // for those below. (winner() is -1 on ANY tie for the lead, so isWinner alone cannot tell a
  // three-way "two share first, one is last" apart from a genuine draw — the reported bug.)
  if (!game.seats.some((s) => s.isWinner)) {
    const myScore = me?.score ?? 0;
    const higher = game.seats.filter((s) => !s.resigned && s.score > myScore).length;
    const allLevel = game.seats.filter((s) => !s.resigned).every((s) => s.score === myScore);
    if (game.endReason === 'aborted' || (higher === 0 && allLevel)) return { key: 'result.draw', emoji: '🏅' };
    return higher === 0 ? { key: 'result.victory', emoji: '🏆' } : placeBadge(1 + higher, game.players);
  }

  // A winner exists but it is not me — even when scores are level (a win by resignation or timeout
  // can leave the winner at or below my score). The winner takes rank 1; place me among the remaining
  // (non-resigned) seats by score, starting at rank 2.
  const ahead = game.seats.filter((s) => !s.isWinner && !s.resigned && !myIds.includes(s.accountId) && s.score > (me?.score ?? 0)).length;
  return placeBadge(2 + ahead, game.players);
}

/**
 * seatMedal returns a per-SEAT place emoji for a finished game (empty while it is still in progress),
 * so a hotseat game can show each player's medal on their plaque instead of a single viewer-centric
 * "you won/lost" (which is meaningless with 2-4 local players). It ranks by the FINAL score with
 * competition ranking (equal scores share a place), so a tie for the lead SHARES the trophy rather
 * than reading as a full draw. A resigned / host-excluded seat places last, with no medal, and does
 * not push the remaining players down. Ranked by score, not by the engine's single-winner flag
 * (which is -1 on any tie for the lead — the reported all-last-place bug).
 */
export function seatMedal(game: GameView, seat: number): string {
  if (game.status !== 'finished') return '';
  const s = game.seats.find((x) => x.seat === seat);
  if (!s || s.resigned) return '';
  const rank = 1 + game.seats.filter((x) => !x.resigned && x.score > s.score).length;
  return rank === 1 ? '🏆' : rank === 2 ? '🥈' : rank === 3 ? '🥉' : '🏅';
}
