class_name BaseEnemyBehavior
extends Node2D

@onready var state_chart : StateChart = get_node("StateChart")
@onready var enemy_nodes : EnemyNodes = get_node("EnemyNodes")

@export var character_body : EnemyBase

@export_category("Params")
@export var max_health : int = 4
@export var attack_distance : float = 22.5 # was 90 before sprite reworks 

@export_group("Timers")
@export var notice_timer : float = 0.5
@export var search_timer : float = 1
@export var alerted_timer : float = 0.5
@export var avoid_timer : float = 0.75

var get_stuck_timer : float = 6.0

var in_notice_state : bool = false
var in_search_state : bool = false
var in_moving_state : bool = false

var health

var _notice_timer : float = 0
var _search_timer : float = 0
var _avoid_timer : float = 0
var _get_stuck_timer : float = 0

var current_goal : Vector2
var player : PlayerBase
var player_position : Vector2

var is_part_of_group : bool = false
var room_enemy_group : String = ""

signal died

var _checked_rotations = 0

var anchor_state : Dictionary = {}
var anchor_statechart_state : SavedState

var large_line_of_sight : bool = false

var avoid_direction : Vector2 = Vector2.ZERO

var reset_during_death : bool = false
var is_dying : bool = false
var set_anchor_during_death : bool = false

var is_in_combat : bool = false

var has_shown_notice_icon : bool = false
var has_shown_alerted_icon : bool = false

func _ready() -> void:
	health = max_health
	enemy_nodes.enemy_health.init(max_health)
	enemy_nodes.set_notice_maximum(notice_timer)

func _process(delta: float) -> void:
	if Globals.paused:
		return
	decrement_timers(delta)
	enemy_nodes.player_detection_zone.update(character_body.current_direction, large_line_of_sight)

#region Avoid Other Enemies
func _on_avoid_state_entered() -> void:
	in_moving_state = true
	_avoid_timer = avoid_timer
	avoid_direction = character_body.current_direction.rotated(deg_to_rad(135))

func _on_slow_avoid_state_processing(_delta: float) -> void:
	if Globals.paused:
		return
	character_body.walk(avoid_direction)

func _on_fast_avoid_state_processing(_delta: float) -> void:
	if Globals.paused:
		return
	character_body.chase(avoid_direction)
#endregion

# TODO: REFACTOR
#region Housekeeping
func _on_timer_timeout() -> void:
	if player:
		current_goal = player.global_position
		if enemy_nodes.navigation_agent.target_position != current_goal:
			enemy_nodes.navigation_agent.target_position = current_goal
	enemy_nodes.timer.start()

func _on_navigation_agent_2d_target_reached() -> void:
	character_body.movement_velocity = Vector2(0,0)
	state_chart.send_event("target_reached")

func decrement_timers(delta : float):
	if _avoid_timer > 0:
		_avoid_timer -= delta
		if _avoid_timer <= 0:
			state_chart.send_event("avoid_finished")
	
	if in_notice_state:
		if _notice_timer > 0:
			_notice_timer-= delta
			enemy_nodes.set_notice_value(_notice_timer)
			if _notice_timer <= 0:
				state_chart.send_event("notice_timer_over")
	elif in_search_state:
		if _notice_timer > 0 and _notice_timer < notice_timer:
			_notice_timer += delta/2
			enemy_nodes.set_notice_value(_notice_timer)
			if _notice_timer >= notice_timer:
				_notice_timer = 0
				enemy_nodes.notice_progress_bar.hide()
	
	if _search_timer > 0:
		_search_timer-= delta
		if _search_timer <= 0:
			_checked_rotations += 1
	
	if in_moving_state:
		if _get_stuck_timer > 0:
			_get_stuck_timer -= delta
			if _get_stuck_timer <= 0:
				for node : Node2D in enemy_nodes.stuck_checker_area_2d.get_overlapping_bodies():
					if node is EnemyBase and node == character_body:
						give_up()
		else:
			_get_stuck_timer = get_stuck_timer
			enemy_nodes.stuck_checker_area_2d.global_position = global_position

func give_up():
	_notice_timer = 0
	enemy_nodes.set_notice_value(_notice_timer)
	enemy_nodes.notice_progress_bar.hide()
	enemy_nodes.alerted_sprite.hide()
	state_chart.send_event("fuck_everything") #just give up and go home man

func join_combat():
	if is_in_combat:
		return
	is_in_combat = true
	SignalBus.enemy_join_combat()

func leave_combat():
	if not is_in_combat:
		return
	is_in_combat = false
	SignalBus.enemy_leave_combat()

#region PlayerDetectionZone
func _on_player_detection_zone_player_detected(body: Node2D) -> void:
	if body is PlayerBase:
		player = body
		state_chart.send_event("see_player")

func _on_player_detection_zone_player_lost(body: Node2D) -> void:
	if body is PlayerBase:
		player_position = body.global_position
		player = null
		state_chart.send_event("player_left_sight")
#endregion

#endregion
#region State Chart 
#region Alive
#region Regular
#region PlayerNotVisible
#region Chill
func _on_chill_state_entered() -> void:
	leave_combat()
	large_line_of_sight = false
	player = null

func _on_return_state_processing(_delta: float) -> void:
	if Globals.paused:
		return
	if !enemy_nodes.navigation_agent.is_target_reached():
		var nav_point_direction = character_body.to_local(enemy_nodes.navigation_agent.get_next_path_position()).normalized()
		character_body.walk(nav_point_direction)
#endregion
#region NotChill
#region Alerted
func _on_alerted_animation_state_entered() -> void:
	in_moving_state = false
	enemy_nodes.notice_progress_bar.hide()
	_notice_timer = 0.1
	large_line_of_sight = true
	if not has_shown_alerted_icon:
		enemy_nodes.alerted_sprite.show()
	has_shown_alerted_icon = true
	if is_inside_tree():
		await get_tree().create_timer(alerted_timer).timeout
	enemy_nodes.alerted_sprite.hide()
	state_chart.send_event("alerted_animation_finished")

func _on_alerted_chase_state_entered() -> void:
	has_shown_alerted_icon = false
	in_moving_state = true
	large_line_of_sight = true
	enemy_nodes.navigation_agent.target_position = current_goal
	enemy_nodes.navigation_agent.target_desired_distance = attack_distance + 50
	enemy_nodes.timer.stop()

func _on_alerted_chase_state_processing(_delta: float) -> void:
	if Globals.paused:
		return
	if !enemy_nodes.navigation_agent.is_target_reached():
		var nav_point_direction = character_body.to_local(enemy_nodes.navigation_agent.get_next_path_position()).normalized()
		character_body.chase(nav_point_direction)
	else:
		state_chart.send_event("start_search")
#endregion
#region SearchState
func _on_search_state_entered() -> void:
	in_moving_state = false
	in_search_state = true
	enemy_nodes.notice_progress_bar.show()
	large_line_of_sight = true
	_checked_rotations = 0
	if player:
		player_position = player.global_position
	enemy_nodes.navigation_agent.target_position = player_position
	enemy_nodes.navigation_agent.target_desired_distance = 3
	enemy_nodes.timer.stop()

func _on_search_state_processing(_delta: float) -> void:
	if Globals.paused:
		return
	if not enemy_nodes.navigation_agent.is_target_reachable():
		give_up()
	if !enemy_nodes.navigation_agent.is_target_reached():
		var nav_point_direction = character_body.to_local(enemy_nodes.navigation_agent.get_next_path_position()).normalized()
		character_body.walk(nav_point_direction)
	else:
		character_body.movement_velocity = Vector2(0,0)
		
	if enemy_nodes.navigation_agent.is_target_reached() and _search_timer <= 0:
		if _checked_rotations == 0:
			_search_timer = search_timer
			character_body.current_direction = Vector2(0, 1)
		elif _checked_rotations < 8:
			_search_timer = search_timer
			character_body.current_direction = character_body.current_direction.rotated(deg_to_rad(45)).normalized()
		else:
			_checked_rotations = 0
			state_chart.send_event("search_timer_over")

func _on_search_state_exited() -> void:
	in_search_state = false
	_checked_rotations = 0
#endregion
#endregion
#endregion
#region NoticeState
func _on_notice_state_entered() -> void:
	in_moving_state = false
	in_notice_state = true
	large_line_of_sight = true
	character_body.current_direction = (player.global_position-global_position).normalized()
	if _notice_timer <= 0:
		_notice_timer = notice_timer
	
	enemy_nodes.set_notice_value(_notice_timer)
	enemy_nodes.notice_progress_bar.show()

func _on_notice_state_processing(_delta: float) -> void:
	character_body.movement_velocity = Vector2(0,0)

func _on_notice_state_exited() -> void:
	in_notice_state = false

#endregion
#region Fight
func _on_fight_state_entered() -> void:
	_notice_timer = 0.1
	enemy_nodes.notice_progress_bar.hide()
	join_combat()

func _on_alert_state_entered() -> void:
	in_moving_state = false
	enemy_nodes.notice_progress_bar.hide()
	if player:
		player_position = player.global_position
	var _player_position := player_position
	large_line_of_sight = true
	enemy_nodes.alert_sprite.show()
	# sfx
	AudioManager.create_audio(SoundEffect.SOUND_EFFECT_TYPE.ENEMY_ALERT)
	if is_inside_tree():
		await get_tree().create_timer(alerted_timer).timeout
	enemy_nodes.alert_sprite.hide()
	if is_part_of_group:
		SignalBus.alert_enemies(player_position, room_enemy_group)
	state_chart.send_event("alert_over")

#region ChaseState
func _on_chase_state_entered() -> void:
	in_moving_state = true
	large_line_of_sight = true
	enemy_nodes.navigation_agent.target_desired_distance = attack_distance
	if player:
		current_goal = player.global_position
		enemy_nodes.navigation_agent.target_position = current_goal
	enemy_nodes.timer.start()

func _on_chase_state_processing(_delta: float) -> void:
	if Globals.paused:
		return
	if !enemy_nodes.navigation_agent.is_target_reached():
		var nav_point_direction = character_body.to_local(enemy_nodes.navigation_agent.get_next_path_position()).normalized()
		character_body.chase(nav_point_direction)
#endregion
#region AttackState
func _on_attack_state_entered() -> void:
	in_moving_state = false
	large_line_of_sight = true
	character_body.attack()
	await character_body.attack_over
	if is_inside_tree():
		await get_tree().create_timer(0.5).timeout
	state_chart.send_event("attack_done")

func _on_attack_state_processing(_delta: float) -> void:
	character_body.movement_velocity = Vector2(0,0)
#endregion
#endregion
#endregion
#endregion
#region DieState
func _on_die_state_entered() -> void:
	in_moving_state = false
	leave_combat()
	is_dying = true
	AudioManager.create_audio(SoundEffect.SOUND_EFFECT_TYPE.DIE)
	if is_inside_tree():
		await get_tree().create_timer(1.0).timeout #animation placeholder
	if Globals.paused:
		await SignalBus.unpaused
	if not reset_during_death or set_anchor_during_death:
		emit_signal("died")
	else:
		is_dying = false
		reset_during_death = false

func _on_die_state_processing(_delta: float) -> void:
	character_body.movement_velocity = Vector2(0,0)
#endregion
#endregion

func take_damage(damage):
	health -= damage
	enemy_nodes.enemy_health.set_relative(-damage)
	if health <= 0:
		state_chart.send_event("killed")
	
	AudioManager.create_audio(SoundEffect.SOUND_EFFECT_TYPE.GET_HIT)

func get_alerted(_player_position : Vector2):
	current_goal = _player_position
	state_chart.send_event("alerted")

#region Anchored Remnant
func save_state():
	if is_dying:
		set_anchor_during_death = true
	anchor_state = {
		"in_notice_state" = in_notice_state,
		"_notice_timer" = _notice_timer,
		"in_search_state" = in_search_state,
		"_search_timer" = _search_timer,
		"_avoid_timer" = _avoid_timer,
		"_checked_rotations" = _checked_rotations,
		"health" = health,
		"current_goal" = current_goal,
		"large_line_of_sight" = large_line_of_sight,
		"avoid_direction" = avoid_direction,
		"player" = player,
		"player_position" = player_position,
		"is_in_combat" = is_in_combat,
		"in_moving_state" = in_moving_state,
		"last_pos" = enemy_nodes.stuck_checker_area_2d.global_position,
		"has_shown_alerted_icon" = has_shown_alerted_icon,
	}
	anchor_statechart_state = SavedState.new()
	state_chart._state._state_save(anchor_statechart_state)

func reload_state():
	if not anchor_statechart_state:
		printerr("Invalid Reload: no anchor state set in BaseEnemyBehavior")
	else:
		if is_dying:
			reset_during_death = true
		in_notice_state = anchor_state.get("in_notice_state", in_notice_state)
		_notice_timer = anchor_state.get("_notice_timer", _notice_timer)
		in_search_state = anchor_state.get("in_search_state", in_search_state)
		_search_timer = anchor_state.get("_search_timer", _search_timer)
		_avoid_timer = anchor_state.get("_avoid_timer", _avoid_timer)
		_checked_rotations = anchor_state.get("_checked_rotations", _checked_rotations)
		health = anchor_state.get("health", health)
		enemy_nodes.enemy_health.set_absolute(health)
		current_goal = anchor_state.get("current_goal", current_goal)
		large_line_of_sight = anchor_state.get("large_line_of_sight", large_line_of_sight)
		avoid_direction = anchor_state.get("avoid_direction", avoid_direction)
		player = anchor_state.get("player", player)
		player_position = anchor_state.get("player_position", player_position)
		var was_in_combat = is_in_combat
		is_in_combat = anchor_state.get("is_in_combat", is_in_combat)
		if is_in_combat and not was_in_combat:
			join_combat()
		elif not is_in_combat and was_in_combat:
			leave_combat()
		in_moving_state = anchor_state.get("in_moving_state", in_moving_state)
		enemy_nodes.stuck_checker_area_2d.global_position = anchor_state.get("last_pos", enemy_nodes.stuck_checker_area_2d.global_position)
		has_shown_alerted_icon = anchor_state.get("has_shown_alerted_icon", has_shown_alerted_icon)
		
		state_chart._state._state_restore(anchor_statechart_state)
#endregion
