/**
 * Bracket Simulator
 *
 * Monte Carlo simulation of single-elimination playoff brackets.
 * Supports NHL (8 teams) and NFL (14 teams) formats.
 *
 * The simulator:
 * 1. Takes teams with Elo ratings
 * 2. Simulates the bracket many times (typically 100,000)
 * 3. Tracks placement frequencies (1st, 2nd, 3-4, 5-8, etc.)
 * 4. Returns probability distribution for each team
 */

import { eloWinProbability } from './probability-engine';

/**
 * Team with Elo rating for simulation
 */
export interface TeamForSimulation {
  participantId: string;
  elo: number;
}

/**
 * Bracket format configurations
 */
export type BracketFormat = 'nhl-8' | 'nfl-14';

/**
 * Result of a single simulation
 * Maps participantId to their placement (1 = champion, 2 = runner-up, etc.)
 */
export type SimulationResult = Map<string, number>;

/**
 * Aggregated results across all simulations
 * For each participant, tracks how many times they finished in each placement
 */
export interface PlacementCounts {
  [participantId: string]: {
    1: number;   // Champion
    2: number;   // Runner-up
    3: number;   // Semifinal loser (tied 3-4)
    4: number;   // Semifinal loser (tied 3-4)
    5: number;   // Quarterfinal loser (tied 5-8)
    6: number;   // Quarterfinal loser (tied 5-8)
    7: number;   // Quarterfinal loser (tied 5-8)
    8: number;   // Quarterfinal loser (tied 5-8)
  };
}

/**
 * Probability distribution for a single participant
 * Array of 8 probabilities [P(1st), P(2nd), P(3rd), P(4th), P(5th), P(6th), P(7th), P(8th)]
 */
export type ProbabilityDistribution = [number, number, number, number, number, number, number, number];

/**
 * Final simulation results
 * Maps participantId to their probability distribution
 */
export type SimulationResults = Map<string, ProbabilityDistribution>;

/**
 * Simulate a single head-to-head matchup
 *
 * @param team1 First team
 * @param team2 Second team
 * @returns Winner of the matchup
 */
function simulateMatchup(team1: TeamForSimulation, team2: TeamForSimulation): TeamForSimulation {
  const team1WinProb = eloWinProbability(team1.elo, team2.elo);
  const random = Math.random();

  return random < team1WinProb ? team1 : team2;
}

/**
 * Simulate a single round of the bracket
 *
 * @param teams Teams in this round (must be even number)
 * @returns Winners advancing to next round
 */
function simulateRound(teams: TeamForSimulation[]): TeamForSimulation[] {
  if (teams.length % 2 !== 0) {
    throw new Error('Number of teams must be even for bracket round');
  }

  const winners: TeamForSimulation[] = [];

  // Simulate each matchup
  for (let i = 0; i < teams.length; i += 2) {
    const winner = simulateMatchup(teams[i], teams[i + 1]);
    winners.push(winner);
  }

  return winners;
}

/**
 * Simulate an entire 8-team bracket
 *
 * Placement determination:
 * - 1st: Finals winner
 * - 2nd: Finals loser
 * - 3-4: Semifinal losers (tied)
 * - 5-8: Quarterfinal losers (tied)
 *
 * @param teams 8 teams in bracket order
 * @returns Map of participantId to placement
 */
function simulate8TeamBracket(teams: TeamForSimulation[]): SimulationResult {
  if (teams.length !== 8) {
    throw new Error('8-team bracket requires exactly 8 teams');
  }

  const placements = new Map<string, number>();

  // Quarterfinals: 8 → 4
  const semifinalists = simulateRound(teams);
  const quarterfinalsLosers = teams.filter(t => !semifinalists.some(s => s.participantId === t.participantId));

  // Quarterfinal losers tie for 5-8
  quarterfinalsLosers.forEach(team => placements.set(team.participantId, 5));

  // Semifinals: 4 → 2
  const finalists = simulateRound(semifinalists);
  const semifinalsLosers = semifinalists.filter(t => !finalists.some(f => f.participantId === t.participantId));

  // Semifinal losers tie for 3-4
  semifinalsLosers.forEach(team => placements.set(team.participantId, 3));

  // Finals: 2 → 1
  const champion = simulateMatchup(finalists[0], finalists[1]);
  const runnerUp = finalists.find(t => t.participantId !== champion.participantId);
  if (!runnerUp) throw new Error("Runner-up not found in finalists");

  placements.set(champion.participantId, 1);
  placements.set(runnerUp.participantId, 2);

  return placements;
}

/**
 * Initialize placement counts for participants
 *
 * @param participantIds List of participant IDs
 * @returns Placement counts initialized to 0
 */
function initializePlacementCounts(participantIds: string[]): PlacementCounts {
  const counts: PlacementCounts = {};

  participantIds.forEach(id => {
    counts[id] = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0 };
  });

  return counts;
}

/**
 * Convert placement counts to probability distributions
 *
 * @param counts Placement counts from simulations
 * @param totalSimulations Total number of simulations run
 * @returns Probability distribution for each participant
 */
function countsToDistributions(
  counts: PlacementCounts,
  totalSimulations: number
): SimulationResults {
  const distributions = new Map<string, ProbabilityDistribution>();

  Object.entries(counts).forEach(([participantId, placementCounts]) => {
    const distribution: ProbabilityDistribution = [
      placementCounts[1] / totalSimulations,
      placementCounts[2] / totalSimulations,
      placementCounts[3] / totalSimulations,
      placementCounts[4] / totalSimulations,
      placementCounts[5] / totalSimulations,
      placementCounts[6] / totalSimulations,
      placementCounts[7] / totalSimulations,
      placementCounts[8] / totalSimulations,
    ];

    distributions.set(participantId, distribution);
  });

  return distributions;
}

/**
 * Run Monte Carlo simulation of playoff bracket
 *
 * @param teams Teams with Elo ratings
 * @param format Bracket format ('nhl-8' or 'nfl-14')
 * @param simulations Number of simulations to run (default: 100,000)
 * @param onProgress Optional progress callback (called every 10,000 simulations)
 * @returns Probability distribution for each team
 *
 * @example
 * const teams = [
 *   { participantId: '1', elo: 1650 },
 *   { participantId: '2', elo: 1600 },
 *   // ... 6 more teams
 * ];
 * const results = await simulateBracket(teams, 'nhl-8', 100000);
 * // Map { '1' => [0.25, 0.18, 0.15, ...], '2' => [0.18, 0.20, ...], ... }
 */
export async function simulateBracket(
  teams: TeamForSimulation[],
  format: BracketFormat = 'nhl-8',
  simulations = 100000,
  onProgress?: (current: number, total: number) => void
): Promise<SimulationResults> {
  // Validate input
  if (format === 'nhl-8' && teams.length !== 8) {
    throw new Error('NHL format requires exactly 8 teams');
  }

  if (simulations <= 0) {
    throw new Error('Number of simulations must be positive');
  }

  // Initialize placement counters
  const participantIds = teams.map(t => t.participantId);
  const placementCounts = initializePlacementCounts(participantIds);

  // Run simulations
  for (let i = 0; i < simulations; i++) {
    // Simulate bracket (currently only 8-team supported)
    const result = simulate8TeamBracket(teams);

    // Record placements
    result.forEach((placement, participantId) => {
      // Handle ties: placement 3 or 4 both count as tied-3rd
      // placement 5-8 all count as tied-5th
      if (placement >= 3 && placement <= 4) {
        placementCounts[participantId][3] += 0.5; // Split tied placements
        placementCounts[participantId][4] += 0.5;
      } else if (placement >= 5 && placement <= 8) {
        // Split across all 5-8 placements
        placementCounts[participantId][5] += 0.25;
        placementCounts[participantId][6] += 0.25;
        placementCounts[participantId][7] += 0.25;
        placementCounts[participantId][8] += 0.25;
      } else {
        // 1st or 2nd place - no ties
        placementCounts[participantId][placement as 1 | 2] += 1;
      }
    });

    // Report progress every 10,000 simulations
    if (onProgress && (i + 1) % 10000 === 0) {
      onProgress(i + 1, simulations);
    }
  }

  // Convert counts to probabilities
  const results = countsToDistributions(placementCounts, simulations);

  // Final progress callback
  if (onProgress) {
    onProgress(simulations, simulations);
  }

  return results;
}

/**
 * Simulate bracket synchronously (blocking)
 *
 * Use this for smaller simulation counts or when you don't need progress updates.
 * For large simulations (100k+), prefer the async version with progress callbacks.
 *
 * @param teams Teams with Elo ratings
 * @param format Bracket format
 * @param simulations Number of simulations to run
 * @returns Probability distribution for each team
 */
export function simulateBracketSync(
  teams: TeamForSimulation[],
  format: BracketFormat = 'nhl-8',
  simulations = 100000
): SimulationResults {
  // Validate input
  if (format === 'nhl-8' && teams.length !== 8) {
    throw new Error('NHL format requires exactly 8 teams');
  }

  if (simulations <= 0) {
    throw new Error('Number of simulations must be positive');
  }

  // Initialize placement counters
  const participantIds = teams.map(t => t.participantId);
  const placementCounts = initializePlacementCounts(participantIds);

  // Run simulations
  for (let i = 0; i < simulations; i++) {
    const result = simulate8TeamBracket(teams);

    // Record placements
    result.forEach((placement, participantId) => {
      if (placement >= 3 && placement <= 4) {
        placementCounts[participantId][3] += 0.5;
        placementCounts[participantId][4] += 0.5;
      } else if (placement >= 5 && placement <= 8) {
        placementCounts[participantId][5] += 0.25;
        placementCounts[participantId][6] += 0.25;
        placementCounts[participantId][7] += 0.25;
        placementCounts[participantId][8] += 0.25;
      } else {
        placementCounts[participantId][placement as 1 | 2] += 1;
      }
    });
  }

  // Convert counts to probabilities
  return countsToDistributions(placementCounts, simulations);
}

/**
 * Get expected placement counts from probability distribution
 *
 * Useful for debugging/validation
 *
 * @param distribution Probability distribution
 * @param simulations Number of simulations that produced this distribution
 * @returns Expected count for each placement
 */
export function getExpectedCounts(
  distribution: ProbabilityDistribution,
  simulations: number
): Record<number, number> {
  return {
    1: Math.round(distribution[0] * simulations),
    2: Math.round(distribution[1] * simulations),
    3: Math.round(distribution[2] * simulations),
    4: Math.round(distribution[3] * simulations),
    5: Math.round(distribution[4] * simulations),
    6: Math.round(distribution[5] * simulations),
    7: Math.round(distribution[6] * simulations),
    8: Math.round(distribution[7] * simulations),
  };
}
