extends SceneTree

## Synthetic admission/identity/negative-state/release/source/timing contract.

const CACHE_STATE_SCRIPT := preload("res://src/render/m2/m2_prototype_cache_state.gd")
const CACHE_STATE_PATH := "res://src/render/m2/m2_prototype_cache_state.gd"
const NATIVE_OBSERVER_PATH := (
	"res://src/render/m2/m2_native_animation_resource_observer.gd"
)
const CACHED_OBSERVER_PATH := (
	"res://src/render/m2/m2_cached_animation_resource_observer.gd"
)
const RESOURCE_FINALIZER_PATH := (
	"res://src/render/m2/m2_animation_resource_finalizer.gd"
)
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"


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


func _verify_admission_identity_and_negative_state(failures: Array[String]) -> void:
	var state: RefCounted = CACHE_STATE_SCRIPT.new()
	_expect_same(state.call("find_static_prototype", "world/missing.m2"), null, "unknown static miss", failures)
	_expect_false(state.call("mark_model_missing", ""), "empty missing path rejected", failures)
	_expect_false(state.call("mark_animation_static", ""), "empty static-animation path rejected", failures)
	var static_prototype := Node3D.new()
	var animated_prototype := Node3D.new()
	_expect_same(
		state.call("adopt_static_prototype", "world/tree.m2", static_prototype),
		static_prototype,
		"static prototype adopted",
		failures
	)
	_expect_same(
		state.call("find_static_prototype", "world/tree.m2"),
		static_prototype,
		"static identity retained",
		failures
	)
	_expect_same(
		state.call("adopt_animated_prototype", "world/tree.m2", animated_prototype),
		animated_prototype,
		"animated prototype independent",
		failures
	)
	var duplicate_static := Node3D.new()
	_expect_same(
		state.call("adopt_static_prototype", "world/tree.m2", duplicate_static),
		static_prototype,
		"first static prototype wins",
		failures
	)
	_expect_false(is_instance_valid(duplicate_static), "duplicate detached prototype released", failures)
	_expect_true(state.call("mark_model_missing", "world/missing.m2"), "missing path marked", failures)
	_expect_true(state.call("is_model_missing", "world/missing.m2"), "missing path retained", failures)
	_expect_true(state.call("mark_animation_static", "world/rock.m2"), "static animation marked", failures)
	_expect_true(state.call("is_animation_static", "world/rock.m2"), "static animation retained", failures)
	var snapshot: Dictionary = state.call("diagnostic_snapshot")
	_expect_equal((snapshot["static_prototype_paths"] as Array).size(), 1, "one static path", failures)
	_expect_equal((snapshot["animated_prototype_paths"] as Array).size(), 1, "one animated path", failures)
	state.call("clear_and_release")


func _verify_release_ownership(failures: Array[String]) -> void:
	var state: RefCounted = CACHE_STATE_SCRIPT.new()
	var detached_static := Node3D.new()
	var in_tree_animated := Node3D.new()
	get_root().add_child(in_tree_animated)
	state.call("adopt_static_prototype", "world/static.m2", detached_static)
	state.call("adopt_animated_prototype", "world/animated.m2", in_tree_animated)
	state.call("mark_model_missing", "world/missing.m2")
	state.call("mark_animation_static", "world/static.m2")
	state.call("clear_and_release")
	state.call("clear_and_release")
	_expect_false(is_instance_valid(detached_static), "detached prototype freed", failures)
	_expect_true(
		not is_instance_valid(in_tree_animated) or in_tree_animated.is_queued_for_deletion(),
		"in-tree prototype queued or released by headless teardown",
		failures
	)
	var snapshot: Dictionary = state.call("diagnostic_snapshot")
	for state_key in snapshot.keys():
		_expect_true((snapshot[state_key] as Array).is_empty(), "%s cleared" % state_key, failures)


func _verify_ownership_boundaries(failures: Array[String]) -> void:
	var state_source := FileAccess.get_file_as_string(CACHE_STATE_PATH)
	var loader_source := FileAccess.get_file_as_string(LOADER_PATH)
	var observer_sources := (
		FileAccess.get_file_as_string(NATIVE_OBSERVER_PATH)
		+ FileAccess.get_file_as_string(CACHED_OBSERVER_PATH)
		+ FileAccess.get_file_as_string(RESOURCE_FINALIZER_PATH)
	)
	_expect_true(
		loader_source.contains("M2_PROTOTYPE_CACHE_STATE_SCRIPT.new()"),
		"loader composes prototype cache state",
		failures
	)
	for removed_loader_field in [
		"var _m2_scene_cache:",
		"var _m2_animated_scene_cache:",
		"var _m2_static_animation_cache:",
		"var _m2_missing_cache:",
	]:
		_expect_false(loader_source.contains(removed_loader_field), "loader omits %s" % removed_loader_field, failures)
	for required_loader_rule in [
		"_m2_prototype_cache_state.find_static_prototype(",
		"_m2_prototype_cache_state.adopt_static_prototype(",
		"\"find_animated_prototype\"",
		"\"adopt_animated_prototype\"",
		"_m2_prototype_cache_state.mark_model_missing(",
		"\"mark_animation_static\"",
		"_m2_prototype_cache_state.clear_and_release()",
	]:
		_expect_true(
			(loader_source + observer_sources).contains(required_loader_rule),
			"renderer delegates %s" % required_loader_rule,
			failures
		)
	for forbidden_dependency in [
		"ResourceLoader.",
		"FileAccess.",
		"ClassDB.",
		"M2Builder",
		"WorkerThreadPool",
		"MultiMesh",
		"MeshInstance3D",
	]:
		_expect_false(state_source.contains(forbidden_dependency), "state omits %s" % forbidden_dependency, failures)


func _verify_bounded_timing(failures: Array[String]) -> float:
	var started_microseconds := Time.get_ticks_usec()
	for _iteration in range(100):
		var state: RefCounted = CACHE_STATE_SCRIPT.new()
		for path_index in range(256):
			var normalized_path := "world/model_%d.m2" % path_index
			state.call("mark_model_missing", normalized_path)
			state.call("mark_animation_static", normalized_path)
			state.call("is_model_missing", normalized_path)
			state.call("is_animation_static", normalized_path)
		state.call("clear_and_release")
	var elapsed_milliseconds := float(Time.get_ticks_usec() - started_microseconds) / 1000.0
	_expect_true(elapsed_milliseconds < 1000.0, "100 by 256 path-state cycles under 1 second", failures)
	return elapsed_milliseconds


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)
