extends SceneTree

## Asset-free lifecycle, ownership, dependency and timing regression for the
## lightweight-WMO render Resource cache state.

const CACHE_STATE_SCRIPT := preload(
	"res://src/render/wmo/wmo_render_resource_cache_state.gd"
)
const CACHE_STATE_PATH := "res://src/render/wmo/wmo_render_resource_cache_state.gd"
const FINALIZER_PATH := "res://src/render/wmo/wmo_render_resource_finalizer.gd"
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"


func _initialize() -> void:
	var failures: Array[String] = []
	_verify_invalid_and_request_state(failures)
	_verify_validated_resource_completion(failures)
	_verify_missing_completion_and_exclusivity(failures)
	_verify_transient_and_full_clear(failures)
	_verify_detached_sorted_diagnostics(failures)
	_verify_ownership_boundaries(failures)
	var elapsed_milliseconds := _verify_bounded_timing(failures)
	if not failures.is_empty():
		for failure in failures:
			push_error("WMO_RENDER_RESOURCE_CACHE_STATE: %s" % failure)
		quit(1)
		return
	print(
		"WMO_RENDER_RESOURCE_CACHE_STATE PASS cases=10 iterations=100 elapsed_ms=%.3f"
		% elapsed_milliseconds
	)
	quit(0)


func _verify_invalid_and_request_state(failures: Array[String]) -> void:
	var cache_state: RefCounted = CACHE_STATE_SCRIPT.new()
	_expect_false(
		cache_state.call("remember_request", "", "res://cache.res"),
		"empty normalized path rejected",
		failures
	)
	_expect_false(
		cache_state.call("remember_request", "world/a.wmo", ""),
		"empty cache path rejected",
		failures
	)
	_expect_true(
		cache_state.call("remember_request", "world/a.wmo", "res://cache/a.res"),
		"valid request accepted",
		failures
	)
	_expect_true(
		cache_state.call("has_request", "world/a.wmo"),
		"pending request observable",
		failures
	)
	_expect_false(
		cache_state.call("remember_request", "world/a.wmo", "res://cache/other.res"),
		"duplicate request rejected",
		failures
	)
	var request_snapshot: Dictionary = cache_state.call("request_paths_snapshot")
	_expect_string_equal(
		String(request_snapshot.get("world/a.wmo", "")),
		"res://cache/a.res",
		"request cache path preserved",
		failures
	)
	request_snapshot.clear()
	_expect_true(
		cache_state.call("has_request", "world/a.wmo"),
		"request snapshot detached",
		failures
	)


func _verify_validated_resource_completion(failures: Array[String]) -> void:
	var cache_state: RefCounted = CACHE_STATE_SCRIPT.new()
	var validated_resource := Resource.new()
	cache_state.call("remember_request", "world/b.wmo", "res://cache/b.res")
	_expect_false(
		cache_state.call("complete_request_with_resource", "world/b.wmo", null),
		"null completion rejected",
		failures
	)
	_expect_true(
		cache_state.call(
			"complete_request_with_resource",
			"world/b.wmo",
			validated_resource
		),
		"validated Resource completes request",
		failures
	)
	_expect_false(
		cache_state.call("has_request", "world/b.wmo"),
		"successful completion removes request",
		failures
	)
	_expect_true(
		cache_state.call("contains_resource", "world/b.wmo"),
		"successful completion cached",
		failures
	)
	_expect_true(
		cache_state.call("resource_for", "world/b.wmo") == validated_resource,
		"cached Resource identity preserved",
		failures
	)
	_expect_false(
		cache_state.call("remember_request", "world/b.wmo", "res://cache/b2.res"),
		"cached path rejects request",
		failures
	)


func _verify_missing_completion_and_exclusivity(failures: Array[String]) -> void:
	var cache_state: RefCounted = CACHE_STATE_SCRIPT.new()
	_expect_false(
		cache_state.call("complete_request_as_missing", "world/unknown.wmo"),
		"unknown completion rejected",
		failures
	)
	cache_state.call("remember_request", "world/missing.wmo", "res://cache/missing.res")
	_expect_true(
		cache_state.call("complete_request_as_missing", "world/missing.wmo"),
		"failed request completes as missing",
		failures
	)
	_expect_true(
		cache_state.call("is_missing", "world/missing.wmo"),
		"negative cache observable",
		failures
	)
	_expect_false(
		cache_state.call("has_request", "world/missing.wmo"),
		"missing completion removes request",
		failures
	)
	_expect_false(
		cache_state.call(
			"remember_request",
			"world/missing.wmo",
			"res://cache/retry.res"
		),
		"negative cache rejects request",
		failures
	)


func _verify_transient_and_full_clear(failures: Array[String]) -> void:
	var cache_state: RefCounted = CACHE_STATE_SCRIPT.new()
	var retained_resource := Resource.new()
	cache_state.call("remember_request", "world/retained.wmo", "res://cache/retained.res")
	cache_state.call(
		"complete_request_with_resource",
		"world/retained.wmo",
		retained_resource
	)
	cache_state.call("remember_request", "world/pending.wmo", "res://cache/pending.res")
	cache_state.call("remember_request", "world/missing.wmo", "res://cache/missing.res")
	cache_state.call("complete_request_as_missing", "world/missing.wmo")
	cache_state.call("clear_transient_state")
	_expect_true(
		cache_state.call("resource_for", "world/retained.wmo") == retained_resource,
		"transient clear retains validated Resource",
		failures
	)
	_expect_equal(
		int(cache_state.call("pending_request_count")),
		0,
		"transient clear removes requests",
		failures
	)
	_expect_false(
		cache_state.call("is_missing", "world/missing.wmo"),
		"transient clear removes negative cache",
		failures
	)
	cache_state.call("clear_all")
	cache_state.call("clear_all")
	_expect_false(
		cache_state.call("contains_resource", "world/retained.wmo"),
		"full clear idempotently releases Resource",
		failures
	)


func _verify_detached_sorted_diagnostics(failures: Array[String]) -> void:
	var cache_state: RefCounted = CACHE_STATE_SCRIPT.new()
	for normalized_path in ["world/z.wmo", "world/a.wmo"]:
		cache_state.call("remember_request", normalized_path, "res://cache/%s.res" % normalized_path)
	var snapshot: Dictionary = cache_state.call("diagnostic_snapshot")
	_expect_string_array(
		snapshot["request_paths"],
		["world/a.wmo", "world/z.wmo"],
		"request diagnostics sorted",
		failures
	)
	(snapshot["request_paths"] as Array).clear()
	var fresh_snapshot: Dictionary = cache_state.call("diagnostic_snapshot")
	_expect_equal(
		(fresh_snapshot["request_paths"] as Array).size(),
		2,
		"diagnostics detached",
		failures
	)


func _verify_ownership_boundaries(failures: Array[String]) -> void:
	var loader_source := _read_text(LOADER_PATH, failures)
	var cache_state_source := _read_text(CACHE_STATE_PATH, failures)
	var finalizer_source := _read_text(FINALIZER_PATH, failures)
	_expect_true(
		loader_source.contains("WMO_RENDER_RESOURCE_CACHE_STATE_SCRIPT.new()"),
		"loader composes cache state",
		failures
	)
	for legacy_field in [
		"var _wmo_render_cache:",
		"var _wmo_render_missing_cache:",
		"var _wmo_render_load_requests:",
	]:
		_expect_false(
			loader_source.contains(legacy_field),
			"loader removes %s" % legacy_field,
			failures
		)
	for loader_owned_token in [
		"ResourceLoader.",
		"FileAccess.",
		"WMO_STREAMING_SCRIPT",
		"format_version",
		"Node3D",
		"RID",
		"Thread",
		"Mutex",
		"Semaphore",
	]:
		_expect_false(
			cache_state_source.contains(loader_owned_token),
			"cache state omits %s dependency" % loader_owned_token,
			failures
		)
	_expect_true(
		finalizer_source.contains("resource.get_script() == _expected_resource_script"),
		"finalizer owns script validation",
		failures
	)
	_expect_true(
		loader_source.contains("WMO_STREAMING_SCRIPT.FORMAT_VERSION"),
		"loader injects cache format version",
		failures
	)


func _verify_bounded_timing(failures: Array[String]) -> float:
	var cache_state: RefCounted = CACHE_STATE_SCRIPT.new()
	var started_microseconds := Time.get_ticks_usec()
	for iteration in range(100):
		for resource_index in range(256):
			var normalized_path := "world/object_%d.wmo" % resource_index
			cache_state.call(
				"remember_request",
				normalized_path,
				"res://cache/object_%d.res" % resource_index
			)
			if resource_index % 2 == 0:
				cache_state.call(
					"complete_request_with_resource",
					normalized_path,
					Resource.new()
				)
			else:
				cache_state.call("complete_request_as_missing", normalized_path)
		cache_state.call("clear_all")
		_expect_equal(
			int(cache_state.call("pending_request_count")),
			0,
			"timing cycle %d clears" % iteration,
			failures
		)
	var elapsed_milliseconds := float(Time.get_ticks_usec() - started_microseconds) / 1000.0
	_expect_true(
		elapsed_milliseconds < 1000.0,
		"100 resource/request cycles remain bounded",
		failures
	)
	return elapsed_milliseconds


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


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


func _expect_true(actual_value: bool, label: String, failures: Array[String]) -> void:
	if not actual_value:
		failures.append("%s expected true" % label)


func _expect_false(actual_value: bool, label: String, failures: Array[String]) -> void:
	if actual_value:
		failures.append("%s expected false" % label)
