extends RigidBody3D
class_name Unit

@export_group("Defence")
@export var max_hp: float = 100

@export_group("Movement")
@export var movement_force: float = 100
@export var rotation_torque: float = 3
@export var max_speed: float = 100

@export_group("Navigation")
@export var minimum_progress_rate: float = 1.0
@export var stuck_time: float = 1.0

@onready var hp: float = max_hp
var target_velocity: Vector3 = Vector3()
var avoidance_velocity: Vector3 = Vector3()
var avoidance_timeout: float = 0.0
var move_radius: float = 5.0

var held_item: Item = null:
	set(val):
		held_item = val
		if held_item_meshinstance != null:
			if held_item != null:
				held_item_meshinstance.mesh = held_item.model
			held_item_meshinstance.visible = (held_item != null)

@onready var shapecast_3d: ShapeCast3D = $ShapeCast3D
@onready var nav_agent_3d: NavigationAgent3D = $NavigationAgent3D
@onready var label_3d: Label3D = $Label3D
@onready var held_item_meshinstance: MeshInstance3D = null

var action: UnitAction = null

var task_timeout: float = 0.0

enum TaskStatus {
	IN_PROGRESS,
	INTERRUPTED,
	TIMED_OUT,
	IMPOSSIBLE,
	DONE,
}
signal task_updated(task_status: TaskStatus)

func _ready() -> void:
	nav_agent_3d.connect("velocity_computed", avoidance_velocity_computed)
	pass
	
func avoidance_velocity_computed(velocity: Vector3) -> void:
	if velocity != target_velocity:
		avoidance_velocity = velocity
		avoidance_timeout = 0.5

func _process(delta: float) -> void:
	label_3d.text = "HP: %d" % hp
	
	if task_timeout > 0:
		task_timeout -= delta
		if task_timeout <= 0:
			send_task_update(TaskStatus.TIMED_OUT)
			
	if action != null:
		var status: TaskStatus = action.process(delta)
		if status != TaskStatus.IN_PROGRESS:
			action = null
			send_task_update(status)
		
	if global_position.y <= -10:
		unstuck()

func unstuck() -> void:
	# teleport to next path point
	linear_velocity = Vector3()
	global_position = nav_agent_3d.get_next_path_position()
	
func hurt(damage: float) -> void:
	hp -= damage
	interrupt()
	#print("%s hit for %f damage, HP=%f" % [name, damage, hp])
	if hp <= 0:
		die()
		
func die() -> void:
	interrupt()
	queue_free()

func _physics_process(delta: float) -> void:
	if shapecast_3d.is_colliding():
		var actual_target_velocity: Vector3 = target_velocity
		if avoidance_timeout > 0:
			avoidance_timeout -= delta
			actual_target_velocity = actual_target_velocity.slerp(avoidance_velocity, 0.25)
			#DebugDraw3D.draw_line(global_position, global_position + actual_target_velocity, Color.ORANGE)
		var force_direction: Vector3 = (actual_target_velocity-linear_velocity)
		var normal: Vector3 = shapecast_3d.get_collision_normal(0)
		#DebugDraw3D.draw_line(global_position, global_position + normal, Color.DODGER_BLUE)
		var force: Vector3 = (force_direction * movement_force).slide(normal)
		#DebugDraw3D.draw_line(global_position, global_position + force, Color.GREEN)
		apply_central_force(force)
		apply_torque(Vector3(0,force_direction.normalized().signed_angle_to(-global_basis.z, Vector3.DOWN) * rotation_torque,0))
		#DebugDraw3D.draw_line(global_position, global_position - global_basis.z, Color.BLUE)
		#DebugDraw3D.draw_line(global_position, global_position + force_direction, Color.RED)
		
func interrupt() -> void:
	send_task_update(TaskStatus.INTERRUPTED)

func send_task_update(task_status: TaskStatus) -> void:
	task_updated.emit(task_status)

func wait_for_task_update() -> TaskStatus:
	var status_received: TaskStatus = await task_updated;
	return status_received
	
func go_to_destination(destination: Vector3) -> bool:
	var move_action: UnitAction.MoveAction = UnitAction.MoveAction.new()
	move_action.unit = self
	move_action.move_target = destination
	action = move_action
	#move_target = destination
	return (await wait_for_task_update()) == TaskStatus.DONE

func take_item_from_building(item: Item, building: Building) -> bool:
	if held_item != null:
		return false
	if building.producer != null:
		if !await building.producer.wait_for_item(item, 3.0):
			return false
	#if building.consumer == null:
		#return false
	#if await building.consumer.wait_for_item(item, 3.0):
		#if !building.consumer.take_item_from_storage(item):
			#return false
	held_item = item
	return true
	
func drop_item() -> bool:
	return false