extends SceneTree

## Asset-free terminal-I/O, probe validation/lifetime, adoption, boundary and
## timing regression for the cached WMO PackedScene finalizer.

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


class FakeSceneCacheValidator extends RefCounted:
	var validated_nodes: Array[Node] = []

	func is_scene_cache_current(node: Node) -> bool:
		validated_nodes.append(node)
		return bool(node.get_meta("current_wmo_cache", false))


class FakeResourceLoaderAdapter extends RefCounted:
	var status_by_path: Dictionary = {}
	var resource_by_path: Dictionary = {}
	var status_paths: Array[String] = []
	var loaded_paths: Array[String] = []

	func load_threaded_get_status(resource_path: String) -> int:
		status_paths.append(resource_path)
		return int(status_by_path.get(resource_path, ResourceLoader.THREAD_LOAD_IN_PROGRESS))

	func load_threaded_get(resource_path: String) -> Resource:
		loaded_paths.append(resource_path)
		return resource_by_path.get(resource_path) as Resource


func _initialize() -> void:
	var failures: Array[String] = []
	_verify_null_and_non_terminal_poll(failures)
	_verify_failed_and_loaded_poll_order(failures)
	_verify_loaded_resource_rejections(failures)
	_verify_current_scene_adoption_and_probe_lifetime(failures)
	_verify_validation_contract(failures)
	_verify_source_boundaries(failures)
	var elapsed_milliseconds := _verify_bounded_timing(failures)
	if not failures.is_empty():
		for failure in failures:
			push_error("WMO_SCENE_RESOURCE_FINALIZER: %s" % failure)
		quit(1)
		return
	print(
		"WMO_SCENE_RESOURCE_FINALIZER PASS cases=26 iterations=1000 elapsed_ms=%.3f"
		% elapsed_milliseconds
	)
	quit(0)


func _verify_null_and_non_terminal_poll(failures: Array[String]) -> void:
	var validator := FakeSceneCacheValidator.new()
	var adapter := FakeResourceLoaderAdapter.new()
	var finalizer: RefCounted = FINALIZER_SCRIPT.new(validator, adapter)
	_expect_equal(
		int(finalizer.call("poll_terminal_requests", null)),
		0,
		"null cache state rejected",
		failures
	)
	var cache_state: RefCounted = CACHE_STATE_SCRIPT.new()
	cache_state.call("remember_request", "world/pending.wmo", "res://cache/pending.tscn")
	_expect_equal(
		int(finalizer.call("poll_terminal_requests", cache_state)),
		0,
		"non-terminal request retained",
		failures
	)
	_expect_true(
		bool(cache_state.call("has_request", "world/pending.wmo")),
		"pending state remains",
		failures
	)
	_expect_string_array(
		adapter.status_paths,
		["res://cache/pending.tscn"],
		"pending path polled once",
		failures
	)
	_expect_equal(adapter.loaded_paths.size(), 0, "pending scene not loaded", failures)


func _verify_failed_and_loaded_poll_order(failures: Array[String]) -> void:
	var validator := FakeSceneCacheValidator.new()
	var adapter := FakeResourceLoaderAdapter.new()
	var finalizer: RefCounted = FINALIZER_SCRIPT.new(validator, adapter)
	var current_scene := _packed_scene(true, true, failures)
	var cache_state: RefCounted = CACHE_STATE_SCRIPT.new()
	var requests := {
		"world/pending.wmo": "res://cache/pending.tscn",
		"world/failed.wmo": "res://cache/failed.tscn",
		"world/loaded.wmo": "res://cache/loaded.tscn",
	}
	for normalized_relative_path in requests:
		cache_state.call(
			"remember_request",
			normalized_relative_path,
			requests[normalized_relative_path]
		)
	adapter.status_by_path = {
		"res://cache/failed.tscn": ResourceLoader.THREAD_LOAD_FAILED,
		"res://cache/loaded.tscn": ResourceLoader.THREAD_LOAD_LOADED,
	}
	adapter.resource_by_path["res://cache/loaded.tscn"] = current_scene
	_expect_equal(
		int(finalizer.call("poll_terminal_requests", cache_state)),
		2,
		"failed and loaded scenes complete",
		failures
	)
	_expect_string_array(
		adapter.status_paths,
		[
			"res://cache/pending.tscn",
			"res://cache/failed.tscn",
			"res://cache/loaded.tscn",
		],
		"detached insertion order preserved",
		failures
	)
	_expect_string_array(
		adapter.loaded_paths,
		["res://cache/loaded.tscn"],
		"only loaded terminal scene retrieved",
		failures
	)
	_expect_true(
		bool(cache_state.call("is_missing", "world/failed.wmo")),
		"failed request publishes missing",
		failures
	)
	_expect_true(
		cache_state.call("scene_for", "world/loaded.wmo") == current_scene,
		"loaded scene identity adopted",
		failures
	)


func _verify_loaded_resource_rejections(failures: Array[String]) -> void:
	var validator := FakeSceneCacheValidator.new()
	var adapter := FakeResourceLoaderAdapter.new()
	var finalizer: RefCounted = FINALIZER_SCRIPT.new(validator, adapter)
	var rejected_resources := {
		"null": null,
		"wrong_type": Resource.new(),
		"stale": _packed_scene(false, true, failures),
		"non_node_3d": _packed_scene(true, false, failures),
	}
	var cache_state: RefCounted = CACHE_STATE_SCRIPT.new()
	for rejection_name in rejected_resources:
		var normalized_relative_path := "world/%s.wmo" % rejection_name
		var cache_path := "res://cache/%s.tscn" % rejection_name
		cache_state.call("remember_request", normalized_relative_path, cache_path)
		adapter.status_by_path[cache_path] = ResourceLoader.THREAD_LOAD_LOADED
		adapter.resource_by_path[cache_path] = rejected_resources[rejection_name]
	_expect_equal(
		int(finalizer.call("poll_terminal_requests", cache_state)),
		4,
		"all invalid loaded Resources complete",
		failures
	)
	for rejection_name in rejected_resources:
		_expect_true(
			bool(cache_state.call("is_missing", "world/%s.wmo" % rejection_name)),
			"%s loaded Resource publishes missing" % rejection_name,
			failures
		)
	_expect_equal(
		validator.validated_nodes.size(),
		1,
		"only Node3D stale scene reaches validator",
		failures
	)
	_expect_false(
		is_instance_valid(validator.validated_nodes[0]),
		"stale validation probe released",
		failures
	)


func _verify_current_scene_adoption_and_probe_lifetime(failures: Array[String]) -> void:
	var validator := FakeSceneCacheValidator.new()
	var adapter := FakeResourceLoaderAdapter.new()
	var finalizer: RefCounted = FINALIZER_SCRIPT.new(validator, adapter)
	var current_scene := _packed_scene(true, true, failures)
	var cache_state: RefCounted = CACHE_STATE_SCRIPT.new()
	cache_state.call("remember_request", "world/current.wmo", "res://cache/current.tscn")
	adapter.status_by_path["res://cache/current.tscn"] = ResourceLoader.THREAD_LOAD_LOADED
	adapter.resource_by_path["res://cache/current.tscn"] = current_scene
	finalizer.call("poll_terminal_requests", cache_state)
	_expect_true(
		cache_state.call("scene_for", "world/current.wmo") == current_scene,
		"current PackedScene exact identity adopted",
		failures
	)
	_expect_equal(
		validator.validated_nodes.size(),
		1,
		"current scene instantiated exactly once",
		failures
	)
	_expect_false(
		is_instance_valid(validator.validated_nodes[0]),
		"accepted validation probe released",
		failures
	)


func _verify_validation_contract(failures: Array[String]) -> void:
	var validator := FakeSceneCacheValidator.new()
	var finalizer: RefCounted = FINALIZER_SCRIPT.new(validator)
	var current_scene := _packed_scene(true, true, failures)
	_expect_true(
		bool(finalizer.call("is_scene_cache_current", current_scene)),
		"current Node3D scene accepted",
		failures
	)
	_expect_false(
		bool(finalizer.call("is_scene_cache_current", null)),
		"null scene rejected",
		failures
	)
	var missing_validator_finalizer: RefCounted = FINALIZER_SCRIPT.new(null)
	_expect_false(
		bool(missing_validator_finalizer.call("is_scene_cache_current", current_scene)),
		"missing validator rejects scene without instantiation",
		failures
	)


func _verify_source_boundaries(failures: Array[String]) -> void:
	var loader_source := _read_text(LOADER_PATH, failures)
	var finalizer_source := _read_text(FINALIZER_PATH, failures)
	var cache_source := _read_text(CACHE_STATE_PATH, failures)
	_expect_true(
		loader_source.contains("WMO_SCENE_RESOURCE_FINALIZER_SCRIPT.new("),
		"loader composes scene finalizer",
		failures
	)
	_expect_true(
		loader_source.contains("_wmo_scene_resource_finalizer.poll_terminal_requests("),
		"loader delegates scene terminal polling",
		failures
	)
	_expect_false(
		loader_source.contains("func _is_wmo_scene_cache_current("),
		"loader releases PackedScene probe validation",
		failures
	)
	for required_token in [
		"ResourceLoader.load_threaded_get_status(resource_path)",
		"ResourceLoader.load_threaded_get(resource_path)",
		"scene.instantiate()",
		"is_scene_cache_current",
		"instance.free()",
		"complete_request_with_scene",
		"complete_request_as_missing",
	]:
		_expect_true(
			finalizer_source.contains(required_token),
			"finalizer owns %s" % required_token,
			failures
		)
	_expect_false(
		cache_source.contains("ResourceLoader."),
		"cache state remains free of ResourceLoader",
		failures
	)


func _verify_bounded_timing(failures: Array[String]) -> float:
	var adapter := FakeResourceLoaderAdapter.new()
	var finalizer: RefCounted = FINALIZER_SCRIPT.new(
		FakeSceneCacheValidator.new(),
		adapter
	)
	var started_microseconds := Time.get_ticks_usec()
	for iteration in range(1000):
		var cache_state: RefCounted = CACHE_STATE_SCRIPT.new()
		var normalized_relative_path := "world/object_%d.wmo" % iteration
		var cache_path := "res://cache/object_%d.tscn" % iteration
		cache_state.call("remember_request", normalized_relative_path, cache_path)
		adapter.status_by_path[cache_path] = ResourceLoader.THREAD_LOAD_FAILED
		finalizer.call("poll_terminal_requests", cache_state)
	var elapsed_milliseconds := float(Time.get_ticks_usec() - started_microseconds) / 1000.0
	if elapsed_milliseconds >= 1000.0:
		failures.append(
			"1000 terminal polls took %.3fms (budget < 1000ms)" % elapsed_milliseconds
		)
	return elapsed_milliseconds


func _packed_scene(
	is_current: bool,
	use_node_3d_root: bool,
	failures: Array[String]
) -> PackedScene:
	var root: Node = Node3D.new() if use_node_3d_root else Node.new()
	root.set_meta("current_wmo_cache", is_current)
	var scene := PackedScene.new()
	var pack_error := scene.pack(root)
	root.free()
	if pack_error != OK:
		failures.append("cannot pack synthetic scene: %d" % pack_error)
	return scene


func _read_text(path: String, failures: Array[String]) -> String:
	var file := FileAccess.open(path, FileAccess.READ)
	if file == null:
		failures.append("cannot read %s" % path)
		return ""
	var source := file.get_as_text()
	file.close()
	return source


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


func _expect_false(value: bool, label: String, failures: Array[String]) -> void:
	if value:
		failures.append(label)


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


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