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


func get_commands() -> Dictionary:
	return {
		"create_animation_tree": _create_animation_tree,
		"get_animation_tree_structure": _get_animation_tree_structure,
		"add_state_machine_state": _add_state_machine_state,
		"remove_state_machine_state": _remove_state_machine_state,
		"add_state_machine_transition": _add_state_machine_transition,
		"remove_state_machine_transition": _remove_state_machine_transition,
		"set_blend_tree_node": _set_blend_tree_node,
		"set_tree_parameter": _set_tree_parameter,
	}


## Find AnimationTree on a node or return null
func _find_animation_tree(node_path: String) -> AnimationTree:
	var node := find_node_by_path(node_path)
	if node is AnimationTree:
		return node as AnimationTree
	return null


## Navigate to a nested state machine by slash-separated path (e.g. "Run/SubState")
## Returns [state_machine, error_or_null]
func _resolve_state_machine(tree: AnimationTree, sm_path: String) -> Array:
	var root := tree.tree_root
	if not root is AnimationNodeStateMachine:
		return [null, error_invalid_params("AnimationTree root is not an AnimationNodeStateMachine")]

	if sm_path.is_empty() or sm_path == ".":
		return [root as AnimationNodeStateMachine, null]

	var current: AnimationNodeStateMachine = root as AnimationNodeStateMachine
	var parts := sm_path.split("/")
	for part in parts:
		if not current.has_node(StringName(part)):
			return [null, error_not_found("State machine node '%s' in path '%s'" % [part, sm_path])]
		var child := current.get_node(StringName(part))
		if not child is AnimationNodeStateMachine:
			return [null, error_invalid_params("Node '%s' is not a StateMachine" % part)]
		current = child as AnimationNodeStateMachine
	return [current, null]


## Resolve a BlendTree inside the tree. bt_path can be a state name inside a state machine,
## or a slash-separated path. The last segment is the BlendTree node name.
## Returns [blend_tree, error_or_null]
func _resolve_blend_tree(tree: AnimationTree, sm_path: String, bt_name: String) -> Array:
	var result := _resolve_state_machine(tree, sm_path)
	if result[1] != null:
		return result

	var sm: AnimationNodeStateMachine = result[0]
	if not sm.has_node(StringName(bt_name)):
		return [null, error_not_found("BlendTree node '%s'" % bt_name)]

	var node := sm.get_node(StringName(bt_name))
	if not node is AnimationNodeBlendTree:
		return [null, error_invalid_params("Node '%s' is not an AnimationNodeBlendTree" % bt_name)]

	return [node as AnimationNodeBlendTree, null]


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

	var root := get_edited_root()
	if root == null:
		return error_no_scene()

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

	var anim_player_path: String = optional_string(params, "anim_player", "")
	var tree_name: String = optional_string(params, "name", "AnimationTree")

	# Create the AnimationTree
	var tree := AnimationTree.new()
	tree.name = tree_name

	# Set root to AnimationNodeStateMachine
	var state_machine := AnimationNodeStateMachine.new()
	tree.tree_root = state_machine

	# Link to AnimationPlayer if provided
	if not anim_player_path.is_empty():
		tree.anim_player = NodePath(anim_player_path)

	add_child_with_undo(parent, tree, root, "MCP: Create AnimationTree")

	return success({
		"name": tree.name,
		"node_path": str(root.get_path_to(tree)),
		"root_type": "AnimationNodeStateMachine",
		"anim_player": anim_player_path,
		"created": true,
	})


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

	var tree := _find_animation_tree(node_path)
	if tree == null:
		return error_not_found("AnimationTree at '%s'" % node_path)

	var root := tree.tree_root
	if root == null:
		return success({"node_path": node_path, "root": null})

	var structure := _read_node_structure(root)
	structure["active"] = tree.active
	structure["anim_player"] = str(tree.anim_player)
	structure["node_path"] = node_path

	return success(structure)


func _read_node_structure(node: AnimationNode) -> Dictionary:
	if node is AnimationNodeStateMachine:
		return _read_state_machine_structure(node as AnimationNodeStateMachine)
	elif node is AnimationNodeBlendTree:
		return _read_blend_tree_structure(node as AnimationNodeBlendTree)
	elif node is AnimationNodeAnimation:
		var anim_node := node as AnimationNodeAnimation
		return {"type": "AnimationNodeAnimation", "animation": str(anim_node.animation)}
	else:
		return {"type": node.get_class()}


func _read_state_machine_structure(sm: AnimationNodeStateMachine) -> Dictionary:
	var states: Array = []
	# Iterate through graph nodes via get_node_name
	# AnimationNodeStateMachine doesn't have get_node_list in 4.x, iterate using _get_child_nodes
	var node_list := _get_sm_node_names(sm)
	for state_name in node_list:
		var child := sm.get_node(StringName(state_name))
		var state_info := {
			"name": state_name,
			"position": {"x": sm.get_node_position(StringName(state_name)).x, "y": sm.get_node_position(StringName(state_name)).y},
		}
		state_info.merge(_read_node_structure(child))
		states.append(state_info)

	var transitions: Array = []
	for i in sm.get_transition_count():
		var from_node := sm.get_transition_from(i)
		var to_node := sm.get_transition_to(i)
		var trans := sm.get_transition(i)
		var trans_info := {
			"from": str(from_node),
			"to": str(to_node),
			"switch_mode": trans.switch_mode,
			"advance_mode": trans.advance_mode,
		}
		if not trans.advance_expression.is_empty():
			trans_info["advance_expression"] = trans.advance_expression
		if trans.advance_mode == AnimationNodeStateMachineTransition.ADVANCE_MODE_AUTO:
			trans_info["auto"] = true
		transitions.append(trans_info)

	return {
		"type": "AnimationNodeStateMachine",
		"states": states,
		"transitions": transitions,
	}


func _get_sm_node_names(sm: AnimationNodeStateMachine) -> Array:
	# Use the internal _get_child_nodes or iterate known patterns
	# AnimationNodeStateMachine doesn't expose a simple list method,
	# but we can use get_graph_offset and iterate via has_node with common checks.
	# Actually in Godot 4.x we can get the node list by checking property list
	# or using the script resource approach. The most reliable is iterating through
	# the resource properties.
	var names: Array = []
	var prop_list := sm.get_property_list()
	for prop in prop_list:
		var pname: String = prop["name"]
		# State machine stores nodes as "states/<name>/node"
		if pname.begins_with("states/") and pname.ends_with("/node"):
			var state_name := pname.get_slice("/", 1)
			if state_name != "Start" and state_name != "End":
				names.append(state_name)
	return names


func _read_blend_tree_structure(bt: AnimationNodeBlendTree) -> Dictionary:
	var nodes_info: Array = []
	var prop_list := bt.get_property_list()
	var node_names: Array = []
	for prop in prop_list:
		var pname: String = prop["name"]
		if pname.begins_with("nodes/") and pname.ends_with("/node"):
			var n := pname.get_slice("/", 1)
			if n != "output":
				node_names.append(n)

	for n_name in node_names:
		var child: AnimationNode = bt.get_node(StringName(n_name))
		var node_info := {
			"name": n_name,
			"type": child.get_class(),
			"position": {"x": bt.get_node_position(StringName(n_name)).x, "y": bt.get_node_position(StringName(n_name)).y},
		}
		if child is AnimationNodeAnimation:
			node_info["animation"] = str((child as AnimationNodeAnimation).animation)
		nodes_info.append(node_info)

	# Read connections
	# BlendTree connections are stored as "node_connections" in properties
	# We can read them from the resource property list
	for prop in prop_list:
		var pname: String = prop["name"]
		if pname.begins_with("nodes/") and pname.ends_with("/node"):
			continue
		if pname.begins_with("nodes/") and pname.ends_with("/position"):
			continue
		# Connection format: "node_connection/<idx>/<input_node>/<input_port>"
		# Actually connections are stored differently - let's skip for now

	return {
		"type": "AnimationNodeBlendTree",
		"nodes": nodes_info,
	}


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

	var result2 := require_string(params, "state_name")
	if result2[1] != null:
		return result2[1]
	var state_name: String = result2[0]

	var tree := _find_animation_tree(node_path)
	if tree == null:
		return error_not_found("AnimationTree at '%s'" % node_path)

	var sm_path: String = optional_string(params, "state_machine_path", "")
	var sm_result := _resolve_state_machine(tree, sm_path)
	if sm_result[1] != null:
		return sm_result[1]
	var sm: AnimationNodeStateMachine = sm_result[0]

	if sm.has_node(StringName(state_name)):
		return error_invalid_params("State '%s' already exists" % state_name)

	var state_type: String = optional_string(params, "state_type", "animation")
	var position_x: float = float(params.get("position_x", 0.0))
	var position_y: float = float(params.get("position_y", 0.0))
	var position := Vector2(position_x, position_y)

	var node: AnimationNode
	match state_type:
		"animation":
			var anim_node := AnimationNodeAnimation.new()
			var anim_name: String = optional_string(params, "animation", "")
			if not anim_name.is_empty():
				anim_node.animation = StringName(anim_name)
			node = anim_node
		"blend_tree":
			node = AnimationNodeBlendTree.new()
		"state_machine":
			node = AnimationNodeStateMachine.new()
		_:
			return error_invalid_params("Unknown state_type: '%s'. Use 'animation', 'blend_tree', or 'state_machine'" % state_type)

	var undo_redo := get_undo_redo()
	undo_redo.create_action("MCP: Add state machine state")
	undo_redo.add_do_method(sm, "add_node", StringName(state_name), node, position)
	undo_redo.add_do_reference(node)
	undo_redo.add_undo_method(sm, "remove_node", StringName(state_name))
	undo_redo.commit_action()

	return success({
		"state_name": state_name,
		"state_type": state_type,
		"position": {"x": position_x, "y": position_y},
		"added": true,
	})


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

	var result2 := require_string(params, "state_name")
	if result2[1] != null:
		return result2[1]
	var state_name: String = result2[0]

	var tree := _find_animation_tree(node_path)
	if tree == null:
		return error_not_found("AnimationTree at '%s'" % node_path)

	var sm_path: String = optional_string(params, "state_machine_path", "")
	var sm_result := _resolve_state_machine(tree, sm_path)
	if sm_result[1] != null:
		return sm_result[1]
	var sm: AnimationNodeStateMachine = sm_result[0]

	if not sm.has_node(StringName(state_name)):
		return error_not_found("State '%s'" % state_name)

	var old_node := sm.get_node(StringName(state_name))
	var old_position := sm.get_node_position(StringName(state_name))
	var undo_redo := get_undo_redo()
	undo_redo.create_action("MCP: Remove state machine state")
	undo_redo.add_do_method(sm, "remove_node", StringName(state_name))
	undo_redo.add_undo_method(sm, "add_node", StringName(state_name), old_node, old_position)
	undo_redo.add_undo_reference(old_node)
	undo_redo.commit_action()

	return success({"state_name": state_name, "removed": true})


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

	var result2 := require_string(params, "from_state")
	if result2[1] != null:
		return result2[1]
	var from_state: String = result2[0]

	var result3 := require_string(params, "to_state")
	if result3[1] != null:
		return result3[1]
	var to_state: String = result3[0]

	var tree := _find_animation_tree(node_path)
	if tree == null:
		return error_not_found("AnimationTree at '%s'" % node_path)

	var sm_path: String = optional_string(params, "state_machine_path", "")
	var sm_result := _resolve_state_machine(tree, sm_path)
	if sm_result[1] != null:
		return sm_result[1]
	var sm: AnimationNodeStateMachine = sm_result[0]

	# Validate states exist (Start and End are special built-in nodes)
	if from_state != "Start" and from_state != "End" and not sm.has_node(StringName(from_state)):
		return error_not_found("State '%s'" % from_state)
	if to_state != "Start" and to_state != "End" and not sm.has_node(StringName(to_state)):
		return error_not_found("State '%s'" % to_state)

	var transition := AnimationNodeStateMachineTransition.new()

	# switch_mode: AT_END=0, IMMEDIATE=1, SYNC=2
	var switch_mode_str: String = optional_string(params, "switch_mode", "immediate")
	match switch_mode_str:
		"at_end": transition.switch_mode = AnimationNodeStateMachineTransition.SWITCH_MODE_AT_END
		"immediate": transition.switch_mode = AnimationNodeStateMachineTransition.SWITCH_MODE_IMMEDIATE
		"sync": transition.switch_mode = AnimationNodeStateMachineTransition.SWITCH_MODE_AT_END  # SYNC maps similarly
		_: transition.switch_mode = AnimationNodeStateMachineTransition.SWITCH_MODE_IMMEDIATE

	# advance_mode: DISABLED=0, ENABLED=1, AUTO=2
	var advance_mode_str: String = optional_string(params, "advance_mode", "enabled")
	match advance_mode_str:
		"disabled": transition.advance_mode = AnimationNodeStateMachineTransition.ADVANCE_MODE_DISABLED
		"enabled": transition.advance_mode = AnimationNodeStateMachineTransition.ADVANCE_MODE_ENABLED
		"auto": transition.advance_mode = AnimationNodeStateMachineTransition.ADVANCE_MODE_AUTO
		_: transition.advance_mode = AnimationNodeStateMachineTransition.ADVANCE_MODE_ENABLED

	# advance_expression
	var expression: String = optional_string(params, "advance_expression", "")
	if not expression.is_empty():
		transition.advance_expression = expression

	# xfade_time
	if params.has("xfade_time"):
		transition.xfade_time = float(params["xfade_time"])

	var undo_redo := get_undo_redo()
	undo_redo.create_action("MCP: Add state machine transition")
	undo_redo.add_do_method(sm, "add_transition", StringName(from_state), StringName(to_state), transition)
	undo_redo.add_do_reference(transition)
	undo_redo.add_undo_method(sm, "remove_transition", StringName(from_state), StringName(to_state))
	undo_redo.commit_action()

	return success({
		"from": from_state,
		"to": to_state,
		"switch_mode": switch_mode_str,
		"advance_mode": advance_mode_str,
		"advance_expression": expression,
		"added": true,
	})


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

	var result2 := require_string(params, "from_state")
	if result2[1] != null:
		return result2[1]
	var from_state: String = result2[0]

	var result3 := require_string(params, "to_state")
	if result3[1] != null:
		return result3[1]
	var to_state: String = result3[0]

	var tree := _find_animation_tree(node_path)
	if tree == null:
		return error_not_found("AnimationTree at '%s'" % node_path)

	var sm_path: String = optional_string(params, "state_machine_path", "")
	var sm_result := _resolve_state_machine(tree, sm_path)
	if sm_result[1] != null:
		return sm_result[1]
	var sm: AnimationNodeStateMachine = sm_result[0]

	# Check if transition exists
	var found := false
	for i in sm.get_transition_count():
		if str(sm.get_transition_from(i)) == from_state and str(sm.get_transition_to(i)) == to_state:
			found = true
			break

	if not found:
		return error_not_found("Transition from '%s' to '%s'" % [from_state, to_state])

	var transition: AnimationNodeStateMachineTransition = null
	for i in sm.get_transition_count():
		if str(sm.get_transition_from(i)) == from_state and str(sm.get_transition_to(i)) == to_state:
			transition = sm.get_transition(i)
			break

	var undo_redo := get_undo_redo()
	undo_redo.create_action("MCP: Remove state machine transition")
	undo_redo.add_do_method(sm, "remove_transition", StringName(from_state), StringName(to_state))
	undo_redo.add_undo_method(sm, "add_transition", StringName(from_state), StringName(to_state), transition)
	undo_redo.add_undo_reference(transition)
	undo_redo.commit_action()

	return success({"from": from_state, "to": to_state, "removed": true})


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

	var result2 := require_string(params, "blend_tree_state")
	if result2[1] != null:
		return result2[1]
	var bt_state: String = result2[0]

	var result3 := require_string(params, "bt_node_name")
	if result3[1] != null:
		return result3[1]
	var bt_node_name: String = result3[0]

	var result4 := require_string(params, "bt_node_type")
	if result4[1] != null:
		return result4[1]
	var bt_node_type: String = result4[0]

	var tree := _find_animation_tree(node_path)
	if tree == null:
		return error_not_found("AnimationTree at '%s'" % node_path)

	var sm_path: String = optional_string(params, "state_machine_path", "")
	var bt_result := _resolve_blend_tree(tree, sm_path, bt_state)
	if bt_result[1] != null:
		return bt_result[1]
	var bt: AnimationNodeBlendTree = bt_result[0]

	var position_x: float = float(params.get("position_x", 0.0))
	var position_y: float = float(params.get("position_y", 0.0))
	var position := Vector2(position_x, position_y)

	var had_old_node := bt.has_node(StringName(bt_node_name))
	var old_node: AnimationNode = bt.get_node(StringName(bt_node_name)) if had_old_node else null
	var old_position := bt.get_node_position(StringName(bt_node_name)) if had_old_node else Vector2.ZERO

	var node: AnimationNode
	match bt_node_type:
		"Animation":
			var anim_node := AnimationNodeAnimation.new()
			var anim_name: String = optional_string(params, "animation", "")
			if not anim_name.is_empty():
				anim_node.animation = StringName(anim_name)
			node = anim_node
		"Add2":
			node = AnimationNodeAdd2.new()
		"Blend2":
			node = AnimationNodeBlend2.new()
		"Add3":
			node = AnimationNodeAdd3.new()
		"Blend3":
			node = AnimationNodeBlend3.new()
		"TimeScale":
			node = AnimationNodeTimeScale.new()
		"TimeSeek":
			node = AnimationNodeTimeSeek.new()
		"Transition":
			node = AnimationNodeTransition.new()
		"OneShot":
			node = AnimationNodeOneShot.new()
		"Sub2":
			node = AnimationNodeSub2.new()
		_:
			return error_invalid_params("Unknown bt_node_type: '%s'. Use: Animation, Add2, Blend2, Add3, Blend3, TimeScale, TimeSeek, Transition, OneShot, Sub2" % bt_node_type)

	var undo_redo := get_undo_redo()
	undo_redo.create_action("MCP: Set blend tree node")
	if had_old_node:
		undo_redo.add_do_method(bt, "remove_node", StringName(bt_node_name))
		undo_redo.add_undo_method(bt, "add_node", StringName(bt_node_name), old_node, old_position)
		undo_redo.add_undo_reference(old_node)
	undo_redo.add_do_method(bt, "add_node", StringName(bt_node_name), node, position)
	undo_redo.add_do_reference(node)
	undo_redo.add_undo_method(bt, "remove_node", StringName(bt_node_name))

	# Connect to another node if specified
	var connect_to: String = optional_string(params, "connect_to", "")
	var connect_port: int = optional_int(params, "connect_port", 0)
	if not connect_to.is_empty():
		undo_redo.add_do_method(bt, "connect_node", StringName(connect_to), connect_port, StringName(bt_node_name))
	undo_redo.commit_action()

	var connected_to_value: Variant = null
	if not connect_to.is_empty():
		connected_to_value = connect_to
	return success({
		"blend_tree_state": bt_state,
		"bt_node_name": bt_node_name,
		"bt_node_type": bt_node_type,
		"position": {"x": position_x, "y": position_y},
		"connected_to": connected_to_value,
		"added": true,
	})


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

	var result2 := require_string(params, "parameter")
	if result2[1] != null:
		return result2[1]
	var parameter: String = result2[0]

	var tree := _find_animation_tree(node_path)
	if tree == null:
		return error_not_found("AnimationTree at '%s'" % node_path)

	if not params.has("value"):
		return error_invalid_params("Missing required parameter: value")

	var value = params["value"]

	# Prefix with "parameters/" if not already
	if not parameter.begins_with("parameters/"):
		parameter = "parameters/" + parameter

	# Parse string values for common types
	if value is String:
		var s: String = value
		var expr := Expression.new()
		if expr.parse(s) == OK:
			var parsed = expr.execute()
			if parsed != null:
				value = parsed

	set_property_with_undo(tree, parameter, value, "MCP: Set AnimationTree parameter")

	# Read back to confirm
	var actual = tree.get(parameter)

	return success({
		"parameter": parameter,
		"value": str(actual),
		"set": true,
	})
