## AudioManager — 音效池 + 节流（Autoload: AudioManager）
## 权威来源：development_plan.md S6 P1（32 AudioStreamPlayer2D 池、0.1s 节流）
## 无音频素材时用程序化蜂鸣占位（同 API，放素材即替换）。SFX 总线 send→Master，受 SettingsManager 音量影响。
extends Node

const POOL_SIZE:    int   = 32
const THROTTLE_SEC: float = 0.1   # 同一 sound_id 两次播放最小间隔

## sound_id → 占位蜂鸣参数 [freq, ms]（有真实 res://audio/sfx/<id>.* 时优先加载）
const _SFX_DEFS: Dictionary = {
	"hit":           [900.0, 60.0],
	"kill":          [420.0, 90.0],
	"hurt":          [180.0, 140.0],
	"wave_complete": [660.0, 220.0],
	"boss":          [120.0, 400.0],
	"shop":          [780.0, 120.0],
	"buy":           [1040.0, 80.0],
	"level_up":      [880.0, 180.0],
}

var _players:     Array = []                # Array[AudioStreamPlayer2D]
var _next:        int = 0                    # round-robin 游标
var _streams:     Dictionary = {}            # sound_id → AudioStream（懒构建缓存）
var _last_play:   Dictionary = {}            # sound_id → 上次播放 ticks_msec

func _ready() -> void:
	_ensure_sfx_bus()
	for i in POOL_SIZE:
		var p := AudioStreamPlayer2D.new()
		p.bus = "SFX"
		add_child(p)
		_players.append(p)
	# 事件挂钩（无素材时静默蜂鸣）
	EventBus.subscribe(EventID.ENEMY_KILLED, func(_p): play("kill"))
	EventBus.subscribe(EventID.BULLET_HIT, func(p): play("hit", p.get("hit_pos", Vector2.ZERO)))
	EventBus.subscribe(EventID.PLAYER_DAMAGED, func(_p): play("hurt"))
	EventBus.subscribe(EventID.WAVE_COMPLETE, func(_p): play("wave_complete"))
	EventBus.subscribe(EventID.BOSS_SPAWNED, func(_p): play("boss"))
	EventBus.subscribe(EventID.SHOP_OPENED, func(_p): play("shop"))
	EventBus.subscribe(EventID.LEVEL_UP, func(_p): play("level_up"))

func _ensure_sfx_bus() -> void:
	if AudioServer.get_bus_index("SFX") < 0:
		var idx: int = AudioServer.bus_count
		AudioServer.add_bus(idx)
		AudioServer.set_bus_name(idx, "SFX")
		AudioServer.set_bus_send(idx, "Master")  # 受 Master 音量（SettingsManager）影响

## 播放音效；返回 true=已触发，false=节流跳过 / 无可用流
func play(sound_id: String, world_pos: Vector2 = Vector2.ZERO) -> bool:
	var now: int = Time.get_ticks_msec()
	var last: int = int(_last_play.get(sound_id, -100000))
	if now - last < int(THROTTLE_SEC * 1000.0):
		return false  # 0.1s 节流：抑制同一音效高频刷屏
	var stream: AudioStream = _get_stream(sound_id)
	if stream == null:
		return false
	_last_play[sound_id] = now
	var p: AudioStreamPlayer2D = _players[_next]
	_next = (_next + 1) % POOL_SIZE
	p.stream = stream
	p.global_position = world_pos
	p.play()
	return true

func _get_stream(sound_id: String) -> AudioStream:
	if _streams.has(sound_id):
		return _streams[sound_id]
	# 优先真实素材
	for ext in [".ogg", ".wav", ".mp3"]:
		var path: String = "res://audio/sfx/%s%s" % [sound_id, ext]
		if ResourceLoader.exists(path):
			var s = load(path)
			if s is AudioStream:
				_streams[sound_id] = s
				return s
	# 回退：程序化蜂鸣
	if _SFX_DEFS.has(sound_id):
		var def: Array = _SFX_DEFS[sound_id]
		var beep := _make_beep(float(def[0]), float(def[1]))
		_streams[sound_id] = beep
		return beep
	_streams[sound_id] = null
	return null

## 生成短蜂鸣（16-bit PCM 正弦 + 线性衰减包络）
func _make_beep(freq: float, ms: float) -> AudioStreamWAV:
	var sr: int = 22050
	var n: int = max(1, int(sr * ms / 1000.0))
	var data := PackedByteArray()
	data.resize(n * 2)
	for i in n:
		var t: float = float(i) / float(sr)
		var env: float = 1.0 - float(i) / float(n)
		var sample: int = int(sin(t * freq * TAU) * 11000.0 * env)
		data.encode_s16(i * 2, sample)
	var w := AudioStreamWAV.new()
	w.format = AudioStreamWAV.FORMAT_16_BITS
	w.mix_rate = sr
	w.stereo = false
	w.data = data
	return w

## 当前正在播放的池实例数（测试 / 调试用）
func active_voices() -> int:
	var c: int = 0
	for p in _players:
		if p.playing:
			c += 1
	return c
