extends SceneTree

## Repository-wide M01 gate against manual cross-space world coordinate formulas.
## Model-local M2/WMO basis transforms and ordinary Godot-space geometry are not
## world-space conversions and are intentionally outside these signatures.

const SCAN_ROOTS: Array[String] = ["res://src", "res://addons"]
const SOURCE_EXTENSIONS: Array[String] = ["gd", "cpp", "h", "hpp"]
const EXCLUDED_DIRECTORY_PREFIXES: Array[String] = ["res://src/native/build/"]
const GATE_PATH := "res://src/tools/verify_coordinate_conversion_boundaries.gd"
const COORDINATE_MAPPER_PATH := "res://src/domain/coordinates/coordinate_mapper.gd"
const CALIBRATION_ORACLE_PATH := "res://src/tools/verify_render_coordinate_calibration.gd"
const NATIVE_BOUNDARY_PATH := "res://src/native/src/wow_chunk_reader.h"
const ALLOWED_LEGACY_NAME_PATHS: Array[String] = [
	NATIVE_BOUNDARY_PATH,
	"res://src/native/src/adt_loader.cpp",
	"res://src/native/src/wdt_loader.cpp",
	CALIBRATION_ORACLE_PATH,
]
const REQUIRED_MAPPER_CALLS := {
	"res://src/scenes/sky/wow_sky_controller.gd": ["godot_to_canonical", "godot_to_adt_tile", "godot_to_adt_tile_local"],
	"res://src/scenes/streaming/streaming_world_loader.gd": ["godot_to_adt_tile", "adt_tile_local_to_godot"],
	"res://src/render/streaming/streaming_target_planner.gd": ["godot_to_adt_tile", "godot_to_adt_tile_local"],
	"res://src/scenes/player/third_person_wow_controller.gd": ["adt_tile_local_to_godot"],
	"res://src/gameplay/terrain/adt_terrain_query.gd": ["godot_to_adt_tile", "godot_to_adt_tile_local", "adt_tile_local_to_chunk"],
}


func _initialize() -> void:
	var failures: Array[String] = []
	var source_paths: Array[String] = []
	for scan_root in SCAN_ROOTS:
		_collect_source_paths(scan_root, source_paths, failures)

	var native_boundary_definition_count := 0
	var independent_oracle_definition_count := 0
	for source_path in source_paths:
		if source_path == GATE_PATH:
			continue
		var source := _read_text(source_path, failures)
		var source_lines := source.split("\n")
		for line_index in range(source_lines.size()):
			var source_line := String(source_lines[line_index])
			var violation := _coordinate_violation(source_path, source_line)
			if not violation.is_empty():
				failures.append("%s:%d %s" % [source_path, line_index + 1, violation])
			if source_path == NATIVE_BOUNDARY_PATH and source_line.contains("inline void wow_to_godot"):
				native_boundary_definition_count += 1
			if source_path == CALIBRATION_ORACLE_PATH and source_line.contains("func _wow_to_godot"):
				independent_oracle_definition_count += 1

	_expect_equal(native_boundary_definition_count, 1, "native world conversion definition count", failures)
	_expect_equal(independent_oracle_definition_count, 1, "independent calibration oracle count", failures)
	_verify_required_mapper_calls(failures)
	_verify_classifier_guards(failures)

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

	print("COORDINATE_CONVERSION_BOUNDARIES PASS files=%d native_boundary=1 oracle=1 consumers=5 classifier_cases=6" % source_paths.size())
	quit(0)


func _coordinate_violation(source_path: String, source_line: String) -> String:
	var stripped_line := source_line.strip_edges()
	if stripped_line.is_empty():
		return ""
	if (source_line.contains("wow_to_godot") or source_line.contains("godot_to_wow")) and source_path not in ALLOWED_LEGACY_NAME_PATHS:
		return "manual WoW/Godot conversion name outside mapper boundary"
	if (source_line.contains("17066.666") or source_line.contains("WOW_WORLD_CENTER")) and source_path not in [NATIVE_BOUNDARY_PATH, CALIBRATION_ORACLE_PATH]:
		return "manual WoW world-center formula outside native boundary/oracle"
	if source_line.contains("1600.0 / 3.0") and source_path != COORDINATE_MAPPER_PATH:
		return "duplicated ADT tile-size formula outside CoordinateMapper"
	if source_path != COORDINATE_MAPPER_PATH and source_line.contains("floor(") and source_line.contains("TILE_SIZE") and (source_line.contains(".x") or source_line.contains(".z")):
		return "manual Godot world-position to ADT tile formula"
	if source_path != COORDINATE_MAPPER_PATH and source_line.contains("fposmod(") and source_line.contains("TILE_SIZE"):
		return "manual Godot world-position to ADT tile-local formula"
	if source_path != COORDINATE_MAPPER_PATH and source_line.contains("+ 0.5) * TILE_SIZE"):
		return "manual ADT tile-center to Godot world-position formula"
	return ""


func _verify_required_mapper_calls(failures: Array[String]) -> void:
	for source_path in REQUIRED_MAPPER_CALLS:
		var source := _read_text(source_path, failures)
		for required_call in REQUIRED_MAPPER_CALLS[source_path]:
			if not source.contains(String(required_call)):
				failures.append("%s must call CoordinateMapper.%s" % [source_path, required_call])


func _verify_classifier_guards(failures: Array[String]) -> void:
	_expect_true(not _coordinate_violation("res://src/example.gd", "const CENTER := 17066.666").is_empty(), "world-center classifier", failures)
	_expect_true(not _coordinate_violation("res://src/example.gd", "func wow_to_godot():").is_empty(), "conversion-name classifier", failures)
	_expect_true(not _coordinate_violation("res://src/example.gd", "floor(world_position.x / TILE_SIZE)").is_empty(), "world-to-tile classifier", failures)
	_expect_true(not _coordinate_violation("res://src/example.gd", "fposmod(world_position.z, TILE_SIZE)").is_empty(), "world-to-tile-local classifier", failures)
	_expect_true(not _coordinate_violation("res://src/example.gd", "(tile_x + 0.5) * TILE_SIZE").is_empty(), "tile-center classifier", failures)
	_expect_true(_coordinate_violation(NATIVE_BOUNDARY_PATH, "inline void wow_to_godot(float wx) { gx = -(wx - 17066.666f); }").is_empty(), "native boundary exception", failures)


func _collect_source_paths(directory_path: String, source_paths: Array[String], failures: Array[String]) -> void:
	var directory := DirAccess.open(directory_path)
	if directory == null:
		failures.append("cannot scan directory %s" % directory_path)
		return
	for entry_name in directory.get_files():
		var source_path := directory_path.path_join(entry_name)
		if entry_name.get_extension().to_lower() in SOURCE_EXTENSIONS:
			source_paths.append(source_path)
	for child_directory_name in directory.get_directories():
		var child_directory_path := directory_path.path_join(child_directory_name) + "/"
		if _has_excluded_prefix(child_directory_path):
			continue
		_collect_source_paths(child_directory_path.trim_suffix("/"), source_paths, failures)


func _has_excluded_prefix(path: String) -> bool:
	for excluded_prefix in EXCLUDED_DIRECTORY_PREFIXES:
		if path.begins_with(excluded_prefix):
			return true
	return false


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_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)
