## NetworkManager — ENet Transport + Player Replication Singleton
##
## Autoload that wraps Godot 4's ENetMultiplayerPeer and provides
## player spawn/despawn broadcasting to all connected clients.
## Server creates RPC broadcasts; clients receive and emit signals.
##
## Architecture:
##   server mode   → start_server(port)   → ENetMultiplayerPeer server
##   client mode   → join_server(host,port)→ ENetMultiplayerPeer client
##   replication   → _broadcast_spawn_player / _broadcast_despawn_player
##                   (server→all clients RPC)
##
## Channels (3-lane layout per Phase 0 research):
##   0  unreliable-ordered → 128Hz input / transform deltas
##   1  reliable-ordered   → game events, spawn, damage, chat
##   2  unreliable         → telemetry / VOIP metadata

extends Node

# ---------------------------------------------------------------------------
# Signals
# ---------------------------------------------------------------------------
signal server_started(port: int)
signal server_stopped()
signal player_connected(peer_id: int)
signal player_disconnected(peer_id: int)
signal connection_succeeded()
signal connection_failed(error_message: String)

# --- Player replication signals (emitted on all peers after RPC broadcast) ---
signal remote_player_spawned(peer_id: int, pos: Vector3)
signal remote_player_despawned(peer_id: int)

# --- Client prediction signals ---
## Emitted on the server when a client sends input. payload: {peer_id, tick, input_dict}
signal client_input_received(peer_id: int, tick: int, input_dict: Dictionary)
## Emitted on the client when the server sends authoritative state.
## entity_id: the simulation entity this state belongs to.
signal server_state_received(entity_id: int, snapshot_dict: Dictionary)

# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------
const DEFAULT_PORT: int = 34197
const CHANNELS: int = 3          # 0=input, 1=events, 2=telemetry

# Max clients is read from ServerConfig at start_server() time.
# This default is used before ServerConfig is available.
var max_clients: int = 16

# Channel indices
enum Chan {
	INPUT = 0,
	EVENTS = 1,
	TELEMETRY = 2,
}

# ---------------------------------------------------------------------------
# State
# ---------------------------------------------------------------------------
var is_server: bool = false : get = _is_server
var is_client: bool = false : get = _is_client
var peer: ENetMultiplayerPeer = null

func _is_server() -> bool:
	return is_server

func _is_client() -> bool:
	return is_client

# ---------------------------------------------------------------------------
# Server API
# ---------------------------------------------------------------------------
## Start a dedicated server on [port].
## Uses max_clients (which should be set from ServerConfig before calling).
## Returns OK or ERR_* on failure.
func start_server(port: int = DEFAULT_PORT) -> Error:
	if peer and peer.get_connection_status() != MultiplayerPeer.CONNECTION_DISCONNECTED:
		stop()

	# If ServerConfig is available, use it for max_clients
	if ServerConfig and ServerConfig.has_method(&"get_config_path"):
		max_clients = ServerConfig.max_players

	peer = ENetMultiplayerPeer.new()
	peer.set_bind_ip("*")
	
	var err: Error = peer.create_server(port, max_clients, CHANNELS, 0, 0)
	if err != OK:
		peer = null
		return err

	multiplayer.multiplayer_peer = peer
	multiplayer.multiplayer_peer.peer_connected.connect(_on_peer_connected)
	multiplayer.multiplayer_peer.peer_disconnected.connect(_on_peer_disconnected)

	is_server = true
	server_started.emit(port)
	print("[NetworkManager] Server started on port %d" % port)
	return OK

## Stop the server / disconnect.
func stop() -> void:
	if not peer:
		return
	
	if is_server:
		multiplayer.multiplayer_peer.peer_connected.disconnect(_on_peer_connected)
		multiplayer.multiplayer_peer.peer_disconnected.disconnect(_on_peer_disconnected)
	
	peer.close()
	multiplayer.multiplayer_peer = null
	peer = null
	is_server = false
	is_client = false
	server_stopped.emit()
	print("[NetworkManager] Stopped")

# ---------------------------------------------------------------------------
# Client API
# ---------------------------------------------------------------------------
## Connect to a remote server.
func join_server(host: String, port: int = DEFAULT_PORT) -> Error:
	if peer and peer.get_connection_status() != MultiplayerPeer.CONNECTION_DISCONNECTED:
		stop()

	peer = ENetMultiplayerPeer.new()
	var err: Error = peer.create_client(host, port, CHANNELS, 0, 0)
	if err != OK:
		peer = null
		return err

	multiplayer.multiplayer_peer = peer
	connection_succeeded.emit()
	print("[NetworkManager] Connecting to %s:%d ..." % [host, port])

	is_client = true
	return OK

# ---------------------------------------------------------------------------
# Player Replication RPCs (broadcast server → all clients)
# ---------------------------------------------------------------------------
## Server calls this when a new player joins.
## Broadcasts to all clients so they can create a visual player node.
@rpc("authority", "call_local", "reliable")
func broadcast_spawn_player(peer_id: int, pos: Vector3, is_team_a: bool) -> void:
	if not multiplayer.is_server():
		print("[NetworkManager] Client received spawn: peer=%d at (%.1f, %.1f)" % [peer_id, pos.x, pos.z])
	remote_player_spawned.emit(peer_id, pos)

## Server calls this when a player leaves.
@rpc("authority", "call_local", "reliable")
func broadcast_despawn_player(peer_id: int) -> void:
	if not multiplayer.is_server():
		print("[NetworkManager] Client received despawn: peer=%d" % peer_id)
	remote_player_despawned.emit(peer_id)

# ---------------------------------------------------------------------------
# Client Prediction RPCs (Phase 1 — client-side prediction)
# ---------------------------------------------------------------------------

## Client → Server: send raw input for the given local tick.
## Called by ClientPrediction.on_after_tick() each physics tick.
## Uses ENet channel 0 (unreliable-ordered) for lowest-latency input delivery.
@rpc("unreliable", "any_peer", "call_remote", Chan.INPUT)
func send_client_input(tick: int, input_dict: Dictionary) -> void:
	if not multiplayer.is_server():
		return
	var peer_id: int = multiplayer.get_remote_sender_id()
	client_input_received.emit(peer_id, tick, input_dict)


## Server → Client: send authoritative entity snapshot for reconciliation.
## Called by server-side code (e.g. GameServer after each tick).
## entity_id identifies which simulation entity this state belongs to.
## Uses ENet channel 1 (reliable-ordered) so state corrections are not dropped.
@rpc("unreliable", "authority", "call_remote", Chan.EVENTS)
func send_server_state(entity_id: int, snapshot_dict: Dictionary) -> void:
	if multiplayer.is_server():
		return
	server_state_received.emit(entity_id, snapshot_dict)


# ---------------------------------------------------------------------------
# Event handlers
# ---------------------------------------------------------------------------
func _on_peer_connected(id: int) -> void:
	print("[NetworkManager] Peer connected: %d" % id)
	player_connected.emit(id)

func _on_peer_disconnected(id: int) -> void:
	print("[NetworkManager] Peer disconnected: %d" % id)
	player_disconnected.emit(id)

func _process(_delta: float) -> void:
	# Godot does internal ENet polling via MultiplayerAPI;
	# explicit polling is reserved for future custom packet handling.
	pass

func _exit_tree() -> void:
	stop()
