extends CharacterBody3D


const SPEED = 5.0
const JUMP_VELOCITY = 4.5

@export var _projectile_scene: PackedScene

@export var _projectile_holder: Node
@export var _projectile_point: Marker3D
@export var _camera_pivot: Node3D
@export var _camera: Camera3D
@export var _line_of_sight: ShapeCast3D
@export var _shot_sound: AudioStreamPlayer3D

@export var id: int
@export var input_dir := Vector2()
@export var input_jumped := false

var _max_speed := 12
var _mouse_sensitivity := 0.008  # radians/pixel

var _projectile_speed := 12.0
## Could be only one thing at a time to consider.
var _interaction_selection: Node3D

var controls_disabled := false
var held_thing: String

# What the others see.
func _init_bystander() -> void:
	$Nickname.text = GameState.fetch().player_data[id].username
	$Nickname.show()


func _ready() -> void:
	if id != multiplayer.get_unique_id():
		_init_bystander()
		return
	
	$Model.hide()
	_camera.make_current()
	
	var spawn_point: Marker3D = get_tree().get_nodes_in_group("spawn_points").pick_random()
	global_position = spawn_point.global_position
	reset_physics_interpolation()


func _process(_delta: float) -> void:
	if id != multiplayer.get_unique_id():
		return
	
	_process_input()


func _physics_process(delta: float) -> void:
	## Process interactivity selection.
	if id == multiplayer.get_unique_id():
		var collider: Object = null
		if held_thing == "" and _line_of_sight.is_colliding():
			collider = _line_of_sight.get_collider(0)
		if collider != _interaction_selection:
			if _interaction_selection != null:
				_interaction_selection.get_parent().mark_non_interactive()
			if collider != null:
				collider.get_parent().mark_interactive()
		_interaction_selection = collider

	# Add the gravity.
	if not is_on_floor():
		velocity += get_gravity() * delta
	
	# Handle jump.
	if input_jumped and is_on_floor():
		velocity.y = JUMP_VELOCITY
	
	# Get the input direction and handle the movement/deceleration.
	# As good practice, you should replace UI actions with custom gameplay actions.
	var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
	if direction:
		velocity.x = direction.x * SPEED
		velocity.z = direction.z * SPEED
	else:
		velocity.x = move_toward(velocity.x, 0, SPEED)
		velocity.z = move_toward(velocity.z, 0, SPEED)
	
	move_and_slide()


@rpc("any_peer", "call_local", "reliable")
func hold_thing() -> void:
	held_thing = "water"
	_camera_pivot.get_node("HeldViewmodel").show()


@rpc("any_peer", "call_local", "reliable")
func throw_thing() -> void:
	held_thing = ""
	_camera_pivot.get_node("HeldViewmodel").hide()


func _unhandled_input(event: InputEvent) -> void:
	if controls_disabled or id != multiplayer.get_unique_id():
		return
	
	if event is InputEventMouseMotion:
		rotate_y(-event.relative.x * _mouse_sensitivity)
		_camera_pivot.rotate_x(-event.relative.y * _mouse_sensitivity)
		_camera_pivot.rotation.x = clamp(_camera_pivot.rotation.x, -1.2, 1.2)
		return
	
	if event.is_action_pressed("pick") and held_thing == "":
		if _interaction_selection != null:
			_interaction_selection.get_parent().get_picked_up.rpc()
			hold_thing.rpc()
	
	if event.is_action_pressed("fire") and held_thing != "":
		var new_projectile: Node3D = _projectile_scene.instantiate()
		_projectile_holder.add_child(new_projectile, true)
		_set_projectile_authority.rpc(new_projectile.get_path(), id)
		new_projectile.set_global_pos.rpc(_projectile_point.global_position)
		new_projectile.sender_body = self
		new_projectile.sender_id = id
		
		new_projectile.body.process_mode = Node.PROCESS_MODE_INHERIT
		new_projectile.body.linear_velocity = -_camera.global_basis.z * _projectile_speed
		
		_shot_sound.play()
		throw_thing.rpc()


@rpc("authority", "call_local", "reliable")
func _set_projectile_authority(path: NodePath, authority_id: int) -> void:
	if not _projectile_holder.has_node(path):
		return
	
	_projectile_holder.get_node(path).set_multiplayer_authority(authority_id)


func _process_input() -> void:
	if controls_disabled:
		input_dir = Vector2.ZERO
		input_jumped = false
		return
	
	input_dir = Input.get_vector("strafe_left", "strafe_right", "move_forward", "move_backward")
	input_jumped = Input.is_action_just_pressed("jump")