extends CanvasLayer

# Chat UI - Handles in-game chat with network sync

const MAX_MESSAGES = 100  # Maximum messages to keep in history
const MESSAGE_FADE_TIME = 5.0  # Seconds before message starts fading
const MESSAGE_FADE_DURATION = 2.0  # Duration of fade out
const RECENT_MESSAGE_TIME = 7.0  # Show background if message is this recent

# Player colors for chat names
var player_colors = [
	Color.RED,
	Color.GREEN,
	Color.BLUE,
	Color.ORANGE,
	Color(0.5, 0.0, 0.5)  # Purple
]

var chat_open: bool = false
var messages: Array = []  # Array of {timestamp: String, player_name: String, message: String, time: float}
var network_manager: Node = null

# UI Nodes
var chat_container: Control = null
var message_scroll: ScrollContainer = null
var message_list: VBoxContainer = null
var chat_input: LineEdit = null
var background: ColorRect = null

# Metropolis font
var metropolis_font: FontFile = null

func _ready():
	add_to_group("chat_ui")
	network_manager = get_node_or_null("/root/NetworkManager")
	
	# Load Metropolis font
	var font_path = "res://assets/fonts/Metropolis/TrueType/Metropolis-Regular.ttf"
	if ResourceLoader.exists(font_path):
		metropolis_font = load(font_path)
	
	# Get UI nodes from scene
	chat_container = get_node_or_null("ChatContainer")
	background = get_node_or_null("ChatContainer/BottomLeft/Background")
	message_scroll = get_node_or_null("ChatContainer/BottomLeft/VBox/MessageScroll")
	message_list = get_node_or_null("ChatContainer/BottomLeft/VBox/MessageScroll/MessageList")
	chat_input = get_node_or_null("ChatContainer/BottomLeft/VBox/ChatInput")
	
	# Apply Metropolis font to chat input if available
	if chat_input and metropolis_font:
		chat_input.add_theme_font_override("font", metropolis_font)
		chat_input.add_theme_font_size_override("font_size", 14)
	
	# Connect input signals
	if chat_input:
		chat_input.text_submitted.connect(_on_chat_input_submitted)
		chat_input.focus_exited.connect(_on_chat_input_focus_exited)
	
	visible = true  # Chat UI is always visible (background and messages fade)
	
	# Hide scrollbar permanently
	if message_scroll:
		var scroll_bar = message_scroll.get_v_scroll_bar()
		if scroll_bar:
			scroll_bar.visible = false

func _input(event):
	# Toggle chat with ENTER key
	if event is InputEventKey and event.pressed and event.keycode == KEY_ENTER:
		if not chat_open:
			_open_chat()
		else:
			_send_and_close_chat()
		get_viewport().set_input_as_handled()
	
	# Don't consume touch events outside chat area - let them pass through to joystick
	# Only consume keyboard events for chat
	if event is InputEventScreenTouch or event is InputEventScreenDrag:
		# Check if touch is in chat area
		if chat_container:
			var bottom_left = chat_container.get_node_or_null("BottomLeft")
			if bottom_left:
				var chat_rect = bottom_left.get_global_rect()
				if event is InputEventScreenTouch:
					if not chat_rect.has_point(event.position):
						# Touch is outside chat area, don't consume it
						return
				elif event is InputEventScreenDrag:
					if not chat_rect.has_point(event.position):
						# Drag is outside chat area, don't consume it
						return

func _open_chat():
	chat_open = true
	chat_input.visible = true
	chat_input.text = ""
	chat_input.grab_focus()
	background.visible = true
	
	# Enable scrolling when chat is open
	if message_scroll:
		message_scroll.mouse_filter = Control.MOUSE_FILTER_STOP  # Allow scrolling
		# Keep scrollbar hidden
		var scroll_bar = message_scroll.get_v_scroll_bar()
		if scroll_bar:
			scroll_bar.visible = false
	if message_list:
		message_list.mouse_filter = Control.MOUSE_FILTER_STOP  # Allow interaction
	
	# Make all messages fully visible when chat is opened
	_show_all_messages()
	
	# Lock player controls
	_lock_player_controls(true)
	
	# Scroll to bottom
	call_deferred("_scroll_to_bottom")

func _send_and_close_chat():
	var message_text = chat_input.text.strip_edges()
	chat_input.text = ""
	chat_open = false
	chat_input.visible = false
	chat_input.release_focus()
	
	# Disable scrolling when chat is closed (so it doesn't block joystick)
	if message_scroll:
		message_scroll.mouse_filter = Control.MOUSE_FILTER_IGNORE  # Don't block input
		# Keep scrollbar hidden
		var scroll_bar = message_scroll.get_v_scroll_bar()
		if scroll_bar:
			scroll_bar.visible = false
	if message_list:
		message_list.mouse_filter = Control.MOUSE_FILTER_IGNORE  # Don't block input
	
	# Unlock player controls
	_lock_player_controls(false)
	
	# Send message if not empty
	if message_text.length() > 0:
		_send_message(message_text)
	
	# Update background visibility
	_update_background_visibility()

func _on_chat_input_submitted(_text: String):
	_send_and_close_chat()

func _on_chat_input_focus_exited():
	# Don't close chat on focus loss - only close when ENTER is pressed
	pass

func send_system_message(message: String):
	# Send a system message (appears as "System" instead of player name)
	# System messages are always sent from server
	if multiplayer.has_multiplayer_peer():
		if multiplayer.is_server():
			# Server broadcasts to all clients and also shows locally
			_add_message("System", message)  # Show locally first
			# Route through game_world to avoid node path issues
			var game_world = get_tree().get_first_node_in_group("game_world")
			if game_world and game_world.has_method("_rpc_to_ready_peers"):
				game_world._rpc_to_ready_peers("_receive_chat_message", ["System", message])
			else:
				# Fallback: try direct RPC if game_world not available
				_receive_message.rpc("System", message)
		else:
			# Client sends to server (system messages should only come from server)
			pass
	else:
		# Offline mode - just show locally
		_add_message("System", message)

func add_local_message(player_name: String, message: String):
	# Add a local-only message (not broadcast to other players)
	# Used for connection status messages that should only appear locally
	_add_message(player_name, message)

func add_colorful_local_message(player_name: String, message: String):
	# Add a local-only message with each character in a different color
	# Used for special connection status messages
	_add_colorful_message(player_name, message)

func _send_message(message: String):
	if not network_manager:
		return
	
	# Get player name
	var player_name = _get_player_name()
	
	# Send to server (or broadcast if we're the server)
	if multiplayer.has_multiplayer_peer():
		if multiplayer.is_server():
			# Server broadcasts to all clients and also shows locally
			_add_message(player_name, message)  # Show locally first
			# Route through game_world to avoid node path issues
			var game_world = get_tree().get_first_node_in_group("game_world")
			if game_world and game_world.has_method("_rpc_to_ready_peers"):
				game_world._rpc_to_ready_peers("_receive_chat_message", [player_name, message])
			else:
				# Fallback: try direct RPC if game_world not available
				_receive_message.rpc(player_name, message)
		else:
			# Client sends to server
			_send_message_to_server.rpc_id(1, player_name, message)
	else:
		# Offline mode - just show locally
		_add_message(player_name, message)

@rpc("any_peer", "reliable")
func _send_message_to_server(player_name: String, message: String):
	# Server receives message from client and broadcasts to all
	if multiplayer.is_server():
		# Show message on server first
		_add_message(player_name, message)
		# Then broadcast to all clients via game_world to avoid node path issues
		var game_world = get_tree().get_first_node_in_group("game_world")
		if game_world and game_world.has_method("_rpc_to_ready_peers"):
			game_world._rpc_to_ready_peers("_receive_chat_message", [player_name, message])
		else:
			# Fallback: try direct RPC if game_world not available
			_receive_message.rpc(player_name, message)

@rpc("authority", "reliable")
func _receive_message(player_name: String, message: String):
	# All clients (and server) receive the message
	_add_message(player_name, message)

func _get_player_color(player_name: String) -> Color:
	# System messages use gray color
	if player_name == "System":
		return Color.GRAY
	
	# Extract peer_id from player name (format: "PlayerX_Y" or "PlayerX")
	var peer_id = 0
	if player_name.begins_with("Player"):
		var parts = player_name.substr(6).split("_")  # Remove "Player" prefix
		if parts.size() > 0:
			var peer_str = parts[0]
			if peer_str.is_valid_int():
				peer_id = peer_str.to_int()
	
	# Use peer_id to consistently assign colors
	return player_colors[peer_id % player_colors.size()]

func _add_message(player_name: String, message: String):
	if not message_list:
		LogManager.log_error("ChatUI: ERROR - message_list is null, cannot add message!", LogManager.CATEGORY_UI)
		return
	
	# Get current time
	var time_dict = Time.get_time_dict_from_system()
	var timestamp = "%02d:%02d:%02d" % [time_dict.hour, time_dict.minute, time_dict.second]
	var current_time = Time.get_ticks_msec() / 1000.0
	
	# Add to messages array
	messages.append({
		"timestamp": timestamp,
		"player_name": player_name,
		"message": message,
		"time": current_time
	})
	
	# Limit message history
	if messages.size() > MAX_MESSAGES:
		messages.pop_front()
	
	# Get player color
	var player_color = _get_player_color(player_name)
	var color_hex = "#%02x%02x%02x" % [int(player_color.r * 255), int(player_color.g * 255), int(player_color.b * 255)]
	
	# Create message label using RichTextLabel for colored text
	var message_label = RichTextLabel.new()
	message_label.name = "Message_%d" % messages.size()
	message_label.bbcode_enabled = true
	message_label.scroll_active = false
	message_label.fit_content = true
	message_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
	message_label.clip_contents = false
	
	# Format message with colored player name using BBCode
	var formatted_text = "%s [color=%s]%s[/color]: %s" % [timestamp, color_hex, player_name, message]
	message_label.text = formatted_text
	
	# Apply Metropolis font if available
	if metropolis_font:
		message_label.add_theme_font_override("normal_font", metropolis_font)
		message_label.add_theme_font_size_override("normal_font_size", 12)
	
	message_label.modulate.a = 1.0
	
	# Add to message list (at the end, so newest messages appear closest to input)
	# Messages flow upwards - oldest at top, newest at bottom
	message_list.add_child(message_label)
	
	# Scroll to bottom
	call_deferred("_scroll_to_bottom")
	
	# Update background visibility
	_update_background_visibility()

func _add_colorful_message(player_name: String, message: String):
	# Add a message with each character in a different color
	if not message_list:
		LogManager.log_error("ChatUI: ERROR - message_list is null, cannot add message!", LogManager.CATEGORY_UI)
		return
	
	# Get current time
	var time_dict = Time.get_time_dict_from_system()
	var timestamp = "%02d:%02d:%02d" % [time_dict.hour, time_dict.minute, time_dict.second]
	var current_time = Time.get_ticks_msec() / 1000.0
	
	# Add to messages array
	messages.append({
		"timestamp": timestamp,
		"player_name": player_name,
		"message": message,
		"time": current_time
	})
	
	# Limit message history
	if messages.size() > MAX_MESSAGES:
		messages.pop_front()
	
	# Get player color for the player name
	var player_color = _get_player_color(player_name)
	var player_color_hex = "#%02x%02x%02x" % [int(player_color.r * 255), int(player_color.g * 255), int(player_color.b * 255)]
	
	# Create message label using RichTextLabel for colored text
	var message_label = RichTextLabel.new()
	message_label.name = "Message_%d" % messages.size()
	message_label.bbcode_enabled = true
	message_label.scroll_active = false
	message_label.fit_content = true
	message_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
	message_label.clip_contents = false
	
	# Color palette for rainbow effect
	var colors = [
		Color.RED,
		Color.ORANGE,
		Color.YELLOW,
		Color.GREEN,
		Color.CYAN,
		Color.BLUE,
		Color(0.5, 0.0, 0.5),  # Purple
		Color.MAGENTA
	]
	
	# Format message with colored player name and colorful message text
	var formatted_text = "%s [color=%s]%s[/color]: " % [timestamp, player_color_hex, player_name]
	
	# Add each character with a different color
	for i in range(message.length()):
		var char_text = message.substr(i, 1)
		var color = colors[i % colors.size()]
		var color_hex = "#%02x%02x%02x" % [int(color.r * 255), int(color.g * 255), int(color.b * 255)]
		formatted_text += "[color=%s]%s[/color]" % [color_hex, char_text]
	
	message_label.text = formatted_text
	
	# Apply Metropolis font if available
	if metropolis_font:
		message_label.add_theme_font_override("normal_font", metropolis_font)
		message_label.add_theme_font_size_override("normal_font_size", 12)
	
	message_label.modulate.a = 1.0
	
	# Add to message list (at the end, so newest messages appear closest to input)
	# Messages flow upwards - oldest at top, newest at bottom
	message_list.add_child(message_label)
	
	# Scroll to bottom
	call_deferred("_scroll_to_bottom")
	
	# Update background visibility
	_update_background_visibility()

func _scroll_to_bottom():
	if message_scroll:
		await get_tree().process_frame  # Wait for layout to update
		var scroll_bar = message_scroll.get_v_scroll_bar()
		if scroll_bar:
			message_scroll.scroll_vertical = int(scroll_bar.max_value)

func _update_background_visibility():
	if not background:
		return
	
	var current_time = Time.get_ticks_msec() / 1000.0
	var has_recent_message = false
	
	# Check if there are any recent messages
	for msg in messages:
		var age = current_time - msg.time
		if age < RECENT_MESSAGE_TIME:
			has_recent_message = true
			break
	
	# Show background if chat is open OR if there are recent messages
	background.visible = chat_open or has_recent_message
	
	# Update message fade (only when chat is closed)
	if not chat_open:
		_update_message_fades()

func _show_all_messages():
	# Make all messages fully visible (used when chat is opened)
	if not message_list:
		return
	
	var message_labels = []
	for child in message_list.get_children():
		if child is RichTextLabel:
			message_labels.append(child)
	
	for label in message_labels:
		label.modulate.a = 1.0

func _update_message_fades():
	if not message_list:
		return
	
	# Don't fade messages when chat is open - they should all be visible
	if chat_open:
		return
	
	var current_time = Time.get_ticks_msec() / 1000.0
	
	# Update fade for each message label
	# Messages are in chronological order (oldest first, newest last)
	var message_labels = []
	for child in message_list.get_children():
		if child is RichTextLabel:
			message_labels.append(child)
	
	# Match labels to messages (same order - oldest first, newest last)
	for i in range(min(messages.size(), message_labels.size())):
		var msg = messages[i]
		var label = message_labels[i]
		var age = current_time - msg.time
		
		if age > MESSAGE_FADE_TIME:
			# Start fading
			var fade_progress = (age - MESSAGE_FADE_TIME) / MESSAGE_FADE_DURATION
			label.modulate.a = max(0.0, 1.0 - fade_progress)
		else:
			# Fully visible
			label.modulate.a = 1.0

func _process(_delta):
	# Update message fades and background visibility
	if not chat_open:
		_update_background_visibility()
		_update_message_fades()

func _get_player_name() -> String:
	# Get local player name from network_manager.players_info
	# This ensures consistency with how player names are generated and used elsewhere
	if network_manager and multiplayer.has_multiplayer_peer():
		var my_id = multiplayer.get_unique_id()
		var player_info = network_manager.get_player_info(my_id)
		if not player_info.is_empty():
			var player_names = player_info.get("player_names", [])
			if player_names.size() > 0:
				return player_names[0]
	
	# Fallback: try to get from player node
	var game_world = get_tree().get_first_node_in_group("game_world")
	if game_world:
		var player_manager = game_world.get_node_or_null("PlayerManager")
		if player_manager:
			var local_players = player_manager.get_local_players()
			if local_players.size() > 0:
				var player = local_players[0]
				# peer_id and local_player_index are always defined in player.gd
				return "Player%d_%d" % [player.peer_id, player.local_player_index + 1]
	
	# Final fallback
	if multiplayer.has_multiplayer_peer():
		return "Player%d" % multiplayer.get_unique_id()
	return "Player"

func _lock_player_controls(lock: bool):
	# Lock/unlock controls for all local players
	var game_world = get_tree().get_first_node_in_group("game_world")
	if game_world:
		var player_manager = game_world.get_node_or_null("PlayerManager")
		if player_manager:
			var local_players = player_manager.get_local_players()
			for player in local_players:
				# controls_disabled is a property in player.gd, so we can set it directly
				player.controls_disabled = lock
