@tool
class_name WorldEntityPresenter
extends Node3D

## Main-thread owner of visual subtrees for session-local world entities.
## The full immutable snapshot is reapplied idempotently by entity identity.

const ENTITY_PRESENTATION_SNAPSHOT_SCRIPT := preload("res://src/render/entities/entity_presentation_snapshot.gd")
const ENTITY_ID_SCRIPT := preload("res://src/domain/identity/entity_id.gd")

var _presented_entities: Dictionary = {}


## Creates or updates one entity visual. A changed visual path is instantiated
## successfully before the previous subtree is detached, so failed replacement
## retains the last valid presentation.
func present_entity(entity_presentation_snapshot: RefCounted) -> bool:
	if not _is_valid_snapshot(entity_presentation_snapshot):
		return false
	var entity_id: RefCounted = entity_presentation_snapshot.get("entity_id")
	var entity_key := _entity_key(entity_id)
	var visual_scene_path := String(entity_presentation_snapshot.get("visual_scene_path"))
	var current_entry: Dictionary = _presented_entities.get(entity_key, {})
	var visual_root := current_entry.get("visual_root") as Node3D
	if visual_root == null or not is_instance_valid(visual_root) or String(current_entry.get("visual_scene_path", "")) != visual_scene_path:
		var replacement_root := _instantiate_visual_root(entity_id, visual_scene_path)
		if replacement_root == null:
			return false
		if visual_root != null and is_instance_valid(visual_root):
			remove_child(visual_root)
		add_child(replacement_root)
		_apply_snapshot_transform(replacement_root, entity_presentation_snapshot)
		if visual_root != null and is_instance_valid(visual_root):
			visual_root.queue_free()
		visual_root = replacement_root
	else:
		_apply_snapshot_transform(visual_root, entity_presentation_snapshot)
	_presented_entities[entity_key] = {
		"visual_root": visual_root,
		"visual_scene_path": visual_scene_path,
		"snapshot": entity_presentation_snapshot,
	}
	return true


## Removes one owned entity visual. Returns false for an invalid/unknown identity.
func remove_entity(entity_id: RefCounted) -> bool:
	if entity_id == null or entity_id.get_script() != ENTITY_ID_SCRIPT or not bool(entity_id.call("is_valid")):
		return false
	var entity_key := _entity_key(entity_id)
	if not _presented_entities.has(entity_key):
		return false
	var entry: Dictionary = _presented_entities[entity_key]
	var visual_root := entry.get("visual_root") as Node3D
	_presented_entities.erase(entity_key)
	if visual_root != null and is_instance_valid(visual_root):
		remove_child(visual_root)
		visual_root.queue_free()
	return true


## Returns detached scalar diagnostics without exposing owned Nodes or snapshots.
func entity_presentation_snapshot() -> Dictionary:
	var entities: Array[Dictionary] = []
	var sorted_entity_keys: Array = _presented_entities.keys()
	sorted_entity_keys.sort()
	for entity_key_variant in sorted_entity_keys:
		var entity_key := String(entity_key_variant)
		var entry: Dictionary = _presented_entities[entity_key]
		var snapshot: RefCounted = entry.get("snapshot")
		entities.append({
			"entity_key": entity_key,
			"visual_scene_path": String(entry.get("visual_scene_path", "")),
			"visible": bool(snapshot.get("is_visible")),
		})
	return {
		"contract_version": ENTITY_PRESENTATION_SNAPSHOT_SCRIPT.CONTRACT_VERSION,
		"entity_count": entities.size(),
		"entities": entities,
	}


func _exit_tree() -> void:
	_presented_entities.clear()


func _is_valid_snapshot(entity_presentation_snapshot: RefCounted) -> bool:
	return (
		entity_presentation_snapshot != null
		and entity_presentation_snapshot.get_script() == ENTITY_PRESENTATION_SNAPSHOT_SCRIPT
		and bool(entity_presentation_snapshot.get("is_valid"))
	)


func _entity_key(entity_id: RefCounted) -> String:
	return String(entity_id.call("to_debug_key"))


func _instantiate_visual_root(entity_id: RefCounted, visual_scene_path: String) -> Node3D:
	if not ResourceLoader.exists(visual_scene_path, "PackedScene"):
		push_warning("WorldEntityPresenter cannot find visual scene: %s" % visual_scene_path)
		return null
	var visual_scene := load(visual_scene_path) as PackedScene
	if visual_scene == null:
		push_warning("WorldEntityPresenter cannot load visual scene: %s" % visual_scene_path)
		return null
	var visual_instance := visual_scene.instantiate()
	if not (visual_instance is Node3D):
		visual_instance.free()
		push_warning("WorldEntityPresenter visual scene root is not Node3D: %s" % visual_scene_path)
		return null
	var visual_root := visual_instance as Node3D
	visual_root.name = "EntityVisual_%020d" % int(entity_id.get("session_sequence"))
	return visual_root


func _apply_snapshot_transform(visual_root: Node3D, entity_presentation_snapshot: RefCounted) -> void:
	var world_position: RefCounted = entity_presentation_snapshot.get("world_position")
	var position_units := Vector3(
		float(world_position.get("x_units")),
		float(world_position.get("y_units")),
		float(world_position.get("z_units"))
	)
	var uniform_scale := float(entity_presentation_snapshot.get("uniform_scale"))
	var yaw_radians := float(entity_presentation_snapshot.get("yaw_radians"))
	visual_root.global_transform = Transform3D(
		Basis(Vector3.UP, yaw_radians).scaled(Vector3.ONE * uniform_scale),
		position_units
	)
	visual_root.visible = bool(entity_presentation_snapshot.get("is_visible"))
