@tool
class_name McpNodeValidator
extends RefCounted

## Shared resolve-or-error helper that subsumes the 38+ sites where
## handlers each rolled their own "is the editor ready, does the path
## resolve, otherwise return EDITOR_NOT_READY / NODE_NOT_FOUND" guard.
##
## audit-v2 #20 (issue #364). Uses the audit-v2 #21 (issue #365) error
## vocabulary.

## Local const names alias the preloaded scripts. The naming choice is
## stylistic, not an upgrade-safety boundary: bare `McpErrorCodes.MEMBER`
## and `ErrorCodes.MEMBER` both depend on the Script object Godot has for
## `error_codes.gd`. The transient #398 parse errors were caused by the
## old runner scanning a mixed old/new plugin snapshot and seeing stale
## Script-object content; the runner now writes one v(N+1) snapshot before
## its scan.
const ScenePath := preload("res://addons/godot_ai/utils/scene_path.gd")
const ErrorCodes := preload("res://addons/godot_ai/utils/error_codes.gd")


## Resolve a scene-relative path to the live Node, or return a structured
## error dict.
##
## Success shape: `{"node": Node, "scene_root": Node, "path": String}`.
## Error shape: matches `ErrorCodes.make(...)` so callers can
## `return resolved` to propagate.
##
## Errors (in order checked):
##   - `MISSING_REQUIRED_PARAM`: `node_path` is empty
##   - `EDITOR_NOT_READY`: no scene open
##   - `EDITED_SCENE_MISMATCH`: caller pinned `scene_file` and the open
##     scene's path doesn't match
##   - `NODE_NOT_FOUND`: `node_path` doesn't resolve under the scene root
##
## `param_name` is the agent-facing name reported in the
## `MISSING_REQUIRED_PARAM` message — handlers pass "node_path",
## "player_path", "target_path", etc. so the error reads like the
## hand-written messages it replaces.
static func resolve_or_error(
	node_path: String,
	param_name: String = "path",
	scene_file: String = "",
) -> Dictionary:
	if node_path.is_empty():
		return ErrorCodes.make(
			ErrorCodes.MISSING_REQUIRED_PARAM,
			"Missing required param: %s" % param_name,
		)
	var scene_check := ScenePath.require_edited_scene(scene_file)
	if scene_check.has("error"):
		return scene_check
	var scene_root: Node = scene_check.node
	var node := ScenePath.resolve(node_path, scene_root)
	if node == null:
		return ErrorCodes.make(
			ErrorCodes.NODE_NOT_FOUND,
			ScenePath.format_node_error(node_path, scene_root),
		)
	return {"node": node, "scene_root": scene_root, "path": node_path}


## When the caller needs the scene root but no specific node yet — e.g.
## handlers that walk children or filter by group. Returns either
## `{"scene_root": Node}` or an `ErrorCodes.make(...)` error dict.
static func require_scene_or_error(scene_file: String = "") -> Dictionary:
	var scene_check := ScenePath.require_edited_scene(scene_file)
	if scene_check.has("error"):
		return scene_check
	return {"scene_root": scene_check.node}
