class_name BossRemnant
extends CharacterBody2D

@onready var boss_attack_manager: BossAttackManager = $BossAttackManager
var map_loader : MapLoader
@onready var force_field: StaticBody2D = $ForceField
@onready var animation_player: AnimationPlayer = $AnimationPlayer

var recording : Array[BossRecording]
var attack_recording : PackedInt32Array
var direction_recording : PackedVector2Array

var is_replaying : bool = false
var current_replay_id : int = 0
var replay_time : float = 0
var current_attack_id : int = 0
var current_direction_id : int = 0
var stunned : bool = false

enum ATTACK_STATE {PREPARE, PERFORM, COOLDOWN}
var current_attack_state : ATTACK_STATE
var attacking : bool = false

var anchor_state : Dictionary = {}

var bodies : Array

var speed : float = 0
var motion_velocity : Vector2
var sprite_direction : Vector2

func _ready() -> void:
	animation_player.play("RESET")
	SignalBus.set_anchor.connect(_player_set_anchored_remnant)
	SignalBus.reload_anchor.connect(_player_reload_anchored_remnant)
	SignalBus.boss_fight_ended.connect(die)

func _physics_process(_delta: float) -> void:
	if Globals.paused or SceneChanger.mid_change or SceneChanger.mid_animation:
		return
	velocity = motion_velocity
	move_and_slide()

func _process(delta: float) -> void:
	if Globals.paused or SceneChanger.mid_change or SceneChanger.mid_animation:
		return
	if is_replaying:
		replay_time += delta
		_replay()
	if attacking:
		match current_attack_state:
			ATTACK_STATE.PREPARE:
				boss_attack_manager.prepare(delta)
			ATTACK_STATE.PERFORM:
				boss_attack_manager.perform(delta)
			ATTACK_STATE.COOLDOWN:
				boss_attack_manager.cooldown(delta)

#region Replay
func init(s : float, ml : MapLoader, r : Array[BossRecording], ar : PackedInt32Array, dr : PackedVector2Array):
	speed = s
	map_loader = ml
	recording = r
	attack_recording = ar
	direction_recording = dr
	start_replaying()

func start_replaying():
	is_replaying = true

func _replay():
	while current_replay_id >= 0 and current_replay_id < recording.size() and replay_time >= recording[current_replay_id].timestamp:
		_handle_action(recording[current_replay_id].action)
		current_replay_id += 1

func _handle_action(action : Utils.BOSS_ACTIONS = Utils.BOSS_ACTIONS.NOTHING):
	if action == Utils.BOSS_ACTIONS.MOVEMENT:
		if current_direction_id > 0 and current_direction_id < direction_recording.size():
			sprite_direction = direction_recording[current_direction_id].normalized()
			motion_velocity = direction_recording[current_direction_id] * speed
		current_direction_id += 1
	else:
		motion_velocity = Vector2.ZERO
	match action:
		Utils.BOSS_ACTIONS.PREPARE_ATTACK:
			attacking = true
			print("prepare_attack")
			var attack_id = attack_recording.get(current_attack_id)
			current_attack_id += 1
			boss_attack_manager.select_attack(attack_id)
			current_attack_state = ATTACK_STATE.PREPARE
			boss_attack_manager.start_prepare()
		Utils.BOSS_ACTIONS.PERFORM_ATTACK:
			attacking = true
			if current_attack_state == ATTACK_STATE.PREPARE:
				boss_attack_manager.end_prepare()
			current_attack_state = ATTACK_STATE.PERFORM
			boss_attack_manager.start_perform()
		Utils.BOSS_ACTIONS.ATTACK_COOLDOWN:
			attacking = true
			if current_attack_state == ATTACK_STATE.PERFORM:
				boss_attack_manager.end_perform()
			current_attack_state = ATTACK_STATE.COOLDOWN
			boss_attack_manager.start_cooldown()
		Utils.BOSS_ACTIONS.TIME_SHIFT_TO_PAST:
			attacking = false
			timeshift(Utils.TIME.PAST)
		Utils.BOSS_ACTIONS.TIME_SHIFT_TO_PRESENT:
			attacking = false
			timeshift(Utils.TIME.PRESENT)
		Utils.BOSS_ACTIONS.STUNNED_START:
			attacking = false
			stunned_start()
		Utils.BOSS_ACTIONS.STUNNED_END:
			attacking = false
			stunned_end()
		Utils.BOSS_ACTIONS.NOTHING:
			print("----NOTHING ACTION----")
			stunned_end()
			is_replaying = false
	if not attacking:
		match current_attack_state:
			ATTACK_STATE.PREPARE:
				boss_attack_manager.end_prepare()
			ATTACK_STATE.PERFORM:
				boss_attack_manager.end_perform()
#endregion

#region Behavior
func timeshift(time : Utils.TIME):
	force_field.hide()
	animation_player.play("timeshift")
	if map_loader:
		global_position.y += map_loader.get_timeshift_offset(time)
	await animation_player.animation_finished
	force_field.show()

func stunned_start():
	stunned = true
	disable_force_field()

func stunned_end():
	stunned = false
	enable_force_field()

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

func take_damage(damage : int):
	SignalBus.boss_change_health_relative(damage)

func die():
	if is_inside_tree():
		await get_tree().create_timer(1).timeout #animation placeholder
	queue_free()

#endregion

#region Anchored Remnant
func _player_set_anchored_remnant():
	anchor_state = {
		"replay_time" = replay_time,
		"current_replay_id" = current_replay_id,
		"global_position" = global_position,
		"attacking" = attacking,
		"current_attack_state" = current_attack_state,
		"current_attack_id" = current_attack_id,
		"stunned" = stunned,
		"current_direction_id" = current_direction_id,
	}

func _player_reload_anchored_remnant():
	replay_time = anchor_state.get("replay_time", replay_time)
	current_replay_id = anchor_state.get("current_replay_id", current_replay_id)
	global_position = anchor_state.get("global_position", global_position)
	attacking = anchor_state.get("attacking", attacking)
	current_attack_state = anchor_state.get("current_attack_state", current_attack_state)
	current_attack_id = anchor_state.get("current_attack_id", current_attack_id)
	stunned = anchor_state.get("stunned", stunned)
	if stunned:
		disable_force_field()
	else:
		enable_force_field()
	current_direction_id = anchor_state.get("current_direction_id", current_direction_id)
#endregion


func _on_area_2d_body_shape_entered(_body_rid: RID, body: Node2D, _body_shape_index: int, _local_shape_index: int) -> void:
	if body is Boss or body is BossRemnant and not body == self and not bodies.has(body):
		bodies.append(body)

func _on_area_2d_body_shape_exited(_body_rid: RID, body: Node2D, _body_shape_index: int, _local_shape_index: int) -> void:
	if body is Boss or body is BossRemnant and bodies.has(body):
		bodies.erase(body)
	if bodies.is_empty():
		set_collision_layer_value(1, true)
		set_collision_layer_value(3, false)
