class_name BaseUnit
extends CharacterBody3D

## 플레이어 유닛 기본 클래스 (RTS 식 이동, 몬스터 자동 감지 및 공격 수행)

@export var unit_data: UnitData

var is_selected: bool = false

# 전투 관련 상태
var _target_monster: Node3D = null
var _attack_timer: float = 0.0

# 자동 탐색 범위 (맵이 소형화되었으므로 15.0m로 축소)
@export var detection_range: float = 15.0
var _auto_target_cooldown: float = 0.0


# 노드 캐싱
@onready var _nav_agent: NavigationAgent3D = $NavigationAgent3D
@onready var _selection_ring: MeshInstance3D = $SelectionRing
@onready var _visual_mesh: MeshInstance3D = $VisualMesh

# 3D 모델 및 애니메이션 연동 변수 (정적 타이핑 캐싱)
var _model_instance: Node3D = null
var _model_anim_player: AnimationPlayer = null
var _last_anim_state: String = ""

func _ready() -> void:
	# 유닛 그룹에 등록
	add_to_group("units")
	
	# 네비게이션 파라미터 조율
	_nav_agent.path_desired_distance = 0.5
	_nav_agent.target_desired_distance = 0.5
	
	# 선택 상태에 맞춤 설정
	deselect()
	
	# 유닛 능력치에 따른 외형(색상) 적용
	if unit_data:
		_apply_unit_style()
		
	# 자식 노드 중 3D 모델(striker, guardian 등) 노드 자동 탐색 및 초기화
	var model_names: Array[String] = ["striker", "guardian"]
	var detected_model_node: Node3D = null
	var detected_model_name: String = ""
	
	for m_name: String in model_names:
		var node: Node3D = get_node_or_null(m_name) as Node3D
		if node:
			detected_model_node = node
			detected_model_name = m_name
			break
			
	if detected_model_node:
		_model_instance = detected_model_node
		
		if detected_model_name == "striker":
			_apply_striker_skin(_model_instance)
			_attach_weapon()
		elif detected_model_name == "guardian":
			_apply_guardian_skin(_model_instance)
			
		_model_anim_player = _find_animation_player(_model_instance)
		if _model_anim_player:
			print("BaseUnit: Linked pre-configured model & AnimationPlayer: ", detected_model_name)
			
			# 모델명_animations.tres 라이브러리를 동적으로 로드하여 등록
			var anim_lib_path: String = "res://assets/models/units/" + detected_model_name + "/" + detected_model_name + "_animations.tres"
			if ResourceLoader.exists(anim_lib_path):
				var lib: AnimationLibrary = load(anim_lib_path) as AnimationLibrary
				if lib:
					_model_anim_player.add_animation_library(detected_model_name, lib)
					print("BaseUnit: Loaded and registered ", detected_model_name, " animations library successfully.")



func _physics_process(delta: float) -> void:
	if GameManager.is_game_over:
		velocity = Vector3.ZERO
		return
		
	# 공격 타이머 및 자동 타겟팅 쿨타임 차감
	if _attack_timer > 0.0:
		_attack_timer -= delta
	if _auto_target_cooldown > 0.0:
		_auto_target_cooldown -= delta
		
	# 1. 공격 대상이 유효한지 검증 (사망했거나 삭제되었는지 체크)
	if _target_monster and (not is_instance_valid(_target_monster) or _target_monster.get("is_dead")):
		_target_monster = null
		
	# 2. 적 자동 탐색 (타겟이 없고 수동 강제 이동 방지 쿨다운이 완료된 경우)
	if not _target_monster and _auto_target_cooldown <= 0.0:
		_find_nearest_target()
		
	# 3. 상태에 따른 AI 동작 분기
	if _target_monster:
		_process_attack_logic(delta)
	else:
		_process_normal_logic(delta)
		
	# 4. 3D 애니메이션 재생 업데이트
	_update_animations()

## 1. 공격 타겟이 존재할 때의 동작 로직
func _process_attack_logic(_delta: float) -> void:
	if not _target_monster:
		return
		
	var distance_to_target: float = global_position.distance_to(_target_monster.global_position)
	
	# 사거리 밖인 경우 추격 이동
	if distance_to_target > unit_data.attack_range:
		_nav_agent.target_position = _target_monster.global_position
		_navigate_to_target(_delta)
	# 사거리 이내인 경우 정지 및 공격 수행
	else:
		velocity = Vector3.ZERO
		# 타겟 몬스터를 바라보도록 회전
		_look_at_target(_target_monster.global_position)
		_try_attack()

## 2. 일반 대기 및 이동 상태의 로직
func _process_normal_logic(_delta: float) -> void:
	# 이동 명령을 수행 중인 경우
	if not _nav_agent.is_navigation_finished():
		_navigate_to_target(_delta)
	else:
		velocity = Vector3.ZERO

## 네비게이션 타겟 방향으로 캐릭터 이동
func _navigate_to_target(_delta: float) -> void:
	if _nav_agent.is_navigation_finished():
		velocity = Vector3.ZERO
		return
		
	var next_path_pos: Vector3 = _nav_agent.get_next_path_position()
	var current_pos: Vector3 = global_position
	var new_velocity: Vector3 = (next_path_pos - current_pos).normalized() * unit_data.move_speed
	
	# Y축 속도는 고정하고 평면 속도만 적용
	velocity = Vector3(new_velocity.x, 0.0, new_velocity.z)
	move_and_slide()
	
	# 이동 방향 바라보도록 회전
	if current_pos.distance_to(next_path_pos) > 0.2:
		_look_at_target(next_path_pos)

## 타겟을 부드럽게(또는 수평 기준) 바라보게 회전
func _look_at_target(target_pos: Vector3) -> void:
	var look_target: Vector3 = Vector3(target_pos.x, global_position.y, target_pos.z)
	if global_position.distance_to(look_target) > 0.1:
		look_at(look_target, Vector3.UP)

## 범위 내 가장 가까운 살아있는 몬스터 탐색
func _find_nearest_target() -> void:
	var monsters: Array[Node] = get_tree().get_nodes_in_group("monsters")
	var nearest_monster: Node3D = null
	var min_distance: float = detection_range
	
	for monster: Node in monsters:
		if is_instance_valid(monster) and monster is Node3D:
			var m_3d: Node3D = monster as Node3D
			# 몬스터의 사망 여부 검사
			if m_3d.get("is_dead") == true:
				continue
				
			var dist: float = global_position.distance_to(m_3d.global_position)
			if dist < min_distance:
				min_distance = dist
				nearest_monster = m_3d
				
	if nearest_monster:
		_target_monster = nearest_monster

## 타겟에게 피격 가함
func _try_attack() -> void:
	if _attack_timer > 0.0 or not _target_monster:
		return
		
	if unit_data.is_ranged:
		# 원거리 유닛: 유도 발사체(Projectile) 생성하여 발사
		var projectile_script = preload("res://src/entities/units/projectile.gd")
		var proj: Node3D = projectile_script.new() as Node3D
		proj.set("damage", unit_data.damage)
		proj.set("target", _target_monster)
		proj.set("color", unit_data.unit_color)
		
		# 유닛 중심보다 높은 곳(어깨 높이)에서 발사 처리
		proj.global_position = global_position + Vector3(0.0, 0.7, 0.0)
		get_tree().current_scene.add_child(proj)
	else:
		# 근접 유닛: 즉발 공격 및 피해 적용
		if _target_monster.has_method("take_damage"):
			_target_monster.call("take_damage", unit_data.damage)
		
	# 쿨타임 타이머 작동
	_attack_timer = unit_data.cooldown
	
	# 공격 비주얼 연출
	_show_attack_visual()

## 공격 관련 비주얼 이펙트 연출
func _show_attack_visual() -> void:
	# 원거리 타격 시 빔 또는 스파크 이펙트 임시 렌더
	if unit_data.is_ranged and _target_monster:
		print("Unit ", name, " fired a projectile/spell at ", _target_monster.name)
	else:
		print("Unit ", name, " strikes ", _target_monster.name)

## 외부에서 호출하는 수동 이동 명령
func move_to(target_pos: Vector3) -> void:
	_target_monster = null # 이동 명령 시 강제 공격 해제
	_auto_target_cooldown = 0.5 # 수동 강제 이동 시 0.5초 동안 자동 공격 타겟 지정을 비활성화하여 피신을 허용
	_nav_agent.target_position = target_pos

## 외부에서 호출하는 특정 몬스터 강제 공격 지정
func attack_target(monster: Node3D) -> void:
	if is_instance_valid(monster) and not monster.get("is_dead"):
		_target_monster = monster
		_nav_agent.target_position = monster.global_position

## 유닛 데이터 스킨 색상 적용
func _apply_unit_style() -> void:
	# VisualMesh의 material에 접근하여 색상 덧칠
	if _visual_mesh and _visual_mesh.mesh:
		# Material override가 있는지 확인하고 동적 생성
		var mat: StandardMaterial3D = StandardMaterial3D.new()
		mat.albedo_color = unit_data.unit_color
		mat.roughness = 0.4
		_visual_mesh.material_override = mat

## 유닛 선택 시 하이라이팅
func select() -> void:
	is_selected = true
	if _selection_ring:
		_selection_ring.visible = true

## 유닛 선택 해제
func deselect() -> void:
	is_selected = false
	if _selection_ring:
		_selection_ring.visible = false


## FBX 모델의 자식 메쉬에 striker_0.png 텍스처를 씌우는 재귀 함수
func _apply_striker_skin(node: Node) -> void:
	if node is MeshInstance3D:
		var mesh_inst: MeshInstance3D = node as MeshInstance3D
		var texture: Texture2D = load("res://assets/models/units/striker_0.png") as Texture2D
		if texture:
			var mat: StandardMaterial3D = StandardMaterial3D.new()
			mat.albedo_texture = texture
			mat.roughness = 0.5
			mesh_inst.material_override = mat
	for child: Node in node.get_children():
		_apply_striker_skin(child)

## FBX 모델의 자식 메쉬에 guardian_0.png 텍스처를 씌우는 재귀 함수
func _apply_guardian_skin(node: Node) -> void:
	if node is MeshInstance3D:
		var mesh_inst: MeshInstance3D = node as MeshInstance3D
		var texture: Texture2D = load("res://assets/models/units/guardian/guardian_0.png") as Texture2D
		if texture:
			var mat: StandardMaterial3D = StandardMaterial3D.new()
			mat.albedo_texture = texture
			mat.roughness = 0.5
			mesh_inst.material_override = mat
	for child: Node in node.get_children():
		_apply_guardian_skin(child)

## 모델 자식 노드 중 AnimationPlayer 탐색
func _find_animation_player(node: Node) -> AnimationPlayer:
	if node is AnimationPlayer:
		return node as AnimationPlayer
	for child: Node in node.get_children():
		var res: AnimationPlayer = _find_animation_player(child)
		if res:
			return res
	return null

## 유닛의 오른손에 지정된 무기 모델을 런타임 장착
func _attach_weapon() -> void:
	if not _model_instance:
		return
		
	# 스켈레톤 노드 탐색
	var skeleton: Skeleton3D = _find_skeleton(_model_instance)
	if not skeleton:
		return
		
	# 오른손 본("RightHand")이 존재하는지 확인
	var bone_idx: int = skeleton.find_bone("RightHand")
	if bone_idx == -1:
		print("BaseUnit: RightHand bone not found in skeleton.")
		return
		
	# 이미 장착된 무기가 있는지 확인하여 제거 (중복 장착 방지)
	var existing_attachment: Node = skeleton.get_node_or_null("RightHandAttachment")
	if existing_attachment:
		existing_attachment.queue_free()
		
	# 1. BoneAttachment3D 노드 동적 생성 및 본 동기화 설정
	var bone_attachment: BoneAttachment3D = BoneAttachment3D.new()
	bone_attachment.name = "RightHandAttachment"
	bone_attachment.bone_name = "RightHand"
	skeleton.add_child(bone_attachment)
	
	# 2. 무기 씬 인스턴스 생성 (azure_blade.fbx)
	var weapon_scene_path: String = "res://assets/models/items/weapons/azure_blade.fbx"
	if ResourceLoader.exists(weapon_scene_path):
		var weapon_scene: PackedScene = load(weapon_scene_path) as PackedScene
		if weapon_scene:
			var weapon_inst: Node3D = weapon_scene.instantiate() as Node3D
			
			# 검의 로컬 스케일, 회전 및 오프셋 보정 (손안에 쏙 들어가도록)
			# FBX 원본 메쉬 크기(AABB 약 0.008)가 매우 작으므로 150배 확대하여 캐릭터 스펙에 맞춤
			weapon_inst.scale = Vector3(1.0, 1.0, 1.0) # 1배 확대 보정
			weapon_inst.rotation_degrees = Vector3(90.0, 0.0, 90.0) # 검날 방향 보정
			weapon_inst.position = Vector3(0.0, 0.0, 0.0)
			
			# 무기 텍스처(azure_blade_0.png) 매핑
			_apply_weapon_skin(weapon_inst)
			
			bone_attachment.add_child(weapon_inst)
			print("BaseUnit: Attached azure_blade to RightHand bone successfully.")

## 무기 메쉬에 azure_blade_0.png 텍스처를 씌우는 재귀 함수
func _apply_weapon_skin(node: Node) -> void:
	if node is MeshInstance3D:
		var mesh_inst: MeshInstance3D = node as MeshInstance3D
		var texture: Texture2D = load("res://assets/models/items/weapons/azure_blade_0.png") as Texture2D
		if texture:
			var mat: StandardMaterial3D = StandardMaterial3D.new()
			mat.albedo_texture = texture
			mat.roughness = 0.4
			mesh_inst.material_override = mat
	for child: Node in node.get_children():
		_apply_weapon_skin(child)

## 헬퍼: 자식 노드 중 Skeleton3D 탐색
func _find_skeleton(node: Node) -> Skeleton3D:
	if node is Skeleton3D:
		return node as Skeleton3D
	for child: Node in node.get_children():
		var res: Skeleton3D = _find_skeleton(child)
		if res:
			return res
	return null

## 상태(대기, 이동, 공격)별 3D 애니메이션 트랙 제어
func _update_animations() -> void:
	if not _model_anim_player:
		return
		
	var model_name: String = ""
	if _model_instance:
		model_name = _model_instance.name.to_lower()
		
	var library_prefix: String = model_name + "/"
	var has_custom_anims: bool = _model_anim_player.has_animation(library_prefix + "idle")
	
	if has_custom_anims:
		# 1. 공격 중 상태: 공격 타이머 차감 후 쿨타임 구간의 초입 0.4초간은 공격 애니메이션 우선 재생
		if _attack_timer > (unit_data.cooldown - 0.4):
			if _last_anim_state != "attack":
				_last_anim_state = "attack"
				_model_anim_player.play(library_prefix + "attack", -1, 1.5)
		# 2. 이동 중 상태: 속도 벡터가 존재할 때 (이동 상태)
		elif velocity.length() > 0.1:
			if _last_anim_state != "move":
				_last_anim_state = "move"
				_model_anim_player.play(library_prefix + "run", -1, 1.2)
		# 3. 대기(Idle) 상태: 정지 상태일 때 대기 애니메이션 재생
		else:
			if _last_anim_state != "idle":
				_last_anim_state = "idle"
				_model_anim_player.play(library_prefix + "idle", -1, 1.0)
	else:
		# Fallback: 기존 Jump 애니메이션 처리
		var anim_name: String = "Armature|Armature|Basic_Jump|baselayer"
		if not _model_anim_player.has_animation(anim_name):
			return
			
		if _attack_timer > (unit_data.cooldown - 0.4):
			if _last_anim_state != "attack":
				_last_anim_state = "attack"
				_model_anim_player.play(anim_name, -1, 2.5)
		elif velocity.length() > 0.1:
			if _last_anim_state != "move":
				_last_anim_state = "move"
				_model_anim_player.play(anim_name, -1, 1.5)
		else:
			if _last_anim_state != "idle":
				_last_anim_state = "idle"
				_model_anim_player.stop()
