extends CharacterBody3D

@export var speed = 5.0
@export var jump_strength = 5.0

@onready var display_name := $DisplayNameLabel3D as Label3D
@onready var input := $Input as ExampleRollbackFPS.PlayerInput
@onready var tick_interpolator := $TickInterpolator as TickInterpolator
@onready var head := $Head as Node3D
@onready var camera := $Head/Camera3D as Camera3D
@onready var hit_sfx := $"Hit SFX" as AudioStreamPlayer3D
#+#
static var _logger := NetfoxLogger.new("game", "Player")

var gravity = ProjectSettings.get_setting(&"physics/3d/default_gravity")
var health: int = 100
var death_tick: int = -1
var respawn_position: Vector3
var did_respawn := false
var deaths := 0

# Track deaths and *acknowledged* deaths
# Acknowledge the number of deaths on tick loop start
# If the value changes by the end of the loop, that means the player has
# respawned, and needs to `teleport()`
var _ackd_deaths := 0

var _was_hit := false

func _ready():
	display_name.text = name

	NetworkTime.before_tick_loop.connect(_before_tick_loop)
	NetworkTime.after_tick_loop.connect(_after_tick_loop)

	# Wait for deps to setup
	await get_tree().process_frame
	if input.is_multiplayer_authority():
		camera.current = true
		ExampleRollbackFPS.HUD.set_player(self)

func _before_tick_loop():
	_ackd_deaths = deaths

func _after_tick_loop():
	if _ackd_deaths != deaths:
		tick_interpolator.teleport()
		_ackd_deaths = deaths

	if _was_hit:
		hit_sfx.play()
		_was_hit = false

func _rollback_tick(delta: float, tick: int, is_fresh: bool) -> void:
	# Handle respawn
	if tick == death_tick:
		global_position = respawn_position
		did_respawn = true
	else:
		did_respawn = false

	# Gravity
	_force_update_is_on_floor()
	if is_on_floor():
		if input.jump:
			velocity.y = jump_strength
	else:
		velocity.y -= gravity * delta

	# Handle look left and right
	rotate_object_local(Vector3(0, 1, 0), input.look_angle.x)

	# Handle look up and down
	head.rotate_object_local(Vector3(1, 0, 0), input.look_angle.y)

	head.rotation.x = clamp(head.rotation.x, -1.57, 1.57)
	head.rotation.z = 0
	head.rotation.y = 0

	# Apply movement
	var input_dir = input.movement
	var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.z)).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 assumes physics delta
	# multiplying velocity by NetworkTime.physics_factor compensates for it
	velocity *= NetworkTime.physics_factor
	move_and_slide()
	velocity /= NetworkTime.physics_factor

	# Handle death
	if health <= 0:
		deaths += 1
		global_position = get_parent().get_next_spawn_point(get_player_id(), deaths)
		health = 100

func _force_update_is_on_floor():
	var old_velocity = velocity
	velocity = Vector3.ZERO
	move_and_slide()
	velocity = old_velocity

func damage(amount: int, is_new_hit: bool = false):
	# Queue hit sound
	if is_new_hit:
		_was_hit = true

	health -= amount
	_logger.info("%s HP now at %s", [name, health])

func get_player_id() -> int:
	return input.get_multiplayer_authority()
