class_name Creeper
extends CharacterBody2D

@onready var _hurtbox: HurtBox = $HurtBox
@onready var _health: HealthComponent = $HealthComponent
@onready var _sprite: AnimatedSprite2D = $AnimatedSprite2D
@onready var _detection_area: Area2D = $DetectionArea
@onready var _hitbox: HitBox = $HitBox
@onready var _hitbox_collision: CollisionShape2D = $HitBox/CollisionShape2D
@onready var _attack_range_area: Area2D = $AttackRangeArea
@onready var _ledge_detector: RayCast2D = $RayCast2D

# Creeper Exported Variables
@export_group("Movement")
@export var move_speed: float = 60.0
@export var patrol_speed: float = 20.0
@export var ledge_check_offset: float = 10.0

@export_group("Combat")
@export var attack_cooldown: float = 1.0
@export var backoff_min_distance: float = 30.0
@export var attack_hit_frame_start: int = 4
@export var attack_hit_frame_end: int = 5

# Creeper Runtime Variables
var _player: Node2D = null
var _player_in_attack_range: bool = false
var _is_attacking: bool = false
var _is_hurt: bool = false
var _is_dead: bool = false
var _attack_cooldown_timer: float = 0.0
var _facing_direction: float = 1.0
var _patrol_direction: float = 1.0

func _ready() -> void:
	_hurtbox.hit_received.connect(_on_hurtbox_hit_received)
	_health.health_changed.connect(_on_health_changed)
	_health.died.connect(_on_died)
	_sprite.animation_finished.connect(_on_animation_finished)
	_detection_area.body_entered.connect(_on_detection_area_body_entered)
	_detection_area.body_exited.connect(_on_detection_area_body_exited)
	_attack_range_area.area_entered.connect(_on_hitbox_area_entered)
	_attack_range_area.area_exited.connect(_on_hitbox_area_exited)

func _physics_process(delta: float) -> void:
	_apply_gravity(delta)
	_update_attack_cooldown(delta)

	if not _is_dead:
		_process_ai()
		_update_hitbox()

	move_and_slide()

func _update_hitbox() -> void:
	var is_active_hit_frame: bool = _sprite.animation == &"attack" \
		and _sprite.frame >= attack_hit_frame_start \
		and _sprite.frame <= attack_hit_frame_end
	_hitbox_collision.disabled = not (_is_attacking and is_active_hit_frame)

func _apply_gravity(delta: float) -> void:
	if not is_on_floor():
		velocity.y += ProjectSettings.get_setting("physics/2d/default_gravity") * delta

func _update_attack_cooldown(delta: float) -> void:
	if _attack_cooldown_timer > 0.0:
		_attack_cooldown_timer -= delta

func _process_ai() -> void:
	if _is_hurt:
		velocity.x = 0.0
		return

	if _is_attacking:
		velocity.x = 0.0
		return

	if _player_in_attack_range and _attack_cooldown_timer <= 0.0:
		_start_attack()
		return

	if _player_in_attack_range:
		_facing_direction = signf(_player.global_position.x - global_position.x)
		var distance_to_player: float = absf(_player.global_position.x - global_position.x)
		if distance_to_player < backoff_min_distance:
			velocity.x = -_facing_direction * move_speed
			if _sprite.animation != &"walk_1":
				_sprite.play(&"walk_1")
		else:
			velocity.x = 0.0
			if _sprite.animation != &"idle":
				_sprite.play(&"idle")
		_sprite.flip_h = _facing_direction > 0.0
		_hitbox.scale.x = -_facing_direction
		_attack_range_area.scale.x = -_facing_direction
		return

	if _player:
		_facing_direction = signf(_player.global_position.x - global_position.x)
		if _has_ground_ahead(_facing_direction):
			velocity.x = _facing_direction * move_speed
			if _sprite.animation != &"walk_1":
				_sprite.play(&"walk_1")
		else:
			velocity.x = 0.0
			if _sprite.animation != &"idle":
				_sprite.play(&"idle")
	else:
		_facing_direction = _patrol_direction
		velocity.x = _patrol_direction * patrol_speed
		if _sprite.animation != &"walk_2":
			_sprite.play(&"walk_2")

	_sprite.flip_h = _facing_direction > 0.0
	_hitbox.scale.x = -_facing_direction
	_attack_range_area.scale.x = -_facing_direction

	if not _player:
		_update_patrol_direction()

func _has_ground_ahead(direction: float) -> bool:
	_ledge_detector.position.x = ledge_check_offset * direction
	_ledge_detector.force_raycast_update()
	return _ledge_detector.is_colliding()

func _update_patrol_direction() -> void:
	if is_on_wall() or not _has_ground_ahead(_patrol_direction):
		_patrol_direction = -_patrol_direction

func _start_attack() -> void:
	_is_attacking = true
	_attack_cooldown_timer = attack_cooldown
	velocity.x = 0.0
	_sprite.play(&"attack")

func _on_detection_area_body_entered(body: Node2D) -> void:
	_player = body

func _on_detection_area_body_exited(body: Node2D) -> void:
	if body == _player:
		_player = null

func _on_hitbox_area_entered(area: Area2D) -> void:
	if area is HurtBox and area.get_parent() == _player:
		_player_in_attack_range = true

func _on_hitbox_area_exited(area: Area2D) -> void:
	if area is HurtBox and area.get_parent() == _player:
		_player_in_attack_range = false

func _on_hurtbox_hit_received(hitbox: HitBox) -> void:
	_health.take_damage(hitbox.damage)

func _on_health_changed(_new_value: int, _max_value: int) -> void:
	_is_attacking = false
	_is_hurt = true
	_sprite.play(&"hurt")

func _on_died() -> void:
	_is_dead = true
	velocity = Vector2.ZERO
	_hurtbox.set_deferred("monitoring", false)
	_hitbox_collision.set_deferred("disabled", true)

func _on_animation_finished() -> void:
	if _sprite.animation == &"hurt":
		_is_hurt = false
		if _is_dead:
			_sprite.play(&"death_1")
		else:
			_sprite.play(&"idle")
	elif _sprite.animation == &"attack":
		_is_attacking = false
	elif _sprite.animation == &"death_1":
		queue_free()
