extends Node3D

var multiplayer_peer = ENetMultiplayerPeer.new()
const PORT = 9999
const ADDRESS = "127.0.0.1"

@export var enable_bots: bool = true  # Add this line

var connected_peer_ids = []
var local_player_character : CharacterBody3D
var player_scene = preload("res://scenes/player.tscn")
var current_turn_index = 0
@export var players = []
var game_started = false
var max_players = 4
var bots = []
var preset_goals = []

@export var turn_based_mode: bool = true
#var bot_move_timer: float = 0.0
#const BOT_MOVE_INTERVAL: float = 2.0

#var moving_bots = {}

enum ActionState {
	NONE,
	MOVING,
	GRABBING,
	PUTTING,
	RANDOMIZING,
	ARRANGING
}

var current_action_state = ActionState.NONE

@onready var action_menu = $ActionMenu
@onready var move_button = $ActionMenu/ActionButtonContainer/MoveButton
@onready var grab_button = $ActionMenu/ActionButtonContainer/GrabButton
@onready var put_button = $ActionMenu/ActionButtonContainer/PutButton
@onready var randomize_button = $ActionMenu/ActionButtonContainer/RandomizeButton
@onready var arrange_button = $ActionMenu/ActionButtonContainer/ArrangeButton
@onready var playerboard_ui = $PlayerboardUI

const item_tex = [
	preload("res://assets/textures/player_board_and_blue_print/tile_null.tres"),
	preload("res://assets/textures/player_board_and_blue_print/tile_heart.tres"),
	preload("res://assets/textures/player_board_and_blue_print/tile_diamond.tres"),
	preload("res://assets/textures/player_board_and_blue_print/tile_star.tres"),
	preload("res://assets/textures/player_board_and_blue_print/tile_coin.tres")
]

func _ready():
	multiplayer_peer.peer_connected.connect(_on_peer_connected)
	multiplayer_peer.peer_disconnected.connect(_on_peer_disconnected)
	setup_action_buttons()
	setup_playerboard_ui()

func _process(delta):
	if multiplayer.is_server() and game_started:
		if turn_based_mode:
			rpc("sync_turn_index", current_turn_index)

func setup_action_buttons():
	move_button.pressed.connect(func(): set_action_state(ActionState.MOVING))
	grab_button.pressed.connect(func(): set_action_state(ActionState.GRABBING))
	put_button.pressed.connect(func(): 
		if local_player_character:
			local_player_character.handle_put_action()
			set_action_state(ActionState.PUTTING)
	)
	randomize_button.pressed.connect(func(): set_action_state(ActionState.RANDOMIZING))
	arrange_button.pressed.connect(func(): 
		if local_player_character and local_player_character.action_points >= 2:
			set_action_state(ActionState.ARRANGING)
	)

func setup_playerboard_ui():
	for child in playerboard_ui.get_children():
		child.queue_free()
	
	playerboard_ui.columns = 5
	
	for i in range(25):
		var slot = TextureRect.new()
		
		var highlight_rect = TextureRect.new()
		var hr_tex = load("res://assets/models/pboard/HighlightRect.tres")
		
		var select_rect = TextureRect.new()
		var sr_tex = load("res://assets/models/pboard/SelectRect.tres")
		
		var adjacent_rect = TextureRect.new()
		var ar_tex = load("res://assets/models/pboard/AdjacentRect.tres")
		
		slot.custom_minimum_size = Vector2(36, 36)
		slot.gui_input.connect(func(event): _on_playerboard_slot_clicked(event, i))
		slot.texture = item_tex[0]
		playerboard_ui.add_child(slot, true)
		
		highlight_rect.texture = hr_tex
		highlight_rect.size = Vector2(36, 36)
		select_rect.texture = sr_tex
		select_rect.size = Vector2(36, 36)
		adjacent_rect.texture = ar_tex
		adjacent_rect.size = Vector2(36, 36)

		slot.add_child(highlight_rect)
		slot.add_child(select_rect)
		slot.add_child(adjacent_rect)
		
		slot.get_child(0).hide()
		slot.get_child(1).hide()
		slot.get_child(2).hide()

func set_action_state(new_state):
	if not local_player_character or not local_player_character.is_multiplayer_authority():
		return
		
	if local_player_character.is_bot or local_player_character.is_in_group("Bots"):
		current_action_state = new_state
		return
		
	if current_action_state == new_state or local_player_character.action_points <= 0:
		return
	
	current_action_state = new_state
	local_player_character.clear_highlights()
	local_player_character.clear_playerboard_highlights()
	
	match new_state:
		ActionState.MOVING:
			local_player_character.highlight_movement_range()
		ActionState.GRABBING:
			local_player_character.highlight_adjacent_cells()
			if local_player_character.has_item_at_current_position():
				local_player_character.highlighted_cells.append(local_player_character.current_position)
				local_player_character.enhanced_gridmap.set_cell_item(
					Vector3i(local_player_character.current_position.x, 0, local_player_character.current_position.y), 
					local_player_character.enhanced_gridmap.hover_item
				)
		ActionState.PUTTING:
			local_player_character.highlight_occupied_playerboard_slots()
		ActionState.RANDOMIZING:
			local_player_character.highlight_random_valid_cells()
		ActionState.ARRANGING:
			show_arrangement_ui()
			local_player_character.highlight_occupied_playerboard_slots()

func update_button_states():
	if not local_player_character or local_player_character.is_in_group("Bots"):
		move_button.visible = false
		grab_button.visible = false
		put_button.visible = false
		randomize_button.visible = false
		arrange_button.visible = false
		return
	
	move_button.visible = true
	grab_button.visible = true
	put_button.visible = true
	randomize_button.visible = true
	arrange_button.visible = true
	
	# Only keep randomize button's disable condition
	randomize_button.disabled = local_player_character.has_performed_action
	
	# Remove disabled conditions for other buttons:
	move_button.disabled = false
	grab_button.disabled = false
	put_button.disabled = false
	arrange_button.disabled = false

func _on_playerboard_slot_clicked(event, slot_index):
	if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
		if not local_player_character:
			return
			
		match current_action_state:
			ActionState.ARRANGING:
				local_player_character.arrange_playerboard_item(slot_index)
			ActionState.GRABBING:
				local_player_character.handle_playerboard_slot_selected(slot_index)
			ActionState.PUTTING:
				local_player_character.handle_put_slot_selected(slot_index)

#func update_goals_ui():
	#if not local_player_character:
		#return
		#
	#for i in range(9):  # 9 slots in the goals UI
		#var slot = $PlayergoalsUI.get_child(i)
		#var goal_value = local_player_character.goals[i]
		#
		## Hide all tile textures first 
		#slot.get_node("TileHeart").hide()
		#slot.get_node("TileDiamond").hide()
		#slot.get_node("TileStar").hide()
		#slot.get_node("TileCoin").hide()
		#
		## Show the appropriate texture based on goal value
		#match goal_value:
			#7: slot.get_node("TileHeart").show()
			#8: slot.get_node("TileDiamond").show()
			#9: slot.get_node("TileStar").show()
			#10: slot.get_node("TileCoin").show()

func update_playerboard_ui():
	if not local_player_character:
		return
	
	#update_goals_ui()  # Update goals UI whenever playerboard updates
	update_all_players_goals()  # Update all players' goals UI
		
	for i in range(25):
		var slot = playerboard_ui.get_child(i)
		var item = local_player_character.playerboard[i]
		
		slot.texture = item_tex[0]
		
		match item:
			7: slot.texture = item_tex[1]
			8: slot.texture = item_tex[2]
			9: slot.texture = item_tex[3]
			10: slot.texture = item_tex[4]

func update_playerboard_highlights(highlighted_slots: Array):
	for i in range(playerboard_ui.get_child_count()):
		var slot = playerboard_ui.get_child(i)
		if slot.get_child_count() > 1:
			slot.get_child(1).visible = highlighted_slots.has(i)

func show_arrangement_ui():
	if playerboard_ui:
		playerboard_ui.visible = true
		update_playerboard_ui()

func _on_host_pressed():
	$NetworkInfo/NetworkSideDisplay.text = "Server"
	$Menu.visible = false
	multiplayer_peer.create_server(PORT)
	multiplayer.multiplayer_peer = multiplayer_peer
	$NetworkInfo/UniquePeerID.text = str(multiplayer.get_unique_id())
	
	# Generate all goals first
	preset_goals.clear()
	for i in range(max_players):
		var goals = initialize_random_goals(9, 7, 10, 1.0)
		# Convert to int array explicitly
		var int_goals: Array[int] = []
		for g in goals:
			int_goals.append(g)
		preset_goals.append(int_goals)
	
	# Sync goals to all clients
	rpc("sync_preset_goals", preset_goals)
	
	# Now add host with first set of goals
	add_player_character(1)
	players.append(1)
	
	# Only add bots if enable_bots is true
	if enable_bots:
		# Add bots with their own goals
		for i in range(2, max_players + 1):
			add_bot(i)
	
	start_game()

@rpc("reliable")
func sync_preset_goals(goals_list: Array):
	preset_goals = goals_list

func _on_join_pressed():
	$NetworkInfo/NetworkSideDisplay.text = "Client"
	$Menu.visible = false
	multiplayer_peer.create_client(ADDRESS, PORT)
	multiplayer.multiplayer_peer = multiplayer_peer
	$NetworkInfo/UniquePeerID.text = str(multiplayer.get_unique_id())

func _on_peer_connected(new_peer_id):
	if multiplayer.is_server():
		# Increase delay to ensure scene is ready
		await get_tree().create_timer(1.5).timeout
		
		# Sync full state first
		rpc_id(new_peer_id, "sync_game_state", players, bots, game_started, turn_based_mode)
		
		# Then add players in correct order
		for peer_id in connected_peer_ids:
			rpc_id(new_peer_id, "add_player_character", peer_id)
			
		# Finally add the new player
		add_player_character(new_peer_id)
		rpc("add_newly_connected_player_character", new_peer_id)
		replace_bot_with_player(new_peer_id)
		
		# Sync all players' goals to the new peer
		for player in get_tree().get_nodes_in_group("Players"):
			var player_id = String(player.name).to_int()  # Convert StringName to String first, then to int
			rpc_id(new_peer_id, "sync_player_goals", player_id, player.goals)

func _on_peer_disconnected(peer_id):
	if multiplayer.is_server():
		connected_peer_ids.erase(peer_id)
		players.erase(peer_id)
		add_bot(get_next_available_bot_id())

@rpc("any_peer", "call_local")
func add_player_character(peer_id):
	connected_peer_ids.append(peer_id)
	var player_character = player_scene.instantiate()
	player_character.set_multiplayer_authority(peer_id)
	
	if multiplayer.is_server() and bots.size() > 0:
		var bot_to_replace = get_node_or_null(str(bots[0]))
		if bot_to_replace:
			player_character.current_position = bot_to_replace.current_position
	
	add_child(player_character)
	player_character.add_to_group("Players", true)
	
	# Set goals based on player ID if server
	if multiplayer.is_server():
		var goal_index = peer_id - 1
		# Verify index is valid
		if goal_index >= 0 and goal_index < preset_goals.size():
			# Convert to int array before assigning
			var goals: Array[int] = []
			for g in preset_goals[goal_index]:
				goals.append(g)
			player_character.goals = goals
			rpc("sync_player_goals", peer_id, goals)
	
	if peer_id == multiplayer.get_unique_id():
		local_player_character = player_character
		update_button_states()
		update_playerboard_ui()
		#update_goals_ui()
		
		# Sync this player's goals to everyone
		if multiplayer.is_server():
			rpc("sync_player_goals", peer_id, player_character.goals)
	
	if multiplayer.is_server():
		if peer_id > 1:  # Not the host
			# Assign preset goals
			var goal_index = peer_id - 2
			if goal_index < preset_goals.size():
				player_character.goals = preset_goals[goal_index]
				rpc("sync_player_goals", peer_id, player_character.goals)
	
	if multiplayer.is_server():
		# If replacing a bot, inherit its goals
		var bot_to_replace = get_node_or_null(str(bots[0])) if bots.size() > 0 else null
		if bot_to_replace:
			player_character.goals = bot_to_replace.goals.duplicate()

func add_bot(bot_id):
	rpc("create_bot", bot_id)
		

@rpc("call_local")
func create_bot(bot_id):
	# Ensure we're not duplicating bots
	if has_node(str(bot_id)):
		push_error("Bot already exists: " + str(bot_id))
		return
		
	var bot_character = player_scene.instantiate()
	if not bot_character:
		push_error("Failed to instantiate bot scene")
		return
		
	bot_character.set_multiplayer_authority(1)  # Server controls bots
	bot_character.name = str(bot_id)
	
	# Add to scene tree
	call_deferred("add_child", bot_character)
	
	# Add to groups after adding to scene tree
	bot_character.add_to_group("Players", true)
	bot_character.add_to_group("Bots", true)
	
	if not enable_bots:
		bot_character.set_process(false)
		bot_character.set_physics_process(false)
		# Disable Beehave tree if it exists
		var behavior_tree = bot_character.get_node_or_null("BehaviorTree")
		if behavior_tree:
			behavior_tree.enabled = false
	
	if multiplayer.is_server():
		bots.append(bot_id)
		players.append(bot_id)
		
		# Assign goals from preset array
		var goal_index = bot_id - 1
		if goal_index < preset_goals.size():
			bot_character.goals = preset_goals[goal_index].duplicate()
			rpc("sync_player_goals", bot_id, bot_character.goals)
		
		# Sync bot status after a short delay to ensure node is ready
		await get_tree().create_timer(0.1).timeout
		bot_character.rpc("sync_bot_status", true)
		# Sync bot's goals
		rpc("sync_player_goals", bot_id, bot_character.goals)
		
		# Only generate goals for new bots, not replacement bots
		#if not (players.size() > max_players):
			#bot_character.append_random_goals()
			
		# Always sync the bot's goals
		rpc("sync_player_goals", bot_id, bot_character.goals)

func replace_bot_with_player(player_id):
	if multiplayer.is_server() and bots.size() > 0:
		var bot_id = bots[0]
		var bot_node = get_node_or_null(str(bot_id))
		if bot_node:
		# Get bot's state
			var goals = bot_node.goals
			var playerboard = bot_node.playerboard.duplicate()
			var current_pos = bot_node.current_position
			
			# Transfer state to new player
			var player_node = get_node_or_null(str(player_id))
			if player_node:
				player_node.goals = goals
				player_node.playerboard = playerboard.duplicate()  # Make sure to duplicate
				player_node.current_position = current_pos
				
				# Sync state
				rpc("sync_player_goals", player_id, goals)
				rpc("sync_playerboard", player_id, playerboard)
			
			# Remove bot but keep board structure intact
			bots.pop_front()
			players.erase(bot_id)
			players.append(player_id)
			rpc("remove_bot_keep_board", bot_id)
			rpc("sync_players", players)


@rpc("call_local")
func remove_bot(bot_id):
	var bot_node = get_node_or_null(str(bot_id))
	if bot_node:
		bot_node.queue_free()

func get_next_available_bot_id() -> int:
	for i in range(2, max_players + 1):
		if not i in players:
			return i
	return -1

@rpc
func add_newly_connected_player_character(new_peer_id):
	add_player_character(new_peer_id)
	
@rpc
func add_previously_connected_player_characters(peer_ids):
	for peer_id in peer_ids:
		add_player_character(peer_id)

@rpc("call_local")
func sync_game_state(current_players, current_bots, is_game_started, is_turn_based):
	players = current_players
	bots = current_bots
	game_started = is_game_started
	turn_based_mode = is_turn_based
	
	for bot_id in bots:
		if not has_node(str(bot_id)):
			create_bot(bot_id)

func start_game():
	if multiplayer.is_server():
		game_started = true
		connected_peer_ids.sort()
		rpc("sync_game_start", connected_peer_ids, players, bots, turn_based_mode)
		if turn_based_mode:
			current_turn_index = -1
			next_turn()

@rpc("call_local")
func sync_game_start(peer_ids, current_players, current_bots, is_turn_based):
	connected_peer_ids = peer_ids
	players = current_players
	bots = current_bots
	turn_based_mode = is_turn_based
	game_started = true

@rpc("reliable")
func sync_turn_index(index):
	current_turn_index = index

@rpc("reliable")
func sync_players(new_players):
	players = new_players

func next_turn():
	if multiplayer.is_server() and turn_based_mode:
		current_turn_index = (current_turn_index + 1) % players.size()
		rpc("set_current_turn", players[current_turn_index])

func request_next_turn():
	if multiplayer.is_server():
		end_current_turn()
	else:
		rpc_id(1, "server_end_current_turn")

@rpc("any_peer")
func server_end_current_turn():
	if multiplayer.is_server():
		end_current_turn()

@rpc("any_peer", "call_local")
func set_current_turn(player_id):
	if not turn_based_mode:
		return
	
	for player in get_tree().get_nodes_in_group("Players"):
		var is_current_turn = player.name == str(player_id)
		player.is_my_turn = is_current_turn
		
		# Only reset state for human players
		if is_current_turn and not (player.is_bot or player.is_in_group("Bots")):
			player.action_points = 2
			player.has_moved_this_turn = false
			player.has_performed_action = false
			player.start_turn()
			# Clear any existing highlights from other players
			player.clear_highlights()
			player.clear_playerboard_highlights()
		else:
			player.is_my_turn = false

func end_current_turn():
	if multiplayer.is_server():
		next_turn()
		rpc("sync_turn_index", current_turn_index)

func update_all_players_boards():
	if not game_started:
		return
	
	var local_id = multiplayer.get_unique_id()
	var all_players = get_tree().get_nodes_in_group("Players")
	var all_player_boards = $AllPlayerBoards
	
	# Store current active tab
	var current_tab = all_player_boards.current_tab
	
	# Board 1 should show host (server)
	var host_player = null
	# Find host player (ID 1)
	for player in all_players:
		if int(String(player.name)) == 1:
			host_player = player
			break
	
	# Update host board
	var host_board = all_player_boards.get_node("1")
	if host_player and host_board and host_board.has_node("PlayerboardUI"):
		host_board.visible = true
		var board_ui = host_board.get_node("PlayerboardUI")
		for slot_idx in range(25):
			update_board_slot(board_ui, slot_idx, host_player.playerboard[slot_idx])
	
	# Sort remaining players by ID for boards 2-4
	var other_players = all_players.filter(func(p): return int(String(p.name)) != 1)
	other_players.sort_custom(func(a, b): return int(String(a.name)) < int(String(b.name)))
	
	# Update boards 2-4 with remaining players
	for i in range(min(other_players.size(), 3)):
		var board_num = i + 2  # boards 2,3,4
		var player = other_players[i]
		var board = all_player_boards.get_node(str(board_num))
		
		if board and board.has_node("PlayerboardUI"):
			board.visible = true
			var board_ui = board.get_node("PlayerboardUI")
			for slot_idx in range(25):
				update_board_slot(board_ui, slot_idx, player.playerboard[slot_idx])
	
	# Restore previous active tab
	all_player_boards.current_tab = current_tab

@rpc("any_peer", "call_local")
func sync_playerboard(player_id: int, new_playerboard: Array):
	# Update local player's board if it's their board
	if player_id == multiplayer.get_unique_id() and local_player_character:
		update_playerboard_ui()
		
	# Important: Always update all boards when any board changes
	update_all_players_boards()
	
	# Update specific board in AllPlayerBoards UI
	var board_index = players.find(player_id)
	if board_index >= 0 and board_index < max_players:
		var target_board_index = board_index + 1
		if target_board_index != 1:  # Skip local player's board
			var container = $AllPlayerBoards.get_node_or_null(str(target_board_index))
			if container and container.has_node("PlayerboardUI"):
				var board_ui = container.get_node("PlayerboardUI")
				for slot_idx in range(25):
					update_board_slot(board_ui, slot_idx, new_playerboard[slot_idx])

func update_board_slot(board_ui: Node, slot_idx: int, value: int):
	var slot_node = board_ui.get_node_or_null("Slot%d" % (slot_idx + 1))
	if slot_node:
		# Hide all tiles first
		for tile in ["TileHeart", "TileDiamond", "TileStar", "TileCoin"]:
			slot_node.get_node(tile).hide()
		
		# Show appropriate tile
		match value:
			7: slot_node.get_node("TileHeart").show()
			8: slot_node.get_node("TileDiamond").show()
			9: slot_node.get_node("TileStar").show()
			10: slot_node.get_node("TileCoin").show()

func update_all_players_goals():
	if not game_started:
		return
		
	var all_players = get_tree().get_nodes_in_group("Players")
	# Fix: Convert name to string and handle potential scene-unique names properly
	all_players.sort_custom(func(a, b): 
		var a_str = String(a.name).get_slice("@", 0)  # Explicitly convert StringName to String
		var b_str = String(b.name).get_slice("@", 0)
		return int(a_str) < int(b_str)
	)
	
	for i in range(min(all_players.size(), 4)):
		var player = all_players[i]
		var goals_grid = $AllPlayerGoals.get_child(i)  # Playergoals_1, _2, _3, or _4
		
		for slot_idx in range(9):
			var slot = goals_grid.get_child(slot_idx)
			var goal_value = player.goals[slot_idx]
			
			# Hide all tile textures first
			slot.get_node("TileHeart").hide()
			slot.get_node("TileDiamond").hide()
			slot.get_node("TileStar").hide()
			slot.get_node("TileCoin").hide()
			
			# Show the appropriate texture based on goal value
			match goal_value:
				7: slot.get_node("TileHeart").show()
				8: slot.get_node("TileDiamond").show()
				9: slot.get_node("TileStar").show()
				10: slot.get_node("TileCoin").show()

@rpc("any_peer", "call_local")
func sync_player_goals(player_id: int, goals: Array):
	var player_idx = players.find(player_id)
	if player_idx >= 0 and player_idx < 4:
		var goals_grid = $AllPlayerGoals.get_child(player_idx)
		for slot_idx in range(9):
			var slot = goals_grid.get_child(slot_idx)
			var goal_value = goals[slot_idx]
			
			# Hide all tiles
			slot.get_node("TileHeart").hide()
			slot.get_node("TileDiamond").hide()
			slot.get_node("TileStar").hide()
			slot.get_node("TileCoin").hide()
			
			# Show appropriate tile
			match goal_value:
				7: slot.get_node("TileHeart").show()
				8: slot.get_node("TileDiamond").show()
				9: slot.get_node("TileStar").show()
				10: slot.get_node("TileCoin").show()

# Add this function near the top with other helper functions
func initialize_random_goals(_size:int, min_value:int, max_value:int, null_count:float) -> Array:
	var goals = []
	var rng = RandomNumberGenerator.new()
	rng.randomize()
	
	var null_val = 0
	var max_nulls = 3
	
	const SPECIAL_VALUES = {1: 7, 2: 8, 3: 9, 4: 10}
	
	for i in range(_size):
		if null_val < max_nulls and rng.randf() < null_count:
			goals.append(-1)
			null_val += 1
		else:
			var val = rng.randi_range(min_value, max_value)
			goals.append(val if not val in SPECIAL_VALUES else SPECIAL_VALUES[val])
	
	return goals
