class_name NYXEnemy
extends CharacterBody2D

signal died(enemy: NYXEnemy)
signal health_changed(enemy: NYXEnemy, health: float, max_health: float)
signal attack_warning(enemy: NYXEnemy, target_position: Vector2, radius: float, delay: float)
signal ranged_warning(enemy: NYXEnemy, start_position: Vector2, target_position: Vector2, radius: float, delay: float)

@export var enemy_type: String = "shade"
@export var speed: float = 165.0
@export var max_health: float = 80.0
@export var damage: float = 14.0
@export var attack_range: float = 36.0
@export var attack_cooldown: float = 0.8
@export var attack_windup: float = 0.34
@export var preferred_range: float = 310.0
@export var ranged_radius: float = 42.0
@export var fear_resistance: float = 1.0
@export var separation_radius: float = 54.0
@export var separation_force: float = 130.0

@onready var health_bar_back: Line2D = $HealthBarBack
@onready var health_bar_fill: Line2D = $HealthBarFill

var health: float = max_health
var target: NYXPlayer
var fear_timer: float = 0.0
var attack_timer: float = 0.0
var b_attacking: bool = false
var knockback_velocity: Vector2 = Vector2.ZERO

func _ready() -> void:
	health = max_health
	_update_health_bar()
	health_changed.emit(self, health, max_health)

func _physics_process(delta: float) -> void:
	if not is_instance_valid(target):
		return

	fear_timer = maxf(0.0, fear_timer - delta)
	attack_timer = maxf(0.0, attack_timer - delta)

	var to_target: Vector2 = target.global_position - global_position
	var direction: Vector2 = to_target.normalized()
	if fear_timer > 0.0:
		direction *= -1.0

	var separation: Vector2 = _get_separation()
	var move_speed: float = speed * (0.25 if b_attacking else 1.0)
	if enemy_type == "archer" and fear_timer <= 0.0:
		direction = _get_archer_direction(to_target)
	elif enemy_type == "boss" and fear_timer <= 0.0:
		direction = direction * 0.68
	velocity = direction * move_speed + separation + knockback_velocity
	move_and_slide()
	knockback_velocity = knockback_velocity.move_toward(Vector2.ZERO, 900.0 * delta)

	if fear_timer <= 0.0 and attack_timer <= 0.0:
		if enemy_type == "archer" and to_target.length() <= attack_range:
			_start_ranged_attack()
		elif enemy_type != "archer" and enemy_type != "boss" and to_target.length() <= attack_range:
			_start_melee_attack()

func apply_damage(amount: float, source_position: Vector2 = Vector2.ZERO, knockback: float = 0.0) -> void:
	health -= amount
	if knockback > 0.0:
		var away: Vector2 = (global_position - source_position).normalized()
		knockback_velocity += away * knockback
	modulate = Color(1.0, 0.35, 0.45)
	var tween: Tween = create_tween()
	tween.tween_property(self, "scale", Vector2(1.2, 1.2), 0.04)
	tween.tween_property(self, "scale", Vector2.ONE, 0.08)
	tween.parallel().tween_property(self, "modulate", Color.WHITE, 0.12)
	_update_health_bar()
	health_changed.emit(self, health, max_health)
	if health <= 0.0:
		died.emit(self)
		queue_free()

func apply_fear(duration: float) -> void:
	fear_timer = maxf(fear_timer, duration * fear_resistance)
	b_attacking = false

func _start_melee_attack() -> void:
	attack_timer = attack_cooldown
	b_attacking = true
	attack_warning.emit(self, target.global_position, attack_range + 18.0, attack_windup)
	modulate = Color(1.0, 0.55, 0.35)
	await get_tree().create_timer(attack_windup).timeout
	b_attacking = false
	modulate = Color.WHITE
	if not is_instance_valid(target) or fear_timer > 0.0 or health <= 0.0:
		return
	if global_position.distance_to(target.global_position) <= attack_range + 18.0:
		target.apply_damage(damage)

func _start_ranged_attack() -> void:
	attack_timer = attack_cooldown
	b_attacking = true
	var shot_target: Vector2 = target.global_position
	ranged_warning.emit(self, global_position, shot_target, ranged_radius, attack_windup)
	modulate = Color(0.52, 0.88, 1.0)
	await get_tree().create_timer(attack_windup).timeout
	b_attacking = false
	modulate = Color.WHITE
	if not is_instance_valid(target) or fear_timer > 0.0 or health <= 0.0:
		return
	if target.global_position.distance_to(shot_target) <= ranged_radius:
		target.apply_damage(damage)

func _get_archer_direction(to_target: Vector2) -> Vector2:
	var distance_to_target: float = to_target.length()
	if distance_to_target < preferred_range * 0.72:
		return -to_target.normalized()
	if distance_to_target > preferred_range * 1.1:
		return to_target.normalized()
	return Vector2(-to_target.y, to_target.x).normalized() * 0.35

func _get_separation() -> Vector2:
	var push: Vector2 = Vector2.ZERO
	for other in get_tree().get_nodes_in_group("enemies"):
		if other == self or not (other is NYXEnemy):
			continue
		var other_enemy: NYXEnemy = other as NYXEnemy
		var offset: Vector2 = global_position - other_enemy.global_position
		var distance: float = offset.length()
		if distance > 0.01 and distance < separation_radius:
			push += offset.normalized() * (1.0 - distance / separation_radius)
	return push * separation_force

func _update_health_bar() -> void:
	if not is_instance_valid(health_bar_back) or not is_instance_valid(health_bar_fill):
		return
	var percent: float = clampf(health / max_health, 0.0, 1.0)
	var half_width: float = _health_bar_half_width()
	var y_offset: float = _health_bar_y_offset()
	health_bar_back.points = PackedVector2Array([Vector2(-half_width, y_offset), Vector2(half_width, y_offset)])
	health_bar_fill.points = PackedVector2Array([Vector2(-half_width, y_offset), Vector2(-half_width + half_width * 2.0 * percent, y_offset)])

func _health_bar_half_width() -> float:
	if enemy_type == "boss":
		return 42.0
	if enemy_type == "knight":
		return 30.0
	if enemy_type == "archer":
		return 26.0
	return 24.0

func _health_bar_y_offset() -> float:
	if enemy_type == "boss":
		return -112.0
	if enemy_type == "knight":
		return -70.0
	if enemy_type == "archer":
		return -58.0
	return -52.0
