class_name ViewMovement extends Node3D

@export var player: Hero

@export_category("Bobbing")
@export var camera: Camera3D
@export var focus_raycast: RayCast3D
@export var enable_head_bob: bool = true
@export var head_bob_max_effect_speed: float = 4.317
@export var head_bob_amplitude: float = 0.08
@export var head_bob_frequency: float = 8.0
@export var target_stabilisation: bool = false

@export_category("Strafe Tilting")
@export var enable_strafe_tilt: bool = false
@export var tilt_max_effect_speed: float = 4.317
@export var tilt_amount_x: float = 0.75
@export var tilt_amount_y: float = 0.0

var sample_point: float = 0.0
var speed_factor: float = 0.0

var trauma: float = 0.0
var trauma_recovery_speed: float = 0.7
var camera_shake: float = 0.0
var noise_sample: float = 0.0
var shake_speed: float = 50.0
var max_shake_angle: float = 45.0
var pitch_noise: FastNoiseLite = FastNoiseLite.new()
var yaw_noise: FastNoiseLite = FastNoiseLite.new()
var roll_noise: FastNoiseLite = FastNoiseLite.new()
var constant_trauma: float = 0.0


func _ready() -> void:
	pitch_noise.noise_type = FastNoiseLite.TYPE_PERLIN
	yaw_noise.noise_type = FastNoiseLite.TYPE_PERLIN
	roll_noise.noise_type = FastNoiseLite.TYPE_PERLIN
	pitch_noise.frequency = 0.03
	yaw_noise.frequency = 0.03
	roll_noise.frequency = 0.01
	var seed: int = randi()
	pitch_noise.seed = seed
	yaw_noise.seed = seed + 1
	roll_noise.seed = seed + 2


func _physics_process(delta: float) -> void:
	if enable_head_bob and player.is_on_floor():
		#TODO: maybe make the speed slower/faster on slopes?
		var player_speed: float = Vector2(player.velocity.x, player.velocity.z).length()
		speed_factor = lerp(speed_factor, player_speed / head_bob_max_effect_speed, 20.0 * delta)
		var test: float = 1.0 / 12.5
		sample_point += delta * head_bob_frequency * speed_factor
	else:
		speed_factor = lerp(speed_factor, 0.0, 20.0 * delta)


func _process(delta: float) -> void:
	#trauma = max(0.0, trauma - ((1.0 / trauma_recovery_speed) * delta))
	#camera_shake = pow(max(trauma, constant_trauma), 2.0)
	#noise_sample += shake_speed * delta
	#$Camera3D/ProgressBar.value = max(trauma, constant_trauma) * 100.0
	#$Camera3D/ProgressBar2.value = camera_shake * 100.0
	#if Input.is_action_just_pressed("damage"):
		#add_trauma()
	#if Input.is_action_just_pressed("big_damage"):
		#add_trauma(1.0)
	#if Input.is_action_just_pressed("increase_trauma"):
		#constant_trauma = min(1.0, constant_trauma + 0.1)
	#if Input.is_action_just_pressed("decrease_trauma"):
		#constant_trauma = max(0.0, constant_trauma - 0.1)
	var camera_translation: Vector3 = Vector3.ZERO
	var camera_tilt: Vector3 = Vector3.ZERO
	camera_translation = minecraft_translation(head_bob_amplitude * speed_factor)
	camera_tilt += minecraft_tilt(head_bob_amplitude * speed_factor)
	if target_stabilisation:
		camera.look_at(focus_target())
	if enable_strafe_tilt:
		camera_tilt += get_strafe_tilt(player.velocity)
	#var shake_vector: Vector3 = Vector3.ZERO
	#shake_vector.x = deg_to_rad(pitch_noise.get_noise_1d(noise_sample) * camera_shake * max_shake_angle)
	#shake_vector.y = deg_to_rad(yaw_noise.get_noise_1d(noise_sample) * camera_shake * max_shake_angle)
	#shake_vector.z = deg_to_rad(roll_noise.get_noise_1d(noise_sample) * camera_shake * max_shake_angle)
	#camera_tilt += shake_vector
	camera.rotation = camera_tilt
	translate_camera(camera_translation)


func hfov_to_vfov(hfov_degrees: float) -> void:
	return rad_to_deg(2.0 * atan(tan(deg_to_rad(hfov_degrees) / 2.0) * 9.0 / 16.0))


func minecraft_translation(amplitude: float) -> Vector3:
	return sample_u_shape(sample_point, amplitude, 0.7)


func minecraft_tilt(amplitude: float) -> Vector3:
	var pitch_angle: float = deg_to_rad(5)
	var roll_angle: float = deg_to_rad(2)
	var x: float = -abs(sin(sample_point + (PI / 2))) * pitch_angle * amplitude
	var z: float = sin(sample_point) * roll_angle * amplitude
	return Vector3(x, 0.0, z)


func sample_sine(sample: float, amplitude: float) -> Vector3:
	return Vector3(0.0, sin(sample) * amplitude, 0.0)


func sample_lemniscate(sample: float, amplitude: float, ratio: float = 1.0) -> Vector3:
	var y: float = sin(sample) * amplitude
	var x: float = (cos(sample / 2.0) * amplitude * 2.0) * ratio
	return Vector3(x, y, 0.0)


func sample_u_shape(sample: float, amplitude: float, ratio: float = 1.0) -> Vector3:
	var y: float = ((abs(sin(sample + (PI / 2.0)))) * amplitude) - (amplitude / 2.0)
	var x: float = sin(sample) * amplitude * ratio
	return Vector3(x, y, 0.0)


func translate_camera(offset: Vector3) -> void:
	var target_position: Vector3 = Vector3.ZERO
	target_position += global_transform.basis.x * offset.x
	target_position += Vector3.UP * offset.y
	target_position += global_transform.basis.z * offset.z
	camera.global_position = global_position + target_position


func focus_target() -> Vector3:
	if focus_raycast.is_colliding():
		return focus_raycast.get_collision_point()
	else:
		return focus_raycast.global_position + -focus_raycast.global_transform.basis.z * 30.0


func tilt_camera(tilt: Vector3, speed: float) -> void:
	camera.rotation.x = lerp(camera.rotation.x, tilt.x, speed)
	camera.rotation.y = lerp(camera.rotation.y, tilt.y, speed)
	camera.rotation.z = lerp(camera.rotation.z, tilt.z, speed)


func get_strafe_tilt(player_velocity: Vector3) -> Vector3:
	var side_dot: float = player_velocity.normalized().dot(-global_transform.basis.z)
	var front_dot: float = player_velocity.normalized().dot(-global_transform.basis.x)
	var speed_factor: float = player_velocity.length() / tilt_max_effect_speed
	var tilt_vector: Vector3 = Vector3.ZERO
	tilt_vector.z = deg_to_rad(tilt_amount_x * front_dot * speed_factor)
	tilt_vector.x = deg_to_rad(tilt_amount_y * -side_dot * speed_factor)
	return tilt_vector


func add_trauma(amount: float = 0.3) -> void:
	trauma = min(1.0, trauma + amount)
