## SpawnManager — Spawn point selection for team-based spawning.
##
## Scans the current scene for Marker3D nodes in team-specific groups
## ("spawn_points_ct", "spawn_points_t") and selects valid spawns.
##
## Spawn validity rules:
##   - No enemies within a configurable minimum distance (proximity_check_radius)
##   - Fallback: first available spawn if all are contested
##   - Preference: farthest from known enemy positions
##
## Usage:
##   var sm = SpawnManager.new()
##   add_child(sm)
##   sm.team_manager = get_node("/root/TeamManager")
##   var spawn_pos: Vector3 = sm.select_spawn(player_id, Team.COUNTER_TERRORIST)
##
## Spawn points are found at runtime by scanning scene groups.
## Map authors place Marker3D nodes and add them to the appropriate group.

extends Node
class_name SpawnManager

# Preload for headless compat
const _td_ref = preload("res://scripts/teams/team_data.gd")

# ---------------------------------------------------------------------------
# Exports
# ---------------------------------------------------------------------------

## Minimum distance (in Godot units) from any enemy player for a spawn to be valid.
@export var proximity_check_radius: float = 15.0

## If true, fall back to first available spawn when all are contested.
@export var allow_fallback: bool = true

## Group name for Counter-Terrorist spawn markers.
@export var ct_spawn_group: String = "spawn_points_ct"

## Group name for Terrorist spawn markers.
@export var t_spawn_group: String = "spawn_points_t"

# ---------------------------------------------------------------------------
# Dependencies
# ---------------------------------------------------------------------------

## Reference to the TeamManager singleton or node.
## If not set, spawn safety checks are skipped (all spawns considered valid).
var team_manager: TeamManager = null

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

## Cached spawn points for the current map.
## Keyed by group name → Array of Marker3D nodes.
var _spawn_points: Dictionary = {}

## Tracks which player last occupied which spawn (for spread).
## player_id (int) → Marker3D global position
var _last_spawn_positions: Dictionary = {}


# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------

## (Re)scan the scene tree for spawn point markers.
## Call this when the map changes.
func refresh_spawn_points() -> void:
	_spawn_points.clear()
	var groups_to_scan: Array[String] = [ct_spawn_group, t_spawn_group]

	for group_name in groups_to_scan:
		var markers: Array[Marker3D] = []
		var nodes: Array[Node] = get_tree().get_nodes_in_group(group_name)
		for node in nodes:
			if node is Marker3D:
				markers.append(node as Marker3D)
		_spawn_points[group_name] = markers

	print("[SpawnManager] Refreshed spawn points — CT: %d, T: %d" % [
		_spawn_points.get(ct_spawn_group, []).size(),
		_spawn_points.get(t_spawn_group, []).size(),
	])


## Get all spawn point markers for a given team.
func get_spawn_points_for_team(team: int) -> Array[Marker3D]:
	var group_name: String = _group_for_team(team)
	return _spawn_points.get(group_name, []).duplicate()


## Select a valid spawn position for a player on a given team.
##
## Strategy:
##   1. Filter to valid spawns (no enemies within proximity_check_radius).
##   2. Among valid spawns, pick the one farthest from known enemies.
##   3. If no spawn is valid, use fallback behaviour.
##   4. If no spawns exist at all, return Vector3.ZERO.
##
## Returns the global position of the chosen spawn point.
func select_spawn(player_id: int, team: int) -> Vector3:
	var markers: Array[Marker3D] = get_spawn_points_for_team(team)
	if markers.is_empty():
		push_warning("[SpawnManager] No spawn points found for team %s" % _td_ref.get_team_name(team))
		return Vector3.ZERO

	# Separate into valid and contested spawns
	var valid_spawns: Array[Marker3D] = []
	var contested_spawns: Array[Marker3D] = []

	if team_manager != null:
		for marker in markers:
			if _is_spawn_safe(marker.global_position, team):
				valid_spawns.append(marker)
			else:
				contested_spawns.append(marker)
	else:
		# No team manager — all spawns are valid
		valid_spawns = markers.duplicate()

	# Choose the best spawn
	var chosen: Marker3D = null

	if not valid_spawns.is_empty():
		# Pick the farthest from known enemy last-positions
		chosen = _pick_farthest_spawn(valid_spawns, team)
	elif allow_fallback:
		# Fallback: pick the first contested spawn
		if not contested_spawns.is_empty():
			chosen = contested_spawns[0]
			print("[SpawnManager] Fallback: all spawns contested, using first available")
		else:
			chosen = markers[0]
			print("[SpawnManager] Fallback: no spawns available, using first marker")
	else:
		chosen = markers[0]
		print("[SpawnManager] No valid spawns and fallback disabled, using first marker")

	# Record this spawn as the player's last position
	var pos: Vector3 = chosen.global_position
	_last_spawn_positions[player_id] = pos
	return pos


## Return the number of spawn points for a team.
func get_spawn_count(team: int) -> int:
	var group_name: String = _group_for_team(team)
	return _spawn_points.get(group_name, []).size()


# ---------------------------------------------------------------------------
# Internal: Spawn selection helpers
# ---------------------------------------------------------------------------

## Check if a position is safe from enemy proximity.
func _is_spawn_safe(pos: Vector3, friendly_team: int) -> bool:
	if team_manager == null:
		return true

	# Get the enemy team(s)
	var enemy_teams: Array[int] = _get_enemy_teams(friendly_team)

	for enemy_team in enemy_teams:
		var enemy_ids: Array[int] = team_manager.get_team_players(enemy_team)
		for enemy_id in enemy_ids:
			# Check last known position
			var enemy_pos: Vector3 = _last_spawn_positions.get(enemy_id, Vector3.ZERO)
			if enemy_pos != Vector3.ZERO:
				var dist: float = pos.distance_to(enemy_pos)
				if dist < proximity_check_radius:
					return false

	return true


## Pick the spawn farthest from all known enemy positions.
func _pick_farthest_spawn(candidates: Array[Marker3D], friendly_team: int) -> Marker3D:
	if candidates.is_empty():
		return null
	if candidates.size() == 1:
		return candidates[0]

	# Collect enemy positions
	var enemy_positions: Array[Vector3] = []
	if team_manager != null:
		var enemy_teams: Array[int] = _get_enemy_teams(friendly_team)
		for enemy_team in enemy_teams:
			var enemy_ids: Array[int] = team_manager.get_team_players(enemy_team)
			for enemy_id in enemy_ids:
				var enemy_pos: Vector3 = _last_spawn_positions.get(enemy_id, Vector3.ZERO)
				if enemy_pos != Vector3.ZERO:
					enemy_positions.append(enemy_pos)

	if enemy_positions.is_empty():
		# No known enemy positions — pick randomly among candidates
		return candidates[randi() % candidates.size()]

	# Pick candidate with max minimum-distance to any enemy
	var best: Marker3D = candidates[0]
	var best_min_dist: float = 0.0

	for marker in candidates:
		var pos: Vector3 = marker.global_position
		var min_dist: float = INF
		for epos in enemy_positions:
			var d: float = pos.distance_to(epos)
			if d < min_dist:
				min_dist = d
		if min_dist > best_min_dist:
			best_min_dist = min_dist
			best = marker

	return best


## Get the teams that are enemies of the given team.
## For now, CT and T are enemies of each other; SPECTATOR has no enemies.
func _get_enemy_teams(team: int) -> Array[int]:
	match team:
		_td_ref.Team.COUNTER_TERRORIST:
			return [_td_ref.Team.TERRORIST]
		_td_ref.Team.TERRORIST:
			return [_td_ref.Team.COUNTER_TERRORIST]
		_:
			return []


## Translate a Team enum to the spawn group name.
func _group_for_team(team: int) -> String:
	match team:
		_td_ref.Team.COUNTER_TERRORIST:
			return ct_spawn_group
		_td_ref.Team.TERRORIST:
			return t_spawn_group
		_:
			return ""
