## TeamManager — Team assignment, tracking, and auto-balance.
##
## Manages which player belongs to which team, tracks team scores,
## and provides auto-balancing logic to keep team sizes fair.
##
## Teams are round-independent — player assignments persist across rounds.
##
## Usage:
##   var tm = TeamManager.new()
##   add_child(tm)
##   tm.assign_player(player_id, Team.COUNTER_TERRORIST)
##   var team: Team = tm.get_player_team(player_id)
##   var ct_players: Array[int] = tm.get_team_players(Team.COUNTER_TERRORIST)
##
## Signals:
##   player_joined_team(player_id, team)
##   player_left_team(player_id, team)
##   team_scores_updated(team_scores)

extends Node
class_name TeamManager

# Force TeamData to be loaded first (Godot 4 class_name loading order: Resources after Nodes)
const _team_data_ref = preload("res://scripts/teams/team_data.gd")

# ---------------------------------------------------------------------------
# Signals
# ---------------------------------------------------------------------------

## Emitted when a player is assigned to a team (including on team switch).
## team is an int (TeamData.Team enum value).
signal player_joined_team(player_id: int, team: int)

## Emitted when a player leaves a team (disconnected or switched).
## team is an int (TeamData.Team enum value).
signal player_left_team(player_id: int, team: int)

## Emitted when scores for any team change.
signal team_scores_updated(team_scores: Dictionary)

# ---------------------------------------------------------------------------
# State
# ---------------------------------------------------------------------------

## Mapping: player_id (int) → Team enum value.
var _player_teams: Dictionary = {}

## Mapping: Team enum → Array of player IDs.
var _team_players: Dictionary = {}

## Score per team: Team enum → int (rounds won, kills, etc.).
var _team_scores: Dictionary = {}

## Maximum players allowed on a single team (0 = unlimited).
## Set from ServerConfig on _ready if available.
var max_players_per_team: int = 0


# ---------------------------------------------------------------------------
# Lifecycle
# ---------------------------------------------------------------------------

func _ready() -> void:
	var cfg = get_node_or_null("/root/ServerConfig")
	if cfg and cfg.has_method(&"get_config_path"):
		max_players_per_team = cfg.players_per_team
	else:
		max_players_per_team = 8


# ---------------------------------------------------------------------------
# Public API — Assignment
# ---------------------------------------------------------------------------

## Assign a player to a team. Returns true if successful, false if the team is full.
## Emits player_left_team for the old team (if any) and player_joined_team for the new team.
func assign_player(player_id: int, team: int) -> bool:
	# Validate team
	if team < 0 or team > TeamData.Team.size() - 1:
		push_warning("[TeamManager] Invalid team value: %d" % team)
		return false

	# Check if team is full (skip for spectator)
	if team != TeamData.Team.SPECTATOR and max_players_per_team > 0:
		var current_count: int = _team_players.get(team, []).size()
		if current_count >= max_players_per_team:
			# Allow if the player is already on this team
			if _player_teams.get(player_id, TeamData.Team.SPECTATOR) != team:
				push_warning("[TeamManager] Team %s is full (%d/%d)" % [TeamData.get_team_name(team), current_count, max_players_per_team])
				return false

	# Remove from old team if assigned
	var old_team: int = _player_teams.get(player_id, TeamData.Team.SPECTATOR)
	if old_team != team:
		_remove_from_team(player_id, old_team)

	# Add to new team
	_player_teams[player_id] = team
	if not _team_players.has(team):
		_team_players[team] = []
	if player_id not in _team_players[team]:
		_team_players[team].append(player_id)

	player_joined_team.emit(player_id, team)
	return true


## Remove a player from any team (equivalent to assigning to SPECTATOR).
func remove_player(player_id: int) -> void:
	var current_team: int = _player_teams.get(player_id, TeamData.Team.SPECTATOR)
	_remove_from_team(player_id, current_team)


## Unregister a player entirely (on disconnect).
func unregister_player(player_id: int) -> void:
	remove_player(player_id)
	_player_teams.erase(player_id)


# ---------------------------------------------------------------------------
# Public API — Querying
# ---------------------------------------------------------------------------

## Get the team a player belongs to. Returns SPECTATOR if unassigned.
## Returns an int (TeamData.Team enum value).
func get_player_team(player_id: int) -> int:
	return _player_teams.get(player_id, TeamData.Team.SPECTATOR)


## Get an array of all player IDs on a given team.
func get_team_players(team: int) -> Array[int]:
	return _team_players.get(team, []).duplicate()


## Get the total number of players on a given team.
func get_team_player_count(team: int) -> int:
	return _team_players.get(team, []).size()


## Check if a player is on a specific team.
func is_player_on_team(player_id: int, team: int) -> bool:
	return _player_teams.get(player_id, TeamData.Team.SPECTATOR) == team


## Get a dictionary of all player-to-team mappings.
func get_all_assignments() -> Dictionary:
	return _player_teams.duplicate()


# ---------------------------------------------------------------------------
# Public API — Auto-balance
# ---------------------------------------------------------------------------

## Assign a player to the team with the fewest members (ties: random).
## Returns the team as an int (TeamData.Team enum value) the player was assigned to,
## or SPECTATOR if both teams are full.
func assign_to_team_with_fewest(player_id: int) -> int:
	var ct_count: int = _team_players.get(TeamData.Team.COUNTER_TERRORIST, []).size()
	var t_count: int = _team_players.get(TeamData.Team.TERRORIST, []).size()

	# If one team is at capacity, force to the other
	if max_players_per_team > 0:
		if ct_count >= max_players_per_team and t_count < max_players_per_team:
			assign_player(player_id, TeamData.Team.TERRORIST)
			return TeamData.Team.TERRORIST
		if t_count >= max_players_per_team and ct_count < max_players_per_team:
			assign_player(player_id, TeamData.Team.COUNTER_TERRORIST)
			return TeamData.Team.COUNTER_TERRORIST
		if ct_count >= max_players_per_team and t_count >= max_players_per_team:
			push_warning("[TeamManager] Both teams are full")
			return TeamData.Team.SPECTATOR

	# Assign to team with fewer players (ties: randomly pick one)
	var team: int = TeamData.Team.COUNTER_TERRORIST
	if ct_count < t_count:
		team = TeamData.Team.COUNTER_TERRORIST
	elif t_count < ct_count:
		team = TeamData.Team.TERRORIST
	else:
		# Tied — pick randomly
		team = TeamData.Team.COUNTER_TERRORIST if randi() % 2 == 0 else TeamData.Team.TERRORIST

	assign_player(player_id, team)
	return team


# ---------------------------------------------------------------------------
# Public API — Scoring
# ---------------------------------------------------------------------------

## Set the score for a given team.
func set_team_score(team: int, score: int) -> void:
	_team_scores[team] = score
	team_scores_updated.emit(_team_scores.duplicate())


## Add to a team's current score.
func add_team_score(team: int, amount: int = 1) -> void:
	var current: int = _team_scores.get(team, 0)
	_team_scores[team] = current + amount
	team_scores_updated.emit(_team_scores.duplicate())


## Get the score for a given team.
func get_team_score(team: int) -> int:
	return _team_scores.get(team, 0)


## Get all team scores as a dictionary: Team enum → int.
func get_all_scores() -> Dictionary:
	return _team_scores.duplicate()


## Reset all scores to zero.
func reset_scores() -> void:
	for team in [TeamData.Team.COUNTER_TERRORIST, TeamData.Team.TERRORIST]:
		_team_scores[team] = 0
	team_scores_updated.emit(_team_scores.duplicate())


# ---------------------------------------------------------------------------
# Public API — Utility
# ---------------------------------------------------------------------------

## Reset all teams (clears all assignments and scores).
func reset_all() -> void:
	# Emit leave signals for all assigned players
	for player_id in _player_teams.keys():
		var team: int = _player_teams[player_id]
		player_left_team.emit(player_id, team)

	_player_teams.clear()
	_team_players.clear()
	reset_scores()


# ---------------------------------------------------------------------------
# Internal
# ---------------------------------------------------------------------------

func _remove_from_team(player_id: int, team: int) -> void:
	if _player_teams.has(player_id):
		_player_teams.erase(player_id)

	if _team_players.has(team):
		var arr: Array = _team_players[team]
		var idx: int = arr.find(player_id)
		if idx >= 0:
			arr.remove_at(idx)
		player_left_team.emit(player_id, team)
