# Enemy.gd
# ─────────────────────────────────────────────────────────────────────────────
# Gegner-KI – State Machine mit NavigationAgent3D
#
# Zustände:
#   IDLE      → wartet bis Spieler in detection_range kommt
#   CHASING   → läuft via NavMesh zum Spieler
#   ATTACKING → steht, dreht sich zum Spieler, greift in attack_speed-Intervallen an
#   DEAD      → Kollision deaktiviert, Node wird nach kurzer Verzögerung entfernt
#
# Signale:
#   enemy_died(spawn_position, xp_reward)        → World.gd → Respawn + XP
#   enemy_dropped_loot(loot, world_pos)          → World.gd → Spieler → LootWindow
# ─────────────────────────────────────────────────────────────────────────────
extends CharacterBody3D

# ═══════════════════════════════════════════════════════════════
# SIGNALE
# ═══════════════════════════════════════════════════════════════

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

# ═══════════════════════════════════════════════════════════════
# STATS
# ═══════════════════════════════════════════════════════════════

@export var max_hp: int = 50
@export var min_damage: int = 3
@export var max_damage: int = 7
@export var attack_range: float = 2.0
@export var attack_speed: float = 2.0   # Sekunden zwischen Angriffen
@export var move_speed: float = 5.5
@export var xp_reward: int = 20
@export var mob_level: int = 1
@export var detection_range: float = 15.0
@export var leash_range: float = 30.0    # Max Entfernung vom Spawn bevor Aggro verloren geht
@export var loot_table: LootTable = null

var current_hp: int
var target = null  # Spieler

# ═══════════════════════════════════════════════════════════════
# ZUSTAND
# ═══════════════════════════════════════════════════════════════

enum State { IDLE, PATROL, CHASING, ATTACKING, DEAD }
var state: State = State.IDLE

var attack_cooldown: float = 0.0
var is_dead: bool = false

# Debuffs
var _slow_timer: float = 0.0
var _slow_factor: float = 1.0  # 1.0 = normal, 0.5 = 50% langsamer

# Pull (Zornfesseln)
var _pull_target:   Node3D = null
var _pull_timer:    float  = 0.0
const PULL_SPEED:   float  = 18.0

# Patrol
@export var patrol_radius: float = 8.0
@export var patrol_speed: float = 1.5    # Laufgeschwindigkeit beim Patrouillieren
var spawn_position: Vector3 = Vector3.ZERO
var patrol_target: Vector3 = Vector3.ZERO
var patrol_wait_timer: float = 0.0

const GRAVITY = 9.8

# ═══════════════════════════════════════════════════════════════
# ANIMATION
# ═══════════════════════════════════════════════════════════════

const ANIM_IDLE         = "res://assets/Goblin+Animation/idle.fbx"
const ANIM_WALK         = "res://assets/Goblin+Animation/walking.fbx"
const ANIM_RUN          = "res://assets/Goblin+Animation/standard run.fbx"
const ANIM_AUTOATTACK   = "res://assets/Goblin+Animation/attack.fbx"
const ANIM_DEATH        = "res://assets/Goblin+Animation/die.fbx"
const ANIM_TURN_LEFT    = "res://assets/Goblin+Animation/left turn 90.fbx"
const ANIM_TURN_RIGHT   = "res://assets/Goblin+Animation/right turn 90.fbx"

var anim_player: AnimationPlayer = null
var current_anim: String = ""
var is_turning: bool = false

# ═══════════════════════════════════════════════════════════════
# NODE-REFERENZEN
# ═══════════════════════════════════════════════════════════════

@onready var nav_agent: NavigationAgent3D = $NavigationAgent3D
@onready var health_label: Label3D = $HealthDisplay/Label3D
@onready var model: Node3D = $Model

# ═══════════════════════════════════════════════════════════════
# LEVEL-SKALIERUNG
# ═══════════════════════════════════════════════════════════════

func _apply_level_scaling():
	if mob_level <= 1:
		return
	var lvl = mob_level - 1
	max_hp = int(max_hp * (1.0 + lvl * 0.3))        # +30% HP pro Level
	min_damage = int(min_damage * (1.0 + lvl * 0.2)) # +20% Schaden pro Level
	max_damage = int(max_damage * (1.0 + lvl * 0.2))
	xp_reward = int(xp_reward * (1.0 + lvl * 0.4))   # +40% XP pro Level

# ═══════════════════════════════════════════════════════════════
# READY
# ═══════════════════════════════════════════════════════════════

func _ready():
	_apply_level_scaling()
	current_hp = max_hp
	_update_health_display()
	health_label.visible = false

	# NavigationAgent konfigurieren
	nav_agent.path_desired_distance = 0.5
	nav_agent.target_desired_distance = attack_range * 0.9
	nav_agent.radius = 0.4  # Gegner-Breite für Pfadberechnung

	# Animationen einrichten
	_setup_animations()

	# Spawnpunkt erst nach dem ersten Frame setzen (global_position ist in _ready() noch nicht final)
	await get_tree().process_frame
	spawn_position = global_position
	_pick_patrol_target()
	patrol_wait_timer = 5.0  # 5 Sekunden idle nach Spawn
	state = State.PATROL

# ═══════════════════════════════════════════════════════════════
# ANIMATIONEN
# ═══════════════════════════════════════════════════════════════

func _setup_animations():
	if model:
		anim_player = model.find_child("AnimationPlayer", true, false) as AnimationPlayer
	if anim_player == null:
		push_warning("Enemy: Kein AnimationPlayer gefunden!")
		return

	# FBX-Import-Animationen komplett aufräumen
	anim_player.stop()
	anim_player.autoplay = ""
	# Alle vorhandenen Libraries entfernen und frische erstellen
	for lib_name in anim_player.get_animation_library_list():
		anim_player.remove_animation_library(lib_name)
	anim_player.add_animation_library("", AnimationLibrary.new())

	_load_anim_from_fbx(ANIM_IDLE,       "idle",       true)
	_load_anim_from_fbx(ANIM_WALK,       "walk",       true)
	_load_anim_from_fbx(ANIM_RUN,        "run",        true)
	_load_anim_from_fbx(ANIM_AUTOATTACK, "autoattack", false)
	_load_anim_from_fbx(ANIM_DEATH,      "death",      false)
	_load_anim_from_fbx(ANIM_TURN_LEFT,  "turn_left",  false)
	_load_anim_from_fbx(ANIM_TURN_RIGHT, "turn_right", false)

	anim_player.animation_finished.connect(_on_animation_finished)
	_play_anim("idle")

func _load_anim_from_fbx(fbx_path: String, anim_name: String, loop: bool = false):
	var scene = load(fbx_path)
	if scene == null:
		push_warning("Enemy: FBX nicht geladen: " + fbx_path)
		return
	var instance = scene.instantiate()
	var ext_ap = instance.find_child("AnimationPlayer", true, false) as AnimationPlayer
	if ext_ap == null:
		instance.queue_free()
		push_warning("Enemy: Kein AnimationPlayer in " + fbx_path)
		return
	var anim_list = ext_ap.get_animation_list()
	if anim_list.is_empty():
		instance.queue_free()
		return
	var anim = ext_ap.get_animation(anim_list[0]).duplicate(true)
	_strip_root_motion(anim)
	anim.loop_mode = Animation.LOOP_LINEAR if loop else Animation.LOOP_NONE
	var lib = anim_player.get_animation_library("")
	if not lib.has_animation(anim_name):
		lib.add_animation(anim_name, anim)
	instance.queue_free()

func _strip_root_motion(anim: Animation):
	for i in range(anim.get_track_count() - 1, -1, -1):
		if anim.track_get_type(i) != Animation.TYPE_POSITION_3D:
			continue
		var np: NodePath = anim.track_get_path(i)
		if np.get_subname_count() == 0:
			# Node-Position-Track (Armature-Root) → komplett entfernen
			anim.remove_track(i)
		else:
			# Knochen-Position: ALLE XZ-Werte nullen, Y behalten
			var key_count = anim.track_get_key_count(i)
			for k in range(key_count):
				var v: Vector3 = anim.track_get_key_value(i, k)
				anim.track_set_key_value(i, k, Vector3(0.0, v.y, 0.0))

func _play_anim(anim_name: String):
	if anim_player == null:
		return
	if anim_name != current_anim:
		current_anim = anim_name
		if anim_player.has_animation(anim_name):
			anim_player.play(anim_name)

func _on_animation_finished(anim_name: StringName):
	if anim_name == "autoattack":
		_play_anim("idle")
	elif anim_name == "turn_left" or anim_name == "turn_right":
		is_turning = false

# ═══════════════════════════════════════════════════════════════
# PHYSICS PROCESS
# ═══════════════════════════════════════════════════════════════

func _physics_process(delta):
	if is_dead:
		return

	# Schwerkraft
	if not is_on_floor():
		velocity.y -= GRAVITY * delta

	# Cooldown herunterzählen
	if attack_cooldown > 0:
		attack_cooldown -= delta

	# Slow-Debuff herunterzählen
	if _slow_timer > 0.0:
		_slow_timer -= delta
		if _slow_timer <= 0.0:
			_slow_factor = 1.0

	# Pull-Effekt (Zornfesseln)
	if _pull_timer > 0.0 and _pull_target != null and is_instance_valid(_pull_target):
		_pull_timer -= delta
		var dir = (_pull_target.global_position - global_position)
		dir.y = 0
		if dir.length() > 1.0:
			dir = dir.normalized()
			velocity.x = dir.x * PULL_SPEED
			velocity.z = dir.z * PULL_SPEED
		else:
			_pull_timer = 0.0  # Nah genug — Pull beendet

	# Pull aktiv → normale KI überspringen
	if _pull_timer > 0.0:
		move_and_slide()
		return

	# Kein Ziel → Spieler suchen
	if target == null or not is_instance_valid(target):
		target = null
		if state == State.CHASING or state == State.ATTACKING:
			state = State.PATROL
			_pick_patrol_target()
		# Spieler in Reichweite? → Aggro
		var players = get_tree().get_nodes_in_group("player")
		if players.size() > 0:
			var player = players[0]
			var dist = global_position.distance_to(player.global_position)
			if dist <= detection_range:
				target = player
				state = State.CHASING
			else:
				_do_patrol(delta)
		else:
			_do_patrol(delta)
		move_and_slide()
		return

	var distance = global_position.distance_to(target.global_position)

	# Leash-Check: Zu weit vom Spawn → Aggro verlieren, zurücklaufen
	var dist_from_spawn = global_position.distance_to(spawn_position)
	if dist_from_spawn > leash_range and (state == State.CHASING or state == State.ATTACKING):
		target = null
		state = State.PATROL
		_pick_patrol_target()
		_do_patrol(delta)
		move_and_slide()
		return

	match state:
		State.IDLE:
			_play_anim("idle")
			if distance <= detection_range:
				state = State.CHASING
		State.PATROL:
			if distance <= detection_range:
				state = State.CHASING
			else:
				_do_patrol(delta)
		State.CHASING:
			if distance <= attack_range:
				state = State.ATTACKING
				velocity.x = 0
				velocity.z = 0
			else:
				_chase_target()
				_play_anim("run")
		State.ATTACKING:
			if distance > attack_range * 1.5:
				state = State.CHASING
			else:
				velocity.x = 0
				velocity.z = 0
				_face_target()
				if attack_cooldown <= 0:
					_perform_attack()
				elif current_anim != "autoattack":
					_play_anim("idle")

	move_and_slide()

func _has_navmesh() -> bool:
	var map_rid = get_world_3d().navigation_map
	return NavigationServer3D.map_get_regions(map_rid).size() > 0

func _move_toward(goal: Vector3, speed: float):
	# Nur neuen Pfad berechnen wenn sich das Ziel deutlich bewegt hat
	if nav_agent.target_position.distance_to(goal) > 1.5:
		nav_agent.target_position = goal
	if nav_agent.is_navigation_finished():
		return
	var path = nav_agent.get_current_navigation_path()
	var next_pos = nav_agent.get_next_path_position()
	var direction = (next_pos - global_position)
	direction.y = 0
	if direction.length() < 0.1:
		if not _has_navmesh() or path.size() <= 1:
			# Kein NavMesh oder kein gültiger Pfad → direkte Bewegung
			direction = (goal - global_position)
			direction.y = 0
		if direction.length() < 0.1:
			return
	direction = direction.normalized()
	velocity.x = direction.x * speed * _slow_factor
	velocity.z = direction.z * speed * _slow_factor
	_face_direction(direction)


func _chase_target():
	if target == null:
		return
	if global_position.distance_to(target.global_position) < 0.5:
		return
	_move_toward(target.global_position, move_speed)

func _do_patrol(delta: float):
	# Turn-Animation läuft → warten
	if is_turning:
		velocity.x = 0
		velocity.z = 0
		return

	# Warten zwischen Patrol-Punkten
	if patrol_wait_timer > 0:
		patrol_wait_timer -= delta
		velocity.x = 0
		velocity.z = 0
		_play_anim("idle")
		if patrol_wait_timer <= 0:
			# Wartezeit vorbei → Turn-Animation zum nächsten Ziel abspielen
			_turn_toward_patrol_target()
		return

	# Zum Patrol-Ziel laufen (via NavMesh mit Fallback)
	if global_position.distance_to(patrol_target) <= 1.0:
		# Ziel erreicht → kurz warten, neues Ziel
		velocity.x = 0
		velocity.z = 0
		_play_anim("idle")
		patrol_wait_timer = randf_range(2.0, 5.0)
		_pick_patrol_target()
		return

	_move_toward(patrol_target, patrol_speed)
	_play_anim("walk")

func _turn_toward_patrol_target():
	var dir = (patrol_target - global_position)
	dir.y = 0
	if dir.length() < 0.1:
		return
	dir = dir.normalized()

	# Winkel zwischen aktueller Blickrichtung und Zielrichtung berechnen
	var forward = Vector3(sin(rotation.y), 0, cos(rotation.y))
	var cross = forward.cross(dir).y  # positiv = Ziel rechts, negativ = Ziel links

	is_turning = true
	if cross >= 0:
		_play_anim("turn_right")
	else:
		_play_anim("turn_left")
	# Schon mal in die Zielrichtung drehen
	_face_direction(dir)

func _pick_patrol_target():
	var angle = randf() * TAU
	var dist = randf_range(3.0, patrol_radius)
	var raw_target = spawn_position + Vector3(cos(angle) * dist, 0, sin(angle) * dist)
	# Auf nächsten begehbaren Punkt snappen (verhindert Ziele in Wänden)
	var map_rid = get_world_3d().navigation_map
	var snapped = NavigationServer3D.map_get_closest_point(map_rid, raw_target)
	# Fallback: wenn Snap fehlschlägt (kein NavMesh), Rohziel verwenden
	if snapped == Vector3.ZERO and raw_target != Vector3.ZERO:
		patrol_target = raw_target
	else:
		patrol_target = snapped

func _face_target():
	if target == null:
		return
	var dir = (target.global_position - global_position)
	dir.y = 0
	if dir.length() > 0.01:
		_face_direction(dir.normalized())

func _face_direction(dir: Vector3):
	if dir.length() > 0.01:
		rotation.y = atan2(dir.x, dir.z)

# ═══════════════════════════════════════════════════════════════
# KAMPF
# ═══════════════════════════════════════════════════════════════

func _perform_attack():
	if target == null or not is_instance_valid(target):
		return
	var damage = randi_range(min_damage, max_damage)
	target.take_damage(damage)
	attack_cooldown = attack_speed
	_play_anim("autoattack")
	print(name + " greift an: " + str(damage) + " Schaden")

func apply_pull(source: Node3D, duration: float):
	_pull_target = source
	_pull_timer  = duration
	state = State.CHASING  # Aggro auslösen

func apply_slow(factor: float, duration: float):
	_slow_factor = clamp(factor, 0.1, 1.0)
	_slow_timer = duration

func take_damage(amount: int):
	if is_dead:
		return
	current_hp = clamp(current_hp - amount, 0, max_hp)
	_update_health_display()
	# Aggro: Bei Schaden sofort den Spieler verfolgen
	if state == State.IDLE or state == State.PATROL:
		var players = get_tree().get_nodes_in_group("player")
		if players.size() > 0:
			target = players[0]
		state = State.CHASING
	if current_hp <= 0:
		_die()

# ═══════════════════════════════════════════════════════════════
# HP-ANZEIGE
# ═══════════════════════════════════════════════════════════════

func show_health():
	if health_label:
		health_label.visible = true

func hide_health():
	if health_label:
		health_label.visible = false

func _update_health_display():
	if health_label:
		health_label.text = str(current_hp) + " / " + str(max_hp)

# ═══════════════════════════════════════════════════════════════
# TOD & LOOT
# ═══════════════════════════════════════════════════════════════

func _die():
	if is_dead:
		return
	is_dead = true
	state = State.DEAD
	velocity = Vector3.ZERO
	_play_anim("death")
	print(name + " gestorben!")

	# Loot generieren
	if loot_table != null:
		var loot = loot_table.generate_loot(mob_level)
		enemy_dropped_loot.emit(loot, global_position)

	# XP und Respawn-Signal (nur einmal!)
	enemy_died.emit(global_position, xp_reward)

	# Kollision deaktivieren und Node entfernen
	set_deferred("collision_layer", 0)
	set_deferred("collision_mask", 0)
	await get_tree().create_timer(10.0).timeout
	queue_free()
