extends SceneTree

const OBSERVER_SCRIPT := preload(
	"res://src/render/m2/m2_native_animation_resource_observer.gd"
)
const PROTOTYPE_CACHE_SCRIPT := preload(
	"res://src/render/m2/m2_prototype_cache_state.gd"
)
const OBSERVER_PATH := (
	"res://src/render/m2/m2_native_animation_resource_observer.gd"
)
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"


class FakeRawModelRepository extends RefCounted:
	var result: Dictionary = {}
	var call_count: int = 0
	var last_extracted_directory: String = ""
	var last_normalized_relative_path: String = ""

	func load_animated_model_data(
		extracted_directory: String,
		normalized_relative_path: String
	) -> Dictionary:
		call_count += 1
		last_extracted_directory = extracted_directory
		last_normalized_relative_path = normalized_relative_path
		return result


class FakeNativeAnimatedBuilder extends RefCounted:
	var result: Node3D = null
	var call_count: int = 0
	var last_model_data: Dictionary = {}
	var last_extracted_directory: String = ""

	func build(
		animated_model_data: Dictionary,
		extracted_directory: String
	) -> Node3D:
		call_count += 1
		last_model_data = animated_model_data
		last_extracted_directory = extracted_directory
		return result


func _initialize() -> void:
	var failures: Array[String] = []
	_verify_candidate_policy(failures)
	_verify_missing_dependencies(failures)
	_verify_cache_and_negative_lifecycle(failures)
	_verify_raw_and_builder_failures(failures)
	_verify_successful_adoption(failures)
	_verify_source_boundaries(failures)
	var elapsed_milliseconds := _verify_bounded_timing(failures)
	if not failures.is_empty():
		for failure in failures:
			push_error("M2_NATIVE_ANIMATION_RESOURCE_OBSERVER: %s" % failure)
		quit(1)
		return
	print(
		"M2_NATIVE_ANIMATION_RESOURCE_OBSERVER PASS "
		+ "cases=33 iterations=20000 elapsed_ms=%.3f" % elapsed_milliseconds
	)
	quit(0)


func _verify_candidate_policy(failures: Array[String]) -> void:
	var observer: RefCounted = OBSERVER_SCRIPT.new()
	_expect_false(
		bool(observer.call("is_native_animation_candidate", "")),
		"empty path is not candidate",
		failures
	)
	_expect_true(
		bool(observer.call(
			"is_native_animation_candidate",
			"World/Generic/GryphonRoost/GryphonRoost.M2"
		)),
		"candidate match ignores case",
		failures
	)
	_expect_false(
		bool(observer.call(
			"is_native_animation_candidate",
			"creature/gryphon/gryphon.m2"
		)),
		"unlisted gryphon is not candidate",
		failures
	)


func _verify_missing_dependencies(failures: Array[String]) -> void:
	var path := "world/gryphonroost/dependency.m2"
	var builder := FakeNativeAnimatedBuilder.new()
	var observer: RefCounted = OBSERVER_SCRIPT.new(builder)
	var repository := FakeRawModelRepository.new()
	var prototype_cache: RefCounted = PROTOTYPE_CACHE_SCRIPT.new()
	_expect_null(
		observer.call("observe", path, "res://data/extracted", null, prototype_cache),
		"missing repository rejected",
		failures
	)
	_expect_null(
		observer.call("observe", path, "res://data/extracted", repository, null),
		"missing cache rejected",
		failures
	)
	repository.result = _animated_model_data()
	observer = OBSERVER_SCRIPT.new(null)
	_expect_null(
		observer.call(
			"observe", path, "res://data/extracted", repository, prototype_cache
		),
		"missing builder rejected",
		failures
	)
	prototype_cache.call("clear_and_release")


func _verify_cache_and_negative_lifecycle(failures: Array[String]) -> void:
	var builder := FakeNativeAnimatedBuilder.new()
	var observer: RefCounted = OBSERVER_SCRIPT.new(builder)
	var repository := FakeRawModelRepository.new()
	var prototype_cache: RefCounted = PROTOTYPE_CACHE_SCRIPT.new()
	_expect_null(
		observer.call(
			"observe", "", "res://data/extracted", repository,
			prototype_cache
		),
		"empty observation rejected",
		failures
	)
	_expect_null(
		observer.call(
			"observe", "world/not_native.m2", "res://data/extracted",
			repository, prototype_cache
		),
		"non-candidate rejected",
		failures
	)
	_expect_equal(repository.call_count, 0, "rejected paths skip repository", failures)
	var cached_prototype := Node3D.new()
	prototype_cache.call(
		"adopt_animated_prototype",
		"world/gryphonroost/cached.m2",
		cached_prototype
	)
	_expect_same(
		observer.call(
			"observe", "world/gryphonroost/cached.m2",
			"res://data/extracted", repository, prototype_cache
		),
		cached_prototype,
		"cached prototype identity",
		failures
	)
	prototype_cache.call(
		"mark_animation_static",
		"world/gryphonroost/static.m2"
	)
	_expect_null(
		observer.call(
			"observe", "world/gryphonroost/static.m2",
			"res://data/extracted", repository, prototype_cache
		),
		"static-only outcome suppresses load",
		failures
	)
	_expect_equal(repository.call_count, 0, "cache outcomes skip repository", failures)
	prototype_cache.call("clear_and_release")


func _verify_raw_and_builder_failures(failures: Array[String]) -> void:
	var builder := FakeNativeAnimatedBuilder.new()
	var observer: RefCounted = OBSERVER_SCRIPT.new(builder)
	var repository := FakeRawModelRepository.new()
	var prototype_cache: RefCounted = PROTOTYPE_CACHE_SCRIPT.new()
	var empty_path := "world/gryphonroost/empty.m2"
	_expect_null(
		observer.call(
			"observe", empty_path, "res://data/extracted", repository,
			prototype_cache
		),
		"empty raw data rejects",
		failures
	)
	_expect_true(
		bool(prototype_cache.call("is_animation_static", empty_path)),
		"empty raw data marks static",
		failures
	)
	repository.result = {"animated_surfaces": []}
	var surfaces_path := "world/gryphonroost/no_surfaces.m2"
	_expect_null(
		observer.call(
			"observe", surfaces_path, "res://data/extracted", repository,
			prototype_cache
		),
		"empty animated surfaces reject",
		failures
	)
	_expect_true(
		bool(prototype_cache.call("is_animation_static", surfaces_path)),
		"empty surfaces mark static",
		failures
	)
	repository.result = _animated_model_data()
	var null_path := "world/gryphonroost/null_build.m2"
	_expect_null(
		observer.call(
			"observe", null_path, "res://data/extracted", repository,
			prototype_cache
		),
		"null build rejects",
		failures
	)
	_expect_true(
		bool(prototype_cache.call("is_animation_static", null_path)),
		"null build marks static",
		failures
	)
	var childless_prototype := Node3D.new()
	builder.result = childless_prototype
	var childless_path := "world/gryphonroost/childless.m2"
	_expect_null(
		observer.call(
			"observe", childless_path, "res://data/extracted", repository,
			prototype_cache
		),
		"childless build rejects",
		failures
	)
	_expect_true(
		bool(prototype_cache.call("is_animation_static", childless_path)),
		"childless build marks static",
		failures
	)
	_expect_true(
		is_instance_valid(childless_prototype),
		"historical childless candidate lifetime retained",
		failures
	)
	childless_prototype.free()
	prototype_cache.call("clear_and_release")


func _verify_successful_adoption(failures: Array[String]) -> void:
	var builder := FakeNativeAnimatedBuilder.new()
	var prototype := Node3D.new()
	prototype.add_child(Node3D.new())
	builder.result = prototype
	var observer: RefCounted = OBSERVER_SCRIPT.new(builder)
	var repository := FakeRawModelRepository.new()
	repository.result = _animated_model_data()
	var prototype_cache: RefCounted = PROTOTYPE_CACHE_SCRIPT.new()
	var path := "World/Generic/GryphonRoost/GryphonRoost.M2"
	var observed: Node3D = observer.call(
		"observe",
		path,
		"res://custom/extracted",
		repository,
		prototype_cache
	)
	_expect_same(observed, prototype, "successful build identity", failures)
	_expect_same(
		prototype_cache.call("find_animated_prototype", path),
		prototype,
		"successful build adopted",
		failures
	)
	_expect_equal(repository.call_count, 1, "one repository read", failures)
	_expect_string_equal(
		repository.last_extracted_directory,
		"res://custom/extracted",
		"repository extracted directory",
		failures
	)
	_expect_string_equal(
		repository.last_normalized_relative_path,
		path,
		"repository normalized path",
		failures
	)
	_expect_equal(builder.call_count, 1, "one builder call", failures)
	_expect_true(
		builder.last_model_data == repository.result,
		"builder receives exact raw data",
		failures
	)
	_expect_string_equal(
		builder.last_extracted_directory,
		"res://custom/extracted",
		"builder extracted directory",
		failures
	)
	prototype_cache.call("clear_and_release")


func _verify_source_boundaries(failures: Array[String]) -> void:
	var loader_source := FileAccess.get_file_as_string(LOADER_PATH)
	var observer_source := FileAccess.get_file_as_string(OBSERVER_PATH)
	_expect_true(
		loader_source.contains("_m2_native_animation_resource_observer.observe("),
		"loader delegates native observation",
		failures
	)
	for removed_token in [
		"func _get_or_load_m2_native_animated_prototype",
		"func _is_m2_native_animation_candidate",
		"const M2_NATIVE_ANIMATED_BUILDER_SCRIPT",
	]:
		_expect_false(
			loader_source.contains(removed_token),
			"loader omits %s" % removed_token,
			failures
		)
	for required_token in [
		"M2_NATIVE_ANIMATED_BUILDER_SCRIPT",
		"\"load_animated_model_data\"",
		"\"adopt_animated_prototype\"",
		"\"mark_animation_static\"",
		"M2_NATIVE_ANIM_CACHE path=%s surfaces=%d bones=%d",
	]:
		_expect_true(
			observer_source.contains(required_token),
			"observer owns %s" % required_token,
			failures
		)
	for forbidden_token in [
		"ResourceLoader",
		"queue_free",
		"RenderingServer",
		"WorkerThreadPool",
		"RenderBudgetScheduler",
		"add_child",
	]:
		_expect_false(
			observer_source.contains(forbidden_token),
			"observer omits %s" % forbidden_token,
			failures
		)


func _verify_bounded_timing(failures: Array[String]) -> float:
	var observer: RefCounted = OBSERVER_SCRIPT.new()
	var started_microseconds := Time.get_ticks_usec()
	for iteration in range(20000):
		observer.call(
			"is_native_animation_candidate",
			"world/model_%d/gryphonroost.m2" % (iteration % 64)
		)
	var elapsed_milliseconds := (
		float(Time.get_ticks_usec() - started_microseconds) / 1000.0
	)
	_expect_true(
		elapsed_milliseconds < 1000.0,
		"20,000 candidate checks remain bounded",
		failures
	)
	return elapsed_milliseconds


func _animated_model_data() -> Dictionary:
	return {
		"animated_surfaces": [{"surface": 1}],
		"bones": [{"bone": 1}, {"bone": 2}],
		"animation_id": 7,
		"animation_sequence_index": 3,
		"animation_activity_score": 9,
		"animation_length": 1.25,
	}


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_null(
	actual: Variant,
	label: String,
	failures: Array[String]
) -> void:
	_expect_true(actual == null, label, failures)


func _expect_same(
	actual: Variant,
	expected: Variant,
	label: String,
	failures: Array[String]
) -> void:
	_expect_true(is_same(actual, expected), 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_string_equal(
	actual: String,
	expected: String,
	label: String,
	failures: Array[String]
) -> void:
	if actual != expected:
		failures.append("%s expected=%s actual=%s" % [label, expected, actual])
