## EnemyManager — 敌人管理器 Autoload（GDScript 接口层）
## 热路径由 C# EnemyManagerCs 子节点驱动。
## 权威来源：implementation_plan.md §2.3.C
##
## SoA 布局（ENEMY_STRIDE=8）：
## [ x, y, vx, vy, hp, hp_max, enemy_type, state ]
##   0  1   2   3   4    5         6          7
extends Node

const ENEMY_STRIDE: int = 8    # 禁止裸整数 8（P6-N54）
const MAX_ENEMIES:  int = 1024

const OFFSCREEN_SEPARATION_INTERVAL: int = 10  # 屏外低频 Boid 分离力更新频率（P6-N1）

# SoA 热数组
var _data: PackedFloat32Array = PackedFloat32Array()
var _active_count: int = 0

# entity_id ↔ SoA 槽位 双向映射
var _entity_index_map: Dictionary = {}  # { entity_id → slot_idx }
var _slot_entity_map:  Dictionary = {}  # { slot_idx → entity_id }
var _next_entity_id:   int = 1

# LOD：屏内可见标志（PackedByteArray）0=屏外，1=屏内
var _visible_flags: PackedByteArray = PackedByteArray()

var _offscreen_sep_counter: int = 0
var _external_movers: Dictionary = {}  # entity_id → true：移动由外部系统（BossManager）接管，_gd_update_movement 跳过
var _cs_node: Node = null

## ADR-A4：精英寻路。W1–14 杂鱼永远 Boid；W15+ Elite/Boss 按需挂 NavigationAgent2D，
## 并发寻路上限 MAX_PATHFINDING_ENEMIES=20，超出降级 Boid（P-S5-AI-01）。
const MAX_PATHFINDING_ENEMIES: int = 20
const ELITE_SPEED: float = 90.0

## 敌人原型（enemy_type 存于 SoA[+6]）。S6 内容：基础/快速/护甲/精英/Boss
enum Type { BASIC = 0, FAST = 1, ARMORED = 2, ELITE = 3, MINIBOSS = 4, BOSS = 5 }
## 各原型数值：纯数据驱动，唯一来源 data/enemies.json（游戏设计器「敌人」面板维护）
## get() 的单值默认仅为防崩溃，非内容副本
var _SPEED: Dictionary = {}        # type → 移动速度
var _ARMOR: Dictionary = {}        # type → 护甲
var _RENDER_SIZE: Dictionary = {}  # type → 渲染直径
var _RENDER_COLOR: Dictionary = {} # type → 渲染颜色

const ARCHETYPE_JSON: String = "res://data/enemies.json"

## 数据驱动：从 data/enemies.json 加载原型数值（速度/护甲/尺寸/颜色）
func _load_json_archetypes() -> void:
	if not FileAccess.file_exists(ARCHETYPE_JSON):
		push_error("EnemyManager: 缺失 res://data/enemies.json（敌人原型数值）")
		return
	var data = JSON.parse_string(FileAccess.get_file_as_string(ARCHETYPE_JSON))
	if not (data is Dictionary):
		push_error("EnemyManager: enemies.json 格式错误")
		return
	for k in data:
		var t: int = int(k)
		var d: Dictionary = data[k]
		_SPEED[t]       = float(d.get("speed", 80.0))
		_ARMOR[t]       = float(d.get("armor", 0.0))
		_RENDER_SIZE[t] = float(d.get("size", 14.0))
		var c: Array = d.get("color", [0.9, 0.3, 0.3])
		_RENDER_COLOR[t] = Color(float(c[0]), float(c[1]), float(c[2]))

var _nav_region: NavigationRegion2D = null   # 程序化矩形导航区
var _pathfinders: Dictionary = {}            # entity_id → {"agent":NavigationAgent2D, "host":Node2D}
var _pathfinder_order: Array = []            # FIFO，用于超上限淘汰

func _ready() -> void:
	_load_json_archetypes()  # 数据驱动：覆盖原型数值（游戏设计器面板维护）
	_data.resize(MAX_ENEMIES * ENEMY_STRIDE)
	_data.fill(0.0)
	_visible_flags.resize(MAX_ENEMIES)
	_visible_flags.fill(0)

func _physics_process(delta: float) -> void:
	_offscreen_sep_counter += 1
	var do_sep: bool = (_offscreen_sep_counter >= OFFSCREEN_SEPARATION_INTERVAL)
	if do_sep:
		_offscreen_sep_counter = 0
	if not _cs_node:
		_gd_update_movement(delta)
	# ADR-A4：精英寻路移动（NavigationAgent2D 跟随），仅当存在 pathfinder 时执行
	if not _pathfinders.is_empty():
		_update_pathfinding_movement(delta)
	# S1: 移动后更新 SpatialGrid（碰撞检测依赖）
	_update_spatial_grid()

func _update_spatial_grid() -> void:
	for i in _active_count:
		var base: int = i * ENEMY_STRIDE
		var eid: int  = _slot_entity_map.get(i, -1)
		if eid >= 0:
			SpatialGrid.insert(eid, Vector2(_data[base + 0], _data[base + 1]))

# GDScript 回退：直线追玩家（C# 没就绪时使用）
func _gd_update_movement(delta: float) -> void:
	var player_pos: Vector2 = PlayerManager.get_position()
	var has_pf: bool = not _pathfinders.is_empty()  # 无精英时零额外开销
	var has_ext: bool = not _external_movers.is_empty()
	for i in _active_count:
		var eid_i: int = _slot_entity_map.get(i, -1)
		if has_pf and _pathfinders.has(eid_i):
			continue  # 寻路精英由 _update_pathfinding_movement 处理
		if has_ext and _external_movers.has(eid_i):
			continue  # Boss 等外部驱动实体由 BossManager 移动
		var base: int = i * ENEMY_STRIDE
		var ex: float = _data[base + 0]
		var ey: float = _data[base + 1]
		var spd: float = _SPEED.get(int(_data[base + 6]), 80.0)  # 按原型查速度
		var dx: float = player_pos.x - ex
		var dy: float = player_pos.y - ey
		var dist: float = sqrt(dx * dx + dy * dy)
		if dist > 1.0:
			_data[base + 2] = (dx / dist) * spd
			_data[base + 3] = (dy / dist) * spd
		_data[base + 0] += _data[base + 2] * delta
		_data[base + 1] += _data[base + 3] * delta

# ── 对外接口 ───────────────────────────────────────────────────
func spawn_enemy(pos: Vector2, hp: float, enemy_type: int = 0, has_pathfinding: bool = false) -> int:
	if _active_count >= MAX_ENEMIES:
		return -1
	var entity_id: int = _next_entity_id
	_next_entity_id += 1
	var slot: int = _active_count
	var base: int = slot * ENEMY_STRIDE
	_data[base + 0] = pos.x
	_data[base + 1] = pos.y
	_data[base + 2] = 0.0
	_data[base + 3] = 0.0
	_data[base + 4] = hp       # hp
	_data[base + 5] = hp       # hp_max
	_data[base + 6] = float(enemy_type)
	_data[base + 7] = 0.0      # state: 0=alive
	_entity_index_map[entity_id] = slot
	_slot_entity_map[slot] = entity_id
	_active_count += 1
	# ADR-A4：W15+ Elite 在并发上限内挂寻路（超限降级 Boid）
	if has_pathfinding and _pathfinders.size() < MAX_PATHFINDING_ENEMIES:
		_register_pathfinder(entity_id, pos)
	return entity_id

# ── ADR-A4 精英寻路子系统 ──────────────────────────────────────
func _ensure_nav_region() -> void:
	if _nav_region != null:
		return
	_nav_region = NavigationRegion2D.new()
	var poly := NavigationPolygon.new()
	# 覆盖竞技场的单个凸矩形导航多边形（无障碍占位；P-S5-AI-01 仅测寻路开销）
	var ext: float = 4000.0
	poly.vertices = PackedVector2Array([
		Vector2(-ext, -ext), Vector2(-ext, ext), Vector2(ext, ext), Vector2(ext, -ext),
	])
	poly.add_polygon(PackedInt32Array([0, 1, 2, 3]))
	_nav_region.navigation_polygon = poly
	add_child(_nav_region)

func _register_pathfinder(entity_id: int, pos: Vector2) -> void:
	_ensure_nav_region()
	var host := Node2D.new()
	host.position = pos
	add_child(host)
	var agent := NavigationAgent2D.new()
	agent.path_desired_distance = 8.0
	agent.target_desired_distance = 8.0
	agent.avoidance_enabled = false  # 与 SoA Boid 分离力共存，避免双重避障
	host.add_child(agent)
	_pathfinders[entity_id] = {"agent": agent, "host": host}
	_pathfinder_order.append(entity_id)

func _unregister_pathfinder(entity_id: int) -> void:
	var pf: Dictionary = _pathfinders.get(entity_id, {})
	if pf.is_empty():
		return
	(pf["host"] as Node).queue_free()  # agent 是 host 子节点，随之释放
	_pathfinders.erase(entity_id)
	_pathfinder_order.erase(entity_id)

## 寻路精英移动：host 同步到 SoA 坐标，agent 朝玩家求下一路径点并转向
func _update_pathfinding_movement(delta: float) -> void:
	var player_pos: Vector2 = PlayerManager.get_position()
	for eid in _pathfinders:
		var slot: int = _entity_index_map.get(eid, -1)
		if slot < 0:
			continue
		var base: int = slot * ENEMY_STRIDE
		var pos: Vector2 = Vector2(_data[base + 0], _data[base + 1])
		var pf: Dictionary = _pathfinders[eid]
		var agent: NavigationAgent2D = pf["agent"]
		(pf["host"] as Node2D).global_position = pos
		agent.target_position = player_pos
		var nxt: Vector2 = agent.get_next_path_position()
		var dir: Vector2 = nxt - pos
		if dir.length() > 1.0:
			dir = dir.normalized()
			_data[base + 2] = dir.x * ELITE_SPEED
			_data[base + 3] = dir.y * ELITE_SPEED
		_data[base + 0] += _data[base + 2] * delta
		_data[base + 1] += _data[base + 3] * delta

func get_pathfinder_count() -> int:
	return _pathfinders.size()

func despawn_enemy(entity_id: int) -> void:
	var slot: int = _entity_index_map.get(entity_id, -1)
	if slot < 0:
		return
	StatusManager.remove_all_for_entity(entity_id)
	SpatialGrid.remove_entity(entity_id)
	if _pathfinders.has(entity_id):
		_unregister_pathfinder(entity_id)
	var last: int = _active_count - 1
	if slot != last:
		var base_slot: int = slot * ENEMY_STRIDE
		var base_last: int = last * ENEMY_STRIDE
		for s in ENEMY_STRIDE:
			_data[base_slot + s] = _data[base_last + s]
		var moved_id: int = _slot_entity_map[last]
		_entity_index_map[moved_id] = slot
		_slot_entity_map[slot] = moved_id
	_entity_index_map.erase(entity_id)
	_slot_entity_map.erase(last)
	_active_count -= 1

## 获取指定 entity_id 的世界坐标（找不到返回 Vector2(-9999,-9999)）
func get_pos_by_id(entity_id: int) -> Vector2:
	var slot: int = _entity_index_map.get(entity_id, -1)
	if slot < 0:
		return Vector2(-9999.0, -9999.0)
	var base: int = slot * ENEMY_STRIDE
	return Vector2(_data[base + 0], _data[base + 1])

## 外部系统（BossManager）接管移动：注册后 _gd_update_movement 跳过该实体
func register_external_mover(entity_id: int) -> void:
	_external_movers[entity_id] = true

func unregister_external_mover(entity_id: int) -> void:
	_external_movers.erase(entity_id)

## 外部系统写回实体坐标（BossManager 驱动移动/冲刺用）
func set_entity_pos(entity_id: int, pos: Vector2) -> void:
	var slot: int = _entity_index_map.get(entity_id, -1)
	if slot < 0:
		return
	var base: int = slot * ENEMY_STRIDE
	_data[base + 0] = pos.x
	_data[base + 1] = pos.y

## S1 完整伤害接口：从 DamageContextPool 取 context，计算公式后扣 HP
## 公式（S1 简化）：final_dmg = base_damage * mult
## S4 起补充 resistance / armor（参见 combat_mechanics_depth.md §4）
func apply_damage_from_context(entity_id: int, ctx_id: int) -> bool:
	var ctx: DamageContext = DamageContextPool.get_context(ctx_id)
	if ctx == null:
		return false
	var final_dmg: float = ctx.calc_damage()
	return apply_damage(entity_id, final_dmg, ctx.owner_id, true)

## 直接扣血接口；source_id 用于击杀归属，record_dps 控制是否计入 DPS
func apply_damage(entity_id: int, damage: float, source_id: int = -1, record_dps: bool = true) -> bool:
	var slot: int = _entity_index_map.get(entity_id, -1)
	if slot < 0:
		return false
	var combo_stacks: int = StatusManager.get_stacks(entity_id, StatusID.COMBO_MARK)
	var combo_bonus: float = 1.0 + float(combo_stacks) * 0.02  # 每层 +2% 伤害
	var base: int = slot * ENEMY_STRIDE
	var armor: float = _ARMOR.get(int(_data[base + 6]), 0.0)  # 原型护甲，扣减后保底 1
	var final_dmg: float = maxf(1.0, damage * combo_bonus - armor)
	_data[base + 4] -= final_dmg
	if record_dps:
		DpsTracker.record_damage(final_dmg)
	if _data[base + 4] <= 0.0:
		StatusManager.remove_all_for_entity(entity_id)
		EventBus.emit(EventID.ENEMY_KILLED, {"entity_id": entity_id, "killer_id": source_id})
		despawn_enemy(entity_id)
		return true
	return false

func has_entity(entity_id: int) -> bool:
	return _entity_index_map.has(entity_id)

## 返回敌人血量百分比（权威来源：arch §5.2 get_hp_percent）
func get_hp_percent(entity_id: int) -> float:
	var slot: int = _entity_index_map.get(entity_id, -1)
	if slot < 0:
		return 0.0
	var base: int = slot * ENEMY_STRIDE
	var hp:     float = _data[base + 4]  # slot +4: hp
	var hp_max: float = _data[base + 5]  # slot +5: hp_max
	return hp / max(hp_max, 0.001)

func get_active_count() -> int:
	return _active_count

## 渲染：活跃敌人 SoA 位置 → MultiMesh（按原型设尺寸/颜色，Boss 大且醒目）
func sync_multimesh(mm: MultiMesh) -> void:
	if mm == null:
		return
	var n: int = min(_active_count, mm.instance_count)
	mm.visible_instance_count = n
	for i in n:
		var base: int = i * ENEMY_STRIDE
		var etype: int = int(_data[base + 6])
		var sz: float = _RENDER_SIZE.get(etype, 14.0)
		mm.set_instance_transform_2d(i, Transform2D(
			0.0, Vector2(sz, sz), 0.0,
			Vector2(_data[base + 0], _data[base + 1])))
		mm.set_instance_color(i, _RENDER_COLOR.get(etype, Color(0.9, 0.3, 0.3)))

## 返回最近敌人位置
func get_nearest_pos(origin: Vector2, max_dist: float = 9999.0) -> Vector2:
	var best_sq: float = max_dist * max_dist
	var best:    Vector2 = origin
	for i in _active_count:
		var base: int = i * ENEMY_STRIDE
		var dx: float = _data[base + 0] - origin.x
		var dy: float = _data[base + 1] - origin.y
		var dsq: float = dx * dx + dy * dy
		if dsq < best_sq:
			best_sq = dsq
			best = Vector2(_data[base + 0], _data[base + 1])
	return best

## 建立敌人位置快照（BulletManager homing 共用）
func fill_pos_snapshot(out_arr: PackedVector2Array) -> void:
	out_arr.resize(_active_count)
	for i in _active_count:
		var base: int = i * ENEMY_STRIDE
		out_arr[i] = Vector2(_data[base + 0], _data[base + 1])

## 同步 Node2D 显示位置（屏内 dirty sync）
func sync_node_positions(enemy_nodes: Array) -> void:
	for i in min(_active_count, enemy_nodes.size()):
		if _visible_flags[i] == int(1):
			var base: int = i * ENEMY_STRIDE
			enemy_nodes[i].position = Vector2(_data[base + 0], _data[base + 1])

func reset() -> void:
	_active_count = 0
	_data.fill(0.0)
	_visible_flags.fill(0)
	_entity_index_map.clear()
	_slot_entity_map.clear()
	_external_movers.clear()
	_next_entity_id = 1
	# ADR-A4：释放所有寻路精英节点
	for eid in _pathfinders.keys():
		(_pathfinders[eid]["host"] as Node).queue_free()
	_pathfinders.clear()
	_pathfinder_order.clear()
