class ActiveGenie::Ranking::Ranking

def self.call(...)

def self.call(...)
  new(...).call
end

def call

def call
  initial_log
  set_initial_player_scores!
  eliminate_obvious_bad_players!
  while @players.elo_eligible?
    elo_report = run_elo_round!
    eliminate_relegation_players!
    rebalance_players!(elo_report)
  end
  run_free_for_all!
  final_logs
  @players.sorted
end

def eliminate_obvious_bad_players!

def eliminate_obvious_bad_players!
  while @players.coefficient_of_variation >= SCORE_VARIATION_THRESHOLD
    @players.eligible.last.eliminated = ELIMINATION_VARIATION
  end
end

def eliminate_relegation_players!

def eliminate_relegation_players!
  @players.calc_relegation_tier.each { |player| player.eliminated = ELIMINATION_RELEGATION }
end

def final_logs

def final_logs
  ActiveGenie::Logger.debug({ code: :ranking_final, players: @players.sorted.map(&:to_h) })
  ActiveGenie::Logger.info({ code: :ranking, **report })
end

def initial_log

def initial_log
  @players.each { |p| ActiveGenie::Logger.debug({ code: :new_player, player: p.to_h }) }
end

def initialize(param_players, criteria, reviewers: [], config: {})

def initialize(param_players, criteria, reviewers: [], config: {})
  @criteria = criteria
  @reviewers = Array(reviewers).compact.uniq
  @config = ActiveGenie::Configuration.to_h(config)
  @players = PlayersCollection.new(param_players)
  @elo_rounds_played = 0
  @elo_round_battle_count = 0
  @free_for_all_battle_count = 0
  @total_tokens = 0
  @start_time = Time.now
end

def log_context

def log_context
  { config: @config[:log], ranking_id: }
end

def ranking_id

def ranking_id
  player_ids = @players.map(&:id).join(',')
  ranking_unique_key = [player_ids, @criteria, @config.to_json].join('-')
  Digest::MD5.hexdigest(ranking_unique_key)
end

def rebalance_players!(elo_report)

def rebalance_players!(elo_report)
  return if elo_report[:highest_elo_diff].negative?
  @players.eligible.each do |player|
    next if elo_report[:players_in_round].include?(player.id)
    player.elo += elo_report[:highest_elo_diff]
  end
end

def report

def report
  {
    ranking_id: ranking_id,
    players_count: @players.size,
    variation_too_high: @players.select { |player| player.eliminated == ELIMINATION_VARIATION }.size,
    elo_rounds_played: @elo_rounds_played,
    elo_round_battle_count: @elo_round_battle_count,
    relegation_tier: @players.select { |player| player.eliminated == ELIMINATION_RELEGATION }.size,
    ffa_round_battle_count: @free_for_all_battle_count,
    top3: @players.eligible[0..2].map(&:id),
    total_tokens: @total_tokens,
    duration_seconds: Time.now - @start_time,
  }
end

def run_elo_round!

def run_elo_round!
  @elo_rounds_played += 1
  elo_report = EloRound.call(@players, @criteria, config: @config)
  @elo_round_battle_count += elo_report[:battles_count]
  elo_report
end

def run_free_for_all!

def run_free_for_all!
  ffa_report = FreeForAll.call(@players, @criteria, config: @config)
  @free_for_all_battle_count += ffa_report[:battles_count]
end

def set_initial_player_scores!

def set_initial_player_scores!
  RankingScoring.call(@players, @criteria, reviewers: @reviewers, config: @config)
end