class_name UpgradeTreeCanvas
extends Control

signal node_selected(id: String)
signal node_hovered(id: String)
signal node_unhovered(id: String)

const NODE_SIZE := Vector2(118, 92)
static var DIRECTIONS: Dictionary = {
	"N": Vector2.UP, "NE": Vector2(1, -1).normalized(), "E": Vector2.RIGHT,
	"SE": Vector2(1, 1).normalized(), "S": Vector2.DOWN, "SW": Vector2(-1, 1).normalized(),
	"W": Vector2.LEFT, "NW": Vector2(-1, -1).normalized(),
}

var positions: Dictionary = {}
var node_controls: Dictionary = {}

func _ready() -> void:
	resized.connect(rebuild)
	call_deferred("rebuild")

func rebuild() -> void:
	if not is_inside_tree() or size.x <= 0.0:
		return
	for child: Node in get_children():
		child.queue_free()
	positions.clear()
	node_controls.clear()
	var root_id := _root_id()
	if root_id.is_empty():
		return
	positions[root_id] = Vector2(size.x * 0.48, size.y * 0.50)
	for id: String in DataCatalog.UPGRADES:
		_compute_position(id, {})
	for id: String in DataCatalog.UPGRADES:
		var state := GameState.get_upgrade_state(id)
		if state == "invisible":
			continue
		var node := UpgradeTreeNode.new()
		node.setup(id, DataCatalog.UPGRADES[id], state)
		node.position = Vector2(positions[id]) - NODE_SIZE * 0.5
		node.node_selected.connect(func(selected_id: String) -> void: node_selected.emit(selected_id))
		node.node_hovered.connect(func(hovered_id: String) -> void: node_hovered.emit(hovered_id))
		node.node_unhovered.connect(func(unhovered_id: String) -> void: node_unhovered.emit(unhovered_id))
		add_child(node)
		node_controls[id] = node
	queue_redraw()

func focus_node(id: String) -> void:
	var node := node_controls.get(id) as UpgradeTreeNode
	if node != null:
		node.grab_focus()

func first_visible_node() -> String:
	for id: String in DataCatalog.UPGRADES:
		if node_controls.has(id):
			return id
	return ""

func navigate_from(id: String, direction: Vector2) -> String:
	if not positions.has(id):
		return first_visible_node()
	var origin: Vector2 = positions[id]
	var best_id := ""
	var best_score := INF
	for candidate_id: String in node_controls:
		if candidate_id == id:
			continue
		var offset: Vector2 = Vector2(positions[candidate_id]) - origin
		if offset.length_squared() < 1.0 or offset.normalized().dot(direction) < 0.55:
			continue
		var score := offset.length() * (1.5 - offset.normalized().dot(direction))
		if score < best_score:
			best_score = score
			best_id = candidate_id
	return best_id

func _compute_position(id: String, visiting: Dictionary) -> Vector2:
	if positions.has(id):
		return positions[id]
	if visiting.has(id):
		return Vector2.ZERO
	visiting[id] = true
	var definition: Dictionary = DataCatalog.UPGRADES[id]
	var parent := str(definition.get("parent", ""))
	if parent.is_empty() or not DataCatalog.UPGRADES.has(parent):
		positions[id] = Vector2(size.x * 0.48, size.y * 0.50)
	else:
		var parent_position := _compute_position(parent, visiting)
		var vector: Vector2 = DIRECTIONS.get(str(definition.get("direction", "N")), Vector2.UP)
		positions[id] = parent_position + vector * float(definition.get("distance", 150.0))
	visiting.erase(id)
	return positions[id]

func _root_id() -> String:
	for id: String in DataCatalog.UPGRADES:
		if str(DataCatalog.UPGRADES[id].get("parent", "")).is_empty():
			return id
	return ""

func _draw() -> void:
	for id: String in DataCatalog.UPGRADES:
		if not node_controls.has(id):
			continue
		var parent := str(DataCatalog.UPGRADES[id].get("parent", ""))
		if parent.is_empty() or not node_controls.has(parent):
			continue
		var color := Color("4ade80") if GameState.has_upgrade(id) else Color("64748b")
		draw_dashed_line(Vector2(positions[parent]), Vector2(positions[id]), color, 3.0, 10.0)
