#!/usr/bin/env -S godot --headless --script
extends SceneTree

## Main entry point for Godot MCP operations.
## Routes operations to specialized modules based on domain.
##
## Modules:
##   - scene_ops.gd: Scene operations (create_scene, add_node, save_scene, load_sprite)
##   - export_ops.gd: Export operations (export_mesh_library, get_uid, resave_resources)
##   - script_ops.gd: Script operations (attach_script)
##   - signal_ops.gd: Signal operations (connect_signal_op)
##   - project_ops.gd: Project settings (set_project_setting_op, get_project_setting_op)
##   - input_ops.gd: Input map operations (setup_input_map_op, list_input_actions_op)
##   - tilemap_ops.gd: TileMap operations (set_tilemap_cell_op, get_tilemap_cell_op)
##   - material_ops.gd: Material operations (create_material_op, get_material_properties_op)
##   - animation_ops.gd: Animation operations (create_animation, add_animation_track, etc.)
##   - collision_ops.gd: Collision operations (add_collision_shape, set_collision_layers, set_physics_body_properties)
##   - camera_ops.gd: Camera operations (add_camera, set_camera_current, set_camera_properties, get_camera_info)
##   - audio_ops.gd: Audio operations (add_audio_stream_player, set_audio_stream, configure_audio_bus)
##   - ui_ops.gd: UI operations (add_ui_control, set_ui_layout, add_ui_container)
##   - autoload_ops.gd: Autoload operations (add_autoload, remove_autoload, list_autoloads)
##   - timer_ops.gd: Timer operations (add_timer, configure_timer)
##   - canvas_layer_ops.gd: CanvasLayer operations (add_canvas_layer, set_canvas_layer_order, configure_canvas_layer)
##   - particle_ops.gd: Particle operations (add_particle_system, set_particle_properties, start_particle_emitting)
##   - light_ops.gd: Light operations (add_light, set_light_properties, set_light_shadow, setup_environment)
##   - viewport_ops.gd: Viewport operations (add_viewport, set_viewport_size, configure_viewport)

# Debug mode flag
var debug_mode: bool = false

# Operation modules (loaded dynamically based on script location)
var SceneOps: GDScript
var ExportOps: GDScript
var ScriptOps: GDScript
var SignalOps: GDScript
var ProjectOps: GDScript
var InputOps: GDScript
var TilemapOps: GDScript
var MaterialOps: GDScript
var AnimationOps: GDScript
var CollisionOps: GDScript
var CameraOps: GDScript
var AudioOps: GDScript
var UIOps: GDScript
var AutoloadOps: GDScript
var TimerOps: GDScript
var CanvasLayerOps: GDScript
var ParticleOps: GDScript
var LightOps: GDScript
var ViewportOps: GDScript
var NavigationOps: GDScript


func _init() -> void:
	var args: PackedStringArray = OS.get_cmdline_args()
	
	# Check for debug flag
	debug_mode = "--debug-godot" in args
	
	# Load operation modules dynamically based on script location
	_load_modules()
	
	# Find the script argument and determine the positions of operation and params
	var script_index: int = args.find("--script")
	if script_index == -1:
		log_error("Could not find --script argument")
		quit(1)
	
	# The operation should be 2 positions after the script path (script_index + 1 is the script path itself)
	var operation_index: int = script_index + 2
	# The params should be 3 positions after the script path
	var params_index: int = script_index + 3
	
	if args.size() <= params_index:
		log_error("Usage: godot --headless --script godot_operations.gd <operation> <json_params>")
		log_error("Not enough command-line arguments provided.")
		quit(1)
	
	# Log all arguments for debugging
	log_debug("All arguments: " + str(args))
	log_debug("Script index: " + str(script_index))
	log_debug("Operation index: " + str(operation_index))
	log_debug("Params index: " + str(params_index))
	
	var operation: String = args[operation_index]
	var params_json: String = args[params_index]
	
	log_info("Operation: " + operation)
	log_debug("Params JSON: " + params_json)
	
	# Parse JSON using Godot 4.x API
	var json: JSON = JSON.new()
	var error: int = json.parse(params_json)
	var params: Variant = null
	
	if error == OK:
		params = json.get_data()
	else:
		log_error("Failed to parse JSON parameters: " + params_json)
		log_error("JSON Error: " + json.get_error_message() + " at line " + str(json.get_error_line()))
		quit(1)
	
	if not params:
		log_error("Failed to parse JSON parameters: " + params_json)
		quit(1)
	
	log_info("Executing operation: " + operation)
	
	# Route to appropriate module
	match operation:
		# Scene operations
		"create_scene":
			SceneOps.create_scene(self, params)
		"add_node":
			SceneOps.add_node(self, params)
		"load_sprite":
			SceneOps.load_sprite(self, params)
		"save_scene":
			SceneOps.save_scene(self, params)
		
		# Scene query operations
		"list_scenes":
			SceneOps.list_scenes_op(self, params)
		"get_scene_tree":
			SceneOps.get_scene_tree_op(self, params)
		"get_node_properties":
			SceneOps.get_node_properties_op(self, params)
		
		# Scene manipulation operations
		"set_node_property":
			SceneOps.set_node_property_op(self, params)
		"delete_node":
			SceneOps.delete_node_op(self, params)
		"instance_scene":
			SceneOps.instance_scene_op(self, params)
		"rename_node":
			SceneOps.rename_node_op(self, params)
		"move_node":
			SceneOps.move_node_op(self, params)
		"duplicate_node":
			SceneOps.duplicate_node_op(self, params)
		"add_to_group":
			SceneOps.add_to_group_op(self, params)
		
		# Export operations
		"export_mesh_library":
			ExportOps.export_mesh_library(self, params)
		"get_uid":
			ExportOps.get_uid(self, params)
		"resave_resources":
			ExportOps.resave_resources(self, params)
		
		# Script operations
		"attach_script":
			ScriptOps.attach_script(self, params)
		"detach_script":
			ScriptOps.detach_script_op(self, params)
		"list_scripts":
			ScriptOps.list_scripts_op(self, params)
		
		# Resource operations
		"list_resources":
			ProjectOps.list_resources_op(self, params)
		
		# Signal operations
		"connect_signal":
			SignalOps.connect_signal_op(self, params)
		"list_node_signals":
			SignalOps.list_node_signals_op(self, params)
		"list_signal_connections":
			SignalOps.list_signal_connections_op(self, params)
		"disconnect_signal":
			SignalOps.disconnect_signal_op(self, params)
		
		# Project settings operations
		"set_project_setting":
			ProjectOps.set_project_setting_op(self, params)
		"get_project_setting":
			ProjectOps.get_project_setting_op(self, params)
		
		# Input map operations
		"setup_input_map":
			InputOps.setup_input_map_op(self, params)
		"list_input_actions":
			InputOps.list_input_actions_op(self, params)
		
		# TileMap operations
		"set_tilemap_cell":
			TilemapOps.set_tilemap_cell_op(self, params)
		"get_tilemap_cell":
			TilemapOps.get_tilemap_cell_op(self, params)
		
		# Material operations
		"create_material":
			MaterialOps.create_material_op(self, params)
		"get_material_properties":
			MaterialOps.get_material_properties_op(self, params)

		# Animation operations
		"create_animation":
			AnimationOps.create_animation(self, params)
		"add_animation_track":
			AnimationOps.add_animation_track(self, params)
		"set_animation_keyframe":
			AnimationOps.set_animation_keyframe(self, params)
		"get_animation_info":
			AnimationOps.get_animation_info(self, params)
		"list_animations":
			AnimationOps.list_animations(self, params)

		# Collision and Physics operations
		"add_collision_shape":
			CollisionOps.add_collision_shape(self, params)
		"set_collision_shape_properties":
			CollisionOps.set_collision_shape_properties(self, params)
		"set_collision_layers":
			CollisionOps.set_collision_layers(self, params)
		"set_physics_body_properties":
			CollisionOps.set_physics_body_properties(self, params)

		# Camera operations
		"add_camera":
			CameraOps.add_camera(self, params)
		"set_camera_current":
			CameraOps.set_camera_current(self, params)
		"set_camera_properties":
			CameraOps.set_camera_properties(self, params)
		"get_camera_info":
			CameraOps.get_camera_info(self, params)

		# Audio operations
		"add_audio_stream_player":
			AudioOps.add_audio_stream_player(self, params)
		"set_audio_stream":
			AudioOps.set_audio_stream(self, params)
		"configure_audio_bus":
			AudioOps.configure_audio_bus(self, params)

		# UI operations
		"add_ui_control":
			UIOps.add_ui_control(self, params)
		"set_ui_layout":
			UIOps.set_ui_layout(self, params)
		"add_ui_container":
			UIOps.add_ui_container(self, params)

		# Autoload operations
		"add_autoload":
			AutoloadOps.add_autoload(self, params)
		"remove_autoload":
			AutoloadOps.remove_autoload(self, params)
		"list_autoloads":
			AutoloadOps.list_autoloads(self, params)

		# Timer operations
		"add_timer":
			TimerOps.add_timer(self, params)
		"configure_timer":
			TimerOps.configure_timer(self, params)

		# CanvasLayer operations
		"add_canvas_layer":
			CanvasLayerOps.add_canvas_layer(self, params)
		"set_canvas_layer_order":
			CanvasLayerOps.set_canvas_layer_order(self, params)
		"configure_canvas_layer":
			CanvasLayerOps.configure_canvas_layer(self, params)

		# Particle operations
		"add_particle_system":
			ParticleOps.add_particle_system(self, params)
		"set_particle_properties":
			ParticleOps.set_particle_properties(self, params)
		"start_particle_emitting":
			ParticleOps.start_particle_emitting(self, params)

		# Light operations
		"add_light":
			LightOps.add_light(self, params)
		"set_light_properties":
			LightOps.set_light_properties(self, params)
		"set_light_shadow":
			LightOps.set_light_shadow(self, params)
		"setup_environment":
			LightOps.setup_environment(self, params)

		# Viewport operations
		"add_viewport":
			ViewportOps.add_viewport(self, params)
		"set_viewport_size":
			ViewportOps.set_viewport_size(self, params)
		"configure_viewport":
			ViewportOps.configure_viewport(self, params)

		# Navigation operations
		"add_navigation_region":
			NavigationOps.add_navigation_region(self, params)
		"set_navigation_navmesh":
			NavigationOps.set_navigation_navmesh(self, params)
		"add_navigation_obstacle":
			NavigationOps.add_navigation_obstacle(self, params)
		"add_navigation_agent":
			NavigationOps.add_navigation_agent(self, params)

		_:
			log_error("Unknown operation: " + operation)
			quit(1)
	
	quit()


# ============================================================================
# Logging functions
# ============================================================================

func log_debug(message: String) -> void:
	if debug_mode:
		print("[DEBUG] " + message)


func log_info(message: String) -> void:
	print("[INFO] " + message)


func log_error(message: String) -> void:
	printerr("[ERROR] " + message)


# ============================================================================
# Shared utility functions (used by operation modules)
# ============================================================================

## Get a script by name or path
func get_script_by_name(name_of_class: String) -> Script:
	if debug_mode:
		print("Attempting to get script for class: " + name_of_class)
	
	# Try to load it directly if it's a resource path
	if ResourceLoader.exists(name_of_class, "Script"):
		if debug_mode:
			print("Resource exists, loading directly: " + name_of_class)
		var script: Script = load(name_of_class) as Script
		if script:
			if debug_mode:
				print("Successfully loaded script from path")
			return script
		else:
			printerr("Failed to load script from path: " + name_of_class)
	elif debug_mode:
		print("Resource not found, checking global class registry")
	
	# Search for it in the global class registry if it's a class name
	var global_classes: Array = ProjectSettings.get_global_class_list()
	if debug_mode:
		print("Searching through " + str(global_classes.size()) + " global classes")
	
	for global_class in global_classes:
		var found_name_of_class: String = global_class["class"]
		var found_path: String = global_class["path"]
		
		if found_name_of_class == name_of_class:
			if debug_mode:
				print("Found matching class in registry: " + found_name_of_class + " at path: " + found_path)
			var script: Script = load(found_path) as Script
			if script:
				if debug_mode:
					print("Successfully loaded script from registry")
				return script
			else:
				printerr("Failed to load script from registry path: " + found_path)
				break
	
	printerr("Could not find script for class: " + name_of_class)
	return null


## Instantiate a class by name
func instantiate_class(name_of_class: String) -> Object:
	if name_of_class.is_empty():
		printerr("Cannot instantiate class: name is empty")
		return null
	
	var result: Object = null
	if debug_mode:
		print("Attempting to instantiate class: " + name_of_class)
	
	# Check if it's a built-in class
	if ClassDB.class_exists(name_of_class):
		if debug_mode:
			print("Class exists in ClassDB, using ClassDB.instantiate()")
		if ClassDB.can_instantiate(name_of_class):
			result = ClassDB.instantiate(name_of_class)
			if result == null:
				printerr("ClassDB.instantiate() returned null for class: " + name_of_class)
		else:
			printerr("Class exists but cannot be instantiated: " + name_of_class)
			printerr("This may be an abstract class or interface that cannot be directly instantiated")
	else:
		# Try to get the script
		if debug_mode:
			print("Class not found in ClassDB, trying to get script")
		var script: Script = get_script_by_name(name_of_class)
		if script is GDScript:
			if debug_mode:
				print("Found GDScript, creating instance")
			result = script.new()
		else:
			printerr("Failed to get script for class: " + name_of_class)
			return null
	
	if result == null:
		printerr("Failed to instantiate class: " + name_of_class)
	elif debug_mode:
		print("Successfully instantiated class: " + name_of_class + " of type: " + result.get_class())
	
	return result


## Find files with a specific extension recursively
func find_files(path: String, extension: String) -> Array:
	var files: Array = []
	var dir: DirAccess = DirAccess.open(path)
	
	if dir:
		dir.list_dir_begin()
		var file_name: String = dir.get_next()
		
		while file_name != "":
			if dir.current_is_dir() and not file_name.begins_with("."):
				files.append_array(find_files(path + file_name + "/", extension))
			elif file_name.ends_with(extension):
				files.append(path + file_name)
			
			file_name = dir.get_next()
	
	return files


## Load operation modules dynamically based on script location
func _load_modules() -> void:
	# Get the script path from command line arguments
	var args: PackedStringArray = OS.get_cmdline_args()
	var script_index: int = args.find("--script")
	
	if script_index == -1 or script_index + 1 >= args.size():
		log_error("Could not find --script argument for module loading")
		quit(1)
	
	var script_path: String = args[script_index + 1]
	var script_dir: String = script_path.get_base_dir()
	var ops_dir: String = script_dir + "/ops/"
	
	if debug_mode:
		print("[DEBUG] Script path: " + script_path)
		print("[DEBUG] Script dir: " + script_dir)
		print("[DEBUG] Loading modules from: " + ops_dir)
	
	# Load each module
	SceneOps = load(ops_dir + "scene_ops.gd")
	ExportOps = load(ops_dir + "export_ops.gd")
	ScriptOps = load(ops_dir + "script_ops.gd")
	SignalOps = load(ops_dir + "signal_ops.gd")
	ProjectOps = load(ops_dir + "project_ops.gd")
	InputOps = load(ops_dir + "input_ops.gd")
	TilemapOps = load(ops_dir + "tilemap_ops.gd")
	MaterialOps = load(ops_dir + "material_ops.gd")
	AnimationOps = load(ops_dir + "animation_ops.gd")
	CollisionOps = load(ops_dir + "collision_ops.gd")
	CameraOps = load(ops_dir + "camera_ops.gd")
	AudioOps = load(ops_dir + "audio_ops.gd")
	UIOps = load(ops_dir + "ui_ops.gd")
	AutoloadOps = load(ops_dir + "autoload_ops.gd")
	TimerOps = load(ops_dir + "timer_ops.gd")
	CanvasLayerOps = load(ops_dir + "canvas_layer_ops.gd")
	ParticleOps = load(ops_dir + "particle_ops.gd")
	LightOps = load(ops_dir + "light_ops.gd")
	ViewportOps = load(ops_dir + "viewport_ops.gd")
	NavigationOps = load(ops_dir + "navigation_ops.gd")

	# Verify all modules loaded successfully
	if not SceneOps or not ExportOps or not ScriptOps or not SignalOps or \
	   not ProjectOps or not InputOps or not TilemapOps or not MaterialOps or \
	   not AnimationOps or not CollisionOps or not CameraOps or not AudioOps or \
	   not UIOps or not AutoloadOps or not TimerOps or not CanvasLayerOps or \
	   not ParticleOps or not LightOps or not ViewportOps or not NavigationOps:
		log_error("Failed to load one or more operation modules from: " + ops_dir)
		log_error("Please ensure the ops/ directory exists with all module files")
		quit(1)
