/**
 * Small read-only helpers about the running round, shared by the reducer and the views so both
 * apply exactly the same rules.
 */
import { windowFor } from './scoring';
import { ROUND_RULES, type GameState, type Id, type QuestionInput, type RoundState, type SessionSettings, type Timings } from './types';

export function currentQuestion(round: RoundState): QuestionInput | null {
  return round.qIndex >= 0 ? (round.questions[round.qIndex] ?? null) : null;
}

export function isWildcardIndex(round: RoundState, qIndex: number): boolean {
  return round.wildcardIndex !== null && round.wildcardIndex === qIndex;
}

/** Answer window of question `qIndex` in this round. */
export function questionWindowMs(round: RoundState, qIndex: number, timings: Timings): number {
  const q = round.questions[qIndex];
  return q ? windowFor(q, isWildcardIndex(round, qIndex), timings) : timings.windowMs;
}

/**
 * Late join (ARCHITECTURE §2.8): a player can still become a participant until question
 * `lateJoinUntilQuestionIndex` OPENS, i.e. while an earlier question is in any phase, or that
 * question is still in INTRO/PENDING.
 */
export function lateJoinOpen(settings: Pick<SessionSettings, 'lateJoinUntilQuestionIndex'>, round: RoundState): boolean {
  const limit = settings.lateJoinUntilQuestionIndex;
  if (round.qIndex < limit) return true;
  return round.qIndex === limit && (round.phase === 'INTRO' || round.phase === 'PENDING');
}

/** Whether `playerId` plays the round now, or would be dealt in on their next action. */
export function canPlayRound(state: Pick<GameState, 'players' | 'settings'>, round: RoundState, playerId: Id): boolean {
  if (round.participants.includes(playerId)) return true;
  if (state.players[playerId]?.status !== 'ACTIVE') return false;
  return !ROUND_RULES[round.kind].finalistsOnly && lateJoinOpen(state.settings, round);
}

/** INTRO / PENDING / REVEALED with at least one question still to open. */
export function isBetweenQuestions(round: RoundState): boolean {
  if (round.phase === 'INTRO' || round.phase === 'PENDING') return true;
  return round.phase === 'REVEALED' && round.qIndex + 1 < round.questions.length;
}

/** The question a Double Down placed now would apply to (the next one to open), or null. */
export function nextQuestionToOpen(round: RoundState): QuestionInput | null {
  if (round.phase === 'INTRO') return round.questions[0] ?? null;
  if (round.phase === 'PENDING') return currentQuestion(round);
  if (round.phase === 'REVEALED') return round.questions[round.qIndex + 1] ?? null;
  return null;
}

/** Expected round end from the current phase deadline plus every remaining phase duration. */
export function estimateRoundEnd(round: RoundState, timings: Timings): number {
  const n = round.questions.length;
  const fullQuestion = (i: number): number => timings.pendingMs + questionWindowMs(round, i, timings) + timings.lockedMs + timings.revealMs;
  const after = (from: number): number => {
    let sum = 0;
    for (let i = Math.max(0, from); i < n; i++) sum += fullQuestion(i);
    return sum;
  };
  const q = round.qIndex;
  let remaining: number;
  switch (round.phase) {
    case 'INTRO':
      remaining = after(0);
      break;
    case 'PENDING':
      remaining = questionWindowMs(round, q, timings) + timings.lockedMs + timings.revealMs + after(q + 1);
      break;
    case 'OPEN':
      remaining = timings.lockedMs + timings.revealMs + after(q + 1);
      break;
    case 'LOCKED':
      remaining = timings.revealMs + after(q + 1);
      break;
    case 'REVEALED':
      remaining = after(q + 1);
      break;
    case 'ROUND_OVER':
      return round.phaseEndsAt;
  }
  return round.phaseEndsAt + remaining + timings.roundOverMs;
}
