extends RigidBody3D
# Initially I used a CharacterBody3D, however, I wanted the player to bounce off
# other objects in the environment and that would have required manual handling
# of collisions. So that's why we're using a RigidBody3D instead.
signal vehicle_entered(vehicle: Node)
signal vehicle_exited(vehicle: Node)

const MOVE_SPEED := 8.0
const SPRINT_MOVE_SPEED :=13
const ACCELLERATION := 30.0
const DECELLERATION := 40.0
const JUMP_SPEED := 4.0
const MAX_NUMBER_OF_JUMPS := 2
const MIN_FOV := 10.0
const MAX_FOV := 179.0
const ZOOM_FACTOR := 1.1 # Zoom out when >1, in when < 1
var mouse_sensitivity := 0.005
var rotation_x := 0.0
var rotation_y := 0.0
var cameraMoveMode := false
var current_number_of_jumps := 0
var _pending_mouse_delta := Vector2.ZERO
var _last_move_forward := Vector3(0, 0, 1)
var _last_move_right := Vector3(1, 0, 0)
var _camera_offset_local := Vector3.ZERO
var _camera_yaw := 0.0
var _camera_pitch := 0.0
var _in_vehicle := false
var _vehicle_collision_layer := 0
var _vehicle_collision_mask := 0
var _vehicle_original_parent: Node = null
var _light_was_on := false
var _jump_triggered := false
@onready var _flashlight: SpotLight3D = $SpotLight3D
@onready var _anim_player: AnimationPlayer = find_child("AnimationPlayer", true, false) as AnimationPlayer
@onready var _anim_tree: AnimationTree = find_child("AnimationTree", true, false) as AnimationTree
@onready var _model_root: Node3D = find_child("TestCharAnimated", true, false) as Node3D

@export var camera_follow_speed := 10.0
@export var anim_idle_name := "Idle"
@export var anim_walk_name := "Walk"
@export var anim_jump_name := "Jump"
@export var anim_run_name := "Run"
@export var anim_walk_speed_threshold := 0.25
@export var anim_sprint_speed_threshold := 10.0

var jump_sound = preload("res://assets/audio/jump.ogg")
var audio_player = AudioStreamPlayer.new()

@export var camera_path: NodePath
@onready var cam: Camera3D = get_node(camera_path) if camera_path != NodePath("") else null
@export var phone_path: NodePath
@onready var phone: CanvasLayer = get_node(phone_path) if phone_path != NodePath("") else null
var phone_visible := false

func _ready() -> void:
	add_to_group("player")
	if _anim_tree:
		_anim_tree.active = false
	axis_lock_angular_x = true
	axis_lock_angular_z = true
	angular_damp = 6.0
	contact_monitor = true
	max_contacts_reported = 4
	add_child(audio_player)
	audio_player.stream = jump_sound
	audio_player.volume_db = -20
	if cam:
		_camera_offset_local = cam.transform.origin
		_camera_pitch = cam.rotation.x
		_camera_yaw = global_transform.basis.get_euler().y
		cam.set_as_top_level(true)
		cam.global_position = global_position + (Basis(Vector3.UP, _camera_yaw) * _camera_offset_local)
		cam.global_rotation = Vector3(_camera_pitch, _camera_yaw, 0.0)
	var move_basis := cam.global_transform.basis if cam else global_transform.basis
	var forward := move_basis.z
	var right := move_basis.x
	forward.y = 0.0
	right.y = 0.0
	if forward.length() > 0.0001:
		_last_move_forward = forward.normalized()
	if right.length() > 0.0001:
		_last_move_right = right.normalized()
	_vehicle_collision_layer = collision_layer
	_vehicle_collision_mask = collision_mask
	
func _integrate_forces(state):
	if _in_vehicle:
		linear_velocity = Vector3.ZERO
		return
	if cameraMoveMode and _pending_mouse_delta != Vector2.ZERO:
		rotation_x -= _pending_mouse_delta.y * mouse_sensitivity
		rotation_y -= _pending_mouse_delta.x * mouse_sensitivity
		rotation_x = clamp(rotation_x, deg_to_rad(-90), deg_to_rad(90))  # Prevent flipping
		_camera_pitch = rotation_x
		rotation.y = rotation_y
		_pending_mouse_delta = Vector2.ZERO

	# Input as 2D vector
	var input2v := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")

	# Camera based movement
	var forward := Vector3.FORWARD * -1.0
	var right := Vector3.RIGHT
	if cam:
		forward = cam.global_transform.basis.z
		right = cam.global_transform.basis.x
	# Project onto ground plane so looking up/down doesn't kill movement.
	forward.y = 0.0
	right.y = 0.0
	if forward.length() > 0.0001:
		forward = forward.normalized()
		_last_move_forward = forward
	else:
		forward = _last_move_forward
	if right.length() > 0.0001:
		right = right.normalized()
		_last_move_right = right
	else:
		right = _last_move_right
		
	var dir := (right * input2v.x + forward * input2v.y).normalized()
	
	var target_v := dir * MOVE_SPEED
	
	# Sprinting
	if Input.is_key_pressed(KEY_SHIFT):
		target_v = dir * SPRINT_MOVE_SPEED
			
	var ax := ACCELLERATION if dir != Vector3.ZERO else DECELLERATION
	linear_velocity.x = move_toward(linear_velocity.x, target_v.x, ax * state.step)
	linear_velocity.z = move_toward(linear_velocity.z, target_v.z, ax * state.step)
	
	# Jump Logic
	var on_floor = false
	for i in state.get_contact_count():
		var normal = state.get_contact_local_normal(i)
		if normal.y > 0.5:
			on_floor = true
			break
	
	if Input.is_action_just_pressed("ui_accept") and (on_floor or current_number_of_jumps == 1):
		current_number_of_jumps = (current_number_of_jumps + 1) % 2
		linear_velocity.y = JUMP_SPEED
		audio_player.play()
		_jump_triggered = true

	if cam:
		var target_yaw := global_transform.basis.get_euler().y
		_camera_yaw = lerp_angle(_camera_yaw, target_yaw, camera_follow_speed * state.step)
		var target_basis := Basis(Vector3.UP, _camera_yaw)
		var target_pos := global_position + (target_basis * _camera_offset_local)
		cam.global_position = cam.global_position.lerp(target_pos, camera_follow_speed * state.step)
		cam.global_rotation = Vector3(_camera_pitch, _camera_yaw, 0.0)

	_update_animation(on_floor, state.linear_velocity)
	_jump_triggered = false

func _input(event):
	if event.is_action_pressed("player_phone"):
		phone_visible = !phone_visible
		if phone:
			phone.visible = phone_visible
		return

	if _in_vehicle:
		return
	if event is InputEventMouseButton:
		if event.button_index == MOUSE_BUTTON_MIDDLE:
			if event.pressed:
				cameraMoveMode = true
				Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
			else:
				cameraMoveMode = false
				Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)

	if event is InputEventMouseMotion and cameraMoveMode:
		_pending_mouse_delta += event.relative
	
	if event is InputEventMouseButton and event.pressed:
		if event.button_index == MOUSE_BUTTON_WHEEL_UP:
			zoom_camera(1.0 / ZOOM_FACTOR) # Zoom in
		elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
			zoom_camera(ZOOM_FACTOR) # Zoom out
			
	if event.is_action_pressed("player_light"):
		_flashlight.visible = !_flashlight.visible
			
func zoom_camera(factor):
	if cam == null:
		return
	var new_fov = cam.fov * factor
	cam.fov = clamp(new_fov, MIN_FOV, MAX_FOV)

func _update_animation(on_floor: bool, velocity: Vector3) -> void:
	if _anim_player == null:
		return
	var horizontal_speed := Vector3(velocity.x, 0.0, velocity.z).length()
	if _jump_triggered and _anim_player.has_animation(anim_jump_name):
		if _anim_player.current_animation != anim_jump_name:
			_anim_player.play(anim_jump_name)
		return
	if not on_floor and _anim_player.has_animation(anim_jump_name):
		if _anim_player.current_animation != anim_jump_name:
			_anim_player.play(anim_jump_name)
		return
	if on_floor and horizontal_speed > anim_sprint_speed_threshold and _anim_player.has_animation(anim_run_name):
		if _anim_player.current_animation != anim_run_name:
			_anim_player.play(anim_run_name)
		return
	if horizontal_speed > anim_walk_speed_threshold and _anim_player.has_animation(anim_walk_name):
		if _anim_player.current_animation != anim_walk_name:
			_anim_player.play(anim_walk_name)
		return
	if _anim_player.has_animation(anim_idle_name):
		if _anim_player.current_animation != anim_idle_name:
			_anim_player.play(anim_idle_name)

func enter_vehicle(_vehicle: Node, seat: Node3D, vehicle_camera: Camera3D) -> void:
	_in_vehicle = true
	freeze = true
	sleeping = true
	collision_layer = 0
	collision_mask = 0
	_vehicle_original_parent = get_parent()
	_light_was_on = _flashlight.visible
	_flashlight.visible = false
	if _model_root:
		_model_root.visible = false
	if seat:
		reparent(seat, true)
		global_transform = seat.global_transform
	if cam:
		cam.current = false
	if vehicle_camera:
		vehicle_camera.current = true
	vehicle_entered.emit(_vehicle)

func exit_vehicle(exit_point: Node3D, vehicle_camera: Camera3D) -> void:
	_in_vehicle = false
	freeze = false
	sleeping = false
	collision_layer = _vehicle_collision_layer
	collision_mask = _vehicle_collision_mask
	if _vehicle_original_parent:
		reparent(_vehicle_original_parent, true)
	_vehicle_original_parent = null
	_flashlight.visible = _light_was_on
	if _model_root:
		_model_root.visible = true
	if exit_point:
		global_transform = exit_point.global_transform
	if vehicle_camera:
		vehicle_camera.current = false
	if cam:
		cam.current = true
	vehicle_exited.emit(null)
