extends CharacterBody3D

var walk_speed : float = 2.0
var run_speed : float = 5.0
@export var waypoint_route : Node3D

var state_change_cooldown : float = 0.1
var alert_escalation_time : float = 3.0
var suspicion_decay_time : float = 1.0

@export var behavior_type : String = "frozen"
var state : String = "patrol"
var current_waypoint : int = 0
var paused : bool = false
var turntoward : float = 0.0
var lastseenpos : Vector3
var pathupdateinterval : float = 0.75

@export var appearance : String = "guard"

var current_path: PackedVector3Array
@onready var default_3d_map_rid: RID = get_world_3d().get_navigation_map()
var current_path_index: int = 0
var current_path_point: Vector3
@onready var overheadstatus : Control = $StatusSprite/SubViewport/OverheadStatus

func _ready() -> void:
	overheadstatus.set_nothing()
	overheadstatus.set_timer(-1)
	
	if appearance == "guard":
		$CharacterArmature/Skeleton3D/head_guard.visible = true
		$CharacterArmature/Skeleton3D/body_guard.visible = true
		$CharacterArmature/Skeleton3D/feet_guard.visible = true
		$CharacterArmature/Skeleton3D/legs_guard.visible = true
	elif appearance == "barista":
		$CharacterArmature/Skeleton3D/head_barista.visible = true
		$CharacterArmature/Skeleton3D/body_barista.visible = true
		$CharacterArmature/Skeleton3D/feet_barista.visible = true
		$CharacterArmature/Skeleton3D/legs_barista.visible = true

func _physics_process(delta: float) -> void:
	if behavior_type == "frozen":
		sight_test()
	elif behavior_type == "guard":
		process_alerts()
	
	if not is_on_floor():
		velocity += get_gravity() * delta
	
	if !current_path.is_empty() and behavior_type == "guard" and (state == "patrol" or state == "pursue_walkto"):
		current_path_point = current_path[current_path_index]
		walk_toward(current_path_point)
		
		if global_transform.origin.distance_to(current_path_point) <= 0.6:
			current_path_index += 1
			if current_path_index >= current_path.size():
				clearpath()
				if state == "patrol":
					var wayedpoint = waypoint_route.get_children()[current_waypoint]
					if wayedpoint.pausetime > 0:
						paused = true
						$WaypointPauseTimer.start(wayedpoint.pausetime)
						$AnimationPlayer.play(wayedpoint.animation)
						if wayedpoint.turntoward == true:
							turntoward = wayedpoint.global_rotation.y
						else:
							turntoward = $CharacterArmature.global_rotation.y
					current_waypoint += 1
					if current_waypoint > waypoint_route.get_child_count()-1:
						current_waypoint = 0
				if state == "pursue_walkto":
					print("done!")
					state = "suspicion_start"
					$StateChangeTimer.start(state_change_cooldown)
					print("player sighted! state changed to suspicion_start")
					overheadstatus.set_3dots()
					overheadstatus.set_timer(-1)
	else:
		stand_still()
	
	if paused and state == "patrol":
		# overheadstatus.set_timer(100-(($WaypointPauseTimer.time_left / $WaypointPauseTimer.wait_time)*100))
		if $CharacterArmature.global_rotation.y != turntoward:
			lerp_toward(turntoward)
	
	move_and_slide()
	animate()

func animate():
	if velocity.length() > 0:
		$AnimationPlayer.play("Walk")
	elif !paused:
		$AnimationPlayer.play("Idle_Neutral")

func walk_toward(point: Vector3):
	var direction = global_transform.origin.direction_to(point)		
	velocity.x = direction.x * walk_speed
	velocity.z = direction.z * walk_speed
	
	lerp_toward(atan2(velocity.x, velocity.z))

func stand_still():
	velocity.x = 0
	velocity.z = 0

func sight_test():
	var seeingplayer : bool
	
	seeingplayer = checksight()
	
	if seeingplayer:
		overheadstatus.set_exclamationmark()
	else:
		overheadstatus.set_nothing()

func process_alerts():
	var seeingplayer : bool
	
	if state == "patrol":
		seeingplayer = checksight()
		if seeingplayer:
			state = "suspicion_start"
			$StateChangeTimer.start(state_change_cooldown)
			print("player sighted! state changed to suspicion_start")
		return
	
	if state == "suspicion":
		seeingplayer = checksight()
		
		if !seeingplayer and $StateChangeTimer.is_stopped():
			$StateChangeTimer.start(suspicion_decay_time)
		elif seeingplayer:
			if !$StateChangeTimer.is_stopped(): $StateChangeTimer.stop()
			overheadstatus.set_questionmark()
			var percent : float = 100 - (($AlertEscalationTimer.time_left / alert_escalation_time) * 100)
			overheadstatus.set_timer(percent)
			if velocity.length() == 0: lerp_toward(turntoward)
			
	if state == "pursue_walkto":
		seeingplayer = checksight()
		if seeingplayer:
			print("update path?")
			if !current_path.is_empty() and (current_path[current_path.size()-1].distance_to(lastseenpos) > 1):
				setpath(lastseenpos)
				print("updated path!")

func nextwaypoint():
	var waypoint = waypoint_route.get_children()[current_waypoint]
	var target_position: Vector3 = waypoint.global_transform.origin
	setpath(target_position)

func setpath(target_position):
	clearpath()
	var start_position: Vector3 = global_transform.origin
	current_path = NavigationServer3D.map_get_path(
			default_3d_map_rid,
			start_position,
			target_position,
			true
		)

func clearpath():
	current_path = []
	current_path_index = 0
	current_path_point = global_transform.origin
	velocity.x = 0
	velocity.z = 0
	
func checksight():
	var seenthings = $CharacterArmature/SightCollider.get_overlapping_bodies()
	
	if !seenthings.is_empty():
		for item in seenthings:
			if item.get_name() == "PlayerVisibilityShape":
				if sightraycheck(item) == true:
					return true
	
	return false

func sightraycheck(target):
	var radius = target.shape_owner_get_shape(0,0).radius
	var height : float
	var tramsorm : Transform3D
		
	if target.get_parent().get_parent().crouched:
		height = target.shape_owner_get_shape(1,0).height
		tramsorm = target.shape_owner_get_transform(1).rotated(Vector3(0,1,0),target.global_rotation.y)
	else:
		height = target.shape_owner_get_shape(0,0).height
		tramsorm = target.shape_owner_get_transform(0).rotated(Vector3(0,1,0),target.global_rotation.y)
	
	var top = to_local(target.global_transform.origin - $CharacterArmature/SightCollider/SightRay.transform.origin + tramsorm.origin + Vector3(0, (height/2)-0.1, 0))
	$CharacterArmature/SightCollider/SightRay.target_position = top
	# $CharacterArmature/SightCollider/SightRay.global_rotation = Vector3(0,0,0)
	$CharacterArmature/SightCollider/SightRay.force_raycast_update()
	if $CharacterArmature/SightCollider/SightRay.is_colliding():
		if $CharacterArmature/SightCollider/SightRay.get_collider().get_name() == "PlayerVisibilityShape":
			turntoward = (Vector2(target.global_transform.origin.z, target.global_transform.origin.x) - Vector2($CharacterArmature.global_transform.origin.z, $CharacterArmature.global_transform.origin.x)).angle()
			lastseenpos = target.global_transform.origin
			return true
	
	return false
	
func lerp_toward(angle: float):
	$CharacterArmature.global_rotation.y = lerp_angle($CharacterArmature.global_rotation.y, angle, 0.15)


func _on_state_change_timer_timeout() -> void:
	$StateChangeTimer.stop()
	$AlertEscalationTimer.stop()
	
	if state == "suspicion_start":
		state = "suspicion"
		$AlertEscalationTimer.start(alert_escalation_time)
		return
		
	if state == "suspicion":
		state = "patrol"
		overheadstatus.set_3dots()
		overheadstatus.set_timer(-1)
		return
		
	if state == "pursue_start":
		setpath(lastseenpos)
		state = "pursue_walkto"
		return


func _on_alert_escalation_timer_timeout() -> void:
	$AlertEscalationTimer.stop()
	if state == "suspicion":
		state = "pursue_start"
		$StateChangeTimer.start(state_change_cooldown)
	overheadstatus.set_timer(-1)
	overheadstatus.set_exclamationmark()


func _on_waypoint_pause_timer_timeout() -> void:
	$WaypointPauseTimer.stop()
	if paused:
		paused = false
