class_name AICharacter 
extends CharacterBase

signal range_attack_initiated(by: CharacterBase)

@export_group("Combat")
@export var player_character: PlayableCharacter
@export var KNOCKBACK_REPOSITION_POWER = 75
@export var MELEE_RADIUS = 75
@export var chase_speed = 65
@export var approach_speed = 40
@export var align_speed = 40
@export var min_recoil_time = 3.0/60.0
@export var max_recoil_time = 1.0
@export var reduce_recoil_time_threshold = 58.0/60.0
@export var projectile: PackedScene
@export var PROJECTILE_SPEED = 100.0
@export var PROJECTILE_POWER = 15

# Node components
@onready var hitbox = $Hitbox
@onready var attack_hitbox = $Attackbox
@onready var attack_hitbox_collider = $Attackbox/Collider
@onready var hitbox_collider = $Attackbox/Collider
@onready var state_machine: StateChart = $StateMachine
@onready var melee_hitbox = $Meleebox
@onready var melee_hitbox_collider = $Meleebox/DetectionArea

@onready var spawners = {
	Vector2(1,0): $BlastSpawnerEast,
	Vector2(-1,0): $BlastSpawnerWest,
	Vector2(0,1): $BlastSpawnerSouth,
	Vector2(0,-1): $BlastSpawnerNorth
}

var timer = 0

# Getting hurt variables
var attacked_from = Vector2.ZERO
var received_damage = 0
var hit_counter = 0
var attack_counter = 0
var attack_counter_limit = 0

var wants_to_melee = false

func _ready():
	state_machine.set_expression_property("melee_stage", 0)

func set_animation_direction(facing_direction: Vector2):
	anim_tree.set("parameters/idle/blend_position", facing_direction)
	anim_tree.set("parameters/walk/blend_position", facing_direction)
	anim_tree.set("parameters/melee/0/blend_position", facing_direction)
	anim_tree.set("parameters/melee/1/blend_position", facing_direction)
	anim_tree.set("parameters/melee/2/blend_position", facing_direction)
	anim_tree.set("parameters/hurt/blend_position", facing_direction)
	anim_tree.set("parameters/range_attack/blend_position", facing_direction)
	anim_tree.set("parameters/cut_idle/blend_position", facing_direction)
	anim_tree.set("parameters/cut_walk/blend_position", facing_direction)
	
func follow_player(speed: float):
	var approach_direction = global_position.direction_to(player_character.global_position)
	velocity = speed * approach_direction
	set_animation_direction(approach_direction)
	
func align_with_player(speed: float):
	var vector_to_player = player_character.global_position - global_position
	var movement_direction: Vector2
	var dist = 0.0
	if abs(vector_to_player.y) < abs(vector_to_player.x):
		# Moving vertically
		dist = abs(vector_to_player.y) - 1.0
		if dist <= 0:
			# Aligned
			state_machine.send_event("will_range")
			set_animation_direction(Vector2(sign(vector_to_player.x), 0.0))
			return
		else:
			movement_direction = Vector2.DOWN if vector_to_player.y > 0.0 else Vector2.UP
	else:
		# Moving horizontally
		dist = abs(vector_to_player.x) - 1.0
		if dist <= 0:
			# Aligned
			state_machine.send_event("will_range")
			set_animation_direction(Vector2(0.0, sign(vector_to_player.y)))
			return
		else:
			movement_direction = Vector2.RIGHT if vector_to_player.x > 0.0 else Vector2.LEFT
	velocity = movement_direction * speed
	set_animation_direction(movement_direction)

func spawn_projectile(direction: Vector2):
	var instance = projectile.instantiate()
	instance.direction = direction
	var spawner = spawners[direction]
	instance.speed = PROJECTILE_SPEED
	instance.position = spawner.global_position
	instance.power = PROJECTILE_POWER
	get_parent().add_child(instance)
	instance.collided_with_area.connect(player_character._on_enemy_attack_did_hit)
	range_attack_initiated.emit(self)

func _physics_process(delta: float):
	super._physics_process(delta)

func _on_player_attack_did_hit(other, attack_power: int, attack_direction: Vector2):
	if other.get_parent().get_instance_id() == get_instance_id() :
		_on_ai_got_hurt(attack_power, attack_direction)
		
func set_ai_process_mode(mode: ProcessMode):
	state_machine.process_mode = mode
	
func set_ai_enabled(enabled: bool):
	set_ai_process_mode(Node.PROCESS_MODE_INHERIT if enabled else Node.PROCESS_MODE_DISABLED)

func set_melee_box_enabled(enabled: bool):
	melee_hitbox_collider.disabled = !enabled

func _on_ai_got_hurt(attack_power: int, attack_direction: Vector2):
	attacked_from = attack_direction
	received_damage = attack_power
	attack_hitbox_collider.set_deferred("disabled", true)
	state_machine.send_event("been_hurt")

## Thinking state
## Once some time passes out, do_thinking is called
func _on_think_state_entered():
	timer = randf_range(min_recoil_time, max_recoil_time)
	if timer <= reduce_recoil_time_threshold:
		timer = timer * 0.75

func _on_think_state_processing(delta):
	timer -= delta
	if timer <= 0:
		do_thinking()

## This function actually determines what to do	
func do_thinking():
	var distance = position.distance_to(player_character.position)
	if distance >= MELEE_RADIUS:
		state_machine.set_expression_property("melee_stage", 0)
		state_machine.send_event("wants_torange")
	else:
		state_machine.send_event("wants_tomelee")

func _on_hurt_state_entered():
	var facing_direction = attacked_from * -1
	health = health - received_damage
	hit_counter = hit_counter + 1
	got_hurt.emit(health)
	
	# 1. If hit_counter variable >= 4, then you need to make the state_machine
	# #  1.1  send an event "will_reposition"
	# 2. On the Hurt state, make a State transition node, name it "ToHurtReposition"
	# # 2.1 Set its transition node to HurtReposition
	# # 2.2 Set its event to "will_reposition"
	# 3. On the HurtReposition, make another State transition node, name it "ToThink"
	# # 3.1 Set its transition node to Think
	# # 3.2 Set its event to "hurt_complete"
	# 4. On the HurtReposition node, connect the signals state_entered() and state_exited() to
	# _on_hurt_reposition_state_entered() and _on_hurt_reposition_state_exited() respectively
	# 5. Inside the _on_hurt_reposition_state_entered() function,
	# you need to reset hit_counter back to 0.
	
	if health <= 0:
		state_machine.send_event("been_defeated")
		defeated.emit()
	
	if hit_counter >= 4:
		state_machine.send_event("will_reposition")
	else:
		set_animation_direction(facing_direction)
		anim_playback.travel("hurt")
		var knockback_direction = attacked_from.rotated(deg_to_rad(randf_range(-1, 1) * KNOCKBACK_RANDOMNESS))
		velocity = KNOCKBACK_POWER * knockback_direction.normalized()
		
		state_machine.set_expression_property("melee_stage", 0)
	
func _on_hurt_state_exited():
	velocity = Vector2.ZERO
	
func _on_hurt_reposition_state_entered():
	var facing_direction = attacked_from * -1
	set_collision_layer_value(5, true)
	set_collision_layer_value(1, false)
	set_collision_mask_value(5, true)
	set_collision_mask_value(1, false)
	set_animation_direction(facing_direction)
	anim_playback.travel("hurt")
	velocity = attacked_from * KNOCKBACK_REPOSITION_POWER * -1
	
func _on_hurt_reposition_state_exited():
	velocity = Vector2.ZERO
	set_collision_layer_value(5, false)
	set_collision_layer_value(1, true)
	set_collision_mask_value(5, false)
	set_collision_mask_value(1, true)
	hit_counter = 0

func _on_animation_tree_animation_finished(anim_name):
	if anim_name.contains("hurt"):
		state_machine.send_event("hurt_complete")
	elif anim_name.contains("range"):
		state_machine.send_event("range_complete")
	elif (anim_name.contains("kick") or anim_name.contains("punch")):
		attack_counter = attack_counter + 1
		wants_to_melee = wants_to_melee and attack_counter < attack_counter_limit
		if wants_to_melee:
			var animation_rng = randf_range(0, 1)
			anim_tree.set("parameters/melee/blend_position", animation_rng)
			anim_tree.set("parameters/conditions/melee", true)
			attacked.emit()
		else:
			state_machine.send_event("melee_complete")

func _on_defeat_state_entered():
	if has_nsfw_animations and Globals.nsfw:
		anim_playback.travel("naked_start")
	else:
		anim_playback.travel("defeated")

## Aproaching
func _on_approach_state_entered():
	follow_player(approach_speed)
	anim_tree.set("parameters/conditions/is_moving", true)
	timer = 1

func _on_approach_state_processing(delta):
	timer -= delta
	if timer < 0:
		var distance = global_position.distance_to(player_character.global_position)
		if distance >= MELEE_RADIUS:
			state_machine.send_event("wants_torange")
		state_machine.send_event("approach_complete")


func _on_approach_state_exited():
	anim_tree.set("parameters/conditions/is_moving", false)
	velocity = Vector2.ZERO


func _on_approach_to_think_taken():
	state_machine.set_expression_property("melee_stage", 1)

## Chasing
func _on_chase_state_entered():
	anim_tree.set("parameters/conditions/is_moving", true)

func _on_chase_state_processing(_delta):
	follow_player(chase_speed)
	var distance = position.distance_to(player_character.position)
	if distance >= MELEE_RADIUS:
		state_machine.send_event("melee_aborted")

func _on_chase_state_exited():
	anim_tree.set("parameters/conditions/is_moving", false)
	velocity = Vector2.ZERO

func _on_chase_to_range_taken():
	state_machine.set_expression_property("melee_stage", 0)

## Melee
func _on_meleebox_body_entered(body):
	if body == player_character:
		wants_to_melee = true
		state_machine.send_event("will_melee")

func _on_melee_state_entered():
	attack_counter = 0
	attack_counter_limit = randi_range(3, 5)
	anim_tree.set("parameters/conditions/melee", true)
	state_machine.set_expression_property("melee_stage", 0)
	attacked.emit()

func _on_meleebox_body_exited(body):
	if body == player_character:
		wants_to_melee = false

func _on_melee_state_processing(_delta):
	follow_player(0)

func _on_melee_state_exited():
	anim_tree.set("parameters/conditions/melee", false)

func _on_attackbox_area_entered(area):
	attack_did_hit.emit(area, attack_power, anim_tree.get("parameters/melee/0/blend_position"))

func _on_victory_state_entered():
	wants_to_melee = false
	anim_tree.set("parameters/conditions/melee", false)
	state_machine.set_expression_property("melee_stage", 0)
	velocity = Vector2.ZERO

func _on_player_been_defeated():
	state_machine.send_event("has_won")

func _on_align_for_range_state_entered():
	anim_tree.set("parameters/conditions/is_moving", true)
	
func _on_align_for_range_state_exited():
	anim_tree.set("parameters/conditions/is_moving", false)

func _on_align_for_range_state_processing(delta):
	align_with_player(align_speed)
	var distance = position.distance_to(player_character.position)
	if distance < MELEE_RADIUS:
		state_machine.send_event("will_melee")

func _on_range_state_entered():
	velocity = Vector2.ZERO
	anim_tree.set("parameters/conditions/range", true)

func _on_range_state_exited():
	anim_tree.set("parameters/conditions/range", false)
