class_name Player extends CharacterBody3D

#region Exported Properties

@export_category("Movement")
@export_group("Speed")
@export var run_speed := 80.0
@export var sprint_speed := 160.0
@export var sneak_speed := 40.0
@export var focus_speed := 25.0
@export var air_speed_factor := 0.1

@export_group("Jump")
@export var jump_force := 4.0

@export_group("Friction")
@export var ground_friction := 0.3
@export var air_friction := 0.03

@export_category("Inventory")
@export var inventory: Dictionary[Item, int] = {}

#endregion

#region Member Variables

var gravity: Vector3 = (
	ProjectSettings.get_setting("physics/3d/default_gravity")
	* ProjectSettings.get_setting("physics/3d/default_gravity_vector")
)

var selected_interactive: Interactive
var firing := false
var sneaking := false

var _was_on_floor := false

@onready var hud: PlayerHUD = %PlayerHUD

@onready var camera_pivot: CameraController = %CameraPivot

@onready var interact_ray: RayCast3D = %InteractRay

@onready var tool_mount: ToolMount = %ToolMount
@onready var point_spray: PointSpray = %PointSpray
@onready var wide_spray: WideSpray = %WideSpray
@onready var toothbrush: Tool = %Toothbrush

@onready var crouch_head_area: Area3D = %CrouchHeadArea
@onready var crouch_animation: AnimationPlayer = %CrouchAnimation

@onready var jump_game_sound_emitter: GameSoundEmitter = %JumpGameSoundEmitter

#endregion

## Global static access to player singleton
static var instance: Player

#region _ready


func _ready() -> void:
	instance = self


#endregion

#region Public Methods


func get_speed() -> float:
	var speed := run_speed
	if Input.is_action_pressed("sprint"):
		speed = sprint_speed
	if sneaking:
		speed = sneak_speed
	if firing:
		speed = focus_speed
	if not is_on_floor():
		speed *= air_speed_factor
	return speed


func get_friction() -> float:
	if is_on_floor():
		return ground_friction
	return air_friction


func get_tool() -> Tool:
	return tool_mount.get_active()


## Add the given item to the player's inventory.
func add_item(item: Item, amount: int = 1) -> void:
	inventory[item] = inventory.get(item, 0) + amount


## Remove the given item from the player's inventory.
func remove_item(item: Item, amount: int = 1) -> void:
	inventory[item] = inventory.get(item, 0) - amount
	if inventory[item] <= 0:
		inventory.erase(item)


func crouch() -> void:
	if not sneaking and not crouch_animation.is_playing():
		crouch_animation.play("crouch")
		sneaking = true


func uncrouch() -> void:
	if (
		sneaking
		and not crouch_animation.is_playing()
		and not crouch_head_area.has_overlapping_bodies()
	):
		crouch_animation.play_backwards("crouch")
		sneaking = false


func toggle_crouch() -> void:
	if sneaking:
		uncrouch()
	else:
		crouch()


#endregion

#region _physics_process


func _physics_process(delta: float) -> void:
	# Will be null if no valid interactor is selected.
	var interactive: Interactive = interact_ray.get_collider() as Interactive
	hud.select_interactive(interactive)
	if interactive:
		interactive.select()

	# World interaction
	if interactive and Input.is_action_just_pressed("interact"):
		interactive.activate()

	# Tool selection
	if Input.is_action_just_pressed("select_next_tool"):
		tool_mount.set_active_relative(1)
	elif Input.is_action_just_pressed("select_prev_tool"):
		tool_mount.set_active_relative(-1)
	elif Input.is_action_just_pressed("select_point_spray"):
		tool_mount.set_active(point_spray)
	elif Input.is_action_just_pressed("select_wide_spray"):
		tool_mount.set_active(wide_spray)
	elif Input.is_action_just_pressed("select_brush"):
		tool_mount.set_active(toothbrush)

	# Tool use
	if Input.is_action_pressed("fire"):
		get_tool().fire()
		firing = true
	else:
		get_tool().idle()
		firing = false

	if Input.is_action_just_pressed("switch_mode"):
		get_tool().switch_mode()

	# Two sneaking modes -- hold and toggle
	if Game.settings.hold_to_sneak:
		if Input.is_action_pressed("sneak"):
			crouch()
		else:
			uncrouch()
	else:
		if Input.is_action_just_pressed("sneak"):
			toggle_crouch()

	if is_on_floor():
		if not _was_on_floor and not sneaking:
			# just landed
			jump_game_sound_emitter.emit_sound_here()
	else:
		# Gravity
		velocity += gravity * delta

	# Input movement
	var input_dir := Input.get_vector("move_left", "move_right", "move_forward", "move_back")
	var rel_input := input_dir.rotated(-camera_pivot.global_rotation.y)
	var direction := Vector3(rel_input.x, 0, rel_input.y).normalized()
	var movement := direction * get_speed() * delta
	velocity.x += movement.x
	velocity.z += movement.z
	if Input.is_action_just_pressed("jump") and is_on_floor():
		velocity.y = jump_force

	# Friction
	var friction := get_friction()
	velocity.x = lerpf(velocity.x, 0, friction)
	velocity.z = lerpf(velocity.z, 0, friction)

	_was_on_floor = is_on_floor()

	move_and_slide()

#endregion
