extends SceneTree

const FINALIZER_SCRIPT := preload(
	"res://src/render/m2/m2_mesh_resource_finalizer.gd"
)
const PIPELINE_SCRIPT := preload(
	"res://src/render/m2/m2_mesh_load_pipeline_state.gd"
)
const MESH_CACHE_SCRIPT := preload(
	"res://src/render/m2/m2_mesh_resource_cache_state.gd"
)
const PROTOTYPE_CACHE_SCRIPT := preload(
	"res://src/render/m2/m2_prototype_cache_state.gd"
)
const FINALIZER_PATH := "res://src/render/m2/m2_mesh_resource_finalizer.gd"
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"


class FakeResourceLoaderAdapter extends RefCounted:
	var statuses: Dictionary = {}
	var resources: Dictionary = {}
	var status_paths: Array[String] = []
	var load_paths: Array[String] = []

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

	func load_threaded_get(resource_path: String) -> Resource:
		load_paths.append(resource_path)
		return resources.get(resource_path, null) as Resource


class FakeMeshResourceExtractor extends RefCounted:
	var mesh_by_resource: Dictionary = {}
	var resources: Array[Resource] = []

	func extract_first_mesh(resource: Resource) -> Mesh:
		resources.append(resource)
		return mesh_by_resource.get(resource, null) as Mesh


class FakeRuntimeMeshFinalizer extends RefCounted:
	var raw_data_required: bool = false
	var finalized_mesh: Mesh = null
	var requirement_meshes: Array[Mesh] = []
	var finalize_calls: Array[Dictionary] = []

	func requires_raw_data_for_refresh(mesh: Mesh) -> bool:
		requirement_meshes.append(mesh)
		return raw_data_required

	func finalize_mesh(
		normalized_relative_path: String,
		mesh: Mesh,
		raw_model_data: Dictionary,
		extracted_directory: String
	) -> Mesh:
		finalize_calls.append({
			"normalized": normalized_relative_path,
			"mesh": mesh,
			"raw_model_data": raw_model_data,
			"extracted_directory": extracted_directory,
		})
		return finalized_mesh


class FakeRawModelRepository extends RefCounted:
	var result: Dictionary = {}
	var calls: Array[Dictionary] = []

	func load_static_model_data(
		extracted_directory: String,
		normalized_relative_path: String
	) -> Dictionary:
		calls.append({
			"extracted_directory": extracted_directory,
			"normalized": normalized_relative_path,
		})
		return result


class EmptyPathPipeline extends RefCounted:
	var discarded_path: String = ""

	func request_records_snapshot() -> Array[Dictionary]:
		return [{"normalized": "world/empty.m2", "path": ""}]

	func discard_request(normalized_relative_path: String) -> bool:
		discarded_path = normalized_relative_path
		return true


func _initialize() -> void:
	var failures: Array[String] = []
	_verify_polling(failures)
	_verify_empty_path_polling(failures)
	_verify_terminal_rejections(failures)
	_verify_current_mesh_adoption(failures)
	_verify_stale_mesh_preparation(failures)
	_verify_source_boundaries(failures)
	var elapsed_milliseconds := _verify_bounded_timing(failures)
	if not failures.is_empty():
		for failure in failures:
			push_error("M2_MESH_RESOURCE_FINALIZER: %s" % failure)
		quit(1)
		return
	print(
		"M2_MESH_RESOURCE_FINALIZER PASS "
		+ "cases=31 iterations=1000 elapsed_ms=%.3f" % elapsed_milliseconds
	)
	quit(0)


func _verify_polling(failures: Array[String]) -> void:
	var dependencies := _dependencies()
	var service: RefCounted = dependencies.service
	var resource_loader: FakeResourceLoaderAdapter = dependencies.resource_loader
	var pipeline: RefCounted = PIPELINE_SCRIPT.new()
	var prototype_cache: RefCounted = PROTOTYPE_CACHE_SCRIPT.new()
	for request in [
		["world/pending.m2", "res://pending.tres"],
		["world/loaded.m2", "res://loaded.tres"],
		["world/failed.m2", "res://failed.tres"],
	]:
		pipeline.call("remember_request", request[0], request[1])
	resource_loader.statuses = {
		"res://pending.tres": ResourceLoader.THREAD_LOAD_IN_PROGRESS,
		"res://loaded.tres": ResourceLoader.THREAD_LOAD_LOADED,
		"res://failed.tres": ResourceLoader.THREAD_LOAD_FAILED,
	}
	_expect_equal(
		int(service.call("poll_terminal_requests", pipeline, prototype_cache)),
		2,
		"two terminal requests completed",
		failures
	)
	_expect_equal(int(pipeline.call("pending_request_count")), 1, "pending retained", failures)
	_expect_equal(int(pipeline.call("finalize_record_count")), 2, "two finalize records", failures)
	_expect_string_array(
		resource_loader.status_paths,
		["res://pending.tres", "res://loaded.tres", "res://failed.tres"],
		"status insertion order",
		failures
	)
	prototype_cache.call("clear_and_release")


func _verify_empty_path_polling(failures: Array[String]) -> void:
	var dependencies := _dependencies()
	var pipeline := EmptyPathPipeline.new()
	var prototype_cache: RefCounted = PROTOTYPE_CACHE_SCRIPT.new()
	_expect_equal(
		int(dependencies.service.call(
			"poll_terminal_requests",
			pipeline,
			prototype_cache
		)),
		1,
		"empty resource path completed",
		failures
	)
	_expect_string_equal(
		pipeline.discarded_path,
		"world/empty.m2",
		"empty resource path discarded",
		failures
	)
	_expect_true(
		bool(prototype_cache.call("is_model_missing", "world/empty.m2")),
		"empty resource path marks missing",
		failures
	)
	_expect_equal(
		dependencies.resource_loader.status_paths.size(),
		0,
		"empty path skips I/O",
		failures
	)
	prototype_cache.call("clear_and_release")


func _verify_terminal_rejections(failures: Array[String]) -> void:
	var dependencies := _dependencies()
	var service: RefCounted = dependencies.service
	var pipeline: RefCounted = PIPELINE_SCRIPT.new()
	var mesh_cache: RefCounted = MESH_CACHE_SCRIPT.new()
	var prototype_cache: RefCounted = PROTOTYPE_CACHE_SCRIPT.new()
	_expect_false(
		bool(service.call(
			"finalize_next_resource",
			pipeline,
			mesh_cache,
			prototype_cache,
			"res://data/extracted"
		)),
		"empty finalize FIFO",
		failures
	)
	_enqueue_terminal(
		pipeline,
		"world/failed.m2",
		"res://failed.tres",
		ResourceLoader.THREAD_LOAD_FAILED
	)
	_expect_true(
		bool(service.call(
			"finalize_next_resource", pipeline, mesh_cache,
			prototype_cache, "res://data/extracted"
		)),
		"failed record consumed",
		failures
	)
	_expect_true(
		bool(prototype_cache.call("is_model_missing", "world/failed.m2")),
		"failed record marks missing",
		failures
	)
	var cached_mesh := ArrayMesh.new()
	mesh_cache.call("store_mesh", "world/cached.m2", cached_mesh)
	_enqueue_terminal(
		pipeline,
		"world/cached.m2",
		"res://cached.tres",
		ResourceLoader.THREAD_LOAD_LOADED
	)
	service.call(
		"finalize_next_resource", pipeline, mesh_cache,
		prototype_cache, "res://data/extracted"
	)
	_enqueue_terminal(
		pipeline,
		"world/null.m2",
		"res://null.tres",
		ResourceLoader.THREAD_LOAD_LOADED
	)
	service.call(
		"finalize_next_resource", pipeline, mesh_cache,
		prototype_cache, "res://data/extracted"
	)
	_expect_true(
		bool(prototype_cache.call("is_model_missing", "world/null.m2")),
		"null Resource marks missing",
		failures
	)
	_expect_string_array(
		dependencies.resource_loader.load_paths,
		["res://null.tres"],
		"cached record skips terminal get",
		failures
	)
	mesh_cache.call("clear")
	prototype_cache.call("clear_and_release")


func _verify_current_mesh_adoption(failures: Array[String]) -> void:
	var dependencies := _dependencies()
	var resource := Resource.new()
	var mesh := ArrayMesh.new()
	dependencies.resource_loader.resources["res://current.tres"] = resource
	dependencies.extractor.mesh_by_resource[resource] = mesh
	var pipeline: RefCounted = PIPELINE_SCRIPT.new()
	var mesh_cache: RefCounted = MESH_CACHE_SCRIPT.new()
	var prototype_cache: RefCounted = PROTOTYPE_CACHE_SCRIPT.new()
	_enqueue_terminal(
		pipeline,
		"world/current.m2",
		"res://current.tres",
		ResourceLoader.THREAD_LOAD_LOADED
	)
	_expect_true(
		bool(dependencies.service.call(
			"finalize_next_resource", pipeline, mesh_cache,
			prototype_cache, "res://custom/extracted"
		)),
		"current Mesh record consumed",
		failures
	)
	_expect_same(
		mesh_cache.call("find_mesh", "world/current.m2"),
		mesh,
		"current Mesh exact adoption",
		failures
	)
	_expect_equal(dependencies.raw_repository.calls.size(), 0, "current Mesh skips raw", failures)
	_expect_equal(
		dependencies.runtime_finalizer.finalize_calls.size(),
		0,
		"current Mesh skips finalize",
		failures
	)
	mesh_cache.call("clear")
	prototype_cache.call("clear_and_release")


func _verify_stale_mesh_preparation(failures: Array[String]) -> void:
	var dependencies := _dependencies()
	dependencies.runtime_finalizer.raw_data_required = true
	dependencies.raw_repository.result = {"vertices": [1]}
	var resource := Resource.new()
	var stale_mesh := ArrayMesh.new()
	var prepared_mesh := ArrayMesh.new()
	dependencies.runtime_finalizer.finalized_mesh = prepared_mesh
	dependencies.resource_loader.resources["res://stale.tres"] = resource
	dependencies.extractor.mesh_by_resource[resource] = stale_mesh
	var pipeline: RefCounted = PIPELINE_SCRIPT.new()
	var mesh_cache: RefCounted = MESH_CACHE_SCRIPT.new()
	var prototype_cache: RefCounted = PROTOTYPE_CACHE_SCRIPT.new()
	_enqueue_terminal(
		pipeline,
		"world/stale.m2",
		"res://stale.tres",
		ResourceLoader.THREAD_LOAD_LOADED
	)
	dependencies.service.call(
		"finalize_next_resource", pipeline, mesh_cache,
		prototype_cache, "res://custom/extracted"
	)
	_expect_same(
		mesh_cache.call("find_mesh", "world/stale.m2"),
		prepared_mesh,
		"prepared Mesh adoption",
		failures
	)
	_expect_equal(dependencies.raw_repository.calls.size(), 1, "one raw read", failures)
	_expect_string_equal(
		String(dependencies.raw_repository.calls[0].normalized),
		"world/stale.m2",
		"raw normalized path",
		failures
	)
	_expect_string_equal(
		String(dependencies.raw_repository.calls[0].extracted_directory),
		"res://custom/extracted",
		"raw extracted directory",
		failures
	)
	_expect_same(
		dependencies.runtime_finalizer.finalize_calls[0].mesh,
		stale_mesh,
		"runtime finalizer source Mesh",
		failures
	)
	_expect_true(
		dependencies.runtime_finalizer.finalize_calls[0].raw_model_data
		== dependencies.raw_repository.result,
		"runtime finalizer raw data",
		failures
	)
	mesh_cache.call("clear")
	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 service_source := FileAccess.get_file_as_string(FINALIZER_PATH)
	var drain_start := loader_source.find("func _drain_m2_mesh_loads()")
	var drain_end := loader_source.find("func _process_m2_build_jobs()", drain_start)
	var drain_source := loader_source.substr(drain_start, drain_end - drain_start)
	_expect_true(
		drain_source.contains("_m2_mesh_resource_finalizer.poll_terminal_requests("),
		"loader delegates polling",
		failures
	)
	_expect_true(
		drain_source.contains("_m2_mesh_resource_finalizer.finalize_next_resource("),
		"loader delegates terminal finalization",
		failures
	)
	for removed_token in [
		"ResourceLoader.load_threaded_get_status",
		"ResourceLoader.load_threaded_get(",
		"_m2_mesh_resource_extractor.extract_first_mesh",
		"_m2_mesh_resource_cache_state.store_mesh",
		"_m2_prototype_cache_state.mark_model_missing",
	]:
		_expect_false(
			drain_source.contains(removed_token),
			"loader drain omits %s" % removed_token,
			failures
		)
	_expect_false(
		loader_source.contains("func _prepare_m2_mesh_for_runtime("),
		"legacy loader preparation removed",
		failures
	)
	for required_token in [
		"ResourceLoader.load_threaded_get_status",
		"ResourceLoader.load_threaded_get(",
		"\"extract_first_mesh\"",
		"\"store_mesh\"",
		"\"mark_model_missing\"",
		"\"load_static_model_data\"",
		"\"finalize_mesh\"",
	]:
		_expect_true(
			service_source.contains(required_token),
			"service owns %s" % required_token,
			failures
		)


func _verify_bounded_timing(failures: Array[String]) -> float:
	var dependencies := _dependencies()
	var pipeline: RefCounted = PIPELINE_SCRIPT.new()
	var prototype_cache: RefCounted = PROTOTYPE_CACHE_SCRIPT.new()
	var started_microseconds := Time.get_ticks_usec()
	for iteration in range(1000):
		dependencies.service.call(
			"poll_terminal_requests",
			pipeline,
			prototype_cache
		)
	var elapsed_milliseconds := float(
		Time.get_ticks_usec() - started_microseconds
	) / 1000.0
	_expect_true(elapsed_milliseconds < 1000.0, "empty polling bounded", failures)
	prototype_cache.call("clear_and_release")
	return elapsed_milliseconds


func _dependencies() -> Dictionary:
	var resource_loader := FakeResourceLoaderAdapter.new()
	var extractor := FakeMeshResourceExtractor.new()
	var runtime_finalizer := FakeRuntimeMeshFinalizer.new()
	var raw_repository := FakeRawModelRepository.new()
	return {
		"resource_loader": resource_loader,
		"extractor": extractor,
		"runtime_finalizer": runtime_finalizer,
		"raw_repository": raw_repository,
		"service": FINALIZER_SCRIPT.new(
			extractor,
			runtime_finalizer,
			raw_repository,
			resource_loader
		),
	}


func _enqueue_terminal(
	pipeline: RefCounted,
	normalized_relative_path: String,
	resource_path: String,
	status: int
) -> void:
	pipeline.call("remember_request", normalized_relative_path, resource_path)
	pipeline.call("complete_request", normalized_relative_path, status)


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_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])


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