extends Weapon

const DAMAGE: float = 1.0
const BULLET_COLLISION_MASK: int = 0x00000001

@onready var animation_player: AnimationPlayer = $AnimationPlayer
@onready var muzzle: Node3D = $Muzzle

@onready var impact_scene: PackedScene = load("res://src/effects/ballistic_impact.tscn")


func up_vector(v: Vector3) -> Vector3:
	# As a consequence of the hairy ball theorem (yes really) there is no
	# single continuous function that can generate a vector in R3 that is
	# orthogonal to a given one for all vector inputs.
	if v.x == 0 and v.y == 0:
		return Vector3(v.z, 0.0, 0.0)
	return Vector3(-v.y, v.x, 0.0)


func fire() -> void:
	# Two-step raycasting process:
	# First, raycast from camera (aimcast) to get what we're aiming at.
	# Then, raycast from muzzle to aimcast point to check the actual ballistic path.
	if aimcast.is_colliding():
		var past_target: Vector3 = 2 * aimcast.get_collision_point() - muzzle.global_position
		var collision: Dictionary = get_world_3d().direct_space_state.intersect_ray(
			PhysicsRayQueryParameters3D.create(
				muzzle.global_position, past_target, BULLET_COLLISION_MASK, _excluded_rid
			)
		)
		if collision:
			@warning_ignore("unsafe_cast")
			var target: Node3D = collision["collider"] as Node3D
			if target is DamageableBody:
				(target as DamageableBody).deal_damage(DAMAGE)

			var impact: GPUParticles3D = impact_scene.instantiate()
			var point: Vector3 = collision["position"]
			var normal: Vector3 = collision["normal"]
			target.add_child(impact)
			impact.reparent(target)
			impact.look_at_from_position(point, normal + point, up_vector(normal))


func trigger() -> void:
	animation_player.get_animation("fire").loop_mode = Animation.LOOP_LINEAR
	animation_player.play("fire")


func release() -> void:
	animation_player.get_animation("fire").loop_mode = Animation.LOOP_NONE


func deactivate() -> void:
	animation_player.get_animation("fire").loop_mode = Animation.LOOP_NONE
