extends SceneTree

## Asset-free contract, lifecycle and ownership regression for world entity visuals.

const SNAPSHOT_SCRIPT := preload("res://src/render/entities/entity_presentation_snapshot.gd")
const PRESENTER_SCRIPT := preload("res://src/render/entities/world_entity_presenter.gd")
const ENTITY_ID_SCRIPT := preload("res://src/domain/identity/entity_id.gd")
const GODOT_WORLD_POSITION_SCRIPT := preload("res://src/domain/coordinates/godot_world_position.gd")

const PRIMARY_VISUAL_PATH := "res://src/tests/scenes/character_presentation_model_fixture.tscn"
const REPLACEMENT_VISUAL_PATH := "res://src/tests/scenes/world_entity_visual_replacement_fixture.tscn"
const SNAPSHOT_SOURCE_PATH := "res://src/render/entities/entity_presentation_snapshot.gd"
const PRESENTER_SOURCE_PATH := "res://src/render/entities/world_entity_presenter.gd"
const FACADE_SOURCE_PATH := "res://src/render/world_render_facade.gd"
const RUNTIME_SCENE_PATHS: Array[String] = [
	"res://src/scenes/streaming/eastern_kingdoms_streaming.tscn",
	"res://src/scenes/streaming/kalimdor_streaming.tscn",
]


func _initialize() -> void:
	_run_verification.call_deferred()


func _run_verification() -> void:
	var failures: Array[String] = []
	_verify_snapshot_contract(failures)
	await _verify_presenter_lifecycle(failures)
	_verify_source_boundaries(failures)

	if not failures.is_empty():
		for failure in failures:
			push_error("WORLD_ENTITY_PRESENTATION: %s" % failure)
		quit(1)
		return
	print("WORLD_ENTITY_PRESENTATION PASS snapshot_cases=8 lifecycle_cases=14 runtime_scenes=2")
	quit(0)


func _verify_snapshot_contract(failures: Array[String]) -> void:
	var entity_id: RefCounted = ENTITY_ID_SCRIPT.new(7)
	var position: RefCounted = GODOT_WORLD_POSITION_SCRIPT.new(10.0, 20.0, 30.0)
	var valid_snapshot := SNAPSHOT_SCRIPT.create(entity_id, position, PRIMARY_VISUAL_PATH, 0.5, 2.0, false)
	_expect_true(bool(valid_snapshot.get("is_valid")), "valid snapshot", failures)
	_expect_true(int(SNAPSHOT_SCRIPT.CONTRACT_VERSION) == 1, "contract version", failures)
	_expect_failure(SNAPSHOT_SCRIPT.create(ENTITY_ID_SCRIPT.new(0), position, PRIMARY_VISUAL_PATH), &"entity_presentation_invalid_entity_id", "invalid entity", failures)
	_expect_failure(SNAPSHOT_SCRIPT.create(entity_id, RefCounted.new(), PRIMARY_VISUAL_PATH), &"entity_presentation_invalid_world_position", "invalid position type", failures)
	_expect_failure(SNAPSHOT_SCRIPT.create(entity_id, GODOT_WORLD_POSITION_SCRIPT.new(INF, 0.0, 0.0), PRIMARY_VISUAL_PATH), &"entity_presentation_position_not_finite", "non-finite position", failures)
	_expect_failure(SNAPSHOT_SCRIPT.create(entity_id, position, ""), &"entity_presentation_visual_path_empty", "empty visual path", failures)
	_expect_failure(SNAPSHOT_SCRIPT.create(entity_id, position, PRIMARY_VISUAL_PATH, NAN), &"entity_presentation_yaw_not_finite", "non-finite yaw", failures)
	_expect_failure(SNAPSHOT_SCRIPT.create(entity_id, position, PRIMARY_VISUAL_PATH, 0.0, 0.0), &"entity_presentation_scale_invalid", "zero scale", failures)


func _verify_presenter_lifecycle(failures: Array[String]) -> void:
	var presenter: Node3D = PRESENTER_SCRIPT.new()
	presenter.name = "WorldEntityPresenter"
	root.add_child(presenter)
	var entity_id: RefCounted = ENTITY_ID_SCRIPT.new(7)
	var initial_snapshot := SNAPSHOT_SCRIPT.create(
		entity_id,
		GODOT_WORLD_POSITION_SCRIPT.new(10.0, 20.0, 30.0),
		PRIMARY_VISUAL_PATH,
		0.5,
		2.0,
		false
	)
	_expect_true(bool(presenter.call("present_entity", initial_snapshot)), "initial present", failures)
	var visual_root := presenter.get_node_or_null("EntityVisual_00000000000000000007") as Node3D
	_expect_true(visual_root != null, "owned visual attached", failures)
	if visual_root != null:
		_expect_vector_near(visual_root.global_position, Vector3(10.0, 20.0, 30.0), "initial position", failures)
		_expect_near(visual_root.scale.x, 2.0, "initial scale", failures)
		_expect_true(not visual_root.visible, "initial visibility", failures)
	var original_instance_id := visual_root.get_instance_id() if visual_root != null else 0

	var updated_snapshot := SNAPSHOT_SCRIPT.create(
		entity_id,
		GODOT_WORLD_POSITION_SCRIPT.new(40.0, 50.0, 60.0),
		PRIMARY_VISUAL_PATH,
		1.0,
		1.5,
		true
	)
	_expect_true(bool(presenter.call("present_entity", updated_snapshot)), "idempotent update", failures)
	visual_root = presenter.get_node_or_null("EntityVisual_00000000000000000007") as Node3D
	_expect_true(visual_root != null and visual_root.get_instance_id() == original_instance_id, "same visual root retained", failures)
	if visual_root != null:
		_expect_vector_near(visual_root.global_position, Vector3(40.0, 50.0, 60.0), "updated position", failures)
		_expect_true(visual_root.visible, "updated visibility", failures)

	var failed_replacement := SNAPSHOT_SCRIPT.create(entity_id, GODOT_WORLD_POSITION_SCRIPT.new(1.0, 2.0, 3.0), "res://missing/entity_visual.tscn")
	_expect_true(not bool(presenter.call("present_entity", failed_replacement)), "missing replacement rejected", failures)
	visual_root = presenter.get_node_or_null("EntityVisual_00000000000000000007") as Node3D
	_expect_true(visual_root != null and visual_root.get_instance_id() == original_instance_id, "failed replacement retains visual", failures)

	var replacement_snapshot := SNAPSHOT_SCRIPT.create(entity_id, GODOT_WORLD_POSITION_SCRIPT.new(70.0, 80.0, 90.0), REPLACEMENT_VISUAL_PATH)
	_expect_true(bool(presenter.call("present_entity", replacement_snapshot)), "valid replacement", failures)
	visual_root = presenter.get_node_or_null("EntityVisual_00000000000000000007") as Node3D
	_expect_true(visual_root != null and visual_root.get_instance_id() != original_instance_id, "replacement swaps root", failures)

	var diagnostic_snapshot: Dictionary = presenter.call("entity_presentation_snapshot")
	_expect_true(int(diagnostic_snapshot.get("contract_version", 0)) == 1, "diagnostic version", failures)
	_expect_true(int(diagnostic_snapshot.get("entity_count", 0)) == 1, "diagnostic entity count", failures)
	(diagnostic_snapshot.get("entities") as Array).clear()
	_expect_true(int((presenter.call("entity_presentation_snapshot") as Dictionary).get("entity_count", 0)) == 1, "diagnostic detached", failures)

	_expect_true(bool(presenter.call("remove_entity", entity_id)), "known entity removed", failures)
	_expect_true(not bool(presenter.call("remove_entity", entity_id)), "duplicate removal rejected", failures)
	_expect_true(presenter.get_child_count() == 0, "owned visual detached", failures)
	presenter.free()
	await process_frame


func _verify_source_boundaries(failures: Array[String]) -> void:
	var snapshot_source := _read_text(SNAPSHOT_SOURCE_PATH, failures)
	for forbidden_snapshot_text in ["extends Node", "Node3D", "PackedScene", "Dictionary", "Vector3"]:
		_expect_true(not snapshot_source.contains(forbidden_snapshot_text), "snapshot omits %s" % forbidden_snapshot_text, failures)
	var presenter_source := _read_text(PRESENTER_SOURCE_PATH, failures)
	for forbidden_presenter_text in ["MoveIntent", "TerrainQuery", "WowGuid", "network", "server_data"]:
		_expect_true(not presenter_source.contains(forbidden_presenter_text), "presenter omits %s" % forbidden_presenter_text, failures)
	for required_presenter_text in ["ResourceLoader.exists", "remove_child(visual_root)", "visual_root.queue_free()"]:
		_expect_true(presenter_source.contains(required_presenter_text), "presenter contains %s" % required_presenter_text, failures)
	var facade_source := _read_text(FACADE_SOURCE_PATH, failures)
	for required_facade_method in ["func present_entity(", "func remove_entity(", "func entity_presentation_snapshot()"]:
		_expect_true(facade_source.contains(required_facade_method), "facade contains %s" % required_facade_method, failures)
	for scene_path in RUNTIME_SCENE_PATHS:
		var scene_source := _read_text(scene_path, failures)
		_expect_true(scene_source.contains("world_entity_presenter.gd"), "%s loads presenter" % scene_path, failures)
		_expect_true(scene_source.contains('world_entity_presenter_path = NodePath("../WorldEntityPresenter")'), "%s facade path" % scene_path, failures)


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_failure(snapshot: RefCounted, expected_code: StringName, label: String, failures: Array[String]) -> void:
	_expect_true(not bool(snapshot.get("is_valid")), "%s invalid" % label, failures)
	_expect_true(StringName(snapshot.get("failure_code")) == expected_code, "%s failure code" % label, failures)


func _expect_vector_near(actual_value: Vector3, expected_value: Vector3, label: String, failures: Array[String]) -> void:
	if not actual_value.is_equal_approx(expected_value):
		failures.append("%s expected %s, got %s" % [label, expected_value, actual_value])


func _expect_near(actual_value: float, expected_value: float, label: String, failures: Array[String]) -> void:
	if not is_equal_approx(actual_value, expected_value):
		failures.append("%s expected %.3f, got %.3f" % [label, expected_value, actual_value])


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