// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
use std::{mem, collections::{BTreeSet, BTreeMap}};

use super::{World, PlayerId, Client, Timer, config::LobbyConfig, events::move_chooser::LobbyMoveChooser};

use crate::world::blocks::Blocks;
use crate::protocol::message::text_io::TextIoString;

pub struct Lobby {
	config: LobbyConfig,
	time_left: f64,
	ready_players: BTreeSet<PlayerId>,
	new_ready_players: BTreeSet<PlayerId>,
	enough_ready: bool,
	move_chooser: LobbyMoveChooser,
}

impl Lobby {
	pub fn new(config: LobbyConfig, move_chooser: LobbyMoveChooser) -> Lobby {
		let time_left = config.time;
		Lobby { config, time_left, ready_players: BTreeSet::new(), new_ready_players: BTreeSet::new(), enough_ready: false, move_chooser }
	}

	pub fn reset(&mut self, blocks: &Blocks, only_reset_time: bool) {
		self.time_left = self.config.time;

		if !only_reset_time {
			self.ready_players.clear();
			self.new_ready_players.clear();
			self.enough_ready = false;

			self.move_chooser.update_spawn_pos(blocks);
		}
	}

	/**
	 * Votes that this player is ready.
	 *
	 * Returns false if the player has already voted.
	 */
	#[must_use]
	pub fn add_ready(&mut self, id: PlayerId) -> bool {
		if self.ready_players.contains(&id) { return false; }
		self.new_ready_players.insert(id)
	}

	/**
	 * Returns a list of server messages about ready players to be sent to all
	 * players.
	 */
	#[must_use]
	pub fn update(&mut self, dt: f32, world: &World, clients: &BTreeMap<PlayerId, Client>) -> Vec<TextIoString> {
		self.time_left -= dt as f64;

		self.ready_players.extend(self.new_ready_players.iter().copied());

		self.ready_players.retain(|&id| {
			let keep = world.has_player(id);
			if !keep {
				self.new_ready_players.remove(&id);
			}
			keep
		});

		let threshold = ((world.human_player_count() as f32 * self.config.ready_threshold).ceil() as usize).max(self.config.min_players);
		let begin_count = self.ready_players.len() - self.new_ready_players.len() + 1;

		self.enough_ready = !self.ready_players.is_empty() && self.ready_players.len() >= threshold;

		mem::take(&mut self.new_ready_players).into_iter().enumerate().map(|(i, id)|
			TextIoString::new_truncate(format!("{} is ready [{}/{}]", Client::get_name(clients, id), begin_count + i, threshold))
		).collect()
	}

	pub fn active(&self) -> bool {
		self.time_left > 0.0 && !self.enough_ready
	}

	pub fn reset_if_no_players(&self) -> bool {
		self.config.reset_if_no_players && self.config.time > 0.0
	}

	pub fn just_started(&self) -> bool {
		self.time_left == self.config.time
	}

	pub fn get_timer(&self) -> Option<Timer> {
		/*
		 * The timer is paused if just started to prevent jitter when all players
		 * are spectators and the time is constant.
		 */
		self.active().then(|| Timer::count_down(self.time_left, self.just_started()))
	}

	pub fn get_move_chooser(&self) -> &LobbyMoveChooser {
		&self.move_chooser
	}

	pub fn get_min_players(&self) -> usize {
		self.config.min_players
	}
}
