@tool
class_name GECSEditorDebuggerTab
extends Control

@onready var query_builder_check_box: CheckBox = %QueryBuilderCheckBox
@onready var entities_filter_line_edit: LineEdit = %EntitiesQueryLineEdit
@onready var systems_filter_line_edit: LineEdit = %SystemsQueryLineEdit
@onready var collapse_all_btn: Button = %CollapseAllBtn
@onready var expand_all_btn: Button = %ExpandAllBtn
@onready var systems_collapse_all_btn: Button = %SystemsCollapseAllBtn
@onready var systems_expand_all_btn: Button = %SystemsExpandAllBtn
@onready var pop_out_btn: Button = %PopOutBtn

var ecs_data: Dictionary = {}
var default_system := {"path": "", "active": true, "metrics": {}, "group": ""}
var default_entity := {"path": "", "active": true, "components": {}, "relationships": {}}
var timer = 5
var active := false
var _pending_components: Dictionary = {} # ent_id -> Array[Dictionary] of pending component data
var _popup_window: Window = null
var _debugger_session: EditorDebuggerSession = null

@onready var system_tree: Tree = %SystemsTree
@onready var entities_tree: Tree = %EntitiesTree
@onready var entity_status_bar: TextEdit = %EntityStatusBar
@onready var systems_status_bar: TextEdit = %SystemsStatusBar
@onready var debug_mode_overlay: Panel = %DebugModeOverlay

# Sorting state
var _system_sort_column: int = -1 # -1 means no sorting
var _system_sort_ascending: bool = true
var _entity_sort_column: int = -1 # -1 means no sorting
var _entity_sort_ascending: bool = true

# Pinned items
var _pinned_entities: Dictionary = {} # entity_id -> bool
var _pinned_systems: Dictionary = {} # system_id -> bool

# Icon constants (using Unicode characters)
const ICON_ENTITY = "📦" # Entity icon
const ICON_COMPONENT = "🔧" # Component icon
const ICON_FLAG = "🚩" # Flag component (no properties)
const ICON_RELATIONSHIP = "🔗" # Relationship icon
const ICON_PIN = "📌" # Pinned item icon


func _ready() -> void:
	_update_debug_mode_overlay()
	if system_tree:
		# Five columns: name, group, execution time, status, and order
		system_tree.columns = 5
		system_tree.set_column_expand(0, true) # Name column expands
		system_tree.set_column_expand(1, false) # Group column resizable
		system_tree.set_column_expand(2, false) # Execution time column resizable
		system_tree.set_column_expand(3, false) # Status column resizable
		system_tree.set_column_expand(4, false) # Order column resizable

		# Set column widths
		system_tree.set_column_custom_minimum_width(1, 100) # Group: 100px min
		system_tree.set_column_custom_minimum_width(2, 100) # Execution time: 100px min
		system_tree.set_column_custom_minimum_width(3, 100) # Status: 100px min
		system_tree.set_column_custom_minimum_width(4, 60) # Order: 60px min

		# Enable column resizing (clip content allows manual resizing)
		system_tree.set_column_clip_content(0, true)
		system_tree.set_column_clip_content(1, true)
		system_tree.set_column_clip_content(2, true)
		system_tree.set_column_clip_content(3, true)
		system_tree.set_column_clip_content(4, true)

		# Set column titles (clickable for sorting)
		system_tree.set_column_title(0, "Name")
		system_tree.set_column_title(1, "Group")
		system_tree.set_column_title(2, "Time (ms)")
		system_tree.set_column_title(3, "Status")
		system_tree.set_column_title(4, "Order")
		system_tree.set_column_titles_visible(true)

		# Create root item
		if system_tree.get_root() == null:
			system_tree.create_item()
	if entities_tree:
		# Four columns: name, components count, relationships count, nodes count
		entities_tree.columns = 4
		entities_tree.set_column_expand(0, true) # Name column expands
		entities_tree.set_column_expand(1, false) # Components count resizable
		entities_tree.set_column_expand(2, false) # Relationships count resizable
		entities_tree.set_column_expand(3, false) # Nodes count resizable

		# Set column widths
		entities_tree.set_column_custom_minimum_width(1, 80) # Components: 80px min
		entities_tree.set_column_custom_minimum_width(2, 80) # Relationships: 80px min
		entities_tree.set_column_custom_minimum_width(3, 80) # Nodes: 80px min

		# Enable column resizing (clip content allows manual resizing)
		entities_tree.set_column_clip_content(0, true)
		entities_tree.set_column_clip_content(1, true)
		entities_tree.set_column_clip_content(2, true)
		entities_tree.set_column_clip_content(3, true)

		# Set column titles
		entities_tree.set_column_title(0, "Entity")
		entities_tree.set_column_title(1, "Comps")
		entities_tree.set_column_title(2, "Rels")
		entities_tree.set_column_title(3, "Nodes")
		entities_tree.set_column_titles_visible(true)

		# Create root item
		if entities_tree.get_root() == null:
			entities_tree.create_item()
		# Polling & pinning removed; tree updates only via incoming messages
	if entities_filter_line_edit and not entities_filter_line_edit.text_changed.is_connected(_on_entities_filter_changed):
		entities_filter_line_edit.text_changed.connect(_on_entities_filter_changed)
	if systems_filter_line_edit and not systems_filter_line_edit.text_changed.is_connected(_on_systems_filter_changed):
		systems_filter_line_edit.text_changed.connect(_on_systems_filter_changed)
	if collapse_all_btn and not collapse_all_btn.pressed.is_connected(_on_collapse_all_pressed):
		collapse_all_btn.pressed.connect(_on_collapse_all_pressed)
	if expand_all_btn and not expand_all_btn.pressed.is_connected(_on_expand_all_pressed):
		expand_all_btn.pressed.connect(_on_expand_all_pressed)
	if systems_collapse_all_btn and not systems_collapse_all_btn.pressed.is_connected(_on_systems_collapse_all_pressed):
		systems_collapse_all_btn.pressed.connect(_on_systems_collapse_all_pressed)
	if systems_expand_all_btn and not systems_expand_all_btn.pressed.is_connected(_on_systems_expand_all_pressed):
		systems_expand_all_btn.pressed.connect(_on_systems_expand_all_pressed)
	if pop_out_btn and not pop_out_btn.pressed.is_connected(_on_pop_out_pressed):
		pop_out_btn.pressed.connect(_on_pop_out_pressed)
	# Connect to system tree for clicking (single click to toggle)
	if system_tree and not system_tree.item_mouse_selected.is_connected(_on_system_tree_item_mouse_selected):
		system_tree.item_mouse_selected.connect(_on_system_tree_item_mouse_selected)
	# Connect to system tree for column clicking (for sorting)
	if system_tree and not system_tree.column_title_clicked.is_connected(_on_system_tree_column_clicked):
		system_tree.column_title_clicked.connect(_on_system_tree_column_clicked)
	# Connect to entities tree for column clicking (for sorting)
	if entities_tree and not entities_tree.column_title_clicked.is_connected(_on_entities_tree_column_clicked):
		entities_tree.column_title_clicked.connect(_on_entities_tree_column_clicked)
	# Connect to entities tree for right-click context menu
	if entities_tree and not entities_tree.item_mouse_selected.is_connected(_on_entities_tree_item_mouse_selected):
		entities_tree.item_mouse_selected.connect(_on_entities_tree_item_mouse_selected)
	# Connect to system tree for right-click context menu
	if system_tree and not system_tree.button_clicked.is_connected(_on_system_tree_button_clicked):
		system_tree.button_clicked.connect(_on_system_tree_button_clicked)


func _process(delta: float) -> void:
	# No periodic polling; rely on debugger messages only
	pass


func _update_debug_mode_overlay() -> void:
	if not debug_mode_overlay:
		return

	# Check if debug mode is enabled in project settings
	var debug_enabled = ProjectSettings.get_setting(GecsSettings.SETTINGS_DEBUG_MODE, false)

	# Show overlay if debug mode is disabled, hide if enabled
	debug_mode_overlay.visible = not debug_enabled


# --- External setters expected by debugger plugin ---
func set_debugger_session(_session):
	# Store session reference for sending messages to game
	_debugger_session = _session


func set_editor_interface(_editor_interface):
	# Store editor interface reference for future use (e.g., selecting nodes)
	# Currently not used, but expected by the debugger plugin
	pass


# Send a message from editor to the running game
func send_to_game(message: String, data: Array = []) -> bool:
	if _debugger_session == null:
		push_warning("GECS Debug: No active debugger session")
		return false
	_debugger_session.send_message(message, data)
	return true


func clear_all_data():
	ecs_data.clear()
	_pending_components.clear()

	# Clear system tree
	if system_tree:
		system_tree.clear()
		# Recreate root
		system_tree.create_item()

	# Clear entities tree
	if entities_tree:
		entities_tree.clear()
		# Recreate root
		entities_tree.create_item()

	# Reset status bars
	_update_entity_status_bar()
	_update_systems_status_bar()


# ---- Filters & Refresh Helpers ----
func _on_entities_filter_changed(new_text: String):
	_refresh_entity_tree_filter()


func _on_systems_filter_changed(new_text: String):
	_refresh_system_tree_filter()


# ---- Button Handlers ----
func _on_collapse_all_pressed():
	collapse_all_entities()


func _on_expand_all_pressed():
	expand_all_entities()


func _on_systems_collapse_all_pressed():
	collapse_all_systems()


func _on_systems_expand_all_pressed():
	expand_all_systems()


func _on_pop_out_pressed():
	if _popup_window != null:
		# Window already exists, close it and restore content
		_on_popup_window_closed()
		return

	# Create a new window
	_popup_window = Window.new()
	_popup_window.title = "GECS Debug Viewer"
	_popup_window.size = Vector2i(1200, 800)
	_popup_window.initial_position = Window.WINDOW_INITIAL_POSITION_CENTER_SCREEN_WITH_MOUSE_FOCUS

	# Move the main content to the window (not duplicate)
	var hsplit = get_node("HSplit")
	remove_child(hsplit)
	_popup_window.add_child(hsplit)

	# Add window to the scene tree
	add_child(_popup_window)

	# Connect close signal
	_popup_window.close_requested.connect(_on_popup_window_closed)

	# Show the window
	_popup_window.show()

	# Update the button text
	pop_out_btn.text = "Pop In"


func _on_popup_window_closed():
	if _popup_window != null:
		# Move content back to main tab
		var hsplit = _popup_window.get_node("HSplit")
		_popup_window.remove_child(hsplit)
		add_child(hsplit)
		move_child(hsplit, 0) # Move to beginning

		# Close and cleanup window
		_popup_window.queue_free()
		_popup_window = null
		pop_out_btn.text = "Pop Out"


func _on_system_tree_item_mouse_selected(position: Vector2, mouse_button_index: int):
	# When user clicks on a system tree item, check if clicking on status column to toggle or right-click for menu
	var selected = system_tree.get_selected()
	if not selected:
		return

	# Check if has system_id metadata with safe default
	var system_id = selected.get_meta("system_id", null)
	if system_id == null:
		return

	# Only process top-level system items (not child details)
	if selected.get_parent() == system_tree.get_root():
		if mouse_button_index == MOUSE_BUTTON_LEFT:
			# Get the column that was clicked
			var column = system_tree.get_column_at_position(position)
			if column == 3: # Status column (now column 3)
				_toggle_system_active()
		elif mouse_button_index == MOUSE_BUTTON_RIGHT:
			_show_system_context_menu(selected, position)


func _on_system_tree_button_clicked(item: TreeItem, column: int, id: int, mouse_button_index: int):
	# Handle button clicks in tree (currently unused, but keeping for future)
	pass


func _on_entities_tree_item_mouse_selected(position: Vector2, mouse_button_index: int):
	# Handle right-click on entity tree items
	if mouse_button_index != MOUSE_BUTTON_RIGHT:
		return

	var selected = entities_tree.get_selected()
	if not selected:
		return

	# Check if has entity_id metadata (top-level entity item)
	var entity_id = selected.get_meta("entity_id", null)
	if entity_id == null:
		return

	# Only show context menu for top-level entity items
	if selected.get_parent() == entities_tree.get_root():
		_show_entity_context_menu(selected, position)


func _show_entity_context_menu(item: TreeItem, position: Vector2):
	var entity_id = item.get_meta("entity_id", null)
	if entity_id == null:
		return

	var popup = PopupMenu.new()
	add_child(popup)

	var is_pinned = _pinned_entities.get(entity_id, false)
	if is_pinned:
		popup.add_item("Unpin Entity", 0)
	else:
		popup.add_item("Pin Entity", 0)

	# Position the popup at the mouse position (use get_screen_position for proper screen coords)
	var screen_pos = entities_tree.get_screen_position() + position
	popup.position = screen_pos
	popup.popup()

	# Connect the selection signal
	popup.id_pressed.connect(func(id):
		if id == 0:
			_toggle_entity_pin(entity_id, item)
		popup.queue_free()
	)

	# Clean up when popup closes
	popup.popup_hide.connect(func():
		if is_instance_valid(popup):
			popup.queue_free()
	)


func _show_system_context_menu(item: TreeItem, position: Vector2):
	var system_id = item.get_meta("system_id", null)
	if system_id == null:
		return

	var popup = PopupMenu.new()
	add_child(popup)

	var is_pinned = _pinned_systems.get(system_id, false)
	if is_pinned:
		popup.add_item("Unpin System", 0)
	else:
		popup.add_item("Pin System", 0)

	# Position the popup at the mouse position (use get_screen_position for proper screen coords)
	var screen_pos = system_tree.get_screen_position() + position
	popup.position = screen_pos
	popup.popup()

	# Connect the selection signal
	popup.id_pressed.connect(func(id):
		if id == 0:
			_toggle_system_pin(system_id, item)
		popup.queue_free()
	)

	# Clean up when popup closes
	popup.popup_hide.connect(func():
		if is_instance_valid(popup):
			popup.queue_free()
	)


func _toggle_entity_pin(entity_id: int, item: TreeItem):
	var is_pinned = _pinned_entities.get(entity_id, false)
	_pinned_entities[entity_id] = not is_pinned
	_update_entity_pin_display(item, not is_pinned)
	# Re-sort to move pinned items to top
	if _entity_sort_column != -1 or not is_pinned:
		_sort_entity_tree()


func _toggle_system_pin(system_id: int, item: TreeItem):
	var is_pinned = _pinned_systems.get(system_id, false)
	_pinned_systems[system_id] = not is_pinned
	_update_system_pin_display(item, not is_pinned)
	# Re-sort to move pinned items to top
	if _system_sort_column != -1 or not is_pinned:
		_sort_system_tree()


func _update_entity_pin_display(item: TreeItem, is_pinned: bool):
	var current_text = item.get_text(0)
	# Remove existing pin icon if present
	if current_text.begins_with(ICON_PIN + " "):
		current_text = current_text.substr(2)

	if is_pinned:
		item.set_text(0, ICON_PIN + " " + current_text)
	else:
		item.set_text(0, current_text)


func _update_system_pin_display(item: TreeItem, is_pinned: bool):
	var current_text = item.get_text(0) # Name is in column 0 now
	# Remove existing pin icon if present
	if current_text.begins_with(ICON_PIN + " "):
		current_text = current_text.substr(2)

	if is_pinned:
		item.set_text(0, ICON_PIN + " " + current_text)
	else:
		item.set_text(0, current_text)


func _toggle_system_active():
	var selected = system_tree.get_selected()
	if not selected:
		push_warning("GECS Debug: No system selected")
		return

	var system_id = selected.get_meta("system_id", null)
	if system_id == null:
		push_warning("GECS Debug: No system selected")
		return
	var systems_data = ecs_data.get("systems", {})
	var system_data = systems_data.get(system_id, {})
	var current_active = system_data.get("active", true)

	# Toggle the state
	var new_active = not current_active

	# Send message to game to toggle system active state
	send_to_game("gecs:set_system_active", [system_id, new_active])

	# Optimistically update local state (will be confirmed by game)
	system_data["active"] = new_active
	_update_system_active_display(selected, new_active)


func _update_system_active_display(system_item: TreeItem, is_active: bool):
	# Update the visual display of the system in column 3 as a button
	if is_active:
		system_item.set_text(3, "ACTIVE")
		system_item.set_custom_color(3, Color(0.5, 1.0, 0.5)) # Green text
	else:
		system_item.set_text(3, "INACTIVE")
		system_item.set_custom_color(3, Color(1.0, 0.3, 0.3)) # Red text

	# Make the status column a clickable button
	system_item.set_cell_mode(3, TreeItem.CELL_MODE_STRING)
	system_item.set_selectable(3, true)
	system_item.set_editable(3, false)


func _on_system_tree_column_clicked(column: int, mouse_button_index: int):
	# Only sort on left click
	if mouse_button_index != MOUSE_BUTTON_LEFT:
		return

	# Cycle through: None -> Asc -> Desc -> None
	if _system_sort_column == column:
		if _system_sort_ascending:
			# Currently ascending, switch to descending
			_system_sort_ascending = false
		else:
			# Currently descending, remove sorting
			_system_sort_column = -1
			_system_sort_ascending = true
	else:
		# New column, start with ascending
		_system_sort_column = column
		_system_sort_ascending = true

	# Update column title indicators
	_update_system_column_indicators()

	# Sort the tree
	_sort_system_tree()


func _update_system_column_indicators():
	# Clear all column indicators first
	for i in range(5):
		var title = ""
		match i:
			0: title = "Name"
			1: title = "Group"
			2: title = "Time (ms)"
			3: title = "Status"
			4: title = "Order"

		# Add arrow indicator if this is the sort column
		if i == _system_sort_column:
			if _system_sort_ascending:
				title += " ▲"
			else:
				title += " ▼"

		system_tree.set_column_title(i, title)


func _sort_system_tree():
	if not system_tree:
		return

	var root = system_tree.get_root()
	if not root:
		return

	# Collect all system items with their data
	var systems: Array = []
	var pinned_systems: Array = []
	var child = root.get_first_child()
	while child:
		var system_id = child.get_meta("system_id", null)
		var system_data = {
			"item": child,
			"name": child.get_text(0),
			"group": child.get_text(1),
			"time": 0.0,
			"status": child.get_text(3),
			"order": int(child.get_text(4)) if child.get_text(4).is_valid_int() else 0,
			"system_id": system_id,
			"is_pinned": _pinned_systems.get(system_id, false)
		}

		# Get execution time from text (remove " ms" suffix if present)
		var time_text = child.get_text(2)
		if time_text:
			system_data["time"] = float(time_text.replace(" ms", ""))

		if system_data["is_pinned"]:
			pinned_systems.append(system_data)
		else:
			systems.append(system_data)
		child = child.get_next()

	# Sort based on column (if sorting is active)
	if _system_sort_column != -1:
		match _system_sort_column:
			0: # Name
				if _system_sort_ascending:
					systems.sort_custom(func(a, b): return a["name"].nocasecmp_to(b["name"]) < 0)
				else:
					systems.sort_custom(func(a, b): return a["name"].nocasecmp_to(b["name"]) > 0)
			1: # Group
				if _system_sort_ascending:
					systems.sort_custom(func(a, b): return a["group"].nocasecmp_to(b["group"]) < 0)
				else:
					systems.sort_custom(func(a, b): return a["group"].nocasecmp_to(b["group"]) > 0)
			2: # Time
				if _system_sort_ascending:
					systems.sort_custom(func(a, b): return a["time"] < b["time"])
				else:
					systems.sort_custom(func(a, b): return a["time"] > b["time"])
			3: # Status
				if _system_sort_ascending:
					systems.sort_custom(func(a, b): return a["status"] < b["status"])
				else:
					systems.sort_custom(func(a, b): return a["status"] > b["status"])
			4: # Order
				if _system_sort_ascending:
					systems.sort_custom(func(a, b): return a["order"] < b["order"])
				else:
					systems.sort_custom(func(a, b): return a["order"] > b["order"])

	# Rebuild tree: pinned items first, then sorted items
	for system_data in pinned_systems:
		var item = system_data["item"]
		root.remove_child(item)
		root.add_child(item)
	for system_data in systems:
		var item = system_data["item"]
		root.remove_child(item)
		root.add_child(item)


func _on_entities_tree_column_clicked(column: int, mouse_button_index: int):
	# Only sort on left click
	if mouse_button_index != MOUSE_BUTTON_LEFT:
		return

	# Cycle through: None -> Asc -> Desc -> None
	if _entity_sort_column == column:
		if _entity_sort_ascending:
			# Currently ascending, switch to descending
			_entity_sort_ascending = false
		else:
			# Currently descending, remove sorting
			_entity_sort_column = -1
			_entity_sort_ascending = true
	else:
		# New column, start with ascending
		_entity_sort_column = column
		_entity_sort_ascending = true

	# Update column title indicators
	_update_entity_column_indicators()

	# Sort the tree
	_sort_entity_tree()


func _update_entity_column_indicators():
	# Clear all column indicators first
	for i in range(4):
		var title = ""
		match i:
			0: title = "Entity"
			1: title = "Comps"
			2: title = "Rels"
			3: title = "Nodes"

		# Add arrow indicator if this is the sort column
		if i == _entity_sort_column:
			if _entity_sort_ascending:
				title += " ▲"
			else:
				title += " ▼"

		entities_tree.set_column_title(i, title)


func _sort_entity_tree():
	if not entities_tree:
		return

	var root = entities_tree.get_root()
	if not root:
		return

	# Collect all entity items with their data
	var entities: Array = []
	var pinned_entities: Array = []
	var child = root.get_first_child()
	while child:
		var entity_id = child.get_meta("entity_id", null)
		var entity_data = {
			"item": child,
			"name": child.get_text(0),
			"comps": 0,
			"rels": 0,
			"nodes": 0,
			"entity_id": entity_id,
			"is_pinned": _pinned_entities.get(entity_id, false)
		}

		# Get numeric counts from columns
		var comps_text = child.get_text(1)
		if comps_text:
			entity_data["comps"] = int(comps_text)

		var rels_text = child.get_text(2)
		if rels_text:
			entity_data["rels"] = int(rels_text)

		var nodes_text = child.get_text(3)
		if nodes_text:
			entity_data["nodes"] = int(nodes_text)

		if entity_data["is_pinned"]:
			pinned_entities.append(entity_data)
		else:
			entities.append(entity_data)
		child = child.get_next()

	# Sort based on column (if sorting is active)
	if _entity_sort_column != -1:
		match _entity_sort_column:
			0: # Name
				if _entity_sort_ascending:
					entities.sort_custom(func(a, b): return a["name"].nocasecmp_to(b["name"]) < 0)
				else:
					entities.sort_custom(func(a, b): return a["name"].nocasecmp_to(b["name"]) > 0)
			1: # Components
				if _entity_sort_ascending:
					entities.sort_custom(func(a, b): return a["comps"] < b["comps"])
				else:
					entities.sort_custom(func(a, b): return a["comps"] > b["comps"])
			2: # Relationships
				if _entity_sort_ascending:
					entities.sort_custom(func(a, b): return a["rels"] < b["rels"])
				else:
					entities.sort_custom(func(a, b): return a["rels"] > b["rels"])
			3: # Nodes
				if _entity_sort_ascending:
					entities.sort_custom(func(a, b): return a["nodes"] < b["nodes"])
				else:
					entities.sort_custom(func(a, b): return a["nodes"] > b["nodes"])

	# Rebuild tree: pinned items first, then sorted items
	for entity_data in pinned_entities:
		var item = entity_data["item"]
		root.remove_child(item)
		root.add_child(item)
	for entity_data in entities:
		var item = entity_data["item"]
		root.remove_child(item)
		root.add_child(item)


# --- Utilities ---
func get_or_create_dict(dict: Dictionary, key, default_val = {}) -> Dictionary:
	if not dict.has(key):
		dict[key] = default_val
	return dict[key]


func collapse_all_entities():
	if not entities_tree:
		return
	var root = entities_tree.get_root()
	if root == null:
		return
	var item = root.get_first_child()
	while item:
		_collapse_item_recursive(item)
		item = item.get_next()


func expand_all_entities():
	if not entities_tree:
		return
	var root = entities_tree.get_root()
	if root == null:
		return
	var item = root.get_first_child()
	while item:
		_expand_item_recursive(item)
		item = item.get_next()


func collapse_all_systems():
	if not system_tree:
		return
	var root = system_tree.get_root()
	if root == null:
		return
	var item = root.get_first_child()
	while item:
		_collapse_item_recursive(item)
		item = item.get_next()


func expand_all_systems():
	if not system_tree:
		return
	var root = system_tree.get_root()
	if root == null:
		return
	var item = root.get_first_child()
	while item:
		_expand_item_recursive(item)
		item = item.get_next()


func _collapse_item_recursive(item: TreeItem):
	if item == null:
		return
	item.collapsed = true
	var child = item.get_first_child()
	while child:
		_collapse_item_recursive(child)
		child = child.get_next()


func _expand_item_recursive(item: TreeItem):
	if item == null:
		return
	item.collapsed = false
	var child = item.get_first_child()
	while child:
		_expand_item_recursive(child)
		child = child.get_next()


# ---- Filters ----
func _refresh_system_tree_filter():
	if not system_tree:
		return
	var root = system_tree.get_root()
	if root == null:
		return
	var filter = systems_filter_line_edit.text.to_lower() if systems_filter_line_edit else ""
	var item = root.get_first_child()
	while item:
		var name = item.get_text(0).to_lower()
		item.visible = filter == "" or name.find(filter) != -1
		item = item.get_next()


func _refresh_entity_tree_filter():
	if not entities_tree:
		return
	var root = entities_tree.get_root()
	if root == null:
		return
	var filter = entities_filter_line_edit.text.to_lower() if entities_filter_line_edit else ""
	var item = root.get_first_child()
	while item:
		var label = item.get_text(0).to_lower()
		var matches = filter == "" or label.find(filter) != -1
		if not matches:
			var comp_child = item.get_first_child()
			while comp_child and not matches:
				if comp_child.get_text(0).to_lower().find(filter) != -1:
					matches = true
					break
				var prop_row = comp_child.get_first_child()
				while prop_row and not matches:
					if prop_row.get_text(0).to_lower().find(filter) != -1:
						matches = true
						break
					prop_row = prop_row.get_next()
				comp_child = comp_child.get_next()
		item.visible = matches
		item = item.get_next()


func world_init(world_id: int, world_path: NodePath):
	# Initialize world tracking
	var world_dict := get_or_create_dict(ecs_data, "world")
	world_dict["id"] = world_id
	world_dict["path"] = world_path
	# Update debug mode overlay in case settings changed
	_update_debug_mode_overlay()


func set_world(world_id: int, world_path: NodePath):
	# Set or update current world
	var world_dict := get_or_create_dict(ecs_data, "world")
	world_dict["id"] = world_id
	world_dict["path"] = world_path


func process_world(delta: float, group_name: String):
	var world_dict := get_or_create_dict(ecs_data, "world")
	world_dict["delta"] = delta
	world_dict["active_group"] = group_name


func exit_world():
	ecs_data["exited"] = true


func _update_entity_counts(entity_item: TreeItem, ent_id: int):
	# Update the count columns for an entity
	if not entity_item:
		return

	var entities = ecs_data.get("entities", {})
	var entity_data = entities.get(ent_id, {})

	# Count components
	var components = entity_data.get("components", {})
	entity_item.set_text(1, str(components.size()))

	# Count relationships
	var relationships = entity_data.get("relationships", {})
	entity_item.set_text(2, str(relationships.size()))

	# Count child nodes (entities tree doesn't track this from game data)
	# We'll count the child TreeItems instead (components + relationships)
	var child_count = 0
	var child = entity_item.get_first_child()
	while child:
		# Count only component and relationship children, not property rows
		if child.has_meta("component_id") or child.has_meta("relationship_id"):
			child_count += 1
		child = child.get_next()
	entity_item.set_text(3, str(child_count))


func _is_flag_component(component_data: Dictionary) -> bool:
	# A flag component has no serializable properties
	# Check if data is empty or only has the placeholder
	if component_data.is_empty():
		return true
	if component_data.size() == 1 and component_data.has("<no_serialized_properties>"):
		return true
	return false


func entity_added(ent: int, path: NodePath) -> void:
	var entities := get_or_create_dict(ecs_data, "entities")
	# Merge with any existing (temporary) entry that may already have buffered components/relationships
	var existing := entities.get(ent, {})
	var existing_components: Dictionary = existing.get("components", {})
	var existing_relationships: Dictionary = existing.get("relationships", {})
	# Update in place instead of overwrite to avoid losing buffered component data
	entities[ent] = {
		"path": path,
		"active": true,
		"components": existing_components,
		"relationships": existing_relationships
	}
	# Add to entities tree
	if entities_tree:
		var root = entities_tree.get_root()
		if root == null:
			root = entities_tree.create_item()
		var item = entities_tree.create_item(root)
		# Column 0: Entity name with icon (and pin icon if pinned)
		var display_name = ICON_ENTITY + " " + str(path).get_file()
		if _pinned_entities.get(ent, false):
			display_name = ICON_PIN + " " + display_name
		item.set_text(0, display_name)
		item.set_tooltip_text(0, str(ent) + " : " + str(path))
		# Columns 1-3: Counts (will be updated as components/relationships are added)
		item.set_text(1, "0")
		item.set_text(2, "0")
		item.set_text(3, "0")
		item.set_meta("entity_id", ent)
		item.set_meta("path", path)
		item.collapsed = true # Start collapsed
		# Flush any pending components that arrived before the entity node was created
		if _pending_components.has(ent):
			for comp_info in _pending_components[ent]:
				_attach_component_to_entity_item(item, ent, comp_info.comp_id, comp_info.comp_path, comp_info.data)
			_pending_components.erase(ent)
		# Update counts
		_update_entity_counts(item, ent)

	# Re-sort if we have an active sort column
	if _entity_sort_column != -1:
		_sort_entity_tree()

	_update_entity_status_bar()


func entity_removed(ent: int, path: NodePath) -> void:
	var entities := get_or_create_dict(ecs_data, "entities")
	entities.erase(ent)
	# Remove from tree
	if entities_tree and entities_tree.get_root():
		var root = entities_tree.get_root()
		var child = root.get_first_child()
		while child:
			if child.get_meta("entity_id", null) == ent:
				root.remove_child(child)
				break
			child = child.get_next()

	# Clean up pinned state
	_pinned_entities.erase(ent)

	_update_entity_status_bar()


func entity_disabled(ent: int, path: NodePath) -> void:
	var entities = get_or_create_dict(ecs_data, "entities")
	if entities.has(ent):
		entities[ent]["active"] = false
	if entities_tree and entities_tree.get_root():
		var child = entities_tree.get_root().get_first_child()
		while child:
			if child.get_meta("entity_id", null) == ent:
				child.set_text(0, child.get_text(0) + " (disabled)")
				break
			child = child.get_next()


func entity_enabled(ent: int, path: NodePath) -> void:
	var entities = get_or_create_dict(ecs_data, "entities")
	if entities.has(ent):
		entities[ent]["active"] = true
	if entities_tree and entities_tree.get_root():
		var child = entities_tree.get_root().get_first_child()
		while child:
			if child.get_meta("entity_id", null) == ent:
				# Remove any (disabled) suffix
				var txt = child.get_text(0)
				if txt.ends_with(" (disabled)"):
					child.set_text(0, txt.substr(0, txt.length() - 11))
				break
			child = child.get_next()


func system_added(
	sys: int, group: String, process_empty: bool, active: bool, paused: bool, path: NodePath
) -> void:
	var systems_data := get_or_create_dict(ecs_data, "systems")
	systems_data[sys] = default_system.duplicate()
	systems_data[sys]["path"] = path
	systems_data[sys]["group"] = group
	systems_data[sys]["process_empty"] = process_empty
	systems_data[sys]["active"] = active
	systems_data[sys]["paused"] = paused

	_update_systems_status_bar()


func system_removed(sys: int, path: NodePath) -> void:
	var systems_data := get_or_create_dict(ecs_data, "systems")
	systems_data.erase(sys)

	# Clean up pinned state
	_pinned_systems.erase(sys)

	_update_systems_status_bar()


func system_metric(system: int, system_name: String, time: float):
	var systems_data := get_or_create_dict(ecs_data, "systems")
	var sys_entry := get_or_create_dict(systems_data, system, default_system.duplicate())
	# Track the last run time separately so it's always visible even when aggregation occurs
	sys_entry["last_time"] = time
	var sys_metrics = ecs_data["systems"][system]["metrics"]
	if not sys_metrics:
		# Initialize metrics if not present
		sys_metrics = {"min_time": time, "max_time": time, "avg_time": time, "count": 1, "last_time": time}

	sys_metrics["min_time"] = min(sys_metrics["min_time"], time)
	sys_metrics["max_time"] = max(sys_metrics["max_time"], time)
	sys_metrics["count"] += 1
	sys_metrics["avg_time"] = (
		((sys_metrics["avg_time"] * (sys_metrics["count"] - 1)) + time) / sys_metrics["count"]
	)
	sys_metrics["last_time"] = time
	ecs_data["systems"][system]["metrics"] = sys_metrics

	_update_systems_status_bar()


func system_last_run_data(system_id: int, system_name: String, last_run_data: Dictionary):
	var systems_data := get_or_create_dict(ecs_data, "systems")
	var sys_entry := get_or_create_dict(systems_data, system_id, default_system.duplicate())
	sys_entry["last_run_data"] = last_run_data
	# Update or create tree item
	if system_tree:
		var root = system_tree.get_root()
		if root == null:
			root = system_tree.create_item()
		# Try to find existing item by metadata matching system_id
		var existing: TreeItem = null
		var child = root.get_first_child()
		while child != null:
			if child.get_meta("system_id", null) == system_id:
				existing = child
				break
			child = child.get_next()
		if existing == null:
			existing = system_tree.create_item(root)
			existing.set_meta("system_id", system_id)
			existing.collapsed = true # Start collapsed

		# Set main system name in column 0
		var display_name = system_name
		# Check if this system is pinned and update display
		if _pinned_systems.get(system_id, false):
			if not display_name.begins_with(ICON_PIN + " "):
				display_name = ICON_PIN + " " + display_name
		existing.set_text(0, display_name)

		# Set group in column 1
		var group = sys_entry.get("group", "")
		existing.set_text(1, group)

		# Set execution time in column 2
		var exec_ms = last_run_data.get("execution_time_ms", 0.0)
		existing.set_text(2, String.num(exec_ms, 3) + " ms")

		# Set active status in column 3
		var is_active = sys_entry.get("active", true)
		_update_system_active_display(existing, is_active)

		# Get execution order (index in systems array from last_run_data) - column 4
		var execution_order = last_run_data.get("execution_order", -1)
		if execution_order >= 0:
			existing.set_text(4, str(execution_order))
		else:
			existing.set_text(4, "-")
		# Clear previous children to avoid stale data
		var prev_child = existing.get_first_child()
		while prev_child:
			var next_child = prev_child.get_next()
			existing.remove_child(prev_child)
			prev_child = next_child
		# Create nested rows for key info
		var ent_count = last_run_data.get("entity_count", null)
		var arch_count = last_run_data.get("archetype_count", null)
		var parallel = last_run_data.get("parallel", false)
		var nested_data := {
			"execution_time_ms": String.num(exec_ms, 3),
			"entity_count": ent_count,
			"archetype_count": arch_count,
			"parallel": parallel,
		}
		for k in nested_data.keys():
			var v = nested_data[k]
			if v == null:
				continue
			var row = system_tree.create_item(existing)
			row.set_text(0, str(k) + ": " + str(v))
		# Subsystem details (numeric keys in last_run_data)
		for key in last_run_data.keys():
			if typeof(key) == TYPE_INT and last_run_data[key] is Dictionary:
				var sub = last_run_data[key]
				var sub_row = system_tree.create_item(existing)
				sub_row.set_text(0, "subsystem[" + str(key) + "] entity_count: " + str(sub.get("entity_count", 0)))
		# Optionally store raw json in metadata for tooltip or future expansion
		existing.set_meta("last_run_data", last_run_data.duplicate())

	# Re-sort if we have an active sort column
	if _system_sort_column != -1:
		_sort_system_tree()

	# Update status bar with latest system data
	_update_systems_status_bar()


func entity_component_added(ent: int, comp: int, comp_path: String, data: Dictionary):
	var entities := get_or_create_dict(ecs_data, "entities")
	var entity := get_or_create_dict(entities, ent)
	if not entity.has("components"):
		entity["components"] = {}
	# Fallback: if serialized data is empty, attempt reflection of exported properties
	var final_data = data
	if final_data.is_empty():
		final_data = {}
		# Try to get the actual Object from instance_id (editor debugger gives us ID only). We can't reliably from here; leave empty.
		# As a workaround store a placeholder so UI shows component node.
		final_data["<no_serialized_properties>"] = true
	entity["components"][comp] = final_data
	# Update tree with component node and property children
	if entities_tree:
		var root = entities_tree.get_root()
		if root != null:
			var entity_item: TreeItem = null
			var child = root.get_first_child()
			while child:
				if child.get_meta("entity_id", null) == ent:
					entity_item = child
					break
				child = child.get_next()
			if entity_item:
				# Try to find existing component item to update instead of duplicating
				var existing_comp_item: TreeItem = null
				var comp_child = entity_item.get_first_child()
				while comp_child:
					if comp_child.has_meta("component_id") and comp_child.get_meta("component_id") == comp:
						existing_comp_item = comp_child
						break
					comp_child = comp_child.get_next()
				if existing_comp_item:
					# Clear previous property rows
					var prev = existing_comp_item.get_first_child()
					while prev:
						var nxt = prev.get_next()
						existing_comp_item.remove_child(prev)
						prev = nxt
					# Update title/path with icon
					var icon = ICON_FLAG if _is_flag_component(final_data) else ICON_COMPONENT
					existing_comp_item.set_text(0, icon + " " + comp_path.get_file().get_basename())
					existing_comp_item.set_tooltip_text(0, comp_path)
					existing_comp_item.set_meta("component_path", comp_path)
					_add_serialized_rows(existing_comp_item, final_data)
				else:
					_attach_component_to_entity_item(entity_item, ent, comp, comp_path, final_data)
				# Update entity counts
				_update_entity_counts(entity_item, ent)
			else:
				# Buffer component until entity_added arrives
				if not _pending_components.has(ent):
					_pending_components[ent] = []
				_pending_components[ent].append({"comp_id": comp, "comp_path": comp_path, "data": final_data})

	# Re-sort if we have an active sort column
	if _entity_sort_column != -1:
		_sort_entity_tree()

	_update_entity_status_bar()


func _attach_component_to_entity_item(entity_item: TreeItem, ent: int, comp: int, comp_path: String, final_data: Dictionary) -> void:
	var comp_item = entities_tree.create_item(entity_item)
	# Use flag icon for components with no properties, otherwise use component icon
	var icon = ICON_FLAG if _is_flag_component(final_data) else ICON_COMPONENT
	comp_item.set_text(0, icon + " " + comp_path.get_file().get_basename())
	comp_item.set_tooltip_text(0, comp_path)
	comp_item.set_meta("component_id", comp)
	comp_item.set_meta("component_path", comp_path)
	comp_item.collapsed = true # Start collapsed
	# Add property rows with recursive serialization
	_add_serialized_rows(comp_item, final_data)
	# Update entity counts
	_update_entity_counts(entity_item, ent)


func entity_component_removed(ent: int, comp: int):
	var entities = get_or_create_dict(ecs_data, "entities")
	if entities.has(ent) and entities[ent].has("components"):
		entities[ent]["components"].erase(comp)
	if entities_tree and entities_tree.get_root():
		var entity_item: TreeItem = null
		var child = entities_tree.get_root().get_first_child()
		while child:
			if child.get_meta("entity_id", null) == ent:
				entity_item = child
				break
			child = child.get_next()
		if entity_item:
			var comp_child = entity_item.get_first_child()
			while comp_child:
				if comp_child.has_meta("component_id") and comp_child.get_meta("component_id") == comp:
					entity_item.remove_child(comp_child)
					break
				comp_child = comp_child.get_next()
			# Update entity counts
			_update_entity_counts(entity_item, ent)

	_update_entity_status_bar()


func entity_component_property_changed(
	ent: int, comp: int, property_name: String, old_value: Variant, new_value: Variant
):
	var entities = get_or_create_dict(ecs_data, "entities")
	if entities.has(ent) and entities[ent].has("components"):
		var component = entities[ent]["components"].get(comp)
		if component:
			component[property_name] = new_value
	# Update tree property row
	if entities_tree and entities_tree.get_root():
		var entity_item: TreeItem = null
		var child = entities_tree.get_root().get_first_child()
		while child:
			if child.get_meta("entity_id", null) == ent:
				entity_item = child
				break
			child = child.get_next()
		if entity_item:
			var comp_child = entity_item.get_first_child()
			while comp_child:
				if comp_child.has_meta("component_id") and comp_child.get_meta("component_id") == comp:
					var prop_row = comp_child.get_first_child()
					var updated := false
					while prop_row:
						if prop_row.has_meta("property_name") and prop_row.get_meta("property_name") == property_name:
							prop_row.set_text(0, property_name + ": " + str(new_value))
							updated = true
							break
						prop_row = prop_row.get_next()
					# If property row not found (added dynamically), append it
					if not updated:
						var new_row = entities_tree.create_item(comp_child)
						new_row.set_text(0, property_name + ": " + str(new_value))
						new_row.set_meta("property_name", property_name)
					# Done updating this component; no need to scan further
					break
				comp_child = comp_child.get_next()


# ---- Recursive Serialization Rendering ----
func _add_serialized_rows(parent_item: TreeItem, data: Dictionary):
	for key in data.keys():
		var value = data[key]
		var row = entities_tree.create_item(parent_item)
		row.set_text(0, str(key) + ": " + _value_to_string(value))
		row.set_meta("property_name", key)
		if value is Dictionary:
			_add_serialized_rows(row, value)
		elif value is Array:
			_add_array_rows(row, value)


func _add_array_rows(parent_item: TreeItem, arr: Array):
	for i in range(arr.size()):
		var value = arr[i]
		var row = entities_tree.create_item(parent_item)
		row.set_text(0, "[" + str(i) + "] " + _value_to_string(value))
		row.set_meta("property_name", str(i))
		if value is Dictionary:
			_add_serialized_rows(row, value)
		elif value is Array:
			_add_array_rows(row, value)


func _value_to_string(v):
	match typeof(v):
		TYPE_DICTIONARY:
			return "{...}" # expanded in children
		TYPE_ARRAY:
			return "[..." + str(v.size()) + "]"
		TYPE_STRING:
			return '"' + v + '"'
		TYPE_OBJECT:
			if v is Resource:
				return "Resource(" + v.resource_path.get_file() + ")"
			return str(v)
		_:
			return str(v)


func entity_relationship_added(ent: int, rel: int, rel_data: Dictionary):
	var entities := get_or_create_dict(ecs_data, "entities")
	var entity := get_or_create_dict(entities, ent)
	var relationships := get_or_create_dict(entity, "relationships")
	relationships[rel] = rel_data

	# Add to tree
	if entities_tree:
		var root = entities_tree.get_root()
		if root != null:
			var entity_item: TreeItem = null
			var child = root.get_first_child()
			while child:
				if child.get_meta("entity_id", null) == ent:
					entity_item = child
					break
				child = child.get_next()

			if entity_item:
				# Try to find existing relationship item to update
				var existing_rel_item: TreeItem = null
				var rel_child = entity_item.get_first_child()
				while rel_child:
					if rel_child.has_meta("relationship_id") and rel_child.get_meta("relationship_id") == rel:
						existing_rel_item = rel_child
						break
					rel_child = rel_child.get_next()

				if existing_rel_item:
					# Clear and rebuild
					var prev = existing_rel_item.get_first_child()
					while prev:
						var nxt = prev.get_next()
						existing_rel_item.remove_child(prev)
						prev = nxt
					_update_relationship_item(existing_rel_item, rel_data)
				else:
					# Create new relationship item
					var rel_item = entities_tree.create_item(entity_item)
					rel_item.set_meta("relationship_id", rel)
					rel_item.collapsed = true # Start collapsed
					_update_relationship_item(rel_item, rel_data)
				# Update entity counts
				_update_entity_counts(entity_item, ent)

	# Re-sort if we have an active sort column
	if _entity_sort_column != -1:
		_sort_entity_tree()

	_update_entity_status_bar()


func _update_relationship_item(rel_item: TreeItem, rel_data: Dictionary):
	# Format the relationship display
	var relation_type = rel_data.get("relation_type", "Unknown")
	var target_type = rel_data.get("target_type", "Unknown")

	# Build the title based on target type with relationship icon
	var title = ICON_RELATIONSHIP + " " + relation_type + " -> "
	if target_type == "Entity":
		var target_data = rel_data.get("target_data", {})
		title += "Entity " + str(target_data.get("path", "Unknown"))
	elif target_type == "Component":
		var target_data = rel_data.get("target_data", {})
		title += target_data.get("type", "Unknown")
	elif target_type == "Archetype":
		var target_data = rel_data.get("target_data", {})
		var script_path = target_data.get("script_path", "")
		title += "Archetype " + script_path.get_file().get_basename()
	elif target_type == "null":
		title += "Wildcard"
	else:
		title += target_type

	rel_item.set_text(0, title)

	# Add relation data as children
	var relation_data = rel_data.get("relation_data", {})
	if not relation_data.is_empty():
		var rel_data_item = entities_tree.create_item(rel_item)
		rel_data_item.set_text(0, "Relation Properties:")
		_add_serialized_rows(rel_data_item, relation_data)

	# Add target data as children (for components with properties)
	if target_type == "Component":
		var target_data = rel_data.get("target_data", {})
		var target_comp_data = target_data.get("data", {})
		if not target_comp_data.is_empty():
			var target_data_item = entities_tree.create_item(rel_item)
			target_data_item.set_text(0, "Target Properties:")
			_add_serialized_rows(target_data_item, target_comp_data)


func entity_relationship_removed(ent: int, rel: int):
	var entities = get_or_create_dict(ecs_data, "entities")
	if entities.has(ent) and entities[ent].has("relationships"):
		entities[ent]["relationships"].erase(rel)

	# Remove from tree
	if entities_tree and entities_tree.get_root():
		var entity_item: TreeItem = null
		var child = entities_tree.get_root().get_first_child()
		while child:
			if child.get_meta("entity_id", null) == ent:
				entity_item = child
				break
			child = child.get_next()

		if entity_item:
			var rel_child = entity_item.get_first_child()
			while rel_child:
				if rel_child.has_meta("relationship_id") and rel_child.get_meta("relationship_id") == rel:
					entity_item.remove_child(rel_child)
					break
				rel_child = rel_child.get_next()
			# Update entity counts
			_update_entity_counts(entity_item, ent)

	_update_entity_status_bar()


# ---- Status Bar Updates ----


## Update the entity status bar with current counts
func _update_entity_status_bar():
	if not entity_status_bar:
		return

	var entities = ecs_data.get("entities", {})
	var entity_count = entities.size()

	# Count total components across all entities
	var total_components = 0
	for entity_data in entities.values():
		var components = entity_data.get("components", {})
		total_components += components.size()

	# Count total relationships across all entities
	var total_relationships = 0
	for entity_data in entities.values():
		var relationships = entity_data.get("relationships", {})
		total_relationships += relationships.size()

	entity_status_bar.text = "Entities: %d | Components: %d | Relationships: %d" % [
		entity_count,
		total_components,
		total_relationships
	]


## Update the systems status bar with execution metrics
func _update_systems_status_bar():
	if not systems_status_bar:
		return

	var systems_data = ecs_data.get("systems", {})
	var system_count = systems_data.size()

	# Calculate total execution time and find most expensive system
	var total_time_ms = 0.0
	var most_expensive_name = ""
	var most_expensive_time = 0.0

	for system_id in systems_data.keys():
		var system_data = systems_data[system_id]
		# Get execution time from last_run_data if available (more accurate than last_time)
		var last_run_data = system_data.get("last_run_data", {})
		var exec_time_ms = last_run_data.get("execution_time_ms", 0.0)

		total_time_ms += exec_time_ms

		if exec_time_ms > most_expensive_time:
			most_expensive_time = exec_time_ms
			# Try to get a readable system name from last_run_data first, then path
			var system_name = last_run_data.get("system_name", "")
			if not system_name:
				var path = system_data.get("path", "")
				if path:
					system_name = str(path).get_file().get_basename()
				else:
					system_name = "System_%d" % system_id
			most_expensive_name = system_name

	# Format the status bar text
	if most_expensive_name:
		systems_status_bar.text = "Systems: %d | Total ms: %.1fms | Most Expensive: %s (%.1fms)" % [
			system_count,
			total_time_ms,
			most_expensive_name,
			most_expensive_time
		]
	else:
		systems_status_bar.text = "Systems: %d | Total ms: %.1fms" % [
			system_count,
			total_time_ms
		]
