class_name BossBehavior
extends Node2D

var boss : Boss

@onready var state_chart: StateChart = $StateChart
@onready var boss_attack_manager: BossAttackManager = $BossAttackManager
@onready var boss_remnant_manager: BossRemnantManager = $BossRemnantManager
@onready var force_field: StaticBody2D = $ForceField
@onready var enemy_health: MarginContainer = $CanvasLayer/EnemyHealth
@onready var boss_stunned: Control = $BossStunned

#region Navigation Management
@onready var navigation_agent_2d: NavigationAgent2D = $NavigationAgent2D
@onready var timer: Timer = $Timer
var navigation_distance : float = 0
var navigation_target_position : Vector2 = Vector2.ZERO
var is_navigating : bool = false
#endregion

var health : int

#region Timeshift Management
@export var time_travel_timer : float = 5.0
var _time_travel_timer : float = 0

var boss_can_change_time : bool = false
var time_change_pending : bool = false
var player_in_same_time : bool = true

var new_phase_loading : bool = false
#endregion

var _stunned_timer : float = 0

#region Anchored Remnant Management
var anchor_state : Dictionary = {}
var anchor_statechart_state : SavedState
#endregion

#region Setup
func _ready() -> void:
	player_in_same_time = Globals.current_time == Utils.TIME.PRESENT
	boss_attack_manager.attack_prepared.connect(func () : state_chart.send_event("attack_prepared"))
	boss_attack_manager.attack_performed.connect(func () : state_chart.send_event("attack_performed"))
	boss_attack_manager.attack_cooldown_over.connect(func () : state_chart.send_event("attack_cooldown_over"))
	SignalBus.changed_time.connect(player_changed_time_handling)
	
	if get_parent() and get_parent() is Boss:
		boss = get_parent()
	
	boss_remnant_manager.starting_position = global_position
	boss_attack_manager.boss_remnant_manager = boss_remnant_manager

func set_health(max_health : int):
	health = max_health
	enemy_health.init(max_health)

func start_bossfight():
	boss_remnant_manager.init()
	print("start bossfight")
	state_chart.send_event("start_bossfight")
	
func _on_intro_state_entered() -> void:
	if Globals.skip_cutscenes:
		start_bossfight()
#endregion

func _exit_tree() -> void:
	SignalBus.changed_time.disconnect(player_changed_time_handling)

func _process(delta: float) -> void:
	if Globals.paused:
		return
	decrement_time_change_timer(delta)

#region UI
func hide_ui():
	enemy_health.hide()
	boss_stunned.hide()
#endregion

#region Movement
func start_navigation(distance_to_player : float):
	is_navigating = true
	navigation_distance = distance_to_player
	_calculate_navigation_target()
	navigation_agent_2d.target_desired_distance = navigation_distance
	navigation_agent_2d.target_position = navigation_target_position
	timer.start()

func _calculate_navigation_target():
	navigation_target_position = boss.get_closest_player_base_position()

func _on_timer_timeout() -> void:
	_calculate_navigation_target()
	if navigation_agent_2d.target_position != navigation_target_position:
		navigation_agent_2d.target_position = navigation_target_position
	timer.start()

func navigate():
	if Globals.paused or SceneChanger.mid_change or SceneChanger.mid_animation:
		boss.sprite_direction = Vector2.ZERO
		return
	if not is_navigating:
		boss.sprite_direction = Vector2.ZERO
		boss.motion_velocity = Vector2.ZERO
	elif navigation_agent_2d.is_target_reachable() and not navigation_agent_2d.is_target_reached():
		var direction : Vector2 = boss.to_local(navigation_agent_2d.get_next_path_position()).normalized()
		boss.sprite_direction = direction
		boss_remnant_manager.append_movement(direction)
		boss.motion_velocity = direction * boss.speed

func _on_navigation_agent_2d_target_reached() -> void:
	state_chart.send_event("attack_prepared")

func end_navigation():
	timer.stop()
	is_navigating = false
	boss.motion_velocity = Vector2.ZERO
#endregion

#region Attack
func _on_attack_state_entered() -> void:
	enable_force_field()

func _on_between_attacks_state_entered() -> void:
	_reactivate_timeshifting()

func _on_randomize_attack_state_entered() -> void:
	boss_attack_manager.randomize_attack()
	
	state_chart.send_event("attack_randomized")

func _on_prepare_attack_state_entered() -> void:
	boss_attack_manager.start_prepare()

func _on_prepare_attack_state_processing(delta: float) -> void:
	if Globals.paused:
		return
	boss_attack_manager.prepare(delta)

func _on_prepare_attack_state_exited() -> void:
	boss_attack_manager.end_prepare()

func _on_perform_attack_state_entered() -> void:
	boss_can_change_time = false
	boss_attack_manager.start_perform()

func _on_perform_attack_state_processing(delta: float) -> void:
	if Globals.paused:
		return
	boss_attack_manager.perform(delta)

func _on_perform_attack_state_exited() -> void:
	boss_attack_manager.end_perform()

func _on_attack_cooldown_state_entered() -> void:
	boss_attack_manager.start_cooldown()

func _on_attack_cooldown_state_processing(delta: float) -> void:
	if Globals.paused:
		return
	boss_attack_manager.cooldown(delta)
#endregion

#region Stunned
func get_stunned(duration : float = 1.0):
	boss_stunned.init(duration)
	boss_stunned.show()
	_stunned_timer = duration
	state_chart.send_event("get_stunned")

func _on_stunned_state_entered() -> void:
	boss_remnant_manager.stunned_start()
	disable_force_field()

func _on_stunned_state_processing(delta: float) -> void:
	if Globals.paused:
		return
	if _stunned_timer > 0:
		_stunned_timer -= delta
		boss_stunned.set_absolute(_stunned_timer)
		if _stunned_timer <= 0:
			boss_stunned.hide()
			boss_remnant_manager.stunned_end()
			enable_force_field()
			state_chart.send_event("stunned_timer_over")

func enable_force_field():
	force_field.show()
	force_field.process_mode = Node.PROCESS_MODE_INHERIT

func disable_force_field():
	force_field.hide()
	force_field.process_mode = Node.PROCESS_MODE_DISABLED
#endregion

#region Health and Phase Management
func take_damage(damage : int):
	health -= damage
	enemy_health.set_relative(-damage)
	AudioManager.create_audio(SoundEffect.SOUND_EFFECT_TYPE.GET_HIT)
	if health <= 0:
		state_chart.send_event("boss_down")

func _on_die_state_entered() -> void:
	boss_remnant_manager.nothing()
	boss_remnant_manager.pause_recording()
	boss.phases -= 1
	if boss.phases <= 0:
		boss.boss_fight_cutscenes.end_cutscene()
	else:
		new_phase_loading = true
		boss.boss_fight_cutscenes.mid_cutscene()

func _on_die_state_exited() -> void:
	enable_force_field()

func reset_for_phase():
	new_phase_loading = false
	boss_can_change_time = true
	time_change_pending = false
	player_in_same_time = true
	boss_stunned.hide()
#endregion

#region TimeTravel
func _reactivate_timeshifting():
	boss_can_change_time = true
	if time_change_pending:
		state_chart.send_event("timeshift")
		time_change_pending = false

func _handle_timeshift():
	if boss_can_change_time:
		state_chart.send_event("timeshift")
	else:
		time_change_pending = true

func player_changed_time_handling():
	if new_phase_loading:
		return
	player_in_same_time = not player_in_same_time
	if not player_in_same_time:
		if boss_can_change_time:
			state_chart.send_event("wait")
		_time_travel_timer = time_travel_timer
	else:
		state_chart.send_event("stop_waiting")
		time_change_pending = false

func decrement_time_change_timer(delta : float):
	if player_in_same_time:
		return
	if _time_travel_timer > 0:
		_time_travel_timer -= delta
		if _time_travel_timer <= 0:
			_handle_timeshift()

func _on_change_time_state_entered() -> void:
	boss_can_change_time = false
	boss_remnant_manager.time_shift()
	player_in_same_time = true
	boss.time_travel()
	
#endregion

#region Anchored Remnant
func save_state():
	anchor_state = {
		"_time_travel_timer" = _time_travel_timer,
		"boss_can_change_time" = boss_can_change_time,
		"time_change_pending" = time_change_pending,
		"player_in_same_time" = player_in_same_time,
		"navigation_target_position" = navigation_target_position,
		"navigation_distance" = navigation_distance,
		"is_navigating" = is_navigating,
		"_stunned_timer" = _stunned_timer,
	}
	anchor_statechart_state = SavedState.new()
	state_chart._state._state_save(anchor_statechart_state)
	boss_attack_manager.save_state()

func reload_state():
	boss_attack_manager.reload_state()
	_time_travel_timer = anchor_state.get("_time_travel_timer", _time_travel_timer)
	boss_can_change_time = anchor_state.get("boss_can_change_time", boss_can_change_time)
	time_change_pending = anchor_state.get("time_change_pending", time_change_pending)
	player_in_same_time = anchor_state.get("player_in_same_time", player_in_same_time)
	navigation_target_position = anchor_state.get("navigation_target_position", navigation_target_position)
	navigation_distance = anchor_state.get("navigation_distance", navigation_distance)
	is_navigating = anchor_state.get("is_navigating", is_navigating)
	_stunned_timer = anchor_state.get("_stunned_timer", _stunned_timer)
	if _stunned_timer > 0:
		boss_stunned.show()
		boss_stunned.set_absolute(_stunned_timer)
	
	state_chart._state._state_restore(anchor_statechart_state)
	boss_remnant_manager.start_recording()
#endregion
