extends CharacterBody3D

const RETARGET_COOLDOWN: float = 1.0

@export var MOVE_SPEED: float = 50.0
@export var target: Node3D

@onready var nav_agent: NavigationAgent3D = $NavigationAgent3D

var _retarget_timer: float = 1.0


func _ready() -> void:
	nav_agent.velocity_computed.connect(_on_velocity_computed)


func _process(p_delta: float) -> void:
	_retarget_timer += p_delta
	if _retarget_timer > RETARGET_COOLDOWN and target:
		# Don't reset the target position every frame. It triggers an A* search, which is expensive.
		_retarget_timer = 0.0
		nav_agent.set_target_position(target.global_position)


func is_on_nav_mesh() -> bool:
	var closest_point := NavigationServer3D.map_get_closest_point(
		nav_agent.get_navigation_map(), global_position
	)
	return global_position.distance_squared_to(closest_point) < nav_agent.path_max_distance ** 2


func _physics_process(p_delta: float) -> void:
	if nav_agent.is_navigation_finished():
		velocity.x = 0.0
		velocity.z = 0.0
	else:
		var next_path_position: Vector3 = nav_agent.get_next_path_position()
		var current_agent_position: Vector3 = global_position
		var velocity_xz := (next_path_position - current_agent_position).normalized() * MOVE_SPEED
		velocity.x = velocity_xz.x
		velocity.z = velocity_xz.z

	velocity.y -= 40 * p_delta

	if nav_agent.avoidance_enabled:
		nav_agent.set_velocity(velocity)
	else:
		_on_velocity_computed(velocity)

	# Ensure enemy doesn't fall through terrain when collision absent
	if get_parent().terrain:
		var height: float = get_parent().terrain.data.get_height(global_position)
		if not is_nan(height):
			global_position.y = maxf(global_position.y, height)


func _on_velocity_computed(p_safe_velocity: Vector3) -> void:
	velocity.x = p_safe_velocity.x
	velocity.z = p_safe_velocity.z
	move_and_slide()
