extends Node3D

@onready var skin_input: LineEdit = $Menu/MainContainer/MainMenu/Option2/SkinInput
@onready var nick_input: LineEdit = $Menu/MainContainer/MainMenu/Option1/NickInput
@onready var address_input: LineEdit = $Menu/MainContainer/MainMenu/Option3/AddressInput
@onready var players_container: Node3D = $PlayersContainer
@onready var weapons_container: Node3D = null  # Will be created if doesn't exist
@onready var menu: Control = $Menu
@onready var main_menu: VBoxContainer = $Menu/MainContainer/MainMenu
@export var player_scene: PackedScene

# Weapon spawning counter (server-side only)
var _weapon_spawn_counter: int = 0
# Track active weapons for late-join sync (server-side only)
var _active_weapons: Dictionary = {}  # weapon_id -> WorldWeapon reference
# Track if we've already initialized to prevent double-spawning
var _multiplayer_initialized: bool = false

# multiplayer chat
@onready var message: LineEdit = $MultiplayerChat/VBoxContainer/HBoxContainer/Message
@onready var send: Button = $MultiplayerChat/VBoxContainer/HBoxContainer/Send
@onready var chat: TextEdit = $MultiplayerChat/VBoxContainer/Chat
@onready var multiplayer_chat: Control = $MultiplayerChat

var chat_visible = false

func _ready():
	print("[Level] _ready() called. Peer ID: ", multiplayer.get_unique_id(), " Is server: ", multiplayer.is_server())

	multiplayer_chat.hide()
	menu.show()
	multiplayer_chat.set_process_input(true)

	# Add quick-fill preset buttons
	_create_preset_buttons()

	# Create or find weapons container
	if has_node("WeaponsContainer"):
		weapons_container = get_node("WeaponsContainer")
	else:
		weapons_container = Node3D.new()
		weapons_container.name = "WeaponsContainer"
		add_child(weapons_container)
		print("Created WeaponsContainer")

	# Don't initialize weapons in _ready() - wait for Host/Join to be pressed
	# This is handled in initialize_multiplayer() which is called after the
	# multiplayer peer is properly set up
	print("[Level] _ready() complete - waiting for Host/Join")

func _on_connected_to_server():
	print("[Level] Connected to server! Cleaning up manual weapons")
	_cleanup_manual_weapons_on_client()

## Called by Network when multiplayer peer is set up
func initialize_multiplayer():
	print("[Level] initialize_multiplayer called. is_server: ", multiplayer.is_server())

	# Prevent double initialization
	if _multiplayer_initialized:
		print("[Level] Already initialized, skipping")
		return

	_multiplayer_initialized = true

	if multiplayer.is_server():
		print("[Level] Running server initialization")
		Network.connect("player_connected", Callable(self, "_on_player_connected"))
		multiplayer.peer_disconnected.connect(_remove_player)

		# Initialize any manually placed weapons in the scene
		_initialize_manual_weapons()

		# Spawn initial weapons when server starts
		_spawn_initial_weapons()

		# Spawn the host player (peer ID 1)
		print("[Level] Spawning host player")
		var host_info = Network.players.get(1, {"nick": "Host", "skin": "blue"})
		_add_player(1, host_info)
	else:
		# Client initialization - clean up manual weapons immediately
		print("[Level] Running client initialization - cleaning up manual weapons")
		_cleanup_manual_weapons_on_client()

func _cleanup_manual_weapons_on_client():
	"""Remove manually placed weapons on clients (server will sync them via RPC)"""
	print("[Client ", multiplayer.get_unique_id(), "] _cleanup_manual_weapons_on_client called")

	if not weapons_container:
		print("[Client] No weapons_container found!")
		return

	print("[Client] WeaponsContainer has ", weapons_container.get_child_count(), " children")

	var weapons_to_remove = []
	for child in weapons_container.get_children():
		print("[Client] Checking child: ", child.name, " (type: ", child.get_class(), ")")
		if child is WorldWeapon:
			print("[Client]   - Is WorldWeapon with weapon_id: ", child.weapon_id)
			if child.weapon_id == -1:
				weapons_to_remove.append(child)
				print("[Client]   - Marked for removal")

	print("[Client] Found ", weapons_to_remove.size(), " weapons to remove")
	for weapon in weapons_to_remove:
		print("[Client] Removing manually placed weapon: ", weapon.name)
		# Use immediate removal to prevent RPC errors
		weapons_container.remove_child(weapon)
		weapon.free()

func _initialize_manual_weapons():
	"""Initialize any WorldWeapon nodes manually placed in the level scene"""
	if not multiplayer.is_server():
		return

	if not weapons_container:
		return

	# Find all WorldWeapon nodes in the weapons container
	var manual_weapons = []
	for child in weapons_container.get_children():
		if child is WorldWeapon:
			manual_weapons.append(child)

	if manual_weapons.is_empty():
		print("[Server] No manually placed weapons found")
		return

	print("[Server] Found ", manual_weapons.size(), " manually placed weapon(s)")

	# Initialize each manually placed weapon
	for weapon in manual_weapons:
		# Skip if already initialized (weapon_id != -1)
		if weapon.weapon_id != -1:
			continue

		# Assign unique ID
		_weapon_spawn_counter += 1
		weapon.weapon_id = _weapon_spawn_counter

		# Set deterministic name for networking
		var old_name = weapon.name
		weapon.name = "WorldWeapon_" + str(weapon.weapon_id)

		# Track in active weapons
		_active_weapons[weapon.weapon_id] = weapon

		# Connect cleanup signal
		weapon.tree_exiting.connect(_on_weapon_removed.bind(weapon.weapon_id))

		print("[Server] Initialized manual weapon '", old_name, "' with ID: ", weapon.weapon_id, " at position: ", weapon.global_position)

		# Verify weapon_data is set
		if not weapon.weapon_data:
			push_error("Manual weapon '", old_name, "' has no WeaponData assigned!")
			continue

func _spawn_initial_weapons():
	if not multiplayer.is_server():
		return

	# Wait a frame for everything to be ready
	await get_tree().process_frame

	print("[Server] _spawn_initial_weapons - Connected peers: ", multiplayer.get_peers())

	# Spawn a sword
	_weapon_spawn_counter += 1
	print("[Server] Calling RPC to spawn sword with ID: ", _weapon_spawn_counter)
	rpc("spawn_world_weapon",
		"res://level/resources/weapon_sword.tres",
		Vector3(5, 1, 0),
		Vector3.ZERO,
		_weapon_spawn_counter
	)

	# Spawn a shield
	_weapon_spawn_counter += 1
	print("[Server] Calling RPC to spawn shield with ID: ", _weapon_spawn_counter)
	rpc("spawn_world_weapon",
		"res://level/resources/weapon_shield.tres",
		Vector3(-5, 1, 0),
		Vector3.ZERO,
		_weapon_spawn_counter
	)

func _on_player_connected(peer_id, player_info):
	print("[Server] _on_player_connected called for peer ", peer_id, " with info: ", player_info)
	_add_player(peer_id, player_info)

	# Sync existing players to the newly joined player
	if multiplayer.is_server() and peer_id != 1:
		print("[Server] Syncing existing players to newly connected peer: ", peer_id)
		# Wait a frame to ensure new player is fully initialized
		await get_tree().process_frame
		for existing_player in players_container.get_children():
			var existing_id = int(existing_player.name)
			if existing_id != peer_id:  # Don't sync the player to themselves
				print("[Server] Syncing existing player ", existing_id, " to peer ", peer_id)
				rpc_id(peer_id, "_spawn_player_local", existing_id, existing_player.position)

	# Sync existing weapons to the newly joined player (but not to server itself)
	if multiplayer.is_server() and peer_id != 1:
		print("[Server] Syncing weapons to newly connected peer: ", peer_id)
		print("[Server] Active weapons in _active_weapons: ", _active_weapons.keys())
		print("[Server] Active weapons count: ", _active_weapons.size())
		for weapon_id in _active_weapons.keys():
			var weapon = _active_weapons[weapon_id]
			if is_instance_valid(weapon) and weapon.weapon_data:
				print("[Server] Sending weapon ", weapon_id, " (", weapon.weapon_data.weapon_name, ") at position ", weapon.global_position, " to peer ", peer_id)
				# Send current position and zero velocity for syncing
				rpc_id(peer_id, "_client_spawn_weapon",
					weapon.weapon_data.resource_path,
					weapon.global_position,
					Vector3.ZERO,
					weapon_id
				)
			else:
				print("[Server] Skipping invalid weapon ", weapon_id)

		# Sync equipped weapons for all existing players to the newly joined player
		print("[Server] Syncing equipped weapons to newly connected peer: ", peer_id)
		for player_node in players_container.get_children():
			var player = player_node as Character
			if player and is_instance_valid(player):
				# Skip the newly joined player (they don't have weapons yet)
				if int(player.name) == peer_id:
					continue

				# Sync main hand weapon
				if player.equipped_weapon and player.equipped_weapon.weapon_data:
					print("[Server] Syncing main hand weapon for player ", player.name, " to peer ", peer_id)
					player.rpc_id(peer_id, "equip_weapon_from_world",
						player.equipped_weapon.weapon_data.resource_path)

				# Sync off-hand weapon
				if player.equipped_offhand and player.equipped_offhand.weapon_data:
					print("[Server] Syncing off-hand weapon for player ", player.name, " to peer ", peer_id)
					player.rpc_id(peer_id, "equip_weapon_from_world",
						player.equipped_offhand.weapon_data.resource_path)

func _on_host_pressed():
	print("[Level] Host button pressed")
	menu.hide()
	print("[Level] Calling Network.start_host()")
	Network.start_host(nick_input.text.strip_edges(), skin_input.text.strip_edges().to_lower())
	print("[Level] Waiting one frame...")
	await get_tree().process_frame
	print("[Level] Calling initialize_multiplayer()")
	initialize_multiplayer()

func _on_join_pressed():
	print("[Level] Join button pressed")
	menu.hide()
	print("[Level] Calling Network.join_game()")
	Network.join_game(nick_input.text.strip_edges(), skin_input.text.strip_edges().to_lower(), address_input.text.strip_edges())
	print("[Level] Waiting one frame...")
	await get_tree().process_frame
	print("[Level] Calling initialize_multiplayer()")
	initialize_multiplayer()

func _add_player(id: int, player_info : Dictionary):
	print("[Level] _add_player called for peer ", id, " with info: ", player_info)

	# Server spawns player and replicates to all clients via RPC
	if multiplayer.is_server():
		if players_container.has_node(str(id)):
			print("[Level] Player ", id, " already exists, skipping")
			return

		var spawn_pos = get_spawn_point()
		print("[Level] Server spawning player ", id, " at ", spawn_pos)

		# Spawn on server and all clients (call_local does both)
		rpc("_spawn_player_local", id, spawn_pos)

@rpc("any_peer", "call_local", "reliable")
func _spawn_player_local(id: int, spawn_pos: Vector3):
	if players_container.has_node(str(id)):
		print("[Peer ", multiplayer.get_unique_id(), "] Player ", id, " already exists, skipping")
		return

	print("[Peer ", multiplayer.get_unique_id(), "] Creating player instance for peer ", id)
	var player = player_scene.instantiate()
	player.name = str(id)
	player.position = spawn_pos
	print("[Peer ", multiplayer.get_unique_id(), "] Adding player to PlayersContainer")
	players_container.add_child(player, true)
	print("[Peer ", multiplayer.get_unique_id(), "] Player ", id, " spawned at ", player.position)

	# Get player info from Network
	var player_info = Network.players.get(id, {"nick": "Player", "skin": "blue"})

	var nick = player_info["nick"]
	# Access nickname directly via node path since @onready hasn't loaded yet
	var nickname_label = player.get_node_or_null("PlayerNick/Nickname")
	if nickname_label:
		nickname_label.text = nick
	# player.rpc("change_nick", nick)

	# Set up HUD for local player
	if id == multiplayer.get_unique_id():
		print("[Level] Setting up HUD for local player")
		# Wait a frame to ensure HUD autoload is ready
		await get_tree().process_frame
		if has_node("/root/HUD"):
			get_node("/root/HUD").set_local_player(player)
		else:
			push_warning("[Level] HUD autoload not found! Make sure to restart Godot to register the new autoload.")

	var skin_enum = player_info["skin"]
	player.set_player_skin(skin_enum)
	# rpc("sync_player_skin", id, skin_enum)

	# rpc("sync_player_position", id, player.position)

func get_spawn_point() -> Vector3:
	var spawn_point = Vector2.from_angle(randf() * 2 * PI) * 10 # spawn radius
	return Vector3(spawn_point.x, 0, spawn_point.y)

func _remove_player(id):
	if not multiplayer.is_server() or not players_container.has_node(str(id)):
		return
	var player_node = players_container.get_node(str(id))
	if player_node:
		player_node.queue_free()

# @rpc("any_peer", "call_local")
# func sync_player_position(id: int, new_position: Vector3):
# 	var player = players_container.get_node(str(id))
# 	if player:
# 		player.position = new_position

# @rpc("any_peer", "call_local")
# func sync_player_skin(id: int, skin_color: Character.SkinColor):
# 	var player = players_container.get_node(str(id))
# 	if player:
# 		player.set_player_skin(skin_color)

func _on_quit_pressed() -> void:
	get_tree().quit()

# ---------- MULTIPLAYER CHAT ----------
func toggle_chat():
	if menu.visible:
		return

	chat_visible = !chat_visible
	if chat_visible:
		multiplayer_chat.show()
		message.grab_focus()
	else:
		multiplayer_chat.hide()
		get_viewport().set_input_as_handled()

func is_chat_visible() -> bool:
	return chat_visible

func _input(event):
	if event.is_action_pressed("toggle_chat"):
		toggle_chat()
	elif event is InputEventKey and event.keycode == KEY_ENTER:
		_on_send_pressed()

func _on_send_pressed() -> void:
	var trimmed_message = message.text.strip_edges()
	if trimmed_message == "":
		return # do not send empty messages

	var nick = Network.players[multiplayer.get_unique_id()]["nick"]

	rpc("msg_rpc", nick, trimmed_message)
	message.text = ""
	message.grab_focus()

@rpc("any_peer", "call_local")
func msg_rpc(nick, msg):
	chat.text += str(nick, " : ", msg, "\n")

# ---------- PRESET BUTTONS ----------
func _create_preset_buttons():
	# Create a container for preset buttons
	var preset_container = HBoxContainer.new()
	preset_container.name = "PresetContainer"

	var preset_label = Label.new()
	preset_label.text = "Quick Fill:"
	preset_container.add_child(preset_label)

	# Scott button
	var scott_button = Button.new()
	scott_button.text = "Scott"
	scott_button.pressed.connect(_on_scott_preset)
	preset_container.add_child(scott_button)

	# Jemz button
	var jemz_button = Button.new()
	jemz_button.text = "Jemz"
	jemz_button.pressed.connect(_on_jemz_preset)
	preset_container.add_child(jemz_button)

	# Add to main menu at the top
	main_menu.add_child(preset_container)
	main_menu.move_child(preset_container, 0)

func _on_scott_preset():
	nick_input.text = "Scott"
	skin_input.text = "Blue"
	address_input.text = "127.0.0.1"

func _on_jemz_preset():
	nick_input.text = "Jemz"
	skin_input.text = "Red"
	address_input.text = "127.0.0.1"

# ---------- WEAPON SPAWNING ----------
## Spawn a weapon in the world (called from server, syncs to all clients)
@rpc("any_peer", "call_local", "reliable")
func spawn_world_weapon(weapon_data_path: String, spawn_position: Vector3, initial_velocity: Vector3, weapon_id: int):
	print("[Client ", multiplayer.get_unique_id(), "] spawn_world_weapon called for weapon_id: ", weapon_id)

	if not weapons_container:
		push_error("WeaponsContainer not found in level!")
		return

	# Load the weapon data resource
	var weapon_data = load(weapon_data_path) as WeaponData
	if not weapon_data:
		push_error("Failed to load weapon data from: " + weapon_data_path)
		return

	# Create WorldWeapon instance
	var world_weapon = WorldWeapon.new()
	world_weapon.weapon_data = weapon_data
	print("[DEBUG] About to set weapon_id. Parameter weapon_id = ", weapon_id)
	world_weapon.weapon_id = weapon_id  # Store the ID
	print("[DEBUG] After setting weapon_id. world_weapon.weapon_id = ", world_weapon.weapon_id)
	world_weapon.name = "WorldWeapon_" + str(weapon_id)  # Deterministic name
	print("[DEBUG] Set weapon name to: ", world_weapon.name, " using weapon_id: ", weapon_id)
	world_weapon.position = spawn_position

	# Remove existing weapon with same name if it exists (prevents duplicates)
	var weapon_path = NodePath(world_weapon.name)
	if weapons_container.has_node(weapon_path):
		print("[DEBUG] Weapon ", world_weapon.name, " already exists, removing old one first")
		var old_weapon = weapons_container.get_node(weapon_path)
		weapons_container.remove_child(old_weapon)
		old_weapon.queue_free()

	# Add to weapons container
	weapons_container.add_child(world_weapon)
	print("[DEBUG] After add_child, weapon name in tree: ", world_weapon.name, " weapon_id: ", world_weapon.weapon_id)

	# Track this weapon on the server (AFTER adding to tree so signals work)
	if multiplayer.is_server():
		_active_weapons[weapon_id] = world_weapon
		print("[Server] Added weapon ", weapon_id, " to _active_weapons. Total: ", _active_weapons.size())
		# Connect to the weapon's removal signal
		world_weapon.tree_exiting.connect(_on_weapon_removed.bind(weapon_id))

	print("[Peer ", multiplayer.get_unique_id(), "] Spawned weapon: ", world_weapon.name, " at ", spawn_position)

	# Apply velocity after physics is ready
	if initial_velocity != Vector3.ZERO:
		await get_tree().process_frame
		world_weapon.linear_velocity = initial_velocity

## Remove a world weapon from all clients (called by WorldWeapon when picked up)
func remove_world_weapon(weapon_id: int):
	print("[Server] remove_world_weapon called for weapon_id: ", weapon_id)
	if not multiplayer.is_server():
		print("[ERROR] remove_world_weapon called on client!")
		return

	# Immediately remove from active weapons to prevent late-join sync issues
	if _active_weapons.has(weapon_id):
		_active_weapons.erase(weapon_id)
		print("[Server] Removed weapon ", weapon_id, " from _active_weapons. Remaining: ", _active_weapons.size())
		print("[Server] Remaining weapon IDs: ", _active_weapons.keys())
	else:
		print("[Server] WARNING: Weapon ", weapon_id, " not found in _active_weapons!")

	# Broadcast removal to all clients
	print("[Server] Broadcasting removal RPC to all clients")
	rpc("_remove_weapon_on_clients", weapon_id)

## RPC to remove weapon on all clients
@rpc("any_peer", "call_local", "reliable")
func _remove_weapon_on_clients(weapon_id: int):
	print("[Peer ", multiplayer.get_unique_id(), "] _remove_weapon_on_clients called for weapon_id: ", weapon_id)
	var weapon_name = "WorldWeapon_" + str(weapon_id)
	if weapons_container and weapons_container.has_node(weapon_name):
		var weapon = weapons_container.get_node(weapon_name)
		print("[Peer ", multiplayer.get_unique_id(), "] Removing weapon ", weapon_name)
		weapon.queue_free()
	else:
		print("[Peer ", multiplayer.get_unique_id(), "] WARNING: Weapon ", weapon_name, " not found in WeaponsContainer")

## Called when a weapon is removed (picked up or destroyed)
func _on_weapon_removed(weapon_id: int):
	if not multiplayer.is_server():
		return

	# Remove from tracking
	if _active_weapons.has(weapon_id):
		_active_weapons.erase(weapon_id)
		print("Removed weapon ", weapon_id, " from active tracking")

## Client-only spawn (called via rpc_id for late-join sync)
@rpc("any_peer", "reliable")
func _client_spawn_weapon(weapon_data_path: String, spawn_position: Vector3, initial_velocity: Vector3, weapon_id: int):
	print("[Client ", multiplayer.get_unique_id(), "] _client_spawn_weapon received for weapon_id: ", weapon_id)
	# This only runs on clients, not server (no call_local)
	if multiplayer.is_server():
		print("[ERROR] _client_spawn_weapon called on server!")
		return

	# Call the regular spawn function to create the weapon
	print("[Client ", multiplayer.get_unique_id(), "] Calling spawn_world_weapon locally")
	spawn_world_weapon(weapon_data_path, spawn_position, initial_velocity, weapon_id)
