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

const NodeUtils := preload("res://addons/godot_mcp/utils/node_utils.gd")
const PropertyParser := preload("res://addons/godot_mcp/utils/property_parser.gd")


func get_commands() -> Dictionary:
	return {
		"get_scene_tree": _get_scene_tree,
		"get_scene_file_content": _get_scene_file_content,
		"create_scene": _create_scene,
		"open_scene": _open_scene,
		"delete_scene": _delete_scene,
		"add_scene_instance": _add_scene_instance,
		"play_scene": _play_scene,
		"stop_scene": _stop_scene,
		"save_scene": _save_scene,
		"get_scene_exports": _get_scene_exports,
	}


func _get_scene_tree(params: Dictionary) -> Dictionary:
	var root := get_edited_root()
	if root == null:
		return error_no_scene()

	var max_depth: int = optional_int(params, "max_depth", -1)
	var tree := NodeUtils.get_node_tree(root, max_depth)
	return success({"scene_path": root.scene_file_path, "tree": tree})


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

	if not FileAccess.file_exists(path):
		return error_not_found("Scene file '%s'" % path)

	var file := FileAccess.open(path, FileAccess.READ)
	if file == null:
		return error_internal("Cannot read file: %s" % error_string(FileAccess.get_open_error()))

	var content := file.get_as_text()
	file.close()
	return success({"path": path, "content": content, "size": content.length()})


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

	var guard := guard_offline_scene_save(path)
	if not guard.is_empty():
		return guard

	var root_type: String = optional_string(params, "root_type", "Node2D")
	var root_name: String = optional_string(params, "root_name", "")

	# Validate root type exists
	if not ClassDB.class_exists(root_type):
		return error_invalid_params("Unknown node type: %s" % root_type)

	# Create the scene
	var root: Node = ClassDB.instantiate(root_type)
	if root_name.is_empty():
		root_name = path.get_file().get_basename()
	root.name = root_name

	var scene := PackedScene.new()
	var err := scene.pack(root)
	root.queue_free()

	if err != OK:
		return error_internal("Failed to pack scene: %s" % error_string(err))

	# Ensure directory exists
	var dir_path := path.get_base_dir()
	if not DirAccess.dir_exists_absolute(dir_path):
		DirAccess.make_dir_recursive_absolute(dir_path)

	err = ResourceSaver.save(scene, path)
	if err != OK:
		return error_internal("Failed to save scene: %s" % error_string(err))

	# Refresh filesystem
	EditorInterface.get_resource_filesystem().scan()

	return success({"path": path, "root_type": root_type, "root_name": root_name})


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

	if not FileAccess.file_exists(path):
		return error_not_found("Scene file '%s'" % path)

	EditorInterface.open_scene_from_path(path)
	return success({"path": path, "opened": true})


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

	if not FileAccess.file_exists(path):
		return error_not_found("Scene file '%s'" % path)

	var err := DirAccess.remove_absolute(path)
	if err != OK:
		return error_internal("Failed to delete scene: %s" % error_string(err))

	# Also remove .import file if exists
	var import_path := path + ".import"
	if FileAccess.file_exists(import_path):
		DirAccess.remove_absolute(import_path)

	EditorInterface.get_resource_filesystem().scan()
	return success({"path": path, "deleted": true})


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

	var parent_path: String = optional_string(params, "parent_path", ".")
	var instance_name: String = optional_string(params, "name", "")

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

	if not FileAccess.file_exists(scene_path):
		return error_not_found("Scene file '%s'" % scene_path)

	var parent := find_node_by_path(parent_path)
	if parent == null:
		return error_not_found("Parent node '%s'" % parent_path, "Use get_scene_tree to see available nodes")

	var packed: PackedScene = load(scene_path)
	if packed == null:
		return error_internal("Failed to load scene: %s" % scene_path)

	var instance := packed.instantiate()
	if not instance_name.is_empty():
		instance.name = instance_name

	var undo_redo := get_undo_redo()
	undo_redo.create_action("MCP: Add scene instance")
	undo_redo.add_do_method(parent, "add_child", instance)
	undo_redo.add_do_method(instance, "set_owner", root)
	undo_redo.add_do_reference(instance)
	undo_redo.add_undo_method(parent, "remove_child", instance)
	undo_redo.commit_action()

	NodeUtils.set_owner_recursive(instance, root)

	return success({
		"node_path": str(root.get_path_to(instance)),
		"scene_path": scene_path,
		"name": instance.name,
	})


func _play_scene(params: Dictionary) -> Dictionary:
	var mode: String = optional_string(params, "mode", "main")  # "main", "current", or path

	match mode:
		"main":
			EditorInterface.play_main_scene()
		"current":
			EditorInterface.play_current_scene()
		_:
			# Treat as scene path
			if not FileAccess.file_exists(mode):
				return error_not_found("Scene file '%s'" % mode)
			EditorInterface.play_custom_scene(mode)

	return success({"playing": true, "mode": mode})


func _stop_scene(_params: Dictionary) -> Dictionary:
	if not EditorInterface.is_playing_scene():
		return success({"stopped": false, "message": "No scene is currently playing"})

	EditorInterface.stop_playing_scene()

	# Clean up temp files
	_cleanup_screenshot_files()
	_cleanup_input_files()
	_cleanup_inspector_files()

	return success({"stopped": true})


func _save_scene(params: Dictionary) -> Dictionary:
	var root := get_edited_root()
	if root == null:
		return error_no_scene()

	var path: String = optional_string(params, "path", "")
	if path.is_empty():
		path = root.scene_file_path

	if path.is_empty():
		return error_invalid_params("No save path specified and scene has no existing path")

	var normalized_path := normalize_project_path(path)
	if is_scene_path_open(normalized_path) and not is_active_scene_path(normalized_path):
		return error_conflict(
			"Refusing to save inactive open scene '%s' from the active editor scene" % normalized_path,
			{
				"path": normalized_path,
				"active_scene": normalize_project_path(root.scene_file_path),
				"open_scenes": get_open_scene_paths(),
				"suggestion": "Open the target scene tab before saving it, or close it before offline edits.",
			}
		)

	var dir_path := normalized_path.get_base_dir()
	if not DirAccess.dir_exists_absolute(dir_path):
		DirAccess.make_dir_recursive_absolute(dir_path)

	var err: int
	var save_method: String
	if root.scene_file_path.is_empty() or normalize_project_path(root.scene_file_path) != normalized_path:
		EditorInterface.save_scene_as(normalized_path)
		err = OK
		save_method = "EditorInterface.save_scene_as"
	else:
		err = EditorInterface.save_scene()
		save_method = "EditorInterface.save_scene"
	if err != OK:
		return error_internal("Failed to save scene via %s: %s" % [save_method, error_string(err)])

	return success({"path": normalized_path, "saved": true, "method": save_method})


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

	if not FileAccess.file_exists(path):
		return error_not_found("Scene file '%s'" % path)

	var packed: PackedScene = load(path)
	if packed == null:
		return error_internal("Failed to load scene: %s" % path)

	var instance: Node = packed.instantiate()
	if instance == null:
		return error_internal("Failed to instantiate scene: %s" % path)

	var nodes_data: Array = []
	_collect_exports_recursive(instance, instance, nodes_data)

	instance.queue_free()

	return success({
		"path": path,
		"nodes": nodes_data,
		"count": nodes_data.size(),
	})


func _collect_exports_recursive(node: Node, root: Node, nodes_data: Array) -> void:
	var script: Script = node.get_script()
	if script != null:
		var exports: Dictionary = {}
		for prop_info in script.get_script_property_list():
			var usage: int = prop_info["usage"]
			if (usage & PROPERTY_USAGE_EDITOR) and (usage & PROPERTY_USAGE_SCRIPT_VARIABLE):
				var prop_name: String = prop_info["name"]
				exports[prop_name] = {
					"value": PropertyParser.serialize_value(node.get(prop_name)),
					"type": prop_info["type"],
					"hint": prop_info.get("hint", 0),
					"hint_string": prop_info.get("hint_string", ""),
				}
		if not exports.is_empty():
			var node_path := "." if node == root else str(root.get_path_to(node))
			nodes_data.append({
				"node_path": node_path,
				"node_name": node.name,
				"node_type": node.get_class(),
				"script_path": script.resource_path,
				"exports": exports,
			})

	for child in node.get_children():
		_collect_exports_recursive(child, root, nodes_data)


func _cleanup_screenshot_files() -> void:
	var user_dir := get_game_user_dir()
	var request_path := user_dir + "/mcp_screenshot_request"
	var screenshot_path := user_dir + "/mcp_screenshot.png"
	if FileAccess.file_exists(request_path):
		DirAccess.remove_absolute(request_path)
	if FileAccess.file_exists(screenshot_path):
		DirAccess.remove_absolute(screenshot_path)


func _cleanup_input_files() -> void:
	var user_dir := get_game_user_dir()
	var commands_path := user_dir + "/mcp_input_commands"
	if FileAccess.file_exists(commands_path):
		DirAccess.remove_absolute(commands_path)


func _cleanup_inspector_files() -> void:
	var user_dir := get_game_user_dir()
	var request_path := user_dir + "/mcp_game_request"
	var response_path := user_dir + "/mcp_game_response"
	if FileAccess.file_exists(request_path):
		DirAccess.remove_absolute(request_path)
	if FileAccess.file_exists(response_path):
		DirAccess.remove_absolute(response_path)
