extends SceneTree

## Synthetic current/stale/ordinary/rebuild/fallback/clear/boundary/timing
## regression for runtime M2 Mesh material finalization.

const FINALIZER_SCRIPT := preload("res://src/render/m2/m2_runtime_mesh_finalizer.gd")
const FINALIZER_PATH := "res://src/render/m2/m2_runtime_mesh_finalizer.gd"
const RAW_REPOSITORY_PATH := "res://src/render/m2/m2_raw_model_repository.gd"
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"
const REFRESH_META := "wow_m2_material_refresh_version"


func _initialize() -> void:
	var failures: Array[String] = []
	_verify_current_and_empty_raw_data(failures)
	_verify_ordinary_and_rebuild_paths(failures)
	_verify_rebuild_failure_fallback_and_clear(failures)
	_verify_ownership_boundaries(failures)
	var elapsed_milliseconds := _verify_bounded_timing(failures)
	if not failures.is_empty():
		for failure in failures:
			push_error("M2_RUNTIME_MESH_FINALIZER: %s" % failure)
		quit(1)
		return
	print(
		"M2_RUNTIME_MESH_FINALIZER PASS cases=11 iterations=100 elapsed_ms=%.3f"
		% elapsed_milliseconds
	)
	quit(0)


func _verify_current_and_empty_raw_data(failures: Array[String]) -> void:
	var finalizer: RefCounted = FINALIZER_SCRIPT.new()
	_expect_same(finalizer.call("finalize_mesh", "world/null.m2", null, {}, ""), null, "null Mesh returns null", failures)
	var current_mesh := ArrayMesh.new()
	current_mesh.set_meta(REFRESH_META, 2)
	_expect_false(
		finalizer.call("requires_raw_data_for_refresh", current_mesh),
		"current Mesh skips raw data",
		failures
	)
	_expect_same(
		finalizer.call("finalize_mesh", "world/current.m2", current_mesh, _rebuild_raw_data(), ""),
		current_mesh,
		"current Mesh returns unchanged",
		failures
	)
	_expect_equal(int(finalizer.call("cached_rebuild_decision_count")), 0, "current Mesh skips classifier", failures)
	var stale_mesh := ArrayMesh.new()
	_expect_true(
		finalizer.call("requires_raw_data_for_refresh", stale_mesh),
		"stale Mesh requests raw data",
		failures
	)
	_expect_same(
		finalizer.call("finalize_mesh", "world/stale.m2", stale_mesh, {}, ""),
		stale_mesh,
		"empty raw data falls back to original",
		failures
	)
	_expect_equal(int(stale_mesh.get_meta(REFRESH_META, 0)), 2, "empty raw fallback marks current", failures)


func _verify_ordinary_and_rebuild_paths(failures: Array[String]) -> void:
	var finalizer: RefCounted = FINALIZER_SCRIPT.new()
	var ordinary_mesh := ArrayMesh.new()
	var ordinary_raw_data := {"batches": [{"has_billboard": false}]}
	_expect_same(
		finalizer.call("finalize_mesh", "world/rock.m2", ordinary_mesh, ordinary_raw_data, ""),
		ordinary_mesh,
		"ordinary raw data retains original",
		failures
	)
	_expect_equal(int(ordinary_mesh.get_meta(REFRESH_META, 0)), 2, "ordinary Mesh marks current", failures)
	var stale_billboard_mesh := ArrayMesh.new()
	var rebuilt_mesh: Mesh = finalizer.call(
		"finalize_mesh",
		"world/tree.m2",
		stale_billboard_mesh,
		_rebuild_raw_data(),
		""
	)
	_expect_true(rebuilt_mesh != null, "billboard raw data rebuilds Mesh", failures)
	_expect_false(is_same(rebuilt_mesh, stale_billboard_mesh), "rebuild replaces original Mesh", failures)
	_expect_equal(int(rebuilt_mesh.get_meta(REFRESH_META, 0)), 2, "rebuilt Mesh carries refresh version", failures)


func _verify_rebuild_failure_fallback_and_clear(failures: Array[String]) -> void:
	var finalizer: RefCounted = FINALIZER_SCRIPT.new()
	var fallback_mesh := ArrayMesh.new()
	var incomplete_billboard_data := {"batches": [{"has_billboard": true}]}
	_expect_same(
		finalizer.call(
			"finalize_mesh",
			"world/incomplete.m2",
			fallback_mesh,
			incomplete_billboard_data,
			""
		),
		fallback_mesh,
		"failed rebuild falls back to original",
		failures
	)
	_expect_equal(int(fallback_mesh.get_meta(REFRESH_META, 0)), 2, "failed rebuild marks fallback current", failures)
	_expect_equal(int(finalizer.call("cached_rebuild_decision_count")), 1, "classifier decision retained", failures)
	finalizer.call("clear")
	finalizer.call("clear")
	_expect_equal(int(finalizer.call("cached_rebuild_decision_count")), 0, "clear drops decisions", failures)


func _verify_ownership_boundaries(failures: Array[String]) -> void:
	var finalizer_source := FileAccess.get_file_as_string(FINALIZER_PATH)
	var raw_repository_source := FileAccess.get_file_as_string(RAW_REPOSITORY_PATH)
	var loader_source := FileAccess.get_file_as_string(LOADER_PATH)
	_expect_true(
		loader_source.contains("M2_RUNTIME_MESH_FINALIZER_SCRIPT.new()"),
		"loader composes runtime Mesh finalizer",
		failures
	)
	_expect_equal(
		loader_source.count("_m2_runtime_mesh_finalizer.clear()"),
		2,
		"two existing clear sites delegate",
		failures
	)
	_expect_false(loader_source.contains("const M2_MATERIAL_REFRESH_VERSION"), "refresh version leaves loader", failures)
	_expect_false(loader_source.contains("func _rebuild_m2_mesh_from_data("), "legacy rebuild adapter removed", failures)
	for retained_loader_rule in [
		"M2_RAW_MODEL_REPOSITORY_SCRIPT.new()",
		"_m2_raw_model_repository.load_static_model_data(",
		"_m2_mesh_resource_cache_state.store_mesh(",
		"_m2_prototype_cache_state.mark_model_missing(normalized_rel)",
	]:
		_expect_true(loader_source.contains(retained_loader_rule), "loader retains %s" % retained_loader_rule, failures)
	for raw_repository_rule in [
		"FileAccess.file_exists(absolute_path)",
		"ClassDB.instantiate(NATIVE_LOADER_CLASS_NAME)",
		"\"load_m2\"",
	]:
		_expect_true(raw_repository_source.contains(raw_repository_rule), "raw repository owns %s" % raw_repository_rule, failures)
	for required_finalizer_rule in [
		"const MATERIAL_REFRESH_VERSION := 2",
		"M2_RUNTIME_MESH_REBUILD_CLASSIFIER_SCRIPT.new()",
		"M2_MESH_RESOURCE_EXTRACTOR_SCRIPT.new()",
		"M2_BUILDER_SCRIPT.build(",
		"temporary_prototype.free()",
	]:
		_expect_true(finalizer_source.contains(required_finalizer_rule), "finalizer owns %s" % required_finalizer_rule, failures)
	for forbidden_dependency in [
		"ResourceLoader.",
		"FileAccess.",
		"ClassDB.",
		"load_m2",
		"_m2_mesh_resource_cache_state",
		"_m2_prototype_cache_state",
		"MultiMesh",
	]:
		_expect_false(
			finalizer_source.contains(forbidden_dependency),
			"finalizer omits %s ownership" % forbidden_dependency,
			failures
		)


func _verify_bounded_timing(failures: Array[String]) -> float:
	var finalizer: RefCounted = FINALIZER_SCRIPT.new()
	var meshes: Array[Mesh] = []
	for _path_index in range(256):
		meshes.append(ArrayMesh.new())
	var ordinary_raw_data := {"batches": [{"has_billboard": false}]}
	var started_microseconds := Time.get_ticks_usec()
	for _iteration in range(100):
		for path_index in range(256):
			var mesh := meshes[path_index]
			mesh.set_meta(REFRESH_META, 0)
			finalizer.call(
				"finalize_mesh",
				"world/model_%d.m2" % path_index,
				mesh,
				ordinary_raw_data,
				""
			)
		finalizer.call("clear")
	var elapsed_milliseconds := float(Time.get_ticks_usec() - started_microseconds) / 1000.0
	_expect_true(elapsed_milliseconds < 1000.0, "100 by 256 ordinary finalizations under 1 second", failures)
	return elapsed_milliseconds


func _rebuild_raw_data() -> Dictionary:
	return {
		"vertices": PackedVector3Array([
			Vector3(0.0, 0.0, 0.0),
			Vector3(1.0, 0.0, 0.0),
			Vector3(0.0, 1.0, 0.0),
		]),
		"indices": PackedInt32Array([0, 1, 2]),
		"batches": [{
			"index_start": 0,
			"index_count": 3,
			"material_id": -1,
			"texture_combo_index": -1,
			"has_billboard": true,
		}],
	}


func _expect_true(condition: bool, label: String, failures: Array[String]) -> void:
	if not condition:
		failures.append(label)


func _expect_false(condition: bool, label: String, failures: Array[String]) -> void:
	_expect_true(not condition, label, failures)


func _expect_equal(actual: int, expected: int, label: String, failures: Array[String]) -> void:
	if actual != expected:
		failures.append("%s expected=%d actual=%d" % [label, expected, actual])


func _expect_same(actual: Variant, expected: Variant, label: String, failures: Array[String]) -> void:
	if not is_same(actual, expected):
		failures.append(label)
