extends CharacterBody3D

@export var walk_speed: float = 5.0
@export var push_speed: float = 2.5
@export var rotation_speed: float = 0.1
@onready var current_speed: float = walk_speed
@export var push_force: float = 20.0
var pushable: Node = null
var push_vector: Vector3
var push_distance: float

# Gameover vars
var player_died : bool = false
var player_won : bool = false

# Child node references
@onready var area = $Area3D
@onready var anim = $AnimationPlayer
@onready var footstep_audio = $FootstepAudioPlayer
@onready var footstep_timer = $FootstepTimer

enum CharacterState {
	IDLE,
	WALKING,
	DYING,
	PUSHING,
	WINNING,
}

func handle_animation(_state: CharacterState) -> void:
	var anim_name: String
	match _state:
		CharacterState.IDLE: anim_name = "PlayerLibrary/IdlePose"
		CharacterState.WALKING: anim_name = "PlayerLibrary/Walk"
		CharacterState.DYING: anim_name = "PlayerLibrary/DeathAnim"
		CharacterState.PUSHING: anim_name = "PlayerLibrary/PushAnim"
		CharacterState.WINNING: anim_name = "PlayerLibrary/IdlePose"

	if anim.current_animation != anim_name:
		anim.play(anim_name)

func player_win():
	player_won = true
	handle_animation(CharacterState.WINNING)

func player_death():
	player_died = true
	# speed up the death animation, it's really slow
	anim.speed_scale = 2.0
	handle_animation(CharacterState.DYING)

	await get_tree().create_timer(4.3333 / 2.0).timeout
	get_tree().reload_current_scene()

func _physics_process(delta: float) -> void:
	if player_died or player_won:
		return

	# Kill the player when they press R to prevent softlocks
	if Input.is_action_just_pressed("reset"):
		player_death()
		return

	# Player input
	var input_dir := Input.get_vector("move_left", "move_right", "move_up", "move_down")
	var direction := Vector3(input_dir.x, 0, input_dir.y)

	if direction == Vector3.ZERO:
	# If not inputting, stop player
		velocity = Vector3.ZERO
		handle_animation(CharacterState.IDLE)

		# Stop footstep timer while idle
		footstep_timer.start(1e-8)
		footstep_timer.paused = true
	else:
		# Handle walking and pushing
		footstep_timer.paused = false
		velocity = direction * current_speed

		# Rotate toward movement direction
		var target_rotation_y = atan2(direction.x, direction.z)
		rotation.y = lerp_angle(rotation.y, target_rotation_y, rotation_speed * delta)

		# If there is something to push, push it
		if pushable:
			var dist = (pushable.global_position - global_position).length()
			if dist < push_distance:
				var push_velocity = (push_distance - dist) * push_force
				pushable.linear_velocity = push_vector * min(push_velocity, 2 * push_speed)


		# Apply movement
		move_and_slide()

		# Handle animation
		if pushable:
			handle_animation(CharacterState.PUSHING)
		else:
			handle_animation(CharacterState.WALKING)

		# Play footsteps
		if footstep_timer.time_left <= 0:
			footstep_audio.pitch_scale = randf_range(0.8, 1.2)
			footstep_audio.play()

			# timer is different for pushing animation since it's slower
			if pushable:
				footstep_timer.start(2.6333 / 2.0)
			else:
				footstep_timer.start(0.8667 / 2.0)

func _on_area_3d_body_entered(body: Node3D) -> void:
	if body.is_in_group("Pushable"):
		current_speed = push_speed
		pushable = body

		var diff = pushable.global_position - global_position

		# Snap to nearest cardinal direction
		if abs(diff.x) > abs(diff.z):
			push_vector = Vector3(signf(diff.x), 0, 0)
		else:
			push_vector = Vector3(0, 0, signf(diff.z))

		var shape = body.get_node("CollisionShape3D").shape
		if shape is BoxShape3D:
			push_distance = 1.0 + abs(shape.size.dot(push_vector) / 2.0)
		elif (
			shape is SphereShape3D
			or shape is CapsuleShape3D
			or shape is CylinderShape3D
		):
			push_distance = 1.0 + shape.radius
		else:
			push_error("Invalid shape: %s. Pushables can only be boxes, spheres, caapsules, or colliders." % [shape])

		# Add a small buffer since codey's push animation is aa bit large
		push_distance += 0.1

func _on_area_3d_body_exited(body: Node3D) -> void:
	if body.is_in_group("Pushable"):
		current_speed = walk_speed
		pushable = null
