extends Control

# Replaces the old menu/server_browser.gd, which baked in its own race picker
# from before TeamSelect existed and was never wired to a button. This screen
# only picks a server (backed by the matchmaking API's GET /servers) and
# connects directly — race/ship selection happens in-world via TeamSelect,
# same as the matchmaking-queue join path.

const STATUS_COLOR := {
	"available": Color(0.4, 0.9, 0.5),
	"full":      Color(0.95, 0.75, 0.3),
	"offline":   Color(0.55, 0.58, 0.65),
}

const PING_TIMEOUT_MSEC := 1500
const PING_COLOR_GOOD := Color(0.4, 0.9, 0.5)
const PING_COLOR_OK := Color(0.95, 0.75, 0.3)
const PING_COLOR_BAD := Color(0.9, 0.4, 0.4)

var _servers: Array = []
var _selected_index: int = -1
var _row_group: ButtonGroup
var _list_vbox: VBoxContainer
var _status_lbl: Label
var _connect_btn: Button

# Per-row PLAYERS/PING labels, index-aligned with _servers. Ping is measured
# by actively probing each server's UDP query port (game port + 1, see
# NetworkManager) rather than trusting the DB's static fields — the demo
# rows seeded by the matchmaking API aren't real reachable servers, so
# probing also doubles as a live "is this actually up" check for the ones
# that are.
var _players_labels: Array[Label] = []
var _ping_labels: Array[Label] = []
var _ping_probes: Array = []  # {udp: PacketPeerUDP, start_msec: int, done: bool} or null


func _ready() -> void:
	_build_ui()
	MusicManager.play_menu_music()
	NetworkManager.connection_succeeded.connect(_on_connected)
	NetworkManager.connection_failed.connect(_on_connect_failed)
	_refresh_servers()


func _process(_delta: float) -> void:
	if _ping_probes.is_empty():
		return
	var now := Time.get_ticks_msec()
	for index in _ping_probes.size():
		var probe = _ping_probes[index]
		if probe == null or probe.done:
			continue
		var udp: PacketPeerUDP = probe.udp
		if udp.get_available_packet_count() > 0:
			var packet := udp.get_packet()
			_apply_ping_result(index, packet.get_string_from_utf8(), now - probe.start_msec)
			probe.done = true
		elif now - probe.start_msec > PING_TIMEOUT_MSEC:
			_apply_ping_result(index, "", -1)
			probe.done = true


func _build_ui() -> void:
	_add_bg()
	_add_header()
	_add_list_panel()
	_add_bottom_bar()


func _add_bg() -> void:
	var bg := TextureRect.new()
	bg.texture = load("res://assets/images/background/skybox/1.png")
	bg.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL
	bg.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED
	bg.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
	add_child(bg)

	var ov := ColorRect.new()
	ov.color = Color(0.0, 0.0, 0.0, 0.6)
	ov.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
	add_child(ov)


func _add_header() -> void:
	var title := Label.new()
	title.text = "SERVER SELECT"
	title.anchor_left = 0.0
	title.anchor_top = 0.0
	title.anchor_right = 0.0
	title.anchor_bottom = 0.0
	title.offset_left = 60.0
	title.offset_top = 48.0
	title.offset_right = 700.0
	title.offset_bottom = 92.0
	title.add_theme_font_size_override("font_size", 32)
	title.add_theme_color_override("font_color", Color(1.0, 1.0, 1.0))
	add_child(title)


func _add_list_panel() -> void:
	var col_hdr := HBoxContainer.new()
	col_hdr.anchor_left = 0.0
	col_hdr.anchor_top = 0.0
	col_hdr.anchor_right = 1.0
	col_hdr.anchor_bottom = 0.0
	col_hdr.offset_left = 60.0
	col_hdr.offset_top = 108.0
	col_hdr.offset_right = -60.0
	col_hdr.offset_bottom = 132.0
	add_child(col_hdr)

	var h_name := _field_label("MODE / ADDRESS")
	h_name.size_flags_horizontal = Control.SIZE_EXPAND_FILL
	col_hdr.add_child(h_name)
	var h_players := _field_label("PLAYERS")
	h_players.custom_minimum_size = Vector2(100, 0)
	h_players.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
	col_hdr.add_child(h_players)
	var h_ping := _field_label("PING")
	h_ping.custom_minimum_size = Vector2(80, 0)
	h_ping.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
	col_hdr.add_child(h_ping)
	var h_status := _field_label("STATUS")
	h_status.custom_minimum_size = Vector2(120, 0)
	h_status.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
	col_hdr.add_child(h_status)

	var sep := HSeparator.new()
	sep.anchor_left = 0.0
	sep.anchor_top = 0.0
	sep.anchor_right = 1.0
	sep.anchor_bottom = 0.0
	sep.offset_left = 60.0
	sep.offset_top = 136.0
	sep.offset_right = -60.0
	sep.offset_bottom = 140.0
	sep.add_theme_color_override("color", Color(1.0, 1.0, 1.0, 0.2))
	add_child(sep)

	var scroll := ScrollContainer.new()
	scroll.anchor_left = 0.0
	scroll.anchor_top = 0.0
	scroll.anchor_right = 1.0
	scroll.anchor_bottom = 1.0
	scroll.offset_left = 60.0
	scroll.offset_top = 148.0
	scroll.offset_right = -60.0
	scroll.offset_bottom = -84.0
	add_child(scroll)

	_list_vbox = VBoxContainer.new()
	_list_vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
	_list_vbox.add_theme_constant_override("separation", 2)
	scroll.add_child(_list_vbox)


func _field_label(txt: String) -> Label:
	var lbl := Label.new()
	lbl.text = txt
	lbl.add_theme_font_size_override("font_size", 12)
	lbl.add_theme_color_override("font_color", Color(0.6, 0.63, 0.7))
	return lbl


func _add_bottom_bar() -> void:
	var back_btn := Button.new()
	back_btn.text = "BACK"
	back_btn.anchor_left = 0.0
	back_btn.anchor_top = 1.0
	back_btn.anchor_right = 0.0
	back_btn.anchor_bottom = 1.0
	back_btn.offset_left = 60.0
	back_btn.offset_top = -60.0
	back_btn.offset_right = 200.0
	back_btn.offset_bottom = -20.0
	back_btn.add_theme_font_size_override("font_size", 15)
	back_btn.pressed.connect(_on_back)
	add_child(back_btn)

	var refresh_btn := Button.new()
	refresh_btn.text = "REFRESH"
	refresh_btn.anchor_left = 0.0
	refresh_btn.anchor_top = 1.0
	refresh_btn.anchor_right = 0.0
	refresh_btn.anchor_bottom = 1.0
	refresh_btn.offset_left = 216.0
	refresh_btn.offset_top = -60.0
	refresh_btn.offset_right = 356.0
	refresh_btn.offset_bottom = -20.0
	refresh_btn.add_theme_font_size_override("font_size", 15)
	refresh_btn.pressed.connect(_refresh_servers)
	add_child(refresh_btn)

	_status_lbl = Label.new()
	_status_lbl.text = ""
	_status_lbl.anchor_left = 0.0
	_status_lbl.anchor_top = 1.0
	_status_lbl.anchor_right = 1.0
	_status_lbl.anchor_bottom = 1.0
	_status_lbl.offset_left = 372.0
	_status_lbl.offset_top = -60.0
	_status_lbl.offset_right = -216.0
	_status_lbl.offset_bottom = -20.0
	_status_lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
	_status_lbl.add_theme_font_size_override("font_size", 13)
	_status_lbl.add_theme_color_override("font_color", Color(0.6, 0.72, 0.86))
	add_child(_status_lbl)

	_connect_btn = Button.new()
	_connect_btn.text = "CONNECT"
	_connect_btn.disabled = true
	_connect_btn.anchor_left = 1.0
	_connect_btn.anchor_top = 1.0
	_connect_btn.anchor_right = 1.0
	_connect_btn.anchor_bottom = 1.0
	_connect_btn.offset_left = -200.0
	_connect_btn.offset_top = -60.0
	_connect_btn.offset_right = -60.0
	_connect_btn.offset_bottom = -20.0
	_connect_btn.add_theme_font_size_override("font_size", 16)
	_connect_btn.add_theme_color_override("font_color", Color(1.0, 1.0, 1.0))
	var sn := StyleBoxFlat.new()
	sn.bg_color = Color(1.0, 1.0, 1.0, 0.12)
	sn.set_corner_radius_all(4)
	var sh := StyleBoxFlat.new()
	sh.bg_color = Color(1.0, 1.0, 1.0, 0.22)
	sh.set_corner_radius_all(4)
	var sd := StyleBoxFlat.new()
	sd.bg_color = Color(1.0, 1.0, 1.0, 0.05)
	sd.set_corner_radius_all(4)
	_connect_btn.add_theme_stylebox_override("normal", sn)
	_connect_btn.add_theme_stylebox_override("hover", sh)
	_connect_btn.add_theme_stylebox_override("disabled", sd)
	_connect_btn.pressed.connect(_on_connect)
	add_child(_connect_btn)


func _refresh_servers() -> void:
	_selected_index = -1
	_connect_btn.disabled = true
	_status_lbl.add_theme_color_override("font_color", Color(0.6, 0.72, 0.86))
	_status_lbl.text = "Loading servers..."
	for child in _list_vbox.get_children():
		child.queue_free()
	for probe in _ping_probes:
		if probe != null:
			probe.udp.close()
	_players_labels.clear()
	_ping_labels.clear()
	_ping_probes.clear()

	var result: Dictionary = await MatchmakingClient.list_servers()
	if not result.ok:
		_status_lbl.add_theme_color_override("font_color", Color(0.85, 0.4, 0.4))
		_status_lbl.text = "Couldn't reach matchmaking service."
		return

	_servers = result.servers
	if _servers.is_empty():
		_status_lbl.text = "No servers online right now."
		return

	_status_lbl.text = ""
	_row_group = ButtonGroup.new()
	for i in _servers.size():
		_list_vbox.add_child(_make_row(_servers[i], i))
	_start_ping_probes()


func _start_ping_probes() -> void:
	for i in _servers.size():
		var server: Dictionary = _servers[i]
		var udp := PacketPeerUDP.new()
		var query_port := int(server.get("port", 0)) + NetworkManager.QUERY_PORT_OFFSET
		if udp.connect_to_host(str(server.get("ip", "")), query_port) != OK:
			_ping_probes.append(null)
			continue
		udp.put_packet(NetworkManager.PING_REQUEST.to_utf8_buffer())
		_ping_probes.append({"udp": udp, "start_msec": Time.get_ticks_msec(), "done": false})


func _apply_ping_result(index: int, response: String, rtt_msec: int) -> void:
	if index >= _ping_labels.size():
		return
	var ping_lbl := _ping_labels[index]
	if response.begins_with("SPACEWAR_PONG:"):
		var parts := response.split(":")
		if parts.size() >= 3:
			_players_labels[index].text = "%s / %s" % [parts[1], parts[2]]
		ping_lbl.text = "%d ms" % rtt_msec
		if rtt_msec <= 80:
			ping_lbl.add_theme_color_override("font_color", PING_COLOR_GOOD)
		elif rtt_msec <= 180:
			ping_lbl.add_theme_color_override("font_color", PING_COLOR_OK)
		else:
			ping_lbl.add_theme_color_override("font_color", PING_COLOR_BAD)
	else:
		ping_lbl.text = "‒‒"
		ping_lbl.add_theme_color_override("font_color", Color(0.5, 0.53, 0.6))


func _make_row(server: Dictionary, index: int) -> Button:
	var row := Button.new()
	row.toggle_mode = true
	row.button_group = _row_group
	row.text = ""
	row.custom_minimum_size = Vector2(0, 46)
	row.add_theme_font_size_override("font_size", 16)

	var sn := StyleBoxEmpty.new()
	var sh := StyleBoxFlat.new()
	sh.bg_color = Color(1.0, 1.0, 1.0, 0.08)
	sh.border_width_left = 3
	sh.border_color = Color(1.0, 1.0, 1.0, 0.9)
	row.add_theme_stylebox_override("normal", sn)
	row.add_theme_stylebox_override("hover", sh)
	row.add_theme_stylebox_override("pressed", sh)

	var hb := HBoxContainer.new()
	hb.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
	hb.offset_left = 16.0
	hb.offset_right = -16.0
	hb.mouse_filter = Control.MOUSE_FILTER_IGNORE
	row.add_child(hb)

	var mode: String = str(server.get("mode", "?")).to_upper()
	var addr_lbl := Label.new()
	addr_lbl.text = "%s   %s:%s" % [mode, server.get("ip", "?"), str(server.get("port", "?"))]
	addr_lbl.size_flags_horizontal = Control.SIZE_EXPAND_FILL
	addr_lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
	addr_lbl.add_theme_color_override("font_color", Color(0.85, 0.87, 0.92))
	hb.add_child(addr_lbl)

	# Seeded from the DB row immediately; overwritten by the live UDP query
	# response in _apply_ping_result if that server actually answers.
	var players_lbl := Label.new()
	players_lbl.text = "%s / %s" % [str(server.get("player_count", "?")), str(server.get("max_players", "?"))]
	players_lbl.custom_minimum_size = Vector2(100, 0)
	players_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
	players_lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
	players_lbl.add_theme_font_size_override("font_size", 13)
	players_lbl.add_theme_color_override("font_color", Color(0.75, 0.78, 0.85))
	hb.add_child(players_lbl)
	_players_labels.append(players_lbl)

	var ping_lbl := Label.new()
	ping_lbl.text = "…"
	ping_lbl.custom_minimum_size = Vector2(80, 0)
	ping_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
	ping_lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
	ping_lbl.add_theme_font_size_override("font_size", 13)
	ping_lbl.add_theme_color_override("font_color", Color(0.6, 0.63, 0.7))
	hb.add_child(ping_lbl)
	_ping_labels.append(ping_lbl)

	var status: String = str(server.get("status", "offline"))
	var col: Color = STATUS_COLOR.get(status, Color(0.6, 0.63, 0.7))
	var status_lbl := Label.new()
	status_lbl.text = status.to_upper()
	status_lbl.custom_minimum_size = Vector2(120, 0)
	status_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
	status_lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
	status_lbl.add_theme_font_size_override("font_size", 13)
	status_lbl.add_theme_color_override("font_color", col)
	hb.add_child(status_lbl)

	row.pressed.connect(_on_row_selected.bind(index))
	return row


func _on_row_selected(index: int) -> void:
	_selected_index = index
	_connect_btn.disabled = false


func _on_connect() -> void:
	if _selected_index < 0 or _selected_index >= _servers.size():
		return
	if GameConfig.player_name.strip_edges().is_empty():
		_status_lbl.add_theme_color_override("font_color", Color(0.85, 0.4, 0.4))
		_status_lbl.text = "Set your callsign from the main menu PROFILE first."
		return

	var server: Dictionary = _servers[_selected_index]
	var status := str(server.get("status", "offline"))
	if status == "offline":
		_status_lbl.add_theme_color_override("font_color", Color(0.85, 0.4, 0.4))
		_status_lbl.text = "That server is offline."
		return
	if status == "full":
		_status_lbl.add_theme_color_override("font_color", Color(0.85, 0.4, 0.4))
		_status_lbl.text = "That server is full."
		return

	_connect_btn.disabled = true
	_status_lbl.add_theme_color_override("font_color", Color(0.6, 0.72, 0.86))
	_status_lbl.text = "Connecting..."
	NetworkManager.join_server(server.ip, int(server.port))


func _on_connected() -> void:
	get_tree().change_scene_to_file("res://world/world.tscn")


func _on_connect_failed() -> void:
	_connect_btn.disabled = false
	_status_lbl.add_theme_color_override("font_color", Color(0.85, 0.4, 0.4))
	_status_lbl.text = "Connection failed — is that server reachable?"


func _on_back() -> void:
	get_tree().change_scene_to_file("res://menu/main_menu.tscn")
