# Enemy.gd
# Steuert den Gegner: KI-Bewegung zum Spieler, Angriff, HP, Zielanzeige
extends CharacterBody3D

signal enemy_died(spawn_position: Vector3, xp_reward: int)
signal enemy_dropped_loot(loot: Dictionary, world_position: Vector3)

const SPEED = 3.0
const PATROL_SPEED = 1.5
const GRAVITY = 9.8
const ATTACK_RANGE = 1.5
const ATTACK_COOLDOWN = 2.0
const AGGRO_RANGE = 8.0  # Entfernung ab der der Gegner angreift
const PATROL_RADIUS = 5.0  # Radius um Spawn-Position für Patrol
const PATROL_WAIT_TIME = 2.0  # Wartezeit am Patrol-Punkt

# Level-Differenz Konstanten
const LEVEL_DIFF_DAMAGE_MOD = 0.1  # 10% mehr/weniger Schaden pro Level-Differenz
const MAX_LEVEL_DIFF_MOD = 0.5  # Maximal 50% Modifikation

enum State { PATROL, CHASE, ATTACK }

# Stats-System
@export var level: int = 1
@export var base_strength: int = 8
@export var base_stamina: int = 10
@export var base_armor: int = 5  # Rüstung reduziert Nahkampfschaden

# Berechnete Stats
var strength: int = 8
var stamina: int = 10
var armor: int = 5
var max_hp: int = 100
var current_hp: int = 100
var attack_damage: int = 5

# XP-Belohnung (skaliert mit Level)
var xp_reward: int = 25

# Loot-System
@export var loot_table: LootTable

var target = null  # Spieler-Referenz (wird von World gesetzt)
var can_attack = true
var spawn_position: Vector3  # Ursprüngliche Spawn-Position
var current_state = State.PATROL
var patrol_target: Vector3  # Aktuelles Patrol-Ziel
var is_waiting = false  # Ob Gegner am Patrol-Punkt wartet

# Animation System
const ANIMATION_FILES = {
	"walk": "res://assets/animations/Walking.fbx",
	"autoattack": "res://assets/animations/Autoattack.fbx",
	"die": "res://assets/animations/Dying Backwards.fbx",
}
var anim_player: AnimationPlayer = null
var current_anim: String = ""

@onready var health_label = $HealthLabel

func _ready():
	_calculate_stats()
	current_hp = max_hp
	health_label.visible = false
	_update_label()
	spawn_position = global_position
	_pick_new_patrol_target()
	_setup_animations()

# Animationen laden
func _setup_animations():
	# Debug: Alle Kinder ausgeben um den Modell-Node zu finden
	print("Enemy Kinder: ")
	for child in get_children():
		print("  - ", child.name, " (", child.get_class(), ")")
	var model = get_node_or_null("EnemyModel")
	if model == null:
		print("Enemy: EnemyModel nicht gefunden!")
		return
	anim_player = _find_node_by_class(model, "AnimationPlayer")
	if anim_player == null:
		anim_player = AnimationPlayer.new()
		anim_player.name = "AnimationPlayer"
		model.add_child(anim_player)
	var lib: AnimationLibrary
	if anim_player.has_animation_library(""):
		lib = anim_player.get_animation_library("")
	else:
		lib = AnimationLibrary.new()
		anim_player.add_animation_library("", lib)
	for anim_id in ANIMATION_FILES:
		var scene = load(ANIMATION_FILES[anim_id]) as PackedScene
		if scene == null:
			print("Enemy: Animation nicht gefunden: ", ANIMATION_FILES[anim_id])
			continue
		var instance = scene.instantiate()
		var source_ap = _find_node_by_class(instance, "AnimationPlayer")
		if source_ap:
			var names = source_ap.get_animation_list()
			if names.size() > 0:
				var anim = source_ap.get_animation(names[0])
				if lib.has_animation(anim_id):
					lib.remove_animation(anim_id)
				lib.add_animation(anim_id, anim)
				print("Enemy: Animation geladen: ", anim_id)
		else:
			print("Enemy: Kein AnimationPlayer in ", ANIMATION_FILES[anim_id])
		instance.queue_free()
	print("Enemy: Verfügbare Animationen: ", anim_player.get_animation_list())
	# Debug: Modell-Hierarchie ausgeben
	_print_tree(model, 0)

func _print_tree(node: Node, depth: int):
	print("  ".repeat(depth), node.name, " (", node.get_class(), ")")
	for child in node.get_children():
		_print_tree(child, depth + 1)

func _find_node_by_class(node: Node, class_name_str: String) -> Node:
	for child in node.get_children():
		if child.get_class() == class_name_str:
			return child
		var result = _find_node_by_class(child, class_name_str)
		if result:
			return result
	return null

func _play_anim(anim_name: String):
	if anim_player == null:
		print("Enemy: anim_player ist null!")
		return
	if anim_name != current_anim:
		current_anim = anim_name
		if anim_name != "" and anim_player.has_animation(anim_name):
			anim_player.play(anim_name)
			print("Enemy: Spiele Animation: ", anim_name)
			# Debug: Track-Pfade und root_node ausgeben
			var anim = anim_player.get_animation(anim_name)
			print("  AnimPlayer root_node: ", anim_player.root_node)
			print("  AnimPlayer absoluter Pfad: ", anim_player.get_path())
			for t in range(mini(3, anim.get_track_count())):
				print("  Track ", t, ": ", anim.track_get_path(t))
		elif anim_name != "":
			print("Enemy: Animation nicht gefunden: ", anim_name, " | Verfügbar: ", anim_player.get_animation_list())
		else:
			anim_player.stop()

# Stats basierend auf Level berechnen
func _calculate_stats():
	var levels_gained = level - 1
	strength = base_strength + levels_gained * 2
	stamina = base_stamina + levels_gained * 3
	armor = base_armor + levels_gained * 2

	# HP = Stamina * 10
	max_hp = stamina * 10
	# Schaden = Stärke / 2
	attack_damage = int(strength * 0.5) + 2
	# XP = 25 * Level
	xp_reward = 25 * level

	print("Enemy Stats (Lv", level, ") - STR:", strength, " STA:", stamina, " ARM:", armor, " HP:", max_hp, " DMG:", attack_damage)

# Schaden mit Rüstung und Level-Differenz berechnen
func calculate_incoming_damage(raw_damage: int, attacker_level: int, is_melee: bool) -> int:
	var damage = float(raw_damage)

	# Rüstung reduziert nur Nahkampfschaden
	if is_melee:
		# Rüstungsreduktion: armor / (armor + 50) = Prozent Reduktion
		# Bei 5 Rüstung: 5/55 = ~9% Reduktion
		# Bei 20 Rüstung: 20/70 = ~29% Reduktion
		var armor_reduction = float(armor) / (float(armor) + 50.0)
		damage = damage * (1.0 - armor_reduction)

	# Level-Differenz Modifikator
	var level_diff = attacker_level - level
	var level_mod = clamp(level_diff * LEVEL_DIFF_DAMAGE_MOD, -MAX_LEVEL_DIFF_MOD, MAX_LEVEL_DIFF_MOD)
	damage = damage * (1.0 + level_mod)

	return maxi(1, int(damage))  # Mindestens 1 Schaden

# HP-Label Text aktualisieren
func _update_label():
	health_label.text = "Lv" + str(level) + "  " + str(current_hp) + "/" + str(max_hp)

# HP-Label anzeigen (wenn Gegner markiert wird)
func show_health():
	health_label.visible = true

# HP-Label verstecken (wenn Markierung aufgehoben wird)
func hide_health():
	health_label.visible = false

# Schaden nehmen und Label aktualisieren
func take_damage(amount):
	current_hp -= amount
	_update_label()
	# Aggro bei Schaden — sofort angreifen
	if current_state == State.PATROL:
		current_state = State.CHASE
		is_waiting = false
		print("Gegner wurde angegriffen und verfolgt den Spieler!")
	if current_hp <= 0:
		die()

# Schaden mit vollem Schadenssystem (Rüstung, Level-Differenz)
func take_damage_from(raw_damage: int, attacker_level: int, is_melee: bool = true):
	var final_damage = calculate_incoming_damage(raw_damage, attacker_level, is_melee)
	print("Eingehender Schaden: ", raw_damage, " -> ", final_damage, " (nach Rüstung/Level)")
	take_damage(final_damage)

# Gegner aus der Szene entfernen
func die():
	print("Gegner besiegt! +", xp_reward, " XP")
	# XP an Spieler geben
	if target and target.has_method("gain_xp"):
		target.gain_xp(xp_reward)
	# Loot generieren und droppen
	_drop_loot()
	enemy_died.emit(spawn_position, xp_reward)
	# Death-Animation abspielen, dann entfernen
	if anim_player and anim_player.has_animation("die"):
		_play_anim("die")
		# Kollision deaktivieren damit der Gegner nicht mehr im Weg ist
		set_physics_process(false)
		$CollisionShape3D.set_deferred("disabled", true)
		await anim_player.animation_finished
	queue_free()

# Loot generieren basierend auf LootTable
func _drop_loot():
	if loot_table == null:
		# Standard-Gold-Drop wenn keine LootTable zugewiesen
		var gold = randi_range(1, 3) * level
		var loot = {"gold": gold, "items": []}
		enemy_dropped_loot.emit(loot, global_position)
		return

	var loot = loot_table.generate_loot()
	# Gold mit Level skalieren
	loot["gold"] = loot["gold"] * level
	enemy_dropped_loot.emit(loot, global_position)

func _physics_process(delta):
	if not is_on_floor():
		velocity.y -= GRAVITY * delta

	if target == null:
		# Ohne Spieler-Referenz nur patrouillieren
		_do_patrol()
		move_and_slide()
		return

	# Prüfe Distanz zum Spieler für Aggro
	var distance_to_player = global_position.distance_to(target.global_position)

	# State-Wechsel basierend auf Distanz
	match current_state:
		State.PATROL:
			if distance_to_player <= AGGRO_RANGE:
				current_state = State.CHASE
				print("Gegner hat Spieler entdeckt!")
			else:
				_do_patrol()
		State.CHASE:
			if distance_to_player <= ATTACK_RANGE:
				current_state = State.ATTACK
			else:
				_chase_player()
		State.ATTACK:
			if distance_to_player > ATTACK_RANGE:
				current_state = State.CHASE
			else:
				velocity.x = 0
				velocity.z = 0
				_play_anim("")  # Idle beim Angriff
				if can_attack:
					_attack()

	move_and_slide()

# Neues Patrol-Ziel in der Nähe der Spawn-Position wählen
func _pick_new_patrol_target():
	var angle = randf() * TAU  # Zufälliger Winkel
	var distance = randf_range(2.0, PATROL_RADIUS)
	patrol_target = spawn_position + Vector3(cos(angle) * distance, 0, sin(angle) * distance)

# Patrol-Verhalten: Zufällig herumlaufen
func _do_patrol():
	if is_waiting:
		return

	var distance_to_patrol = global_position.distance_to(patrol_target)

	if distance_to_patrol <= 0.5:
		# Am Ziel angekommen, warten und neues Ziel wählen
		velocity.x = 0
		velocity.z = 0
		_play_anim("")
		_wait_at_patrol_point()
	else:
		# Zum Patrol-Ziel laufen
		var direction = (patrol_target - global_position)
		direction.y = 0
		direction = direction.normalized()
		velocity.x = direction.x * PATROL_SPEED
		velocity.z = direction.z * PATROL_SPEED
		_play_anim("walk")
		look_at(Vector3(patrol_target.x, global_position.y, patrol_target.z))

# Am Patrol-Punkt warten
func _wait_at_patrol_point():
	is_waiting = true
	await get_tree().create_timer(PATROL_WAIT_TIME).timeout
	is_waiting = false
	_pick_new_patrol_target()

# Spieler verfolgen
func _chase_player():
	_play_anim("walk")
	var direction = (target.global_position - global_position)
	direction.y = 0
	direction = direction.normalized()
	velocity.x = direction.x * SPEED
	velocity.z = direction.z * SPEED
	look_at(Vector3(target.global_position.x, global_position.y, target.global_position.z))

# Angriff mit Cooldown
func _attack():
	can_attack = false
	_play_anim("autoattack")
	# Gegner verwendet auch das Schadenssystem mit Level-Differenz
	if target.has_method("take_damage_from"):
		target.take_damage_from(attack_damage, level, true)
	else:
		target.take_damage(attack_damage)
	print("Gegner (Lv", level, ") greift an: ", attack_damage, " Schaden")
	await get_tree().create_timer(ATTACK_COOLDOWN).timeout
	can_attack = true
