/** Number, time and rank formatting shared by the phone screens (pure, locale en-US). */

const INT = new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 });
const ONE_DECIMAL = new Intl.NumberFormat('en-US', { maximumFractionDigits: 1 });
/** U+2212 MINUS SIGN: reads as a real minus next to a "+", and screen readers say "minus". */
const MINUS = '−';

/** 2310 → "2,310". */
export function formatPoints(points: number): string {
  return INT.format(Math.round(points));
}

/** Team scores are means with one decimal: 1234.5 → "1,234.5". */
export function formatScore(score: number): string {
  return ONE_DECIMAL.format(score);
}

/** 250 → "+250", -100 → "−100", 0 → "0". */
export function formatSigned(points: number): string {
  const rounded = Math.round(points);
  if (rounded > 0) return `+${INT.format(rounded)}`;
  if (rounded < 0) return `${MINUS}${INT.format(-rounded)}`;
  return '0';
}

/** 1 → "1st", 2 → "2nd", 11 → "11th", 23 → "23rd". */
export function ordinal(n: number): string {
  const mod100 = n % 100;
  if (mod100 >= 11 && mod100 <= 13) return `${n}th`;
  switch (n % 10) {
    case 1:
      return `${n}st`;
    case 2:
      return `${n}nd`;
    case 3:
      return `${n}rd`;
    default:
      return `${n}th`;
  }
}

/** Whole seconds left, rounded up so "0" only shows once time is really out. */
export function secondsLeft(ms: number): number {
  return Math.max(0, Math.ceil(ms / 1000));
}

/** 42_000 → "0:42", 65_000 → "1:05", 3_725_000 → "1:02:05". */
export function formatClock(ms: number): string {
  const total = secondsLeft(ms);
  const h = Math.floor(total / 3600);
  const m = Math.floor((total % 3600) / 60);
  const s = total % 60;
  const ss = String(s).padStart(2, '0');
  return h > 0 ? `${h}:${String(m).padStart(2, '0')}:${ss}` : `${m}:${ss}`;
}

/** Server-measured answer time: 1234 → "1.2s". */
export function formatElapsed(ms: number): string {
  return `${(Math.max(0, ms) / 1000).toFixed(1)}s`;
}

/**
 * When a scheduled show starts, relative to `now` (both server epoch ms), in the phone's own time
 * zone (the phone is in the bar). "Today 9:00 PM", "Tomorrow 8:30 PM", "Fri 9:00 PM" or "Nov 14, 9:00 PM".
 */
export function formatShowTime(at: number, now: number, timeZone?: string): string {
  const tz = timeZone ? { timeZone } : {};
  const time = new Intl.DateTimeFormat('en-US', { hour: 'numeric', minute: '2-digit', ...tz }).format(at);
  const dayKey = (t: number) => new Intl.DateTimeFormat('en-CA', { year: 'numeric', month: '2-digit', day: '2-digit', ...tz }).format(t);
  const days = Math.round((Date.parse(dayKey(at)) - Date.parse(dayKey(now))) / 86_400_000);
  if (days === 0) return `Today ${time}`;
  if (days === 1) return `Tomorrow ${time}`;
  if (days > 1 && days < 7) return `${new Intl.DateTimeFormat('en-US', { weekday: 'short', ...tz }).format(at)} ${time}`;
  return `${new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric', ...tz }).format(at)}, ${time}`;
}

/** An open lobby shows its clock time only when the show is due within this window. */
export const LOBBY_TIME_WINDOW_MS = 60 * 60_000;

/**
 * Start line for an open championship lobby. Hosts can open a lobby hours (or a day) early, so the
 * scheduled time is shown only when it is coming up within the hour, in the phone's own time zone
 * (the phone is in the bar); otherwise, and once it has passed, the host decides.
 */
export function lobbyStartText(scheduledAt: number | null, now: number, timeZone?: string): string {
  if (scheduledAt !== null && scheduledAt > now && scheduledAt - now <= LOBBY_TIME_WINDOW_MS) {
    const time = new Intl.DateTimeFormat('en-US', { hour: 'numeric', minute: '2-digit', ...(timeZone ? { timeZone } : {}) }).format(scheduledAt);
    return `Starts at ${time}`;
  }
  return 'Starts when the host is ready';
}

/** "1 player" / "12 players". */
export function plural(n: number, one: string, many = `${one}s`): string {
  return `${formatPoints(n)} ${n === 1 ? one : many}`;
}
