class_name CameraEffects extends Camera3D

@export_category("References")
@export var player: PlayerController

@export_category("Effects")
@export var enable_tilt: bool = true
@export var enable_fall_kick: bool = true
@export var enable_damage_kick: bool = true
@export var enable_weapon_kick: bool = true
@export var enable_screen_shake: bool = true
@export var enable_head_bob: bool = true
@export_category("Kick & Recoil")
@export_group("Run Tilt")
@export var run_pitch: float = 0.1 # Degrees
@export var run_roll: float = 0.25 # Degrees
@export var max_pitch: float = 1.0 # Degrees
@export var max_roll: float = 2.5 # Degrees
@export_group("Camera Kick")
@export_subgroup("Fall Kick")
@export var fall_time: float = 0.3
@export_subgroup("Damage Kick")
@export var damage_time: float = 0.3
@export_subgroup("Weapon Kick")
@export var weapon_decay: float = 0.5
@export_subgroup("HeadBob")
@export_range(0.0, 0.1, 0.001) var bob_pitch: float = 0.05
@export_range(0.0, 0.1, 0.001) var bob_roll: float = 0.025
@export_range(0.0, 0.04, 0.001) var bob_up: float = 0.005
@export_range(3.0, 8.0, 0.1) var bob_frequency: float = 6.0

# fall kick private vars
var _fall_value: float = 0.0
var _fall_timer: float = 0.0

# damage kick private vars
var _damage_pitch: float = 0.0
var _damage_roll: float = 0.0
var _damage_timer: float = 0.0

# weapon kick private variable
var _weapon_kick_angle: Vector3 = Vector3.ZERO

# screen shake private variables
var _screen_shake_tween: Tween

# head bob private variables 
var _step_timer: float = 0.0

const MIN_SCREEN_SHAKE: float = 0.05
const MAX_SCREEN_SHAKE: float = 0.5

func _process(delta: float) -> void:
	calc_view_offset(delta)

func get_camera_center():
	return self.get_viewport().get_size() / 2

func calc_view_offset(delta: float):
	assert(player, "player is not attached to camera effects script")
	
	var offset = Vector3.ZERO
	var angles = Vector3.ZERO
	var velocity = player.velocity
	
	if enable_tilt:
		var forward = global_transform.basis.z
		var right = global_transform.basis.x
		
		var forward_dot = velocity.dot(forward)
		var forward_tilt = clampf(forward_dot * deg_to_rad(run_pitch), deg_to_rad(-max_pitch), deg_to_rad(max_pitch))
		
		var right_dot = velocity.dot(right)
		var side_tilt = clampf(right_dot * deg_to_rad(run_roll), deg_to_rad(-max_roll), deg_to_rad(max_roll))
		
		angles.z -= side_tilt
		angles.x += forward_tilt
	
	if enable_fall_kick:
		_fall_timer -= delta

		var fall_ratio = max(0.0, _fall_timer / fall_time)
		var fall_kick_amount = fall_ratio * _fall_value
		
		offset.y -= fall_kick_amount
		angles.x -= fall_kick_amount
	
	if enable_fall_kick:
		_damage_timer -= delta
		
		var damage_ratio = max(0.0, _damage_timer / damage_time)
		angles.x += damage_ratio * _damage_pitch
		angles.z += damage_ratio * _damage_roll
	
	if enable_weapon_kick:
		_weapon_kick_angle = _weapon_kick_angle.move_toward(Vector3.ZERO, weapon_decay * delta)
		angles += _weapon_kick_angle
	
	if enable_head_bob:
		var speed = Vector2(velocity.x, velocity.z).length()
	
		if speed > 0.1 and player.is_on_floor():
			_step_timer += delta * (speed / bob_frequency)
			_step_timer = fmod(_step_timer, 1.0)
		else:
			_step_timer = 0.0
		
		var bob_sin = sin(_step_timer * 2.0 * PI) * 0.5
		
		var pitch_delta = bob_sin * deg_to_rad(bob_pitch) * speed
		var roll_delta = bob_sin * deg_to_rad(bob_roll) * speed
		var height_delta = bob_sin * delta * bob_up
		
		angles.x -= pitch_delta
		angles.z -= roll_delta
		offset.y += height_delta
	
	self.position = offset
	self.rotation = angles


func add_fall_kick(fall_strength: float):
	_fall_value = deg_to_rad(fall_strength)
	_fall_timer = fall_time


func add_damage_kick(pitch: float, roll: float, source: Vector3):
	var forward = global_transform.basis.z
	var right = global_transform.basis.x
	var direction = global_position.direction_to(source)
	var forward_dot = direction.dot(forward)
	var right_dot = direction.dot(right)
	_damage_pitch = deg_to_rad(pitch) * forward_dot
	_damage_roll = deg_to_rad(roll) * right_dot
	_damage_timer = damage_time


func add_weapon_kick(pitch: float, yaw: float, roll: float):
	_weapon_kick_angle.x += deg_to_rad(pitch)
	_weapon_kick_angle.y += deg_to_rad(randf_range(-yaw, yaw))
	_weapon_kick_angle.z += deg_to_rad(randf_range(-roll, roll))


func add_screen_shake(amount: float, seconds: float) -> void:
	if _screen_shake_tween:
		_screen_shake_tween.kill()
	
	_screen_shake_tween = create_tween()
	_screen_shake_tween.tween_method(update_screen_shake.bind(amount), 0.0, 1.0, seconds).set_ease(Tween.EASE_OUT)


func update_screen_shake(alpha: float, amount: float) -> void:
	amount = remap(amount, 0.0, 1.0, MIN_SCREEN_SHAKE, MAX_SCREEN_SHAKE)
	var current_shake_amount = amount * (1.0 - alpha)
	h_offset = randf_range(-current_shake_amount, current_shake_amount)
	v_offset = randf_range(-current_shake_amount, current_shake_amount)
