class_name FPVController extends CharacterBody3D

@export var move_speed: float = 3.0
@export var mouse_sensitivity: float = 0.0025
@export var gravity: float = 9.8

@onready var camera: Camera3D = $Camera3D
@onready var interact_ray: RayCast3D = $Camera3D/InteractRay

signal interactable_focused(obj: Node)
signal interactable_unfocused
signal interact_pressed(obj: Node)

var _captured: bool = false
var _locked: bool = false
var _focused_obj: Node = null


func _ready() -> void:
	_capture_mouse()


func _capture_mouse() -> void:
	Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
	_captured = true


func _release_mouse() -> void:
	Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
	_captured = false


func _unhandled_input(event: InputEvent) -> void:
	if event is InputEventMouseMotion and _captured and not _locked:
		var sens: float = GameState.mouse_sensitivity
		rotate_y(-event.relative.x * sens)
		camera.rotate_x(-event.relative.y * sens)
		camera.rotation.x = clamp(camera.rotation.x, -PI * 0.45, PI * 0.45)
	elif event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
		if not _captured:
			_capture_mouse()
			return
		if _locked:
			return
		if _focused_obj != null:
			interact_pressed.emit(_focused_obj)
			if _focused_obj.has_method("interact"):
				_focused_obj.interact(self)
	elif event is InputEventKey and event.pressed:
		if event.keycode == KEY_Q:
			GameState.save_game(get_tree().current_scene.scene_file_path if get_tree().current_scene else "")
			get_tree().quit()


func _physics_process(delta: float) -> void:
	if _locked:
		velocity = Vector3.ZERO
		move_and_slide()
		_update_focus()
		return
	var input_dir: Vector2 = Input.get_vector("move_left", "move_right", "move_forward", "move_back")
	var direction: Vector3 = (transform.basis * Vector3(input_dir.x, 0.0, input_dir.y)).normalized()
	velocity.x = direction.x * move_speed
	velocity.z = direction.z * move_speed
	if not is_on_floor():
		velocity.y -= gravity * delta
	else:
		velocity.y = 0.0
	move_and_slide()
	_update_focus()


func _update_focus() -> void:
	interact_ray.force_raycast_update()
	var new_focus: Node = null
	if interact_ray.is_colliding():
		var col: Object = interact_ray.get_collider()
		if col != null and col is Node and col.has_method("get_prompt"):
			new_focus = col
	if new_focus != _focused_obj:
		if new_focus != null:
			interactable_focused.emit(new_focus)
		else:
			interactable_unfocused.emit()
		_focused_obj = new_focus


func lock_input() -> void:
	_locked = true


func unlock_input() -> void:
	_locked = false


func get_focused() -> Node:
	return _focused_obj
