extends CharacterBody3D

const LERP_VALUE : float = 0.15

@onready var camera : Node = $SpringArmPivot/SpringArm3D/Camera3D
@onready var cameraarm : Node = $SpringArmPivot/SpringArm3D
const fov_normal : float = 75.0
const fov_aiming : float = 50.0
@onready var camerapos_normal : Vector3 = cameraarm.position
const camerapos_aimoffset : Vector3 = Vector3(0.3, 0.15, 0)
@onready var camerapos_aim : Vector3 = camerapos_normal + camerapos_aimoffset

@onready var promptbox = $hud/hud_bottom/promptbox

var snap_vector : Vector3 = Vector3.DOWN
var speed : float

var crouched : bool = false
var aiming : bool = false

var walk_speed : float = 2.0
var creep_speed : float = 3.0
var run_speed : float = 5.0
var gravity : float = 50.0

@onready var player_mesh : Node3D = $CharacterArmature
@onready var spring_arm_pivot : Node3D = $SpringArmPivot

var interaction_points : Array[Node]
var ipoints_initialized : bool = false

var current_interactables : Array[Node3D]
var selected_interactable : Node3D = null
var held_object : Node3D = null
var locked : bool = false
var lock_rotation : float = 0.0

func _physics_process(delta):	
	var move_direction : Vector3 = Vector3.ZERO
	move_direction.x = Input.get_action_strength("move_right") - Input.get_action_strength("move_left")
	move_direction.z = Input.get_action_strength("move_backwards") - Input.get_action_strength("move_forwards")
	move_direction = move_direction.rotated(Vector3.UP, spring_arm_pivot.rotation.y)
	
	velocity.y -= gravity * delta
	
	if Input.is_action_just_pressed("ui_cancel"):
		if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
			Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
		else:
			get_tree().quit()
	
	if !locked:
		if Input.is_action_just_pressed("crouch"):
			if crouched:
				crouched = false
				$CharacterArmature/PlayerVisibilityShape/Crouching.disabled = true
				$CharacterArmature/PlayerVisibilityShape/Standing.disabled = false
			else:
				crouched = true
				$CharacterArmature/PlayerVisibilityShape/Standing.disabled = true
				$CharacterArmature/PlayerVisibilityShape/Crouching.disabled = false
		
		if Input.is_action_pressed("run") and !crouched:
			speed = run_speed
		elif crouched:
			speed = creep_speed
		else:
			speed = walk_speed
		
		velocity.x = move_direction.x * speed
		velocity.z = move_direction.z * speed
		
		if move_direction:
			player_mesh.rotation.y = lerp_angle(player_mesh.rotation.y, atan2(velocity.x, velocity.z), LERP_VALUE)
		
		aiming = false
		if Input.is_action_pressed("aim"):
			handle_aim()
			aiming = true
		elif Input.is_action_just_pressed("use"):
			if Input.mouse_mode == Input.MOUSE_MODE_VISIBLE:
				Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
			else:
				handle_use()
		
		if Input.is_action_just_pressed("use_alt"):
			handle_use_alt()
		
		if aiming:
			camera.fov = lerpf(camera.fov, fov_aiming, LERP_VALUE)
			cameraarm.position = lerp(cameraarm.position, camerapos_aim, LERP_VALUE)
		elif !aiming:
			camera.fov = lerpf(camera.fov, fov_normal, LERP_VALUE)
			cameraarm.position = lerp(cameraarm.position, camerapos_normal, LERP_VALUE)
		
		apply_floor_snap()
		move_and_slide()
	else:
		player_mesh.rotation.y = lerp_angle(player_mesh.rotation.y, lock_rotation, LERP_VALUE)
	
	animate(delta)
	check_interactables()
	handle_ui()

func handle_use():
	if held_object == null and selected_interactable != null:
		if !selected_interactable.grabject and selected_interactable.note_style != "none":
			show_note(selected_interactable.note_style, selected_interactable.note)
		elif selected_interactable.grabject:
			lock_rotation = angle_toward(selected_interactable.global_transform.origin)
			locked = true
			if selected_interactable.low_height:
				$AnimationPlayer_new.play("GrabLow")
			else:
				$AnimationPlayer_new.play("Grab")
			$LockTimer.start(0.62)
			held_object = selected_interactable
			selected_interactable = null
			held_object.hold(0.62)
	elif held_object != null:
		var location : Vector3 = $CharacterArmature/Skeleton3D/ItemHold_BoneAttach/ItemHoldPoint.global_transform.origin
		var direction : Vector3 = get_viewport().get_camera_3d().global_transform.basis.z
		location = location + -Vector3(0, 0.1, 0)
		held_object.unhold(location)
		held_object = null
		hide_hand_objects()

func handle_use_alt():
	if held_object == null and current_interactables.size() > 1:
		current_interactables.pop_front()
		selected_interactable = current_interactables[0]

func handle_aim():
	if held_object != null:
		var cameraangle = -get_viewport().get_camera_3d().global_transform.basis.z
		player_mesh.rotation.y = lerp_angle(player_mesh.rotation.y, atan2(cameraangle.x, cameraangle.z), LERP_VALUE)
	if held_object != null and Input.is_action_just_pressed("use"):
		var location : Vector3 = $CharacterArmature/Skeleton3D/ItemHold_BoneAttach/ItemHoldPoint.global_transform.origin
		var direction : Vector3 = get_viewport().get_camera_3d().global_transform.basis.z
		location = location + 0.1*(-Vector3(direction.x, 0, direction.z))
		held_object.throw(location, -direction, 20)
		held_object = null
		hide_hand_objects()

func handle_ui():
	$hud/icon_eye.visible = false
	$hud/icon_hand.visible = false
	if selected_interactable != null and not get_viewport().get_camera_3d().is_position_behind(selected_interactable.global_transform.origin):
		var offset : Vector3 = Vector3(0, 0, 0)
		if selected_interactable.grabject and selected_interactable.icon_offset:
			offset.y = selected_interactable.icon_offset
		var object_coords = get_viewport().get_camera_3d().unproject_position(selected_interactable.global_transform.origin + offset)
		var icon : Control
		
		if selected_interactable.icon == "eye":
			icon = $hud/icon_eye
		elif selected_interactable.icon == "hand":
			icon = $hud/icon_hand
		else: pass
		
		var half_size = icon.size.x / 2
		icon.set_position(object_coords - Vector2(half_size, half_size))
		icon.visible = true

func unlock():
	locked = false
	if held_object.model == "mug":
		$CharacterArmature/Skeleton3D/ItemHold_BoneAttach/ItemHoldPoint/mug.visible = true

func hide_hand_objects():
	for object in $CharacterArmature/Skeleton3D/ItemHold_BoneAttach/ItemHoldPoint.get_children():
		object.visible = false

func angle_toward(target : Vector3):
	return (Vector2(target.z, target.x) - Vector2($CharacterArmature.global_transform.origin.z, $CharacterArmature.global_transform.origin.x)).angle()

func animate(delta):
	if !locked:
		if velocity.length() > 0:
			if speed == run_speed:
				$AnimationPlayer_new.play("Run")
			elif crouched:
				$AnimationPlayer_new.play("Walk_Crouch")
			else:
				$AnimationPlayer_new.play("Walk")
		else:
			if crouched:
				$AnimationPlayer_new.play("Idle_Crouch")
			elif aiming and held_object != null:
				$AnimationPlayer_new.play("Idle_Gun_Pointing")
			else:
				$AnimationPlayer_new.play("Idle_Neutral")

func check_interactables():
	var new_interactables : Array[Node3D]
	
	if selected_interactable != null and selected_interactable.global_transform.origin.distance_to(global_transform.origin + Vector3(0, 0.9, 0)) > 1:
		selected_interactable = null
	
	var objects = $CharacterArmature/GrabArea_Upper.get_overlapping_bodies()
	for object in objects:
		object.low_height = false
		new_interactables.append(object)
	
	objects = $CharacterArmature/GrabArea_Lower.get_overlapping_bodies()
	for object in objects:
		object.low_height = true
		new_interactables.append(object)
	
	if ipoints_initialized:
		for point in interaction_points:
			if point.global_transform.origin.distance_to(global_transform.origin + Vector3(0, 0.9, 0)) < 1:
				new_interactables.append(point)
	
	var the_interactables : Array[Node3D]
	for object in current_interactables:
		if object in new_interactables:
			the_interactables.append(object)
	for object in new_interactables:
		if not object in current_interactables:
			the_interactables.append(object)
	current_interactables.clear()
	current_interactables = the_interactables.duplicate()
	
	if current_interactables.is_empty():
		$hud/hud_bottom/promptbox/prompt_primary.visible = false
		$hud/hud_bottom/promptbox/prompt_secondary.visible = false
		$hud/hud_bottom/promptbox/prompt_tertiary.visible = false
	else:
		populate_prompts()

func populate_prompts():
	if selected_interactable == null:
		selected_interactable = current_interactables[0]
	$hud/hud_bottom/promptbox/prompt_primary.text = selected_interactable.prompt
	$hud/hud_bottom/promptbox/prompt_primary.visible = true
	if current_interactables.size() > 1:
		for index in current_interactables.size():
			var object = current_interactables[index]
			if selected_interactable == object and index != 0:
				current_interactables.push_front(current_interactables.pop_at(index))
		$hud/hud_bottom/promptbox/prompt_secondary.text = current_interactables[1].prompt
		$hud/hud_bottom/promptbox/prompt_secondary.visible = true
		if current_interactables.size() > 2:
			$hud/hud_bottom/promptbox/prompt_tertiary.visible = true
			$hud/hud_bottom/promptbox/prompt_tertiary.text = current_interactables[2].prompt
			if current_interactables.size() > 3:
				$hud/hud_bottom/promptbox/prompt_overflow.visible = true
			else:
				$hud/hud_bottom/promptbox/prompt_overflow.visible = false
		else:
			$hud/hud_bottom/promptbox/prompt_tertiary.visible = false
	else:
		$hud/hud_bottom/promptbox/prompt_secondary.visible = false
		$hud/hud_bottom/promptbox/prompt_tertiary.visible = false

func init_interactionpoints(pointgroup):
	for point in pointgroup.get_children():
		interaction_points.append(point)
	ipoints_initialized = true

func show_note(style, string):
	$note.setup(get_viewport().size, style, string)
	$hud.visible = false
	$note.visible = true
	get_tree().paused = true
