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

use glam::Vec2;

use crate::world::{changes::Changes, player::{Player, PlayerId}, bullet::Bullet, powerup::PowerupType, effects::Effect, event::MoveType, team::TeamId};

#[derive(Default)]
pub struct ServerChanges {
	pub kills: Vec<Kill>, // Player ids duplicated if they get multiple kills in a single update
	pub dead: BTreeSet<PlayerId>,
	pub hit: BTreeMap<PlayerId, Vec<PlayerId>>, // Maps the hit player to all bullets that hit them
	pub ammo_crates_collected: Vec<PlayerId>,
	pub effects_to_apply: Vec<(PlayerId, Effect)>,
	pub randomly_moved: BTreeMap<PlayerId, (MoveType, Option<TeamId>)>,
}

pub struct Kill {
	pub killer: Option<PlayerId>,
	pub killed: PlayerId,
}

impl Changes for ServerChanges {
	fn removed_players(&mut self, old_ids: BTreeSet<PlayerId>) {
		for id in &old_ids {
			self.randomly_moved.remove(id);
			self.dead.remove(id);
			self.hit.remove(id);
		}

		self.ammo_crates_collected.retain(|id| !old_ids.contains(id));
		self.effects_to_apply.retain(|(id, _)| !old_ids.contains(id));
		for hits in self.hit.values_mut() {
			hits.retain(|id| old_ids.contains(id));
		}
		// Not changing self.kills as that is used before the players are eliminated
	}

	fn add_hit(&mut self, bullet: Bullet, player: &Player, player_id: PlayerId, killed: bool) {
		if killed {
			self.kills.push(Kill {
				killer: bullet.get_player_owner(),
				killed: player_id,
			});

			self.randomly_moved.insert(player_id, (MoveType::Spawn, player.get_team()));
			self.dead.insert(player_id);
		} else if let Some(bullet_id) = bullet.get_player_owner() {
			self.hit.entry(player_id).or_default().push(bullet_id);
		}
	}

	fn ammo_crate_collected(&mut self, id: PlayerId, _pos: Vec2) { self.ammo_crates_collected.push(id); }

	fn powerup_collected(&mut self, id: PlayerId, player: &Player, typ: PowerupType, _pos: Vec2) {
		if let Some(effect) = Effect::try_from_type(typ) {
			self.effects_to_apply.push((id, effect));
		} else if typ == PowerupType::Teleportation {
			self.randomly_moved.insert(id, (MoveType::Teleportation, player.get_team()));
		}
	}
}

