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


func get_commands() -> Dictionary:
	return {
		"get_audio_bus_layout": _get_audio_bus_layout,
		"add_audio_bus": _add_audio_bus,
		"set_audio_bus": _set_audio_bus,
		"add_audio_bus_effect": _add_audio_bus_effect,
		"add_audio_player": _add_audio_player,
		"get_audio_info": _get_audio_info,
	}


func _get_audio_bus_layout(_params: Dictionary) -> Dictionary:
	var buses: Array[Dictionary] = []
	for i in range(AudioServer.bus_count):
		var bus_data := {
			"index": i,
			"name": AudioServer.get_bus_name(i),
			"volume_db": AudioServer.get_bus_volume_db(i),
			"solo": AudioServer.is_bus_solo(i),
			"mute": AudioServer.is_bus_mute(i),
			"bypass_effects": AudioServer.is_bus_bypassing_effects(i),
			"send": AudioServer.get_bus_send(i),
			"effects": [],
		}
		var effects: Array[Dictionary] = []
		for j in range(AudioServer.get_bus_effect_count(i)):
			var effect := AudioServer.get_bus_effect(i, j)
			var effect_data := {
				"index": j,
				"type": effect.get_class(),
				"enabled": AudioServer.is_bus_effect_enabled(i, j),
			}
			# Include effect-specific parameters
			effect_data["params"] = _get_effect_params(effect)
			effects.append(effect_data)
		bus_data["effects"] = effects
		buses.append(bus_data)
	return success({"bus_count": AudioServer.bus_count, "buses": buses})


func _get_effect_params(effect: AudioEffect) -> Dictionary:
	var params := {}
	if effect is AudioEffectReverb:
		var rev := effect as AudioEffectReverb
		params = {"room_size": rev.room_size, "damping": rev.damping, "wet": rev.wet, "dry": rev.dry, "spread": rev.spread}
	elif effect is AudioEffectDelay:
		var d := effect as AudioEffectDelay
		params = {"tap1_active": d.tap1_active, "tap1_delay_ms": d.tap1_delay_ms, "tap1_level_db": d.tap1_level_db, "tap2_active": d.tap2_active, "tap2_delay_ms": d.tap2_delay_ms, "tap2_level_db": d.tap2_level_db}
	elif effect is AudioEffectCompressor:
		var c := effect as AudioEffectCompressor
		params = {"threshold": c.threshold, "ratio": c.ratio, "attack_us": c.attack_us, "release_ms": c.release_ms, "gain": c.gain, "mix": c.mix, "sidechain": c.sidechain}
	elif effect is AudioEffectLimiter:
		var l := effect as AudioEffectLimiter
		params = {"ceiling_db": l.ceiling_db, "threshold_db": l.threshold_db, "soft_clip_db": l.soft_clip_db, "soft_clip_ratio": l.soft_clip_ratio}
	elif effect is AudioEffectDistortion:
		var dist := effect as AudioEffectDistortion
		params = {"mode": dist.mode, "pre_gain": dist.pre_gain, "post_gain": dist.post_gain, "keep_hf_hz": dist.keep_hf_hz, "drive": dist.drive}
	elif effect is AudioEffectChorus:
		var ch := effect as AudioEffectChorus
		params = {"voice_count": ch.voice_count, "dry": ch.dry, "wet": ch.wet}
	elif effect is AudioEffectPhaser:
		var ph := effect as AudioEffectPhaser
		params = {"range_min_hz": ph.range_min_hz, "range_max_hz": ph.range_max_hz, "rate_hz": ph.rate_hz, "feedback": ph.feedback, "depth": ph.depth}
	elif effect is AudioEffectFilter:
		# Covers LowPassFilter, HighPassFilter, BandPassFilter, etc.
		var f := effect as AudioEffectFilter
		params = {"cutoff_hz": f.cutoff_hz, "resonance": f.resonance, "gain": f.gain, "db": f.db}
	elif effect is AudioEffectAmplify:
		var a := effect as AudioEffectAmplify
		params = {"volume_db": a.volume_db}
	return params


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

	# Check if bus name already exists
	for i in range(AudioServer.bus_count):
		if AudioServer.get_bus_name(i) == bus_name:
			return error_invalid_params("Audio bus '%s' already exists at index %d" % [bus_name, i])

	var at_position: int = optional_int(params, "at_position", -1)
	AudioServer.add_bus(at_position)

	var idx: int = AudioServer.bus_count - 1 if at_position < 0 else at_position
	AudioServer.set_bus_name(idx, bus_name)

	if params.has("volume_db"):
		AudioServer.set_bus_volume_db(idx, float(params["volume_db"]))

	var send: String = optional_string(params, "send", "")
	if not send.is_empty():
		AudioServer.set_bus_send(idx, send)

	if params.has("solo"):
		AudioServer.set_bus_solo(idx, bool(params["solo"]))

	if params.has("mute"):
		AudioServer.set_bus_mute(idx, bool(params["mute"]))

	return success({"name": bus_name, "index": idx, "bus_count": AudioServer.bus_count})


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

	var idx := AudioServer.get_bus_index(bus_name)
	if idx < 0:
		return error_not_found("Audio bus '%s'" % bus_name)

	var changes := 0

	if params.has("volume_db"):
		AudioServer.set_bus_volume_db(idx, float(params["volume_db"]))
		changes += 1

	if params.has("solo"):
		AudioServer.set_bus_solo(idx, bool(params["solo"]))
		changes += 1

	if params.has("mute"):
		AudioServer.set_bus_mute(idx, bool(params["mute"]))
		changes += 1

	if params.has("bypass_effects"):
		AudioServer.set_bus_bypass_effects(idx, bool(params["bypass_effects"]))
		changes += 1

	var send: String = optional_string(params, "send", "")
	if not send.is_empty():
		AudioServer.set_bus_send(idx, send)
		changes += 1

	if params.has("rename"):
		var new_name: String = str(params["rename"])
		AudioServer.set_bus_name(idx, new_name)
		bus_name = new_name
		changes += 1

	return success({"name": bus_name, "index": idx, "changes": changes})


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

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

	var bus_idx := AudioServer.get_bus_index(bus_name)
	if bus_idx < 0:
		return error_not_found("Audio bus '%s'" % bus_name)

	var effect: AudioEffect = null
	var effect_params: Dictionary = params.get("params", {}) if params.has("params") else {}

	match effect_type.to_lower():
		"reverb":
			var e := AudioEffectReverb.new()
			if effect_params.has("room_size"):
				e.room_size = float(effect_params["room_size"])
			if effect_params.has("damping"):
				e.damping = float(effect_params["damping"])
			if effect_params.has("wet"):
				e.wet = float(effect_params["wet"])
			if effect_params.has("dry"):
				e.dry = float(effect_params["dry"])
			if effect_params.has("spread"):
				e.spread = float(effect_params["spread"])
			effect = e
		"chorus":
			var e := AudioEffectChorus.new()
			if effect_params.has("voice_count"):
				e.voice_count = int(effect_params["voice_count"])
			if effect_params.has("dry"):
				e.dry = float(effect_params["dry"])
			if effect_params.has("wet"):
				e.wet = float(effect_params["wet"])
			effect = e
		"delay":
			var e := AudioEffectDelay.new()
			if effect_params.has("tap1_active"):
				e.tap1_active = bool(effect_params["tap1_active"])
			if effect_params.has("tap1_delay_ms"):
				e.tap1_delay_ms = float(effect_params["tap1_delay_ms"])
			if effect_params.has("tap1_level_db"):
				e.tap1_level_db = float(effect_params["tap1_level_db"])
			if effect_params.has("tap2_active"):
				e.tap2_active = bool(effect_params["tap2_active"])
			if effect_params.has("tap2_delay_ms"):
				e.tap2_delay_ms = float(effect_params["tap2_delay_ms"])
			if effect_params.has("tap2_level_db"):
				e.tap2_level_db = float(effect_params["tap2_level_db"])
			effect = e
		"compressor":
			var e := AudioEffectCompressor.new()
			if effect_params.has("threshold"):
				e.threshold = float(effect_params["threshold"])
			if effect_params.has("ratio"):
				e.ratio = float(effect_params["ratio"])
			if effect_params.has("attack_us"):
				e.attack_us = float(effect_params["attack_us"])
			if effect_params.has("release_ms"):
				e.release_ms = float(effect_params["release_ms"])
			if effect_params.has("gain"):
				e.gain = float(effect_params["gain"])
			if effect_params.has("mix"):
				e.mix = float(effect_params["mix"])
			effect = e
		"limiter":
			var e := AudioEffectLimiter.new()
			if effect_params.has("ceiling_db"):
				e.ceiling_db = float(effect_params["ceiling_db"])
			if effect_params.has("threshold_db"):
				e.threshold_db = float(effect_params["threshold_db"])
			if effect_params.has("soft_clip_db"):
				e.soft_clip_db = float(effect_params["soft_clip_db"])
			if effect_params.has("soft_clip_ratio"):
				e.soft_clip_ratio = float(effect_params["soft_clip_ratio"])
			effect = e
		"phaser":
			var e := AudioEffectPhaser.new()
			if effect_params.has("range_min_hz"):
				e.range_min_hz = float(effect_params["range_min_hz"])
			if effect_params.has("range_max_hz"):
				e.range_max_hz = float(effect_params["range_max_hz"])
			if effect_params.has("rate_hz"):
				e.rate_hz = float(effect_params["rate_hz"])
			if effect_params.has("feedback"):
				e.feedback = float(effect_params["feedback"])
			if effect_params.has("depth"):
				e.depth = float(effect_params["depth"])
			effect = e
		"distortion":
			var e := AudioEffectDistortion.new()
			if effect_params.has("mode"):
					e.mode = int(effect_params["mode"]) as AudioEffectDistortion.Mode
			if effect_params.has("pre_gain"):
				e.pre_gain = float(effect_params["pre_gain"])
			if effect_params.has("post_gain"):
				e.post_gain = float(effect_params["post_gain"])
			if effect_params.has("keep_hf_hz"):
				e.keep_hf_hz = float(effect_params["keep_hf_hz"])
			if effect_params.has("drive"):
				e.drive = float(effect_params["drive"])
			effect = e
		"lowpassfilter", "lowpass":
			var e := AudioEffectLowPassFilter.new()
			if effect_params.has("cutoff_hz"):
				e.cutoff_hz = float(effect_params["cutoff_hz"])
			if effect_params.has("resonance"):
				e.resonance = float(effect_params["resonance"])
			effect = e
		"highpassfilter", "highpass":
			var e := AudioEffectHighPassFilter.new()
			if effect_params.has("cutoff_hz"):
				e.cutoff_hz = float(effect_params["cutoff_hz"])
			if effect_params.has("resonance"):
				e.resonance = float(effect_params["resonance"])
			effect = e
		"bandpassfilter", "bandpass":
			var e := AudioEffectBandPassFilter.new()
			if effect_params.has("cutoff_hz"):
				e.cutoff_hz = float(effect_params["cutoff_hz"])
			if effect_params.has("resonance"):
				e.resonance = float(effect_params["resonance"])
			effect = e
		"amplify":
			var e := AudioEffectAmplify.new()
			if effect_params.has("volume_db"):
				e.volume_db = float(effect_params["volume_db"])
			effect = e
		"eq":
			var e := AudioEffectEQ.new()
			effect = e
		_:
			return error_invalid_params("Unknown effect type: '%s'. Valid types: reverb, chorus, delay, compressor, limiter, phaser, distortion, lowpassfilter, highpassfilter, bandpassfilter, amplify, eq" % effect_type)

	var at_position: int = optional_int(params, "at_position", -1)
	AudioServer.add_bus_effect(bus_idx, effect, at_position)

	var effect_idx: int = AudioServer.get_bus_effect_count(bus_idx) - 1 if at_position < 0 else at_position
	return success({"bus": bus_name, "bus_index": bus_idx, "effect_type": effect.get_class(), "effect_index": effect_idx})


func _add_audio_player(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, "name")
	if result2[1] != null:
		return result2[1]
	var player_name: String = result2[0]

	var player_type: String = optional_string(params, "type", "AudioStreamPlayer")
	var valid_types := ["AudioStreamPlayer", "AudioStreamPlayer2D", "AudioStreamPlayer3D"]
	if player_type not in valid_types:
		return error_invalid_params("Invalid player type '%s'. Valid: %s" % [player_type, ", ".join(valid_types)])

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

	var player: Node = null
	match player_type:
		"AudioStreamPlayer":
			player = AudioStreamPlayer.new()
		"AudioStreamPlayer2D":
			player = AudioStreamPlayer2D.new()
		"AudioStreamPlayer3D":
			player = AudioStreamPlayer3D.new()

	player.name = player_name

	# Set stream if provided
	var stream_path: String = optional_string(params, "stream", "")
	if not stream_path.is_empty():
		if ResourceLoader.exists(stream_path):
			var stream = ResourceLoader.load(stream_path)
			if stream is AudioStream:
				player.set("stream", stream)
			else:
				player.queue_free()
				return error_invalid_params("Resource at '%s' is not an AudioStream" % stream_path)
		else:
			player.queue_free()
			return error_not_found("Audio stream at '%s'" % stream_path)

	# Common properties
	if params.has("volume_db"):
		player.set("volume_db", float(params["volume_db"]))

	var bus: String = optional_string(params, "bus", "")
	if not bus.is_empty():
		player.set("bus", bus)

	if params.has("autoplay"):
		player.set("autoplay", bool(params["autoplay"]))

	# 2D-specific properties
	if player is AudioStreamPlayer2D:
		if params.has("max_distance"):
			(player as AudioStreamPlayer2D).max_distance = float(params["max_distance"])
		if params.has("attenuation"):
			(player as AudioStreamPlayer2D).attenuation = float(params["attenuation"])

	# 3D-specific properties
	if player is AudioStreamPlayer3D:
		if params.has("max_distance"):
			(player as AudioStreamPlayer3D).max_distance = float(params["max_distance"])
		if params.has("attenuation_model"):
			(player as AudioStreamPlayer3D).attenuation_model = int(params["attenuation_model"]) as AudioStreamPlayer3D.AttenuationModel
		if params.has("unit_size"):
			(player as AudioStreamPlayer3D).unit_size = float(params["unit_size"])

	add_child_with_undo(parent, player, root, "MCP: Add audio player")

	return success({
		"name": player_name,
		"type": player_type,
		"parent": node_path,
		"stream": stream_path,
		"bus": player.get("bus"),
		"volume_db": player.get("volume_db"),
		"autoplay": player.get("autoplay"),
	})


func _get_audio_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 players: Array[Dictionary] = []
	_collect_audio_players(node, players)

	return success({"node_path": node_path, "audio_player_count": players.size(), "players": players})


func _collect_audio_players(node: Node, result: Array[Dictionary]) -> void:
	if node is AudioStreamPlayer or node is AudioStreamPlayer2D or node is AudioStreamPlayer3D:
		var info := {
			"name": node.name,
			"path": str(get_edited_root().get_path_to(node)),
			"type": node.get_class(),
			"volume_db": node.get("volume_db"),
			"bus": node.get("bus"),
			"autoplay": node.get("autoplay"),
			"playing": node.get("playing"),
			"stream": "",
		}
		var stream = node.get("stream")
		if stream != null and stream is AudioStream:
			info["stream"] = stream.resource_path

		if node is AudioStreamPlayer2D:
			info["max_distance"] = (node as AudioStreamPlayer2D).max_distance
			info["attenuation"] = (node as AudioStreamPlayer2D).attenuation
		elif node is AudioStreamPlayer3D:
			info["max_distance"] = (node as AudioStreamPlayer3D).max_distance
			info["attenuation_model"] = (node as AudioStreamPlayer3D).attenuation_model
			info["unit_size"] = (node as AudioStreamPlayer3D).unit_size

		result.append(info)

	for child in node.get_children():
		_collect_audio_players(child, result)
