@tool
class_name WorldRenderFacade
extends Node

## Stable main-thread boundary between runtime/tool callers and the monolithic
## streaming renderer. The facade owns no queues, caches, nodes or RIDs.

const GODOT_WORLD_POSITION_SCRIPT := preload("res://src/domain/coordinates/godot_world_position.gd")
const STREAMING_FOCUS_SCRIPT := preload("res://src/domain/streaming/streaming_focus.gd")
const RENDERED_GROUND_SAMPLE_SCRIPT := preload("res://src/render/terrain/rendered_ground_sample.gd")
const WORLD_ENVIRONMENT_SNAPSHOT_SCRIPT := preload("res://src/render/environment/world_environment_snapshot.gd")
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")

## Internal renderer implementation resolved relative to this node.
## The referenced node remains owned by its scene parent.
@export var streaming_world_loader_path: NodePath = NodePath("..")

## Scene-owned outdoor environment implementation resolved relative to this node.
## The facade delegates snapshots but owns neither the controller nor Environment.
@export var world_environment_controller_path: NodePath = NodePath("../WowSkyController")

## Scene-owned service that creates and owns session-local world entity visuals.
@export var world_entity_presenter_path: NodePath = NodePath("../WorldEntityPresenter")

## Optional explicit Node3D source sampled in Godot world coordinates.
## Runtime scenes use the player; capture and probe tools replace it with their camera.
@export var streaming_focus_source_path: NodePath

var _streaming_world_loader: Node
var _world_environment_controller: Node
var _world_entity_presenter: Node
var _missing_loader_reported := false
var _missing_environment_controller_reported := false
var _missing_entity_presenter_reported := false
var _missing_focus_source_reported := false


func _ready() -> void:
	_resolve_streaming_world_loader()
	set_process(streaming_focus_source_path != NodePath())
	if streaming_focus_source_path != NodePath():
		# Child nodes become ready before their parent StreamingWorldLoader. Only
		# publish the initial focus here; the loader applies it after initialization.
		_capture_streaming_focus_from_source()


func _process(_delta_seconds: float) -> void:
	_capture_streaming_focus_from_source()


## Replaces the renderer focus. The immutable value remains caller-owned; the
## internal streamer retains the reference for its next throttled refresh.
func set_streaming_focus(streaming_focus: StreamingFocus) -> void:
	var loader := _resolve_streaming_world_loader()
	if loader == null:
		return
	loader.call("set_streaming_focus", streaming_focus)


## Samples the configured source and requests target refresh from the internal
## streamer. Returns false when either dependency or a valid focus is unavailable.
func refresh_streaming_focus(force: bool = false) -> bool:
	_capture_streaming_focus_from_source()
	var loader := _resolve_streaming_world_loader()
	if loader == null:
		return false
	return bool(loader.call("refresh_streaming_focus", force))


## Returns a detached read-only metrics snapshot for diagnostics and baselines.
## Mutating the returned Dictionary cannot mutate renderer-owned state.
func renderer_metrics_snapshot() -> Dictionary:
	var loader := _resolve_streaming_world_loader()
	if loader == null:
		return {}
	var metrics_variant = loader.call("render_baseline_snapshot")
	if not (metrics_variant is Dictionary):
		push_error("WorldRenderFacade received a non-Dictionary renderer metrics snapshot.")
		return {}
	return (metrics_variant as Dictionary).duplicate(true)


## Samples the currently loaded render terrain at a typed Godot world position.
## This read-only renderer view is diagnostic and does not replace authoritative
## gameplay collision or the independently composed [TerrainQuery].
func sample_ground_height(godot_world_position: GodotWorldPosition) -> RefCounted:
	var loader := _resolve_streaming_world_loader()
	if loader == null:
		return RENDERED_GROUND_SAMPLE_SCRIPT.unavailable(&"render_facade_unavailable")
	var sample_variant = loader.call("sample_rendered_ground_height", godot_world_position)
	if sample_variant is RefCounted and sample_variant.get_script() == RENDERED_GROUND_SAMPLE_SCRIPT:
		return sample_variant
	push_error("WorldRenderFacade received an invalid rendered ground sample.")
	return RENDERED_GROUND_SAMPLE_SCRIPT.unavailable(&"render_ground_sample_invalid")


## Returns a detached diagnostic snapshot for one rendered-ground query.
## Mutating the result cannot mutate streamer queues, tile state or resources.
func renderer_ground_query_snapshot(godot_world_position: GodotWorldPosition) -> Dictionary:
	var loader := _resolve_streaming_world_loader()
	if loader == null:
		return {}
	var snapshot_variant = loader.call("render_ground_query_snapshot", godot_world_position)
	if not (snapshot_variant is Dictionary):
		push_error("WorldRenderFacade received a non-Dictionary ground query snapshot.")
		return {}
	return (snapshot_variant as Dictionary).duplicate(true)


## Applies an immutable environment snapshot through the renderer-owned sky path.
## Returns false for an invalid value or unavailable/incompatible controller.
func apply_environment_snapshot(world_environment_snapshot: RefCounted) -> bool:
	if (
		world_environment_snapshot == null
		or world_environment_snapshot.get_script() != WORLD_ENVIRONMENT_SNAPSHOT_SCRIPT
		or not bool(world_environment_snapshot.get("is_valid"))
	):
		return false
	var controller := _resolve_world_environment_controller()
	if controller == null:
		return false
	return bool(controller.call("apply_environment_snapshot", world_environment_snapshot))


## Idempotently creates or updates one world entity visual through the renderer-
## owned presenter. The immutable full snapshot remains caller-owned.
func present_entity(entity_presentation_snapshot: RefCounted) -> bool:
	if (
		entity_presentation_snapshot == null
		or entity_presentation_snapshot.get_script() != ENTITY_PRESENTATION_SNAPSHOT_SCRIPT
		or not bool(entity_presentation_snapshot.get("is_valid"))
	):
		return false
	var presenter := _resolve_world_entity_presenter()
	if presenter == null:
		return false
	return bool(presenter.call("present_entity", entity_presentation_snapshot))


## Removes one session-local entity visual without changing authoritative state.
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 presenter := _resolve_world_entity_presenter()
	if presenter == null:
		return false
	return bool(presenter.call("remove_entity", entity_id))


## Returns detached entity-presentation diagnostics with no Node references.
func entity_presentation_snapshot() -> Dictionary:
	var presenter := _resolve_world_entity_presenter()
	if presenter == null:
		return {}
	var snapshot_variant = presenter.call("entity_presentation_snapshot")
	if not (snapshot_variant is Dictionary):
		push_error("WorldRenderFacade received a non-Dictionary entity presentation snapshot.")
		return {}
	return (snapshot_variant as Dictionary).duplicate(true)


func _capture_streaming_focus_from_source() -> void:
	if streaming_focus_source_path == NodePath():
		return
	var focus_source := get_node_or_null(streaming_focus_source_path) as Node3D
	if focus_source == null:
		if not _missing_focus_source_reported:
			push_warning("WorldRenderFacade focus source is missing or is not Node3D: %s" % streaming_focus_source_path)
			_missing_focus_source_reported = true
		return

	_missing_focus_source_reported = false
	var godot_position: Vector3 = focus_source.global_position
	var typed_world_position = GODOT_WORLD_POSITION_SCRIPT.new(
		godot_position.x,
		godot_position.y,
		godot_position.z
	)
	set_streaming_focus(STREAMING_FOCUS_SCRIPT.new(typed_world_position))


func _resolve_streaming_world_loader() -> Node:
	if is_instance_valid(_streaming_world_loader):
		return _streaming_world_loader
	var candidate := get_node_or_null(streaming_world_loader_path)
	if candidate == null:
		if not _missing_loader_reported:
			push_error("WorldRenderFacade cannot resolve StreamingWorldLoader at %s" % streaming_world_loader_path)
			_missing_loader_reported = true
		return null
	for required_method in [
		&"set_streaming_focus",
		&"refresh_streaming_focus",
		&"render_baseline_snapshot",
		&"sample_rendered_ground_height",
		&"render_ground_query_snapshot",
	]:
		if not candidate.has_method(required_method):
			if not _missing_loader_reported:
				push_error("WorldRenderFacade renderer implementation lacks %s" % required_method)
				_missing_loader_reported = true
			return null
	_streaming_world_loader = candidate
	_missing_loader_reported = false
	return _streaming_world_loader


func _resolve_world_environment_controller() -> Node:
	if is_instance_valid(_world_environment_controller):
		return _world_environment_controller
	var candidate := get_node_or_null(world_environment_controller_path)
	if candidate == null or not candidate.has_method(&"apply_environment_snapshot"):
		if not _missing_environment_controller_reported:
			push_error(
				"WorldRenderFacade cannot resolve a compatible environment controller at %s"
				% world_environment_controller_path
			)
			_missing_environment_controller_reported = true
		return null
	_world_environment_controller = candidate
	_missing_environment_controller_reported = false
	return _world_environment_controller


func _resolve_world_entity_presenter() -> Node:
	if is_instance_valid(_world_entity_presenter):
		return _world_entity_presenter
	var candidate := get_node_or_null(world_entity_presenter_path)
	if candidate == null:
		if not _missing_entity_presenter_reported:
			push_error("WorldRenderFacade cannot resolve entity presenter at %s" % world_entity_presenter_path)
			_missing_entity_presenter_reported = true
		return null
	for required_method in [&"present_entity", &"remove_entity", &"entity_presentation_snapshot"]:
		if not candidate.has_method(required_method):
			if not _missing_entity_presenter_reported:
				push_error("WorldRenderFacade entity presenter lacks %s" % required_method)
				_missing_entity_presenter_reported = true
			return null
	_world_entity_presenter = candidate
	_missing_entity_presenter_reported = false
	return _world_entity_presenter
