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 definition: Dictionary = DataCatalog.UPGRADES[id]
		if not bool(definition.get("show_parent_link", true)):
			continue
		var parent := str(definition.get("parent", ""))
		if parent.is_empty() or not node_controls.has(parent):
			continue
		var theme_id := str(definition.get("theme", ""))
		var theme: Dictionary = DataCatalog.UPGRADE_THEMES.get(theme_id, DataCatalog.DEFAULT_UPGRADE_THEMES.values()[0])
		var states: Dictionary = theme.get("states", {})
		var style: Dictionary = states.get(GameState.get_upgrade_state(id), states.get("available", {}))
		_draw_connector(Vector2(positions[parent]), Vector2(positions[id]), style)
		for raw_secondary_parent: Variant in definition.get("secondary_parents", []):
			var secondary_parent := str(raw_secondary_parent)
			if not node_controls.has(secondary_parent):
				continue
			var secondary_style := style.duplicate()
			secondary_style["connector_pattern"] = "dashed"
			secondary_style["connector_width"] = float(style.get("connector_width", 3.0)) * 0.8
			var secondary_color_value: Variant = style.get("connector_color", Color("64748b"))
			var secondary_color: Color = secondary_color_value if secondary_color_value is Color else Color.from_string(str(secondary_color_value), Color("64748b"))
			secondary_color.a *= 0.72
			secondary_style["connector_color"] = secondary_color
			_draw_connector(Vector2(positions[secondary_parent]), Vector2(positions[id]), secondary_style)

func _draw_connector(from: Vector2, to: Vector2, style: Dictionary) -> void:
	var color_value: Variant = style.get("connector_color", Color("64748b"))
	var color: Color = color_value if color_value is Color else Color.from_string(str(color_value), Color("64748b"))
	var width := float(style.get("connector_width", 3.0))
	if width <= 0.0:
		return
	match str(style.get("connector_pattern", "solid")):
		"dashed":
			draw_dashed_line(from, to, color, width, maxf(8.0, width * 2.5), true, true)
		"dotted":
			draw_dashed_line(from, to, color, width, maxf(2.0, width), true, true)
		_:
			draw_line(from, to, color, width, true)
