## WeaponManager — hitscan weapon system for tactical FPS.
##
## Manages a single hitscan weapon: fire rate limiting, ammo, reload.
## Communicates with SimulationServer for server-authoritative hit detection.
##
## Architecture:
##   - Client-side: fire rate limiting, muzzle VFX, hit marker feedback
##   - Server-side (via SimulationServer): hit detection, damage, lag compensation
##   - Both sides: ammo tracking (server-authoritative, client-predicted)
##
## Usage (attach as child of FPSCharacterController):
##
##   FPSCharacterController
##   ├── WeaponManager        — fire rate, ammo, VFX
##   │   ├── MuzzleFlash      — instantiated VFX
##   │   └── HitMarker        — instantiated VFX
##   └── FpsCamera
##
class_name WeaponManager
extends Node

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

## Emitted when the weapon fires (for visual feedback).
signal weapon_fired(origin: Vector3, direction: Vector3)

## Emitted when this weapon hits an entity.
signal hit_marked(hit_pos: Vector3, damage: float, killed: bool)

## Emitted when ammo changes.
signal ammo_changed(current: int, max: int)

## Emitted when player is killed.
signal player_killed()

# ---------------------------------------------------------------------------
# Exports — Assault Rifle Config
# ---------------------------------------------------------------------------

## Maximum ammo capacity.
@export var max_ammo: int = 30

## Starting ammo (including one magazine).
@export var start_ammo: int = 90

## Time between shots (seconds). 10 Hz = 0.1s between shots.
@export var fire_rate: float = 0.1

## Reload time in seconds.
@export var reload_time: float = 2.0

## Spread increases while firing (degrees).
@export var spread_per_shot: float = 0.5

## Max spread when firing continuously (degrees).
@export var max_spread: float = 4.0

## Spread recovery per second (degrees/s).
@export var spread_recovery: float = 8.0

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

var current_ammo: int = 30
var reserve_ammo: int = 90
var is_reloading: bool = false
var _last_shot_time: float = 0.0
var _current_spread: float = 0.0

## Reference to the parent FPSCharacterController (set in _ready).
var _controller: FPSCharacterController = null

## Reference to SimulationServer (if available).
var _server: Object = null

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

func _ready() -> void:
	_controller = get_parent() as FPSCharacterController
	if _controller == null:
		push_warning("WeaponManager: Parent is not FPSCharacterController. Disabled.")
		set_process(false)
		return

	# Find SimulationServer singleton
	if Engine.has_singleton("SimulationServer"):
		_server = Engine.get_singleton("SimulationServer")
	elif Engine.get_main_loop() and Engine.get_main_loop().root:
		# Try finding it in the scene tree
		var game_server = Engine.get_main_loop().root.find_child("GameServer", true, false)
		if game_server and game_server.has_method(&"get_simulation_server"):
			_server = game_server.get_simulation_server()

	# Initialize ammo
	current_ammo = max_ammo
	reserve_ammo = start_ammo

	# Signal that ammo changed
	ammo_changed.emit(current_ammo, max_ammo)

# ---------------------------------------------------------------------------
# Main loop — called from FPSCharacterController._physics_process per tick
# ---------------------------------------------------------------------------

## Try to fire the weapon. Returns true if a shot was fired.
## Called by the controller when shoot input is detected.
func try_fire(time: float) -> bool:
	# Check fire rate
	if time - _last_shot_time < fire_rate:
		return false

	# Check ammo
	if current_ammo <= 0:
		# Auto-reload on empty — or play empty-click sound
		if not is_reloading:
			start_reload()
		return false

	# Check reloading
	if is_reloading:
		return false

	# Deduct ammo
	current_ammo -= 1
	_last_shot_time = time

	# Increase spread
	_current_spread = min(_current_spread + spread_per_shot, max_spread)

	# Emit fire signal (for visual/hit marker)
	# Direction comes from the controller's look direction
	if _controller:
		var origin: Vector3 = _controller.global_position
		origin.y += _controller.eye_height_stand
		var direction: Vector3 = _controller.get_look_direction()
		weapon_fired.emit(origin, direction)

	ammo_changed.emit(current_ammo, max_ammo)
	return true

## Called every frame to recover spread and handle spread animation.
func _process(delta: float) -> void:
	# Recover spread
	if _current_spread > 0.0:
		_current_spread = max(0.0, _current_spread - spread_recovery * delta)

## Start reloading.
func start_reload() -> void:
	if is_reloading or current_ammo >= max_ammo or reserve_ammo <= 0:
		return

	is_reloading = true
	await get_tree().create_timer(reload_time).timeout

	# Reload logic
	var needed: int = max_ammo - current_ammo
	var from_reserve: int = min(needed, reserve_ammo)
	current_ammo += from_reserve
	reserve_ammo -= from_reserve
	is_reloading = false

	ammo_changed.emit(current_ammo, max_ammo)

## Called when a hit is confirmed by the server.
func on_hit_confirmed(hit_pos: Vector3, damage: float, killed: bool) -> void:
	hit_marked.emit(hit_pos, damage, killed)
	if killed:
		player_killed.emit()

## Get current spread (degrees).
func get_current_spread() -> float:
	return _current_spread

## Get the current weapon fire direction including spread cone.
func get_spread_direction(base_direction: Vector3) -> Vector3:
	if _current_spread <= 0.001:
		return base_direction

	var spread_rad: float = deg_to_rad(_current_spread)
	var theta: float = randf() * TAU
	var phi: float = randf() * spread_rad

	# Create a random perpendicular vector
	var up := Vector3.UP
	if abs(base_direction.dot(up)) > 0.99:
		up = Vector3.RIGHT
	var right := base_direction.cross(up).normalized()
	up = right.cross(base_direction).normalized()

	var offset := right * sin(theta) * sin(phi) + up * cos(theta) * sin(phi)
	return (base_direction + offset).normalized()

## Reset weapon state (for respawn).
func reset() -> void:
	current_ammo = max_ammo
	reserve_ammo = start_ammo
	is_reloading = false
	_current_spread = 0.0
	_last_shot_time = 0.0
	ammo_changed.emit(current_ammo, max_ammo)
