extends SceneTree

## Headless contract verification for M01 canonical coordinate value objects and
## [CoordinateMapper]. Uses synthetic boundaries plus five positions observed in
## the original WoW 3.3.5a build 12340 during the M00 paired renderer session.
## The Elwynn filename case regression-checks a raw `Azeroth_31_49.adt` corpus.

const CoordinateMapperScript = preload("res://src/domain/coordinates/coordinate_mapper.gd")
const CanonicalWowWorldPositionScript = preload("res://src/domain/coordinates/canonical_wow_world_position.gd")
const ServerWorldPositionScript = preload("res://src/domain/coordinates/server_world_position.gd")
const AdtPlacementPositionScript = preload("res://src/domain/coordinates/adt_placement_position.gd")
const AdtTileCoordinateScript = preload("res://src/domain/coordinates/adt_tile_coordinate.gd")
const AdtTileLocalPositionScript = preload("res://src/domain/coordinates/adt_tile_local_position.gd")
const CanonicalWowWorldYawScript = preload("res://src/domain/coordinates/canonical_wow_world_yaw.gd")
const ServerWorldYawScript = preload("res://src/domain/coordinates/server_world_yaw.gd")

const RENDER_BASELINE_MANIFEST_PATH := "res://src/tools/render_baseline_manifest.json"
const GOLDEN_POSITION_TOLERANCE_YARDS := 0.002
const ROUND_TRIP_TOLERANCE_YARDS := 0.000001
const BOUNDARY_EPSILON_YARDS := 0.001


func _initialize() -> void:
	var failures: Array[String] = []
	_verify_build_12340_golden_positions(failures)
	_verify_server_and_adt_round_trips(failures)
	_verify_tile_and_chunk_boundaries(failures)
	_verify_recorded_raw_adt_chunk_axes(failures)
	_verify_world_yaw_conversions(failures)

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

	print("COORDINATE_MAPPER PASS golden_points=5 tile_boundaries=8 raw_mcnk=2 yaw_cases=5")
	quit(0)


func _verify_build_12340_golden_positions(failures: Array[String]) -> void:
	var manifest := _load_json_object(RENDER_BASELINE_MANIFEST_PATH, failures)
	if manifest.is_empty():
		return

	var golden_point_count := 0
	for checkpoint_variant in manifest.get("checkpoints", []):
		if not (checkpoint_variant is Dictionary):
			continue
		var checkpoint: Dictionary = checkpoint_variant
		var reference_wow_camera_variant = checkpoint.get("reference_wow_camera", null)
		if not (reference_wow_camera_variant is Array) or reference_wow_camera_variant.size() != 3:
			continue

		var expected_godot_camera_variant = checkpoint.get("camera", null)
		if not (expected_godot_camera_variant is Array) or expected_godot_camera_variant.size() != 3:
			failures.append("%s has no three-component renderer camera" % checkpoint.get("name", "checkpoint"))
			continue

		var canonical_position = CanonicalWowWorldPositionScript.new(
			float(reference_wow_camera_variant[0]),
			float(reference_wow_camera_variant[1]),
			float(reference_wow_camera_variant[2])
		)
		var godot_position = CoordinateMapperScript.canonical_to_godot(canonical_position)
		var checkpoint_name := String(checkpoint.get("name", "checkpoint"))
		_expect_near(godot_position.x_units, float(expected_godot_camera_variant[0]), GOLDEN_POSITION_TOLERANCE_YARDS, "%s Godot X" % checkpoint_name, failures)
		_expect_near(godot_position.y_units, float(expected_godot_camera_variant[1]), GOLDEN_POSITION_TOLERANCE_YARDS, "%s Godot Y" % checkpoint_name, failures)
		_expect_near(godot_position.z_units, float(expected_godot_camera_variant[2]), GOLDEN_POSITION_TOLERANCE_YARDS, "%s Godot Z" % checkpoint_name, failures)

		var round_trip_position = CoordinateMapperScript.godot_to_canonical(godot_position)
		_expect_canonical_position(round_trip_position, canonical_position, ROUND_TRIP_TOLERANCE_YARDS, "%s Godot round trip" % checkpoint_name, failures)
		golden_point_count += 1

	_expect_equal(golden_point_count, 5, "build 12340 golden point count", failures)


func _verify_server_and_adt_round_trips(failures: Array[String]) -> void:
	var server_position = ServerWorldPositionScript.new(-9153.334, 386.666, 180.0)
	var canonical_position = CoordinateMapperScript.server_to_canonical(server_position)
	_expect_near(canonical_position.x_yards, server_position.x_yards, ROUND_TRIP_TOLERANCE_YARDS, "server X remains canonical X", failures)
	_expect_near(canonical_position.y_yards, server_position.y_yards, ROUND_TRIP_TOLERANCE_YARDS, "server Y remains canonical Y", failures)
	_expect_near(canonical_position.z_yards, server_position.z_yards, ROUND_TRIP_TOLERANCE_YARDS, "server Z remains canonical Z", failures)

	var round_trip_server = CoordinateMapperScript.canonical_to_server(canonical_position)
	_expect_near(round_trip_server.x_yards, server_position.x_yards, ROUND_TRIP_TOLERANCE_YARDS, "server X round trip", failures)
	_expect_near(round_trip_server.y_yards, server_position.y_yards, ROUND_TRIP_TOLERANCE_YARDS, "server Y round trip", failures)
	_expect_near(round_trip_server.z_yards, server_position.z_yards, ROUND_TRIP_TOLERANCE_YARDS, "server Z round trip", failures)

	var adt_position = CoordinateMapperScript.canonical_to_adt_placement(canonical_position)
	_expect_near(adt_position.x_offset_yards, 26220.000666666667, GOLDEN_POSITION_TOLERANCE_YARDS, "ADT placement X offset", failures)
	_expect_near(adt_position.height_yards, 180.0, ROUND_TRIP_TOLERANCE_YARDS, "ADT placement height", failures)
	_expect_near(adt_position.z_offset_yards, 16680.000666666667, GOLDEN_POSITION_TOLERANCE_YARDS, "ADT placement Z offset", failures)
	var adt_round_trip = CoordinateMapperScript.adt_placement_to_canonical(adt_position)
	_expect_canonical_position(adt_round_trip, canonical_position, ROUND_TRIP_TOLERANCE_YARDS, "ADT placement round trip", failures)

	var direct_adt_position = AdtPlacementPositionScript.new(26220.000666666667, 180.0, 16680.000666666667)
	var direct_canonical = CoordinateMapperScript.adt_placement_to_canonical(direct_adt_position)
	_expect_canonical_position(direct_canonical, canonical_position, GOLDEN_POSITION_TOLERANCE_YARDS, "direct ADT placement conversion", failures)


func _verify_tile_and_chunk_boundaries(failures: Array[String]) -> void:
	var half_extent := CoordinateMapperScript.WOW_WORLD_HALF_EXTENT_YARDS
	var tile_size := CoordinateMapperScript.ADT_TILE_SIZE_YARDS

	_expect_tile(CoordinateMapperScript.canonical_to_adt_tile(CanonicalWowWorldPositionScript.new(half_extent, half_extent, 0.0)), 0, 0, true, "north-west extent", failures)
	_expect_tile(CoordinateMapperScript.canonical_to_adt_tile(CanonicalWowWorldPositionScript.new(0.0, 0.0, 0.0)), 32, 32, true, "map center", failures)
	_expect_tile(CoordinateMapperScript.canonical_to_adt_tile(CanonicalWowWorldPositionScript.new(BOUNDARY_EPSILON_YARDS, BOUNDARY_EPSILON_YARDS, 0.0)), 31, 31, true, "north-west of center boundary", failures)
	_expect_tile(CoordinateMapperScript.canonical_to_adt_tile(CanonicalWowWorldPositionScript.new(-BOUNDARY_EPSILON_YARDS, -BOUNDARY_EPSILON_YARDS, 0.0)), 32, 32, true, "south-east of center boundary", failures)
	_expect_tile(CoordinateMapperScript.canonical_to_adt_tile(CanonicalWowWorldPositionScript.new(-half_extent + BOUNDARY_EPSILON_YARDS, -half_extent + BOUNDARY_EPSILON_YARDS, 0.0)), 63, 63, true, "inside south-east extent", failures)
	_expect_tile(CoordinateMapperScript.canonical_to_adt_tile(CanonicalWowWorldPositionScript.new(-half_extent, -half_extent, 0.0)), 64, 64, false, "exclusive south-east extent", failures)
	_expect_tile(CoordinateMapperScript.canonical_to_adt_tile(CanonicalWowWorldPositionScript.new(tile_size, -tile_size, 0.0)), 33, 31, true, "whole-tile offsets", failures)
	_expect_tile(CoordinateMapperScript.canonical_to_adt_tile(CanonicalWowWorldPositionScript.new(-9153.334, 386.666, 180.0)), 31, 49, true, "raw Azeroth_31_49 filename", failures)

	var tile_coordinate = AdtTileCoordinateScript.new(31, 49)
	var local_position = CoordinateMapperScript.canonical_to_adt_tile_local(CanonicalWowWorldPositionScript.new(-9153.334, 386.666, 180.0))
	var local_round_trip = CoordinateMapperScript.adt_tile_local_to_canonical(tile_coordinate, local_position)
	_expect_canonical_position(local_round_trip, CanonicalWowWorldPositionScript.new(-9153.334, 386.666, 180.0), ROUND_TRIP_TOLERANCE_YARDS, "ADT tile-local round trip", failures)
	_expect_true(local_position.east_yards >= 0.0 and local_position.east_yards < tile_size, "tile-local east offset is half-open", failures)
	_expect_true(local_position.south_yards >= 0.0 and local_position.south_yards < tile_size, "tile-local south offset is half-open", failures)

	var godot_position = CoordinateMapperScript.canonical_to_godot(CanonicalWowWorldPositionScript.new(-9153.334, 386.666, 180.0))
	var direct_godot_tile = CoordinateMapperScript.godot_to_adt_tile(godot_position)
	var direct_godot_local = CoordinateMapperScript.godot_to_adt_tile_local(godot_position)
	var direct_godot_round_trip = CoordinateMapperScript.adt_tile_local_to_godot(direct_godot_tile, direct_godot_local)
	_expect_equal(direct_godot_tile.tile_x, 31, "direct Godot ADT tile X", failures)
	_expect_equal(direct_godot_tile.tile_y, 49, "direct Godot ADT tile Y", failures)
	_expect_near(direct_godot_local.east_yards, 146.66677, GOLDEN_POSITION_TOLERANCE_YARDS, "direct Godot tile-local east", failures)
	_expect_near(direct_godot_local.south_yards, 86.66677, GOLDEN_POSITION_TOLERANCE_YARDS, "direct Godot tile-local south", failures)
	_expect_near(direct_godot_round_trip.x_units, godot_position.x_units, ROUND_TRIP_TOLERANCE_YARDS, "direct Godot tile-local X round trip", failures)
	_expect_near(direct_godot_round_trip.y_units, godot_position.y_units, ROUND_TRIP_TOLERANCE_YARDS, "direct Godot tile-local Y round trip", failures)
	_expect_near(direct_godot_round_trip.z_units, godot_position.z_units, ROUND_TRIP_TOLERANCE_YARDS, "direct Godot tile-local Z round trip", failures)

	var chunk_local_position = AdtTileLocalPositionScript.new(
		CoordinateMapperScript.ADT_CHUNK_SIZE_YARDS * 2.0,
		180.0,
		CoordinateMapperScript.ADT_CHUNK_SIZE_YARDS
	)
	var chunk_coordinate = CoordinateMapperScript.adt_tile_local_to_chunk(tile_coordinate, chunk_local_position)
	_expect_equal(chunk_coordinate.chunk_x, 2, "exact east chunk boundary", failures)
	_expect_equal(chunk_coordinate.chunk_y, 1, "exact south chunk boundary", failures)
	_expect_true(chunk_coordinate.is_within_map_grid(), "chunk coordinate is inside map grid", failures)


func _verify_recorded_raw_adt_chunk_axes(failures: Array[String]) -> void:
	var half_chunk_size_yards := CoordinateMapperScript.ADT_CHUNK_SIZE_YARDS * 0.5
	var recorded_chunk_origins := [
		{
			"label": "raw MCNK 0,0",
			"chunk_x": 0,
			"chunk_y": 0,
			"canonical_x_origin": -9066.666015625,
			"canonical_y_origin": 533.333984375,
		},
		{
			"label": "raw MCNK 15,15",
			"chunk_x": 15,
			"chunk_y": 15,
			"canonical_x_origin": -9566.666015625,
			"canonical_y_origin": 33.333984375,
		},
	]
	for recorded_chunk_origin in recorded_chunk_origins:
		var chunk_center = CanonicalWowWorldPositionScript.new(
			float(recorded_chunk_origin.canonical_x_origin) - half_chunk_size_yards,
			float(recorded_chunk_origin.canonical_y_origin) - half_chunk_size_yards,
			0.0
		)
		var tile_coordinate = CoordinateMapperScript.canonical_to_adt_tile(chunk_center)
		_expect_tile(tile_coordinate, 31, 49, true, String(recorded_chunk_origin.label), failures)
		var local_position = CoordinateMapperScript.canonical_to_adt_tile_local(chunk_center)
		var chunk_coordinate = CoordinateMapperScript.adt_tile_local_to_chunk(tile_coordinate, local_position)
		_expect_equal(chunk_coordinate.chunk_x, int(recorded_chunk_origin.chunk_x), "%s chunk X" % recorded_chunk_origin.label, failures)
		_expect_equal(chunk_coordinate.chunk_y, int(recorded_chunk_origin.chunk_y), "%s chunk Y" % recorded_chunk_origin.label, failures)


func _verify_world_yaw_conversions(failures: Array[String]) -> void:
	var yaw_cases := [0.0, PI * 0.5, -PI * 0.5, PI, TAU + 0.25]
	for yaw_radians in yaw_cases:
		var server_yaw = ServerWorldYawScript.new(float(yaw_radians))
		var canonical_yaw = CoordinateMapperScript.server_to_canonical_yaw(server_yaw)
		var godot_yaw = CoordinateMapperScript.canonical_to_godot_yaw(canonical_yaw)
		var canonical_round_trip = CoordinateMapperScript.godot_to_canonical_yaw(godot_yaw)
		var server_round_trip = CoordinateMapperScript.canonical_to_server_yaw(canonical_round_trip)
		var expected_normalized_yaw := fposmod(float(yaw_radians) + PI, TAU) - PI
		_expect_near(canonical_yaw.radians, expected_normalized_yaw, ROUND_TRIP_TOLERANCE_YARDS, "canonical yaw normalization", failures)
		_expect_near(godot_yaw.radians, expected_normalized_yaw, ROUND_TRIP_TOLERANCE_YARDS, "Godot yaw basis", failures)
		_expect_near(server_round_trip.radians, expected_normalized_yaw, ROUND_TRIP_TOLERANCE_YARDS, "server yaw round trip", failures)

	var canonical_pi_yaw = CanonicalWowWorldYawScript.new(PI)
	var normalized_godot_yaw = CoordinateMapperScript.canonical_to_godot_yaw(canonical_pi_yaw)
	_expect_near(normalized_godot_yaw.radians, -PI, ROUND_TRIP_TOLERANCE_YARDS, "positive PI normalizes to negative PI", failures)


func _expect_tile(tile_coordinate, expected_x: int, expected_y: int, expected_valid: bool, label: String, failures: Array[String]) -> void:
	_expect_equal(tile_coordinate.tile_x, expected_x, "%s tile X" % label, failures)
	_expect_equal(tile_coordinate.tile_y, expected_y, "%s tile Y" % label, failures)
	_expect_equal(tile_coordinate.is_within_map_grid(), expected_valid, "%s validity" % label, failures)


func _expect_canonical_position(actual_position, expected_position, tolerance: float, label: String, failures: Array[String]) -> void:
	_expect_near(actual_position.x_yards, expected_position.x_yards, tolerance, "%s X" % label, failures)
	_expect_near(actual_position.y_yards, expected_position.y_yards, tolerance, "%s Y" % label, failures)
	_expect_near(actual_position.z_yards, expected_position.z_yards, tolerance, "%s Z" % label, failures)


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


func _expect_equal(actual_value, expected_value, label: String, failures: Array[String]) -> void:
	if actual_value != expected_value:
		failures.append("%s expected %s, got %s" % [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)


func _load_json_object(path: String, failures: Array[String]) -> Dictionary:
	var file := FileAccess.open(path, FileAccess.READ)
	if file == null:
		failures.append("cannot open JSON fixture: %s" % path)
		return {}
	var parsed = JSON.parse_string(file.get_as_text())
	if not (parsed is Dictionary):
		failures.append("JSON fixture is not an object: %s" % path)
		return {}
	return parsed
