# Unit tests for static helpers on `res://scripts/player_locomotion_wasd.gd` (NEO-22 seam math).
extends GdUnitTestSuite

const LocomotionWasdScript: GDScript = preload("res://scripts/player_locomotion_wasd.gd")
## Must match [constant FLOOR_RAY_FEET_INVALID] in [code]player_locomotion_wasd.gd[/code].
const FLOOR_RAY_FEET_INVALID: float = -1.0e9


func test_evaluate_floor_ray_hit_y_rejects_hit_too_far_below_feet() -> void:
	# Arrange
	var feet_y := 0.0
	var hit_y := feet_y - 0.2
	var n := Vector3.UP
	# Act
	var out: float = LocomotionWasdScript.evaluate_floor_ray_hit_y(feet_y, hit_y, n, 0.108, 0.42)
	# Assert
	assert_that(out).is_equal(FLOOR_RAY_FEET_INVALID)


func test_evaluate_floor_ray_hit_y_rejects_shallow_floor_normal() -> void:
	# Arrange
	var feet_y := 0.0
	var hit_y := 0.0
	# Up component ~0.37 so dot(Vector3.UP, n) is below min_floor_up_dot (0.42).
	var n := Vector3(0.85, 0.35, 0.0).normalized()
	# Act
	var out: float = LocomotionWasdScript.evaluate_floor_ray_hit_y(feet_y, hit_y, n, 0.108, 0.42)
	# Assert
	assert_that(out).is_equal(FLOOR_RAY_FEET_INVALID)


func test_evaluate_floor_ray_hit_y_accepts_valid_hit() -> void:
	# Arrange
	var feet_y := 0.0
	var hit_y := -0.04
	var n := Vector3.UP
	# Act
	var out: float = LocomotionWasdScript.evaluate_floor_ray_hit_y(feet_y, hit_y, n, 0.108, 0.42)
	# Assert
	assert_that(out).is_equal(hit_y)


func test_median_feet_y_from_samples_empty_is_invalid() -> void:
	# Arrange
	var empty: Array[float] = []
	# Act
	var out: float = LocomotionWasdScript.median_feet_y_from_samples(empty)
	# Assert
	assert_that(out).is_equal(FLOOR_RAY_FEET_INVALID)


func test_median_feet_y_from_samples_single() -> void:
	# Arrange
	var samples: Array[float] = [0.12]
	# Act
	var out: float = LocomotionWasdScript.median_feet_y_from_samples(samples)
	# Assert
	assert_that(out).is_equal(0.12)


func test_median_feet_y_from_samples_three_sorted_middle() -> void:
	# Arrange
	var samples: Array[float] = [0.3, 0.1, 0.2]
	# Act
	var out: float = LocomotionWasdScript.median_feet_y_from_samples(samples)
	# Assert
	assert_that(out).is_equal(0.2)


func test_median_feet_y_from_samples_two_uses_lower_index() -> void:
	# Arrange
	# Matches runtime: mid index (n-1)>>1 picks the smaller of two sorted values.
	var samples: Array[float] = [0.5, 0.1]
	# Act
	var out: float = LocomotionWasdScript.median_feet_y_from_samples(samples)
	# Assert
	assert_that(out).is_equal(0.1)


func test_xz_after_micro_slip_projection_zero_wish_unchanged() -> void:
	# Arrange
	var anchor := Vector2.ZERO
	var player_xz := Vector2(0.02, 0.03)
	# Act
	var out: Vector2 = LocomotionWasdScript.xz_after_micro_slip_projection(
		anchor, player_xz, Vector2.ZERO, 0.032
	)
	# Assert
	assert_that(out).is_equal(player_xz)


func test_xz_after_micro_slip_projection_large_perp_unchanged() -> void:
	# Arrange
	var anchor := Vector2.ZERO
	var player_xz := Vector2(0.0, 0.2)
	var wish := Vector2(1.0, 0.0)
	# Act
	var out: Vector2 = LocomotionWasdScript.xz_after_micro_slip_projection(
		anchor, player_xz, wish, 0.032
	)
	# Assert
	assert_that(out).is_equal(player_xz)


func test_xz_after_micro_slip_projection_nudges_small_perp() -> void:
	# Arrange
	var anchor := Vector2.ZERO
	var player_xz := Vector2(0.0, 0.01)
	var wish := Vector2(1.0, 0.0)
	# Act
	var out: Vector2 = LocomotionWasdScript.xz_after_micro_slip_projection(
		anchor, player_xz, wish, 0.032
	)
	# Assert
	assert_that(out.x).is_equal(0.0)
	assert_that(out.y).is_equal(0.0)


func test_horizontal_velocity_aligned_to_wish_zero_wish_returns_velocity() -> void:
	# Arrange
	# Act
	var out: Vector2 = LocomotionWasdScript.horizontal_velocity_aligned_to_wish(
		Vector2.ZERO, Vector2(3.0, 4.0), 5.0
	)
	# Assert
	assert_that(out).is_equal(Vector2(3.0, 4.0))


func test_horizontal_velocity_aligned_to_wish_clamps_negative_along_to_zero() -> void:
	# Arrange
	# Act
	var out: Vector2 = LocomotionWasdScript.horizontal_velocity_aligned_to_wish(
		Vector2(1.0, 0.0), Vector2(-2.0, 0.0), 5.0
	)
	# Assert
	assert_that(out).is_equal(Vector2.ZERO)


func test_horizontal_velocity_aligned_to_wish_caps_speed() -> void:
	# Arrange
	# Act
	var out: Vector2 = LocomotionWasdScript.horizontal_velocity_aligned_to_wish(
		Vector2(1.0, 0.0), Vector2(10.0, 0.0), 5.0
	)
	# Assert
	assert_that(out).is_equal(Vector2(5.0, 0.0))


func test_horizontal_velocity_aligned_to_wish_preserves_wish_direction() -> void:
	# Arrange
	# Act
	var out: Vector2 = LocomotionWasdScript.horizontal_velocity_aligned_to_wish(
		Vector2(0.0, 1.0), Vector2(0.0, 2.0), 5.0
	)
	# Assert
	assert_that(out).is_equal(Vector2(0.0, 2.0))
