@tool
extends "res://addons/godot_mcp/commands/base_command.gd"


func get_commands() -> Dictionary:
	return {
		"setup_navigation_region": _setup_navigation_region,
		"bake_navigation_mesh": _bake_navigation_mesh,
		"setup_navigation_agent": _setup_navigation_agent,
		"set_navigation_layers": _set_navigation_layers,
		"get_navigation_info": _get_navigation_info,
	}


func _is_3d_context(node: Node) -> bool:
	if node is Node3D:
		return true
	if node is Node2D:
		return false
	# Walk up to detect context
	var parent := node.get_parent()
	while parent != null:
		if parent is Node3D:
			return true
		if parent is Node2D:
			return false
		parent = parent.get_parent()
	return false


func _setup_navigation_region(params: Dictionary) -> Dictionary:
	var result := require_string(params, "node_path")
	if result[1] != null:
		return result[1]
	var node_path: String = result[0]

	var node := find_node_by_path(node_path)
	if node == null:
		return error_not_found("Node at '%s'" % node_path)
	var root := get_edited_root()
	if root == null:
		return error_no_scene()

	var force_mode: String = optional_string(params, "mode", "auto")
	var is_3d: bool
	match force_mode:
		"2d": is_3d = false
		"3d": is_3d = true
		_: is_3d = _is_3d_context(node)

	if is_3d:
		var region := NavigationRegion3D.new()
		region.name = optional_string(params, "name", "NavigationRegion3D")

		var nav_mesh := NavigationMesh.new()
		nav_mesh.agent_radius = float(params.get("agent_radius", 0.5))
		nav_mesh.agent_height = float(params.get("agent_height", 1.5))
		nav_mesh.agent_max_climb = float(params.get("agent_max_climb", 0.25))
		nav_mesh.agent_max_slope = float(params.get("agent_max_slope", 45.0))
		nav_mesh.cell_size = float(params.get("cell_size", 0.25))
		nav_mesh.cell_height = float(params.get("cell_height", 0.25))
		region.navigation_mesh = nav_mesh

		if params.has("navigation_layers"):
			region.navigation_layers = int(params["navigation_layers"])

		add_child_with_undo(node, region, root, "MCP: Add NavigationRegion3D")

		return success({
			"node_path": str(region.get_path()),
			"type": "NavigationRegion3D",
			"agent_radius": nav_mesh.agent_radius,
			"agent_height": nav_mesh.agent_height,
			"cell_size": nav_mesh.cell_size,
			"created": true,
		})
	else:
		var region := NavigationRegion2D.new()
		region.name = optional_string(params, "name", "NavigationRegion2D")

		var nav_poly := NavigationPolygon.new()

		# Set parsed geometry source if available
		if params.has("source_geometry_mode"):
			var mode_str: String = str(params["source_geometry_mode"])
			match mode_str:
				"root_node": nav_poly.source_geometry_mode = NavigationPolygon.SOURCE_GEOMETRY_ROOT_NODE_CHILDREN
				"groups_with_children": nav_poly.source_geometry_mode = NavigationPolygon.SOURCE_GEOMETRY_GROUPS_WITH_CHILDREN
				"groups_explicit": nav_poly.source_geometry_mode = NavigationPolygon.SOURCE_GEOMETRY_GROUPS_EXPLICIT

		if params.has("cell_size"):
			nav_poly.cell_size = float(params["cell_size"])

		if params.has("agent_radius"):
			nav_poly.agent_radius = float(params["agent_radius"])

		region.navigation_polygon = nav_poly

		if params.has("navigation_layers"):
			region.navigation_layers = int(params["navigation_layers"])

		add_child_with_undo(node, region, root, "MCP: Add NavigationRegion2D")

		return success({
			"node_path": str(region.get_path()),
			"type": "NavigationRegion2D",
			"cell_size": nav_poly.cell_size,
			"created": true,
		})


func _bake_navigation_mesh(params: Dictionary) -> Dictionary:
	var result := require_string(params, "node_path")
	if result[1] != null:
		return result[1]
	var node_path: String = result[0]

	var node := find_node_by_path(node_path)
	if node == null:
		return error_not_found("Node at '%s'" % node_path)
	var root := get_edited_root()
	if root == null:
		return error_no_scene()

	if node is NavigationRegion3D:
		var region: NavigationRegion3D = node as NavigationRegion3D
		if region.navigation_mesh == null:
			return error_invalid_params("NavigationRegion3D has no NavigationMesh resource")
		region.bake_navigation_mesh()
		mark_current_scene_unsaved()
		return success({
			"node_path": node_path,
			"type": "NavigationRegion3D",
			"baked": true,
		})

	elif node is NavigationRegion2D:
		var region: NavigationRegion2D = node as NavigationRegion2D
		if region.navigation_polygon == null:
			var nav_poly := NavigationPolygon.new()
			region.navigation_polygon = nav_poly

		# Set outline vertices from params
		if params.has("outline"):
			var outline_data: Array = params["outline"]
			var outline := PackedVector2Array()
			for point in outline_data:
				if point is Array and point.size() >= 2:
					outline.append(Vector2(float(point[0]), float(point[1])))
				elif point is Dictionary:
					outline.append(Vector2(float(point.get("x", 0)), float(point.get("y", 0))))

			if outline.size() >= 3:
				# Clear existing outlines
				while region.navigation_polygon.get_outline_count() > 0:
					region.navigation_polygon.remove_outline(0)
				region.navigation_polygon.add_outline(outline)
				region.navigation_polygon.make_polygons_from_outlines()
				mark_current_scene_unsaved()
				return success({
					"node_path": node_path,
					"type": "NavigationRegion2D",
					"outline_vertices": outline.size(),
					"baked": true,
				})
			else:
				return error_invalid_params("Outline must have at least 3 vertices")
		else:
			# Try baking from source geometry
			region.bake_navigation_polygon()
			mark_current_scene_unsaved()
			return success({
				"node_path": node_path,
				"type": "NavigationRegion2D",
				"baked": true,
			})

	return error_invalid_params("Node '%s' is not a NavigationRegion2D or NavigationRegion3D" % node_path)


func _setup_navigation_agent(params: Dictionary) -> Dictionary:
	var result := require_string(params, "node_path")
	if result[1] != null:
		return result[1]
	var node_path: String = result[0]

	var node := find_node_by_path(node_path)
	if node == null:
		return error_not_found("Node at '%s'" % node_path)
	var root := get_edited_root()
	if root == null:
		return error_no_scene()

	var force_mode: String = optional_string(params, "mode", "auto")
	var is_3d: bool
	match force_mode:
		"2d": is_3d = false
		"3d": is_3d = true
		_: is_3d = _is_3d_context(node)

	var agent_name: String = optional_string(params, "name", "NavigationAgent3D" if is_3d else "NavigationAgent2D")

	if is_3d:
		var agent := NavigationAgent3D.new()
		agent.name = agent_name

		if params.has("path_desired_distance"):
			agent.path_desired_distance = float(params["path_desired_distance"])
		if params.has("target_desired_distance"):
			agent.target_desired_distance = float(params["target_desired_distance"])
		if params.has("radius"):
			agent.radius = float(params["radius"])
		if params.has("neighbor_distance"):
			agent.neighbor_distance = float(params["neighbor_distance"])
		if params.has("max_neighbors"):
			agent.max_neighbors = int(params["max_neighbors"])
		if params.has("max_speed"):
			agent.max_speed = float(params["max_speed"])
		if params.has("avoidance_enabled"):
			agent.avoidance_enabled = bool(params["avoidance_enabled"])
		if params.has("navigation_layers"):
			agent.navigation_layers = int(params["navigation_layers"])

		add_child_with_undo(node, agent, root, "MCP: Add NavigationAgent3D")

		return success({
			"node_path": str(agent.get_path()),
			"type": "NavigationAgent3D",
			"radius": agent.radius,
			"max_speed": agent.max_speed,
			"avoidance_enabled": agent.avoidance_enabled,
			"navigation_layers": agent.navigation_layers,
			"created": true,
		})
	else:
		var agent := NavigationAgent2D.new()
		agent.name = agent_name

		if params.has("path_desired_distance"):
			agent.path_desired_distance = float(params["path_desired_distance"])
		if params.has("target_desired_distance"):
			agent.target_desired_distance = float(params["target_desired_distance"])
		if params.has("radius"):
			agent.radius = float(params["radius"])
		if params.has("neighbor_distance"):
			agent.neighbor_distance = float(params["neighbor_distance"])
		if params.has("max_neighbors"):
			agent.max_neighbors = int(params["max_neighbors"])
		if params.has("max_speed"):
			agent.max_speed = float(params["max_speed"])
		if params.has("avoidance_enabled"):
			agent.avoidance_enabled = bool(params["avoidance_enabled"])
		if params.has("navigation_layers"):
			agent.navigation_layers = int(params["navigation_layers"])

		add_child_with_undo(node, agent, root, "MCP: Add NavigationAgent2D")

		return success({
			"node_path": str(agent.get_path()),
			"type": "NavigationAgent2D",
			"radius": agent.radius,
			"max_speed": agent.max_speed,
			"avoidance_enabled": agent.avoidance_enabled,
			"navigation_layers": agent.navigation_layers,
			"created": true,
		})


func _set_navigation_layers(params: Dictionary) -> Dictionary:
	var result := require_string(params, "node_path")
	if result[1] != null:
		return result[1]
	var node_path: String = result[0]

	var node := find_node_by_path(node_path)
	if node == null:
		return error_not_found("Node at '%s'" % node_path)

	# Support setting by bitmask value
	if params.has("layers"):
		var layers_val: int = int(params["layers"])
		if node is NavigationRegion2D:
			set_property_with_undo(node, "navigation_layers", layers_val, "MCP: Set navigation layers")
		elif node is NavigationRegion3D:
			set_property_with_undo(node, "navigation_layers", layers_val, "MCP: Set navigation layers")
		elif node is NavigationAgent2D:
			set_property_with_undo(node, "navigation_layers", layers_val, "MCP: Set navigation layers")
		elif node is NavigationAgent3D:
			set_property_with_undo(node, "navigation_layers", layers_val, "MCP: Set navigation layers")
		else:
			return error_invalid_params("Node '%s' is not a navigation region or agent" % node_path)

		return success({
			"node_path": node_path,
			"navigation_layers": layers_val,
			"updated": true,
		})

	# Support setting individual layer bits by number
	if params.has("layer_bits"):
		var bits: Array = params["layer_bits"]
		var current_layers: int = 0

		# Calculate bitmask from layer numbers (1-based)
		for bit in bits:
			var layer_num: int = int(bit)
			if layer_num >= 1 and layer_num <= 32:
				current_layers |= (1 << (layer_num - 1))

		if node is NavigationRegion2D:
			set_property_with_undo(node, "navigation_layers", current_layers, "MCP: Set navigation layers")
		elif node is NavigationRegion3D:
			set_property_with_undo(node, "navigation_layers", current_layers, "MCP: Set navigation layers")
		elif node is NavigationAgent2D:
			set_property_with_undo(node, "navigation_layers", current_layers, "MCP: Set navigation layers")
		elif node is NavigationAgent3D:
			set_property_with_undo(node, "navigation_layers", current_layers, "MCP: Set navigation layers")
		else:
			return error_invalid_params("Node '%s' is not a navigation region or agent" % node_path)

		return success({
			"node_path": node_path,
			"navigation_layers": current_layers,
			"layer_bits": bits,
			"updated": true,
		})

	# Support named layers from ProjectSettings
	if params.has("layer_names"):
		var names: Array = params["layer_names"]
		var current_layers: int = 0
		var is_2d: bool = node is NavigationRegion2D or node is NavigationAgent2D
		var prefix: String = "layer_names/2d_navigation/layer_" if is_2d else "layer_names/3d_navigation/layer_"

		for i in range(1, 33):
			var setting_key: String = prefix + str(i)
			if ProjectSettings.has_setting(setting_key):
				var layer_name: String = str(ProjectSettings.get_setting(setting_key))
				if layer_name in names:
					current_layers |= (1 << (i - 1))

		if node is NavigationRegion2D:
			set_property_with_undo(node, "navigation_layers", current_layers, "MCP: Set navigation layers")
		elif node is NavigationRegion3D:
			set_property_with_undo(node, "navigation_layers", current_layers, "MCP: Set navigation layers")
		elif node is NavigationAgent2D:
			set_property_with_undo(node, "navigation_layers", current_layers, "MCP: Set navigation layers")
		elif node is NavigationAgent3D:
			set_property_with_undo(node, "navigation_layers", current_layers, "MCP: Set navigation layers")
		else:
			return error_invalid_params("Node '%s' is not a navigation region or agent" % node_path)

		return success({
			"node_path": node_path,
			"navigation_layers": current_layers,
			"layer_names": names,
			"updated": true,
		})

	return error_invalid_params("Must provide 'layers' (bitmask), 'layer_bits' (array of layer numbers), or 'layer_names' (array of named layers)")


func _get_navigation_info(params: Dictionary) -> Dictionary:
	var result := require_string(params, "node_path")
	if result[1] != null:
		return result[1]
	var node_path: String = result[0]

	var node := find_node_by_path(node_path)
	if node == null:
		return error_not_found("Node at '%s'" % node_path)

	var regions: Array = []
	var agents: Array = []

	_collect_navigation_nodes(node, regions, agents)

	# Collect named layers from ProjectSettings
	var layer_names_2d: Dictionary = {}
	var layer_names_3d: Dictionary = {}
	for i in range(1, 33):
		var key_2d: String = "layer_names/2d_navigation/layer_" + str(i)
		var key_3d: String = "layer_names/3d_navigation/layer_" + str(i)
		if ProjectSettings.has_setting(key_2d):
			var name_2d: String = str(ProjectSettings.get_setting(key_2d))
			if not name_2d.is_empty():
				layer_names_2d[i] = name_2d
		if ProjectSettings.has_setting(key_3d):
			var name_3d: String = str(ProjectSettings.get_setting(key_3d))
			if not name_3d.is_empty():
				layer_names_3d[i] = name_3d

	return success({
		"node_path": node_path,
		"regions": regions,
		"agents": agents,
		"region_count": regions.size(),
		"agent_count": agents.size(),
		"layer_names_2d": layer_names_2d,
		"layer_names_3d": layer_names_3d,
	})


func _collect_navigation_nodes(node: Node, regions: Array, agents: Array) -> void:
	if node is NavigationRegion2D:
		var region: NavigationRegion2D = node as NavigationRegion2D
		var region_info := {
			"path": str(region.get_path()),
			"type": "NavigationRegion2D",
			"enabled": region.enabled,
			"navigation_layers": region.navigation_layers,
			"has_polygon": region.navigation_polygon != null,
		}
		if region.navigation_polygon != null:
			var nav_poly: NavigationPolygon = region.navigation_polygon
			region_info["outline_count"] = nav_poly.get_outline_count()
			region_info["polygon_count"] = nav_poly.get_polygon_count()
			region_info["cell_size"] = nav_poly.cell_size
			region_info["agent_radius"] = nav_poly.agent_radius
		regions.append(region_info)

	elif node is NavigationRegion3D:
		var region: NavigationRegion3D = node as NavigationRegion3D
		var region_info := {
			"path": str(region.get_path()),
			"type": "NavigationRegion3D",
			"enabled": region.enabled,
			"navigation_layers": region.navigation_layers,
			"has_mesh": region.navigation_mesh != null,
		}
		if region.navigation_mesh != null:
			var nav_mesh: NavigationMesh = region.navigation_mesh
			region_info["agent_radius"] = nav_mesh.agent_radius
			region_info["agent_height"] = nav_mesh.agent_height
			region_info["agent_max_climb"] = nav_mesh.agent_max_climb
			region_info["agent_max_slope"] = nav_mesh.agent_max_slope
			region_info["cell_size"] = nav_mesh.cell_size
			region_info["cell_height"] = nav_mesh.cell_height
		regions.append(region_info)

	if node is NavigationAgent2D:
		var agent: NavigationAgent2D = node as NavigationAgent2D
		agents.append({
			"path": str(agent.get_path()),
			"type": "NavigationAgent2D",
			"radius": agent.radius,
			"max_speed": agent.max_speed,
			"path_desired_distance": agent.path_desired_distance,
			"target_desired_distance": agent.target_desired_distance,
			"neighbor_distance": agent.neighbor_distance,
			"max_neighbors": agent.max_neighbors,
			"avoidance_enabled": agent.avoidance_enabled,
			"navigation_layers": agent.navigation_layers,
		})

	elif node is NavigationAgent3D:
		var agent: NavigationAgent3D = node as NavigationAgent3D
		agents.append({
			"path": str(agent.get_path()),
			"type": "NavigationAgent3D",
			"radius": agent.radius,
			"max_speed": agent.max_speed,
			"path_desired_distance": agent.path_desired_distance,
			"target_desired_distance": agent.target_desired_distance,
			"neighbor_distance": agent.neighbor_distance,
			"max_neighbors": agent.max_neighbors,
			"avoidance_enabled": agent.avoidance_enabled,
			"navigation_layers": agent.navigation_layers,
		})

	for child in node.get_children():
		_collect_navigation_nodes(child, regions, agents)
