class_name Player
extends CharacterBody3D

const SPEED: float = 5.0
const JUMP_VELOCITY: float = 4.5

@onready var camera_x: Node3D = $ViewY/ViewX
@onready var spring_arm: SpringArm3D = $ViewY/ViewX/Arm
@onready var camera: Camera3D = $ViewY/ViewX/Arm/View

@onready var syringe: Node3D = $Syringe
@onready var needle: Area3D = $Syringe/SyringeNeedle

@onready var bag: Node3D = $Bag
@onready var captured: Node3D = $Bag/Captured

@onready var mesh: MeshInstance3D = $Mesh
@onready var collision: CollisionShape3D = $Collision

var hunter: bool = true
var caught_player: Player = null
var new_spawn: Vector3

var crouching: bool = false
var speed_penalty: float = 0.0

@export var health: int = 100

func _ready() -> void:
	if is_multiplayer_authority():
		camera.current = true
		($HUD as Control).visible = true
		Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
	if hunter:
		syringe.visible = true
		bag.visible = true
	else:
		needle.monitoring = false

func _physics_process(delta: float) -> void:
	# Zoom
	if Input.is_action_just_released("zoom_in") and camera.position.z > 0:
		spring_arm.spring_length -= 1
	elif Input.is_action_just_released("zoom_out"):
		spring_arm.spring_length += 1
	
	if is_multiplayer_authority():
		# Focus
		if Input.is_action_just_pressed("unfocus"):
			Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
		elif Input.is_action_just_pressed("focus"):
			Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
			pass
		
		if visible:
			# Gravity
			if not is_on_floor():
				velocity += get_gravity() * delta
			
			# Jumping
			if Input.is_action_just_pressed("jump") and is_on_floor() and !crouching:
				velocity.y = JUMP_VELOCITY
				if hunter:
					speed_penalty += 2.0
					if speed_penalty > SPEED - 1.0:
						speed_penalty = SPEED - 1.0
			
			# Crouching
			if Input.is_action_just_pressed("crouch") and is_on_floor() and !hunter:
				if crouching:
					mesh.rotation_degrees.x = 0.0
					collision.rotation_degrees.x = 0.0
					camera_x.position.y = 0.5
				else:
					mesh.rotation_degrees.x = -90.0
					collision.rotation_degrees.x = -90.0
					camera_x.position.y = 0.0
				crouching = !crouching
			
			# Movement
			var input_dir: Vector2 = Input.get_vector("move_left", "move_right", "move_forwards", "move_backwards")
			var direction: Vector3 = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
			
			velocity.x = direction.x * (SPEED - speed_penalty)
			velocity.z = direction.z * (SPEED - speed_penalty)
			
			# Hunter specific
			if hunter:
				if Input.is_action_pressed("stab"):
					syringe.position.z -= 0.1
				else:
					syringe.position.z += 0.1
				syringe.position.z = clampf(syringe.position.z, -0.8, -0.3)
			
			if speed_penalty > 0.0:
				speed_penalty -= delta / 3.0
				if speed_penalty <= 0.0:
					speed_penalty = 0.0
			
			if move_and_slide():
				pass

func _unhandled_input(event: InputEvent) -> void:
	# Camera Movement
	if event is InputEventMouseMotion:
		var mouse_event: InputEventMouseMotion = event
		if is_multiplayer_authority():
			rotate_y(deg_to_rad(-mouse_event.relative.x))
		else:
			($ViewY as Node3D).rotate_y(deg_to_rad(-mouse_event.relative.x))
		camera_x.rotate_x(deg_to_rad(-mouse_event.relative.y))
		camera_x.rotation.x = clampf(camera_x.rotation.x, -1.6, 1.6)

 # Gets called when the Player gets stabbed by the syringe.
func _on_stab(body: Node3D) -> void:
	if multiplayer.is_server() and body is Player and body.visible == true:
		var player: Player = body
		caught_player = player
		player.get_caught.rpc_id(player.get_multiplayer_authority(), camera.get_path())
		capture_player.rpc_id(get_multiplayer_authority(), player.get_path())
		($ReleaseTimer as Timer).start()

# Player gets released through the timer
func _on_release() -> void:
	if multiplayer.is_server():
		caught_player.go_free.rpc_id(caught_player.get_multiplayer_authority())
		released_player.rpc_id(get_multiplayer_authority())

# This is called on any player that was just stabbed/caught.
@rpc("any_peer", "call_local", "reliable")
func get_caught(spectator_camera_path: NodePath) -> void:
	if multiplayer.get_remote_sender_id() == 1:
		var spectator_camera: Camera3D = get_node(spectator_camera_path)
		visible = false
		($Collision as CollisionShape3D).disabled = true
		spectator_camera.current = true
		new_spawn = position

# This is called on any player when they go free.
@rpc("any_peer", "call_local", "reliable")
func go_free() -> void:
	if multiplayer.get_remote_sender_id() == 1:
		camera.current = true
		visible = true
		($Collision as CollisionShape3D).disabled = false
		position = new_spawn

# This is called on any player when they go free.
@rpc("any_peer", "call_local", "reliable")
func get_imprisoned(coffin_path: NodePath) -> void:
	if multiplayer.get_remote_sender_id() == 1:
		var coffin: Coffin = get_node(coffin_path)
		camera.current = true
		position = coffin.position
		new_spawn = (coffin.get_node("RespawnPoint") as Node3D).position

# This is called on any player that has just stabbed/caught another.
@rpc("any_peer", "call_local", "reliable")
func capture_player(player_path: NodePath) -> void:
	if multiplayer.get_remote_sender_id() == 1 and !caught_player:
		var player: Player = get_node(player_path)
		caught_player = player
		captured.visible = true

# This is called on any player that was just stabbed/caught.
@rpc("any_peer", "call_local", "reliable")
func released_player() -> void:
	if multiplayer.get_remote_sender_id() == 1:
		caught_player = null
		captured.visible = false

# Damages the player
@rpc("any_peer", "call_local", "reliable")
func damage_player() -> void:
	if multiplayer.get_remote_sender_id() == 1:
		health -= 2
		($HUD/Health as Label).text = "Health: " + str(health)
