extends Node

# Network Manager - Handles multiplayer connections and player spawning
# Supports both hosting and joining games
# Auto-detects platform and uses appropriate networking:
# - ENet for native builds (Windows, Linux, Mac)
# - WebRTC for web builds (requires signaling server)

signal player_connected(peer_id, player_info)
signal player_disconnected(peer_id, player_info)
signal connection_failed()
signal connection_succeeded()
signal rooms_fetched(rooms: Array)  # Forwarded from room_registry

const DEFAULT_PORT = 21212
const MAX_PLAYERS = 8
const MATCHBOX_SERVER = "wss://matchbox.thefirstboss.com"
const STUN_SERVER = "stun:ruinborn.thefirstboss.com:3478"
const TURN_SERVER = "turn:ruinborn.thefirstboss.com:3478"
const TURN_USERNAME = "myuser"
const TURN_PASSWORD = "mypassword"

var players_info = {} # Dictionary of peer_id -> {local_player_count: int, player_names: []}
var local_player_count = 1 # How many local players on this machine
var is_hosting = false
var show_room_labels = false # Show room labels when in debug mode
var network_mode: int = 0  # 0 = ENet, 1 = WebRTC, 2 = WebSocket
var room_id = "" # Room ID for Matchbox (WebRTC) or WebSocket server URL
var matchbox_client: Node = null # Matchbox client instance
const WEBSOCKET_SERVER_URL = "ws://ruinborn.thefirstboss.com:21212"  # WebSocket server URL
var room_registry: Node = null # Room registry client for room discovery

# Reconnection state (for joiners only)
var reconnection_room_id: String = ""  # Store room_id for reconnection
var reconnection_level: int = 0  # Store level for reconnection
var reconnection_attempting: bool = false  # Track if we're attempting to reconnect
var reconnection_timer: float = 0.0  # Timer for reconnection delay
const RECONNECTION_DELAY: float = 1.0  # Delay before attempting reconnection (reduced from 2.0)

# Logging - use LogManager for categorized logging
func log_print(message: String):
	LogManager.log(message, LogManager.CATEGORY_NETWORK)

func log_error(message: String):
	LogManager.log_error(message, LogManager.CATEGORY_NETWORK)

func _ready():
	# Detect if running in browser - default to WebRTC on web, ENet on native
	if OS.get_name() == "Web":
		network_mode = 1  # WebRTC default for web
		log_print("NetworkManager: Detected Web platform, defaulting to WebRTC")
		log_print("NetworkManager: Matchbox server: " + MATCHBOX_SERVER)
	else:
		network_mode = 0  # ENet default for native
		log_print("NetworkManager: Using ENet by default for native platform")
		log_print("NetworkManager: You can switch network modes in the menu")
	
	# Connect multiplayer signals
	multiplayer.peer_connected.connect(_on_peer_connected)
	multiplayer.peer_disconnected.connect(_on_peer_disconnected)
	multiplayer.connected_to_server.connect(_on_connected_to_server)
	multiplayer.connection_failed.connect(_on_connection_failed)
	multiplayer.server_disconnected.connect(_on_server_disconnected)
	
	# Create room registry client
	var registry_script = load("res://scripts/room_registry_client.gd")
	room_registry = Node.new()
	room_registry.set_script(registry_script)
	add_child(room_registry)
	room_registry.rooms_fetched.connect(_on_rooms_fetched)

func set_network_mode(mode: int):
	# 0 = ENet, 1 = WebRTC, 2 = WebSocket
	# WebRTC is now available on native platforms with webrtc-native extension
	network_mode = mode
	match mode:
		0:
			log_print("NetworkManager: ENet mode enabled")
		1:
			log_print("NetworkManager: WebRTC mode enabled")
			log_print("NetworkManager: Matchbox server: " + MATCHBOX_SERVER)
			if OS.get_name() != "Web":
				log_print("NetworkManager: Using webrtc-native extension for native platform")
		2:
			log_print("NetworkManager: WebSocket mode enabled")
			log_print("NetworkManager: WebSocket server: " + WEBSOCKET_SERVER_URL)

func force_webrtc_mode(enable: bool):
	# Legacy function for backwards compatibility
	if enable:
		set_network_mode(1)
	else:
		set_network_mode(0)

func host_game(port: int = DEFAULT_PORT, matchbox_room: String = "") -> bool:
	var peer
	var error
	
	if network_mode == 1:  # WebRTC
		# WebRTC for browser builds using Matchbox signaling
		# Generate room ID if not provided
		if matchbox_room.is_empty():
			room_id = _generate_room_id()
		else:
			room_id = matchbox_room
		
		log_print("NetworkManager: Creating WebRTC host with room ID: " + room_id)
		log_print("NetworkManager: Share this room code with players!")
		log_print("NetworkManager: Matchbox URL: " + MATCHBOX_SERVER + "/" + room_id)
		
		# Create Matchbox client
		var matchbox_script = load("res://scripts/matchbox_client.gd")
		matchbox_client = Node.new()
		matchbox_client.set_script(matchbox_script)
		add_child(matchbox_client)
		
		# Connect Matchbox signals
		matchbox_client.peer_joined.connect(_on_matchbox_peer_joined)
		matchbox_client.peer_left.connect(_on_matchbox_peer_left)
		matchbox_client.peer_connected.connect(_on_matchbox_peer_connected)
		matchbox_client.connection_succeeded.connect(_on_matchbox_connected)
		matchbox_client.connection_failed.connect(_on_matchbox_connection_failed)
		matchbox_client.webrtc_ready.connect(_on_matchbox_webrtc_ready)
		
		# Setup WebRTC peer (will be initialized when Matchbox connects)
		# For now, we'll set it up after Matchbox connection
		is_hosting = true
		
		# Connect to Matchbox room (pass is_hosting = true)
		if not matchbox_client.connect_to_room(room_id, true):
			log_error("Failed to connect to Matchbox room")
			return false
		
		# Register the host as a player (peer_id 1)
		players_info[1] = {
			"local_player_count": local_player_count,
			"player_names": _generate_player_names(local_player_count, 1)
		}
		
		return true
	elif network_mode == 2:  # WebSocket
		# WebSocket for both native and web builds
		# Generate room ID if not provided
		if matchbox_room.is_empty():
			room_id = _generate_room_id()
		else:
			room_id = matchbox_room
		
		log_print("NetworkManager: Creating WebSocket host with room ID: " + room_id)
		log_print("NetworkManager: Share this room code with players!")
		log_print("NetworkManager: WebSocket URL: " + WEBSOCKET_SERVER_URL + "/" + room_id)
		
		peer = WebSocketMultiplayerPeer.new()
		var url = WEBSOCKET_SERVER_URL + "/" + room_id
		error = peer.create_server(port)
		
		if error != OK:
			log_error("Failed to create WebSocket server: " + str(error))
			return false
		
		multiplayer.multiplayer_peer = peer
		is_hosting = true
		
		print("WebSocket server started on port ", port)
		print("Players can join at: ", url)
		
		# Register the host as a player
		var my_id = multiplayer.get_unique_id()
		players_info[my_id] = {
			"local_player_count": local_player_count,
			"player_names": _generate_player_names(local_player_count, my_id)
		}
		
		return true
	else:  # ENet (mode 0)
		# ENet for native builds
		peer = ENetMultiplayerPeer.new()
		error = peer.create_server(port, MAX_PLAYERS)
		
		if error != OK:
			push_error("Failed to create ENet server: " + str(error))
			return false
		
		multiplayer.multiplayer_peer = peer
		is_hosting = true
		
		print("ENet server started on port ", port)
		print("Players can join at: ", get_local_ip(), ":", port)
		
		# Register the host as a player
		var my_id = multiplayer.get_unique_id()
		players_info[my_id] = {
			"local_player_count": local_player_count,
			"player_names": _generate_player_names(local_player_count, my_id)
		}
		
		return true

func join_game(address: String, port: int = DEFAULT_PORT) -> bool:
	var peer
	var error
	
	if network_mode == 1:  # WebRTC
		# WebRTC for browser builds using Matchbox signaling
		# 'address' is the room ID for WebRTC
		room_id = address
		
		log_print("NetworkManager: Joining WebRTC game with room ID: " + room_id)
		log_print("NetworkManager: Matchbox URL: " + MATCHBOX_SERVER + "/" + room_id)
		
		# Create Matchbox client
		var matchbox_script = load("res://scripts/matchbox_client.gd")
		matchbox_client = Node.new()
		matchbox_client.set_script(matchbox_script)
		add_child(matchbox_client)
		
		# Connect Matchbox signals
		matchbox_client.peer_joined.connect(_on_matchbox_peer_joined)
		matchbox_client.peer_left.connect(_on_matchbox_peer_left)
		matchbox_client.peer_connected.connect(_on_matchbox_peer_connected)
		matchbox_client.connection_succeeded.connect(_on_matchbox_connected)
		matchbox_client.connection_failed.connect(_on_matchbox_connection_failed)
		matchbox_client.webrtc_ready.connect(_on_matchbox_webrtc_ready)
		
		is_hosting = false
		
		# Connect to Matchbox room (pass is_hosting = false)
		if not matchbox_client.connect_to_room(room_id, false):
			log_error("Failed to connect to Matchbox room")
			return false
		
		return true
	elif network_mode == 2:  # WebSocket
		# WebSocket for both native and web builds
		# 'address' is the room ID for WebSocket
		room_id = address
		
		log_print("NetworkManager: Joining WebSocket game with room ID: " + room_id)
		log_print("NetworkManager: WebSocket URL: " + WEBSOCKET_SERVER_URL + "/" + room_id)
		
		peer = WebSocketMultiplayerPeer.new()
		var url = WEBSOCKET_SERVER_URL + "/" + room_id
		error = peer.create_client(url)
		
		if error != OK:
			log_error("Failed to create WebSocket client: " + str(error))
			return false
		
		multiplayer.multiplayer_peer = peer
		is_hosting = false
		
		log_print("Attempting to connect to WebSocket server: " + url)
		
		return true
	else:  # ENet (mode 0)
		# ENet for native builds
		peer = ENetMultiplayerPeer.new()
		error = peer.create_client(address, port)
		
		if error != OK:
			log_error("Failed to create ENet client: " + str(error))
			return false
		
		multiplayer.multiplayer_peer = peer
		is_hosting = false
		
		log_print("Attempting to connect to " + address + ":" + str(port))
		
		return true

func refresh_webrtc_servers(room_override: String = "") -> bool:
	# Recreate Matchbox client to refresh WebRTC signaling connection
	if network_mode != 1:
		return false
	
	var target_room = room_override if not room_override.is_empty() else room_id
	if target_room.is_empty():
		log_error("NetworkManager: Cannot refresh WebRTC servers - no room ID")
		return false
	
	# Tear down existing client
	if matchbox_client:
		matchbox_client.disconnect_from_room()
		matchbox_client.queue_free()
		matchbox_client = null
	
	# Recreate Matchbox client
	var matchbox_script = load("res://scripts/matchbox_client.gd")
	matchbox_client = Node.new()
	matchbox_client.set_script(matchbox_script)
	add_child(matchbox_client)
	
	matchbox_client.peer_joined.connect(_on_matchbox_peer_joined)
	matchbox_client.peer_left.connect(_on_matchbox_peer_left)
	matchbox_client.peer_connected.connect(_on_matchbox_peer_connected)
	matchbox_client.connection_succeeded.connect(_on_matchbox_connected)
	matchbox_client.connection_failed.connect(_on_matchbox_connection_failed)
	matchbox_client.webrtc_ready.connect(_on_matchbox_webrtc_ready)
	
	is_hosting = false
	room_id = target_room
	log_print("NetworkManager: Refreshing WebRTC servers for room ID: " + room_id)
	
	if not matchbox_client.connect_to_room(room_id, false):
		log_error("NetworkManager: Failed to reconnect to Matchbox room during refresh")
		return false
	
	return true

func disconnect_from_game():
	# Unregister room from registry
	if room_registry:
		room_registry.unregister_room()
	
	if matchbox_client:
		matchbox_client.disconnect_from_room()
		matchbox_client.queue_free()
		matchbox_client = null
	
	if multiplayer.multiplayer_peer:
		multiplayer.multiplayer_peer.close()
		multiplayer.multiplayer_peer = null
	players_info.clear()
	is_hosting = false
	room_id = ""

func set_local_player_count(count: int):
	local_player_count = max(1, min(count, 4)) # Limit to 1-4 local players

func _generate_player_names(count: int, peer_id: int) -> Array:
	var names = []
	for i in range(count):
		if count == 1:
			# Only one player - don't add index suffix
			names.append("Player%d" % peer_id)
		else:
			# Multiple players - add index
			names.append("Player%d_%d" % [peer_id, i + 1])
	return names

# Called when a peer connects to the server
func _on_peer_connected(id: int):
	log_print("Peer connected: " + str(id))
	# Peer is now actually available for RPCs - emit player_connected signal
	# Make sure player info is registered (should have been done in _on_matchbox_peer_connected)
	if not players_info.has(id):
		# Fallback: register default player info if not already registered
		players_info[id] = {
			"local_player_count": 1,  # Default, will be updated via RPC
			"player_names": _generate_player_names(1, id)
		}
	
	# For joiners, emit connection_succeeded when the first peer (host) connects
	# This ensures the connection is actually established and RPCs can be received
	# Note: On web, multiplayer.peer_connected might not fire automatically,
	# so we also check in _on_matchbox_peer_connected as a fallback
	if not is_hosting and id == 1:  # Host connected to joiner
		log_print("NetworkManager: Joiner - host connected via multiplayer.peer_connected, emitting connection_succeeded")
		connection_succeeded.emit()
	
	# Emit player connected signal (peer is now available for RPCs)
	player_connected.emit(id, players_info[id])
	
	# Update room registry if hosting (player count changed)
	if is_hosting and room_registry and not room_id.is_empty():
		var player_count = get_all_player_ids().size()
		var _level = 1
		var game_world = get_tree().get_first_node_in_group("game_world")
		if game_world:
			_level = game_world.current_level
		room_registry.send_room_update(player_count)

# Called when a peer disconnects
func _on_peer_disconnected(id: int):
	log_print("Peer disconnected: " + str(id))
	# Get player_info before erasing it
	var player_info = {}
	if players_info.has(id):
		player_info = players_info[id]
		players_info.erase(id)
	player_disconnected.emit(id, player_info)
	
	# If joiner and host (peer ID 1) disconnected, trigger server disconnection logic
	if not is_hosting and id == 1:
		log_print("NetworkManager: Host (peer ID 1) disconnected, triggering reconnection...")
		_on_server_disconnected()
		return
	
	# Update room registry if hosting (player count changed)
	if is_hosting and room_registry and not room_id.is_empty():
		var player_count = get_all_player_ids().size()
		var _level = 1
		var game_world = get_tree().get_first_node_in_group("game_world")
		if game_world:
			_level = game_world.current_level
		room_registry.send_room_update(player_count)

# Called on client when successfully connected to server
func _on_connected_to_server():
	log_print("Successfully connected to server")
	connection_succeeded.emit()
	
	# Send our player info to the server
	var my_id = multiplayer.get_unique_id()
	_register_player.rpc_id(1, my_id, local_player_count)
	
	# Update room registry if hosting (player count changed)
	if is_hosting and room_registry and not room_id.is_empty():
		var player_count = get_all_player_ids().size()
		var _level = 1
		var game_world = get_tree().get_first_node_in_group("game_world")
		if game_world:
			_level = game_world.current_level
		room_registry.send_room_update(player_count)

# Called on client when connection fails
func _on_connection_failed():
	log_print("Connection failed")
	multiplayer.multiplayer_peer = null
	connection_failed.emit()

# Called on client when disconnected from server
func _on_server_disconnected():
	log_print("Server disconnected")
	
	# Store reconnection info if we're a joiner (not host)
	if not is_hosting and not room_id.is_empty():
		reconnection_room_id = room_id
		# Get current level from game_world
		var game_world = get_tree().get_first_node_in_group("game_world")
		if game_world and "current_level" in game_world:
			reconnection_level = game_world.current_level
			log_print("NetworkManager: Stored reconnection info - room_id: " + reconnection_room_id + ", level: " + str(reconnection_level))
		else:
			reconnection_level = 0  # Unknown level
		
		# Add local-only chat message "Connection lost..."
		if game_world:
			var chat_ui = game_world.get_node_or_null("ChatUI")
			if chat_ui and chat_ui.has_method("add_local_message"):
				chat_ui.add_local_message("System", "Connection lost...")
		
		# Start reconnection attempt
		reconnection_attempting = true
		reconnection_timer = RECONNECTION_DELAY
		log_print("NetworkManager: Will attempt to reconnect in " + str(RECONNECTION_DELAY) + " seconds")
		
		# Emit connection_failed signal to show UI message
		connection_failed.emit()
	
	multiplayer.multiplayer_peer = null
	players_info.clear()

# RPC to register a player with the server
@rpc("any_peer", "reliable")
func _register_player(peer_id: int, local_count: int):
	if not multiplayer.is_server():
		return
	
	players_info[peer_id] = {
		"local_player_count": local_count,
		"player_names": _generate_player_names(local_count, peer_id)
	}
	
	print("NetworkManager: Registered player ", peer_id, " with ", local_count, " local players")
	print("NetworkManager: Total players_info: ", players_info)
	
	# Sync all player info to the new client (so they know about everyone)
	# Defer RPC to ensure the peer is fully registered in the multiplayer system
	call_deferred("_sync_players_to_peer", peer_id, players_info)
	
	# Notify all clients (including the new one) about the new player
	_notify_player_joined.rpc(peer_id, players_info[peer_id])
	
	# Emit signal on server
	player_connected.emit(peer_id, players_info[peer_id])

func _sync_players_to_peer(peer_id: int, all_players_info: Dictionary):
	# Helper function to send _sync_players RPC (called via call_deferred)
	if multiplayer.is_server():
		_sync_players.rpc_id(peer_id, all_players_info)

# RPC to sync all player info to a newly connected client
@rpc("authority", "reliable")
func _sync_players(all_players_info: Dictionary):
	players_info = all_players_info
	print("NetworkManager: Client synced all player info: ", players_info)
	
	# Emit signals for all existing players (so game world can spawn them)
	for peer_id in players_info.keys():
		# Emit for ALL players including ourselves (game world will handle it)
		print("NetworkManager: Emitting player_connected for peer ", peer_id)
		player_connected.emit(peer_id, players_info[peer_id])

# RPC to notify all clients when a player joins
@rpc("authority", "reliable")
func _notify_player_joined(peer_id: int, player_info: Dictionary):
	print("NetworkManager: Notified about player ", peer_id, " joining")
	if not players_info.has(peer_id):
		players_info[peer_id] = player_info
	player_connected.emit(peer_id, player_info)

func get_all_player_ids() -> Array:
	return players_info.keys()

func get_player_info(peer_id: int) -> Dictionary:
	return players_info.get(peer_id, {})

# Matchbox callback handlers
func _on_matchbox_connected(was_reconnecting: bool = false):
	log_print("NetworkManager: Connected to Matchbox server")
	# WebRTC peer will be set up when Welcome message is received with our peer ID
	
	# If host was reconnecting, show colorful reconnection message
	if is_hosting and was_reconnecting:
		var game_world = get_tree().get_first_node_in_group("game_world")
		if game_world:
			var chat_ui = game_world.get_node_or_null("ChatUI")
			if chat_ui and chat_ui.has_method("add_colorful_local_message"):
				chat_ui.add_colorful_local_message("System", "Matchbox connection re-established!")
	
	# For joiners: if we're connected to Matchbox but in reconnection mode, 
	# we're waiting for the host to reconnect and assign our peer ID
	# Don't clear reconnection state yet - wait for peer ID assignment
	if not is_hosting and reconnection_attempting and matchbox_client:
		if matchbox_client.is_network_connected:
			log_print("NetworkManager: Joiner connected to Matchbox, waiting for host to reconnect and assign peer ID...")
			# Don't clear reconnection_attempting yet - wait for peer ID assignment
			# The matchbox_client will clear it when peer ID is assigned

func _on_matchbox_webrtc_ready():
	log_print("NetworkManager: WebRTC mesh is ready")
	
	# Register room with room registry if hosting
	if is_hosting and room_registry and not room_id.is_empty():
		var player_count = get_all_player_ids().size()
		var level = 1
		var game_world = get_tree().get_first_node_in_group("game_world")
		if game_world:
			level = game_world.current_level
		room_registry.register_room(room_id, player_count, level)
		log_print("NetworkManager: Registered room " + room_id + " with registry")
		
		# Host can start immediately
		connection_succeeded.emit()
	else:
		# For joiners, wait for peer_connected to be emitted before starting
		# This ensures the connection is actually established and RPCs can be received
		log_print("NetworkManager: Joiner - waiting for peer connection before starting game")
		# connection_succeeded will be emitted in _on_peer_connected when the first peer (host) connects

func _on_matchbox_connection_failed():
	log_print("NetworkManager: Failed to connect to Matchbox server")
	
	# If hosting, add local chat message and show UI
	if is_hosting:
		var game_world = get_tree().get_first_node_in_group("game_world")
		if game_world:
			var chat_ui = game_world.get_node_or_null("ChatUI")
			if chat_ui and chat_ui.has_method("add_local_message"):
				chat_ui.add_local_message("System", "Lost connection to matchbox server...")
		
		# Emit connection_failed to show UI message
		connection_failed.emit()
	else:
		# For joiners, just emit the signal (they already have their own handling)
		connection_failed.emit()

func _on_matchbox_peer_joined(peer_id: int):
	log_print("NetworkManager: Matchbox peer joined: " + str(peer_id))
	# Peer connection will be created by Matchbox client
	# Once connected, we'll add it to WebRTC mesh

func _on_matchbox_peer_left(peer_id: int):
	print("NetworkManager: Matchbox peer left: ", peer_id)
	# Player disconnect will be handled by multiplayer signals

func _on_matchbox_peer_connected(peer_id: int):
	print("NetworkManager: Matchbox peer connected: ", peer_id)
	# Note: Peer connection is already added to WebRTC mesh in _create_peer_connection()
	# This signal is emitted after the answer is received, but the peer might not be available for RPCs yet
	# We'll emit player_connected from _on_peer_connected() when the peer is actually available
	
	# Register player info (we'll emit player_connected when multiplayer.peer_connected fires)
	if not players_info.has(peer_id):
		players_info[peer_id] = {
			"local_player_count": 1,  # Default, will be updated via RPC
			"player_names": _generate_player_names(1, peer_id)
		}
	
	# For joiners, if this is the host (peer_id 1) and connection_succeeded hasn't been emitted yet,
	# wait a bit and then emit it as a fallback (in case multiplayer.peer_connected doesn't fire on web)
	if not is_hosting and peer_id == 1:
		# Check if we've already set up a timer for this peer (prevent multiple timers)
		if has_meta("connection_succeeded_timer_set"):
			log_print("NetworkManager: Joiner - timer already set for peer " + str(peer_id) + ", skipping")
			return
		
		set_meta("connection_succeeded_timer_set", true)
		log_print("NetworkManager: Joiner - host connected via Matchbox, waiting for multiplayer.peer_connected...")
		# Store reference to scene tree before timer
		var scene_tree = get_tree()
		if not scene_tree:
			# Node is not in scene tree, emit immediately
			log_print("NetworkManager: Joiner - node not in scene tree, emitting connection_succeeded immediately")
			connection_succeeded.emit()
			return
		
		# Wait a short time to see if multiplayer.peer_connected fires
		# If it doesn't fire within 2 seconds, emit connection_succeeded anyway
		var timer = scene_tree.create_timer(2.0)
		timer.timeout.connect(func():
			# Check if connection_succeeded was already emitted (by checking if we're in game scene)
			if not is_inside_tree():
				log_print("NetworkManager: Joiner - node no longer in tree, skipping fallback emit")
				return
			
			var current_tree = get_tree()
			if not current_tree:
				log_print("NetworkManager: Joiner - no scene tree, skipping fallback emit")
				return
			
			var game_world = current_tree.get_first_node_in_group("game_world")
			if not game_world:
				# Still in main menu, so connection_succeeded wasn't emitted
				log_print("NetworkManager: Joiner - multiplayer.peer_connected didn't fire, emitting connection_succeeded as fallback")
				# Emit signal immediately - NetworkManager is an autoload so it should always be in tree
				log_print("NetworkManager: is_inside_tree() = " + str(is_inside_tree()) + ", emitting connection_succeeded signal (fallback)")
				connection_succeeded.emit()
				log_print("NetworkManager: connection_succeeded signal emitted")
				
				# Also try to get GameUI and call _start_game directly as a fallback
				# Search for GameUI by script (it has game_ui.gd script)
				var game_ui = null
				var root = current_tree.get_root()
				
				# Try different possible paths (GameUI is at /root/GameUI)
				var possible_paths = [
					"/root/GameUI",
					"GameUI",
					"Main/GameUI",
					"/root/Main/GameUI"
				]
				
				for path in possible_paths:
					var node = root.get_node_or_null(path)
					if node and node.get_script() and node.get_script().resource_path.ends_with("game_ui.gd"):
						game_ui = node
						log_print("NetworkManager: Found GameUI at path: " + path)
						break
				
				# If not found by path, search all nodes recursively
				if not game_ui:
					game_ui = _find_node_by_script(root, "game_ui.gd")
					if game_ui:
						log_print("NetworkManager: Found GameUI by recursive script search: " + str(game_ui.get_path()))
				
				if game_ui and game_ui.has_method("_start_game"):
					log_print("NetworkManager: Found GameUI node, calling _start_game directly as fallback")
					game_ui.call_deferred("_start_game")
				else:
					log_print("NetworkManager: GameUI node not found or doesn't have _start_game method")
					# Try to change scene directly as last resort
					var game_world_scene = load("res://scenes/game_world.tscn")
					if game_world_scene:
						log_print("NetworkManager: Attempting to change scene directly to game_world.tscn")
						current_tree.change_scene_to_packed(game_world_scene)
			else:
				log_print("NetworkManager: Joiner - already in game scene, connection_succeeded was already emitted")
		)
	
	# On web, multiplayer.peer_connected might not fire for either host or joiner
	# So we need to manually emit player_connected as fallback
	# For host: emit player_connected for client peers (peer_id > 1)
	# For joiner: emit player_connected for host (peer_id == 1)
	
	# Host fallback: emit player_connected for client connections
	if is_hosting and peer_id > 1:
		var host_fallback_key = "player_connected_fallback_host_" + str(peer_id)
		if has_meta(host_fallback_key):
			return
		
		set_meta(host_fallback_key, true)
		
		# Wait a bit to see if multiplayer.peer_connected fires first
		get_tree().create_timer(1.0).timeout.connect(func():
			if not is_inside_tree():
				return
			
			# Check if player_connected was already emitted (check if multiplayer peer is recognized)
			if players_info.has(peer_id):
				log_print("NetworkManager: Emitting player_connected as fallback for peer " + str(peer_id) + " (host, Matchbox connection established, multiplayer.peer_connected didn't fire)")
				player_connected.emit(peer_id, players_info[peer_id])
				remove_meta(host_fallback_key)
		)
	
	# Joiner fallback: emit player_connected for host connection
	if not is_hosting and peer_id == 1:
		# Register player info if not already registered
		if not players_info.has(peer_id):
			players_info[peer_id] = {
				"local_player_count": 1,  # Default, will be updated via RPC
				"player_names": _generate_player_names(1, peer_id)
			}
		
		var fallback_key = "player_connected_fallback_" + str(peer_id)
		if has_meta(fallback_key):
			# Already scheduled fallback emission
			return
		
		set_meta(fallback_key, true)
		
		# Wait a bit to see if multiplayer.peer_connected fires first
		get_tree().create_timer(1.0).timeout.connect(func():
			if not is_inside_tree():
				return
			
			# Check if player_connected was already emitted by checking if multiplayer peer is recognized
			# On web, we can't reliably check this, so we just emit anyway (idempotency handled in GameWorld)
			if players_info.has(peer_id):
				log_print("NetworkManager: Emitting player_connected as fallback for peer " + str(peer_id) + " (Matchbox connection established, multiplayer.peer_connected didn't fire)")
				player_connected.emit(peer_id, players_info[peer_id])
				remove_meta(fallback_key)
		)

func _emit_connection_succeeded_safe():
	"""Safely emit connection_succeeded signal - checks if node is still valid"""
	if is_inside_tree():
		log_print("NetworkManager: Emitting connection_succeeded signal (fallback)")
		connection_succeeded.emit()
	else:
		log_print("NetworkManager: Cannot emit connection_succeeded - node not in tree")

func _find_node_by_script(node: Node, script_name: String) -> Node:
	"""Recursively search for a node with a specific script"""
	if node.get_script() and node.get_script().resource_path.ends_with(script_name):
		return node
	
	for child in node.get_children():
		var result = _find_node_by_script(child, script_name)
		if result:
			return result
	
	return null

func get_room_id() -> String:
	return room_id

func _process(delta: float):
	# Handle reconnection timer
	if reconnection_attempting:
		reconnection_timer -= delta
		if reconnection_timer <= 0.0:
			reconnection_attempting = false
			_attempt_reconnect()

func _attempt_reconnect():
	# Only reconnect if we're a joiner and have reconnection info
	if is_hosting or reconnection_room_id.is_empty():
		log_print("NetworkManager: Cannot reconnect - is_hosting: " + str(is_hosting) + ", reconnection_room_id: " + reconnection_room_id)
		return
	
	# Check if we're already connected to Matchbox (host might have reconnected and we're waiting for peer ID)
	if matchbox_client and ("is_network_connected" in matchbox_client) and matchbox_client.is_network_connected:
		log_print("NetworkManager: Already connected to Matchbox, waiting for host to assign peer ID...")
		# Cancel reconnection attempt - we're already connected, just waiting for host
		reconnection_attempting = false
		reconnection_timer = 0.0
		
		# Show chat message
		var game_world_waiting = get_tree().get_first_node_in_group("game_world")
		if game_world_waiting:
			var chat_ui = game_world_waiting.get_node_or_null("ChatUI")
			if chat_ui and chat_ui.has_method("add_local_message"):
				chat_ui.add_local_message("System", "Waiting for host to reconnect...")
		return
	
	log_print("NetworkManager: Attempting to reconnect to room: " + reconnection_room_id)
	
	# Show chat message for reconnection attempt
	var game_world = get_tree().get_first_node_in_group("game_world")
	if game_world:
		var chat_ui = game_world.get_node_or_null("ChatUI")
		if chat_ui and chat_ui.has_method("add_local_message"):
			chat_ui.add_local_message("System", "Attempting to reconnect...")
	
	# Attempt to reconnect using the stored room_id
	var success = join_game(reconnection_room_id)
	if not success:
		log_error("NetworkManager: Reconnection attempt failed, will retry in " + str(RECONNECTION_DELAY) + " seconds")
		# Retry after delay
		reconnection_attempting = true
		reconnection_timer = RECONNECTION_DELAY
		
		# Show chat message for failed attempt
		if game_world:
			var chat_ui = game_world.get_node_or_null("ChatUI")
			if chat_ui and chat_ui.has_method("add_local_message"):
				chat_ui.add_local_message("System", "Reconnection failed, retrying in " + str(int(RECONNECTION_DELAY)) + " seconds...")

func fetch_available_rooms() -> bool:
	"""Fetch available rooms from the registry"""
	if room_registry:
		return room_registry.fetch_available_rooms()
	return false

func _on_rooms_fetched(rooms: Array):
	"""Callback when rooms are fetched from registry"""
	log_print("NetworkManager: Received " + str(rooms.size()) + " available rooms")
	# Forward the signal to listeners
	rooms_fetched.emit(rooms)

func get_local_ip() -> String:
	var addresses = IP.get_local_addresses()
	for addr in addresses:
		# Skip localhost and IPv6
		if addr.begins_with("127.") or ":" in addr:
			continue
		return addr
	return "127.0.0.1"

func _generate_room_id() -> String:
	# Generate a random 6-character room code
	var chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"  # Avoid confusing chars like O/0, I/1
	var code = ""
	for i in range(6):
		code += chars[randi() % chars.length()]
	return code


func get_webrtc_peer() -> WebRTCMultiplayerPeer:
	if network_mode == 1 and multiplayer.multiplayer_peer is WebRTCMultiplayerPeer:
		return multiplayer.multiplayer_peer as WebRTCMultiplayerPeer
	return null

# Create a WebRTC peer connection with STUN and TURN servers configured
func create_peer_connection() -> WebRTCPeerConnection:
	var peer_connection = WebRTCPeerConnection.new()
	
	# Configure STUN and TURN servers according to Godot documentation format
	# Each server should be a separate entry in the iceServers array
	var ice_servers = []
	
	# Add STUN server as separate entry
	if not STUN_SERVER.is_empty():
		var stun_config = {
			"urls": [STUN_SERVER]  # urls must be an array
		}
		ice_servers.append(stun_config)
	
	# Add TURN server as separate entry with credentials
	if not TURN_SERVER.is_empty():
		# For TURN, create URLs array with appropriate transports
		# Note: libjuice (native builds) only supports UDP transport
		# WebAssembly (browser) supports both UDP and TCP/TLS
		var turn_urls = []
		var is_web = OS.get_name() == "Web"
		
		if TURN_SERVER.begins_with("turns:"):
			# TURNS over TLS: TCP transport (browser only, libjuice doesn't support it)
			if is_web:
				turn_urls.append(TURN_SERVER + "?transport=tcp")
			else:
				# Native builds: skip TLS (libjuice limitation), use UDP fallback if available
				print("NetworkManager: Skipping TLS transport for native build (libjuice limitation)")
				# Convert turns: to turn: and use UDP transport for native builds
				var udp_turn_server = TURN_SERVER.replace("turns:", "turn:")
				turn_urls.append(udp_turn_server + "?transport=udp")
		else:
			# Standard TURN: UDP works everywhere, TCP only on browser
			turn_urls.append(TURN_SERVER + "?transport=udp")
			if is_web:
				# Add TCP transport for browsers (can help with restrictive firewalls)
				turn_urls.append(TURN_SERVER + "?transport=tcp")
		
		# Create TURN server configuration
		var turn_config = {
			"urls": turn_urls  # urls must be an array
		}
		
		# Add credentials if configured (for long-term credentials)
		if not TURN_USERNAME.is_empty() and not TURN_PASSWORD.is_empty():
			turn_config["username"] = TURN_USERNAME
			turn_config["credential"] = TURN_PASSWORD
		
		ice_servers.append(turn_config)
	
	var config = {
		"iceServers": ice_servers
	}
	
	var error = peer_connection.initialize(config)
	if error != OK:
		push_error("Failed to initialize WebRTC peer connection: " + str(error))
		return null
	
	print("WebRTC peer connection initialized with STUN/TURN servers: ", STUN_SERVER, " / ", TURN_SERVER)
	return peer_connection

# Add a peer connection for WebRTC mesh networking
func add_webrtc_peer(peer_id: int) -> bool:
	var webrtc = get_webrtc_peer()
	if not webrtc:
		push_error("Not using WebRTC")
		return false
	
	var peer_connection = create_peer_connection()
	if not peer_connection:
		return false
	
	var error = webrtc.add_peer(peer_connection, peer_id)
	
	if error != OK:
		push_error("Failed to add WebRTC peer: " + str(error))
		return false
	
	print("Added WebRTC peer: ", peer_id, " with STUN server configured")
	return true

# Get the peer connection info for a specific peer
func get_peer_connection(peer_id: int) -> Dictionary:
	var webrtc = get_webrtc_peer()
	if not webrtc:
		return {}
	
	return webrtc.get_peer(peer_id)

# Get connection info to share with remote peer (for manual signaling)
# This would typically be sent via a signaling server
# Note: With matchbox, signaling is handled automatically
func get_connection_offer(peer_id: int) -> Dictionary:
	var peer_conn = get_peer_connection(peer_id)
	if peer_conn.is_empty():
		return {}
	
	# In a real implementation, you'd get the SDP offer from the peer connection
	# With matchbox, this is handled automatically via the signaling server
	print("To implement: Get SDP offer for peer ", peer_id)
	return {}
