extends SceneTree

## Headless M02 unit and boundary regression for local player movement state.

const MOVEMENT_CONTROLLER_PATH := "res://src/gameplay/movement/local_player_movement_controller.gd"
const PLAYER_CONTROLLER_PATH := "res://src/scenes/player/third_person_wow_controller.gd"


func _initialize() -> void:
	var failures: Array[String] = []
	_verify_directional_speeds(failures)
	_verify_acceleration_and_deceleration(failures)
	_verify_sandbox_sprint(failures)
	_verify_flight_state_and_basis(failures)
	_verify_keyboard_turn(failures)
	_verify_invalid_delta(failures)
	_verify_scene_boundary(failures)

	if not failures.is_empty():
		for failure in failures:
			push_error("LOCAL_PLAYER_MOVEMENT: %s" % failure)
		quit(1)
		return

	print("LOCAL_PLAYER_MOVEMENT PASS cases=15 state_transitions=2 scene_boundary=1")
	quit(0)


func _verify_directional_speeds(failures: Array[String]) -> void:
	var forward_controller := _new_controller()
	var forward_displacement := forward_controller.advance(_intent(1.0, 0.0), Basis.IDENTITY, 1.0)
	_expect_vector_near(forward_controller.godot_world_velocity_units_per_second, Vector3(0.0, 0.0, -7.0), "forward velocity", failures)
	_expect_vector_near(forward_displacement, Vector3(0.0, 0.0, -7.0), "forward displacement", failures)

	var backward_controller := _new_controller()
	backward_controller.advance(_intent(-1.0, 0.0), Basis.IDENTITY, 1.0)
	_expect_vector_near(backward_controller.godot_world_velocity_units_per_second, Vector3(0.0, 0.0, 4.5), "backward velocity", failures)

	var strafe_controller := _new_controller()
	strafe_controller.advance(_intent(0.0, 1.0), Basis.IDENTITY, 1.0)
	_expect_vector_near(strafe_controller.godot_world_velocity_units_per_second, Vector3(4.5, 0.0, 0.0), "strafe velocity", failures)

	var rotated_controller := _new_controller()
	var rotated_basis := Basis(Vector3.UP, deg_to_rad(90.0))
	rotated_controller.advance(_intent(1.0, 0.0), rotated_basis, 1.0)
	_expect_vector_near(
		rotated_controller.godot_world_velocity_units_per_second,
		(-rotated_basis.z).normalized() * 7.0,
		"rotated character basis",
		failures
	)


func _verify_acceleration_and_deceleration(failures: Array[String]) -> void:
	var controller := _new_controller()
	var forward_intent := _intent(1.0, 0.0)
	_expect_vector_near(controller.advance(forward_intent, Basis.IDENTITY, 0.1), Vector3(0.0, 0.0, -0.28), "first acceleration displacement", failures)
	_expect_vector_near(controller.godot_world_velocity_units_per_second, Vector3(0.0, 0.0, -2.8), "first acceleration velocity", failures)
	controller.advance(forward_intent, Basis.IDENTITY, 0.1)
	_expect_vector_near(controller.godot_world_velocity_units_per_second, Vector3(0.0, 0.0, -5.6), "second acceleration velocity", failures)
	controller.advance(_intent(0.0, 0.0), Basis.IDENTITY, 0.1)
	_expect_vector_near(controller.godot_world_velocity_units_per_second, Vector3(0.0, 0.0, -2.8), "deceleration velocity", failures)


func _verify_sandbox_sprint(failures: Array[String]) -> void:
	var controller := _new_controller()
	controller.advance(_intent(1.0, 0.0, 0.0, true), Basis.IDENTITY, 2.0)
	_expect_vector_near(controller.godot_world_velocity_units_per_second, Vector3(0.0, 0.0, -42.0), "sandbox sprint velocity", failures)


func _verify_flight_state_and_basis(failures: Array[String]) -> void:
	var controller := _new_controller()
	controller.advance(_intent(1.0, 0.0), Basis.IDENTITY, 1.0)
	_expect_true(controller.toggle_sandbox_flight(), "flight toggles on", failures)
	_expect_vector_near(controller.godot_world_velocity_units_per_second, Vector3.ZERO, "flight toggle clears velocity", failures)

	var pitched_basis := Basis(Vector3.RIGHT, deg_to_rad(30.0))
	controller.advance(_intent(1.0, 0.0, 1.0), pitched_basis, 1.0)
	var expected_flight_velocity := (-pitched_basis.z).normalized() * 7.0 + Vector3.UP * 7.0
	_expect_vector_near(controller.godot_world_velocity_units_per_second, expected_flight_velocity, "camera-relative flight velocity", failures)
	_expect_true(not controller.toggle_sandbox_flight(), "flight toggles off", failures)
	_expect_vector_near(controller.godot_world_velocity_units_per_second, Vector3.ZERO, "landing toggle clears velocity", failures)


func _verify_invalid_delta(failures: Array[String]) -> void:
	var controller := _new_controller()
	var displacement := controller.advance(_intent(1.0, 0.0), Basis.IDENTITY, -1.0)
	_expect_vector_near(displacement, Vector3.ZERO, "negative delta displacement", failures)
	_expect_vector_near(controller.godot_world_velocity_units_per_second, Vector3.ZERO, "negative delta state", failures)


func _verify_keyboard_turn(failures: Array[String]) -> void:
	var controller := _new_controller()
	_expect_near(controller.calculate_yaw_delta_radians(_intent(0.0, 0.0, 0.0, false, 1.0), 0.5), -PI * 0.5, "right turn yaw", failures)
	_expect_near(controller.calculate_yaw_delta_radians(_intent(0.0, 0.0, 0.0, false, -1.0), 0.5), PI * 0.5, "left turn yaw", failures)
	_expect_near(controller.calculate_yaw_delta_radians(_intent(0.0, 0.0, 0.0, false, 1.0), -1.0), 0.0, "negative turn delta", failures)


func _verify_scene_boundary(failures: Array[String]) -> void:
	var movement_source := _read_text(MOVEMENT_CONTROLLER_PATH, failures)
	for forbidden_text in ["extends Node", "extends Resource", "Input.", "Camera3D", "ADTLoader", "global_position"]:
		_expect_true(not movement_source.contains(forbidden_text), "movement controller omits %s" % forbidden_text, failures)

	var player_source := _read_text(PLAYER_CONTROLLER_PATH, failures)
	_expect_true(player_source.contains("_local_movement_controller.advance"), "scene delegates movement update", failures)
	_expect_true(not player_source.contains("move_toward(target_velocity"), "scene omits velocity integration", failures)
	_expect_true(not player_source.contains("func _movement_vector"), "scene omits movement-vector calculation", failures)
	_expect_true(not player_source.contains("_horizontal_velocity"), "scene omits movement velocity state", failures)
	_expect_true(not player_source.contains("var _flight_enabled"), "scene omits flight state", failures)


func _new_controller() -> LocalPlayerMovementController:
	return LocalPlayerMovementController.new(
		7.0,
		4.5,
		4.5,
		7.0,
		28.0,
		6.0,
		PlayerMovementCapabilities.render_sandbox()
	)


func _intent(
		forward_axis: float,
		strafe_axis: float,
		vertical_axis: float = 0.0,
		sprint_requested: bool = false,
		turn_axis: float = 0.0
) -> MoveIntent:
	return MoveIntent.new(forward_axis, strafe_axis, vertical_axis, sprint_requested, turn_axis)


func _read_text(path: String, failures: Array[String]) -> String:
	var file := FileAccess.open(path, FileAccess.READ)
	if file == null:
		failures.append("cannot open %s" % path)
		return ""
	return file.get_as_text()


func _expect_vector_near(
		actual_value: Vector3,
		expected_value: Vector3,
		label: String,
		failures: Array[String]
) -> void:
	if not actual_value.is_equal_approx(expected_value):
		failures.append("%s expected %s, got %s" % [label, expected_value, actual_value])


func _expect_near(actual_value: float, expected_value: float, label: String, failures: Array[String]) -> void:
	if absf(actual_value - expected_value) > 0.000001:
		failures.append("%s expected %.6f, got %.6f" % [label, expected_value, actual_value])


func _expect_true(actual_value: bool, label: String, failures: Array[String]) -> void:
	if not actual_value:
		failures.append("%s expected true" % label)
