extends CharacterBody2D

class_name Player

@export_file('*.tscn') var LOSE_SCREEN;

const SPEED = 100.0
const JUMP_VELOCITY = 200.0
const APNEA_DURATION = 10
const DURATION_BEFORE_LOSE_SCREEN = 2



const STATES= [
	"climb",
	"die",
	"idle",
	"jump",
	"spawn",
	"walk",
	"swim"
	]

var use_gravity = {
	"climb": false,
	"die": true,
	"idle": true,
	"jump": true,
	"spawn": true,
	"walk": true,
	"swim": true
}

var can_grab_obstacles = {
	"climb": true,
	"die": false,
	"idle": true,
	"jump": false,
	"spawn": false,
	"walk": true,
	"swim": true
}


@onready var grab_ray = get_node('oriented_container/grab_ray')
@onready var sprite = get_node('oriented_container/sprite')

# Get the gravity from the project settings to be synced with RigidBody nodes.
var WORLD_GRAVITY = ProjectSettings.get_setting("physics/2d/default_gravity")
var gravity = WORLD_GRAVITY

var grabbed_obstacle = null

func _ready():
	$fsm.set_states(STATES)
	add_to_group("player")
	g_game_state.restore_checkpoint.connect(on_restore_checkpoint)

func _exit_tree():
	g_game_state.restore_checkpoint.disconnect(on_restore_checkpoint)

func on_enter_spawn_state():
	$fsm.set_next_state("idle")

func on_enter_idle_state():
	pass

func idle_state(_delta):
	var direction = Input.get_axis("player_left", "player_right")

	if is_on_floor():
		velocity.x = 0

	if direction:
		$fsm.set_next_state("walk")
	elif Input.is_action_just_pressed("player_jump") and is_on_floor():
		$fsm.set_next_state("jump")

func walk_state(_delta):
	var direction = Input.get_axis("player_left", "player_right")

	if direction != 0:
		velocity.x = direction * SPEED
	else:
		velocity.x = 0
		$fsm.set_next_state("idle")

	if Input.is_action_just_pressed("player_jump") and is_on_floor():
		$fsm.set_next_state("jump")

	if is_on_wall() or (Input.is_action_pressed('player_up') && is_on_ceiling()):
		velocity.y = 0
		$fsm.set_next_state("climb")

func on_enter_jump_state():
	if is_on_floor():
		velocity.y = -JUMP_VELOCITY

func jump_state(_delta):
	var direction = Input.get_axis("player_left", "player_right")

	if(direction):
		velocity.x = move_toward(velocity.x, direction * SPEED, SPEED)


	if is_on_floor():
		if direction:
			$fsm.set_next_state("walk")
		else:
			velocity.x = 0
			$fsm.set_next_state("idle")

	if is_on_wall() || (is_on_ceiling() ):
		$fsm.set_next_state("climb")

func on_enter_climb_state():
	velocity = Vector2(0, 0)

func climb_state(_delta):
	if is_on_wall():
		var direction = Input.get_axis("player_up", "player_down")
		velocity = Vector2(0, direction * SPEED)

	if is_on_ceiling():
		var direction = Input.get_axis("player_left", "player_right")
		velocity = Vector2(direction * SPEED, -1)

	if Input.is_action_just_pressed("player_jump") and is_on_wall():
		var wall_position = get_slide_collision(0).get_normal().x
		var jump_normal = Vector2(wall_position, -0.5).normalized()
		velocity = jump_normal * JUMP_VELOCITY

		$fsm.set_next_state("jump")
	if is_on_ceiling() && Input.is_action_just_pressed("player_down"):
		velocity.y = 0
		$fsm.set_next_state("idle")

	if !is_on_wall() && !is_on_ceiling():
		$fsm.set_next_state("idle")


func after_state(delta):
	move_and_slide()
	if is_dead():
		return

	if use_gravity[$fsm.current_state] == true:
		handle_gravity(delta)

	if can_grab_obstacles[$fsm.current_state] == true:
		handle_grab()

	handle_sprite_orientation()

func handle_gravity(delta):
	var gravity_delta = gravity
	if velocity.y > 0:
		if Input.is_action_pressed('player_jump'):
			gravity_delta = WORLD_GRAVITY / 10
		else:
			gravity_delta = WORLD_GRAVITY

	velocity.y += gravity_delta * delta

func handle_grab():
	if Input.is_action_just_pressed('player_action'):
		if grabbed_obstacle == null:
			var collision = grab_ray.get_collider()

			if collision:
				var obstacle = collision
				if obstacle.is_in_group("obstacle"):
					do_grab(obstacle)
		else:
			drop_obstacle()

func do_grab(obstacle):
	# BUGFIX : When grabbing a rock, allows other rocks to fall
	for ob in get_tree().get_nodes_in_group("obstacle"):
		if ob.has_method("set_sleeping"):
			ob.sleeping = false

	grabbed_obstacle = obstacle
	obstacle.grabbed($oriented_container)


func drop_obstacle():
	grabbed_obstacle.dropped()
	grabbed_obstacle = null

func handle_sprite_orientation():
	if is_on_wall_only():
		if velocity.y != 0:
			$oriented_container.rotation = PI/2 if (velocity.y > 0) else - PI / 2
			var wall_position = get_slide_collision(0).get_position().x
			var player_position = $oriented_container.get_global_position().x
			var should_flip_with_wall_left = (wall_position > player_position) && (velocity.y > 0)
			var should_flip_with_wall_right = (wall_position < player_position) && (velocity.y < 0)
			sprite.flip_v = should_flip_with_wall_left || should_flip_with_wall_right
			sprite.flip_h = false


	elif is_on_ceiling_only():
		if velocity.x != 0:
			$oriented_container.rotation = 0 if (velocity.x > 0) else PI
			sprite.flip_v = velocity.x > 0
			sprite.flip_h = false
	else:
		if velocity.x != 0:
			$oriented_container.rotation = 0 if (velocity.x > 0) else PI
			sprite.flip_v = velocity.x < 0
			sprite.flip_h = false

func on_enter_swim_state():
	sprite.modulate = Color(0, 0, 1)
	gravity = WORLD_GRAVITY / 2

func swim_state(_delta):
	var direction_x = Input.get_axis("player_left", "player_right")
	var direction_y = Input.get_axis("player_up", "player_down")
	velocity = Vector2(direction_x, direction_y).normalized() * SPEED

	var progress_to_black = 1 - clamp($fsm.state_duration / APNEA_DURATION, 0, 1)
	sprite.modulate = Color(progress_to_black, progress_to_black, progress_to_black )

	if $fsm.state_duration >= APNEA_DURATION:
		die()

func on_exit_swim_state():
	sprite.modulate = Color(1, 1, 1)
	gravity = WORLD_GRAVITY
	sprite.modulate = Color(1,1,1)

func swim():
	if is_dead():
		return

	$fsm.set_next_state("swim")

func unswim():
	if is_dead():
		return

	$fsm.set_next_state("idle")

func die():
	$fsm.set_next_state("die")

func is_dead():
	return $fsm.current_state == "die"

func on_enter_die_state():
	velocity = Vector2(0, 0)
	$shape.disabled = true
	$oriented_container.rotation = 0
	sprite.flip_v = true


func die_state(_delta):
	if $fsm.state_duration > DURATION_BEFORE_LOSE_SCREEN:
		get_tree().change_scene_to_file(LOSE_SCREEN)

func on_restore_checkpoint(_level_rising):
	global_position = g_game_state.player_position_at_checkpoint
