extends SceneTree

## Measures camera clearance against the active rendered terrain mesh.
## Usage: godot --path . --script res://src/tools/probe_render_terrain_height.gd --
##   [--wait 3.0] [--output user://render_terrain_height/report.json]

const MANIFEST_PATH := "res://src/tools/render_baseline_manifest.json"
const GODOT_WORLD_POSITION_SCRIPT := preload("res://src/domain/coordinates/godot_world_position.gd")


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


func _run_async() -> void:
	var arguments := OS.get_cmdline_user_args()
	var wait_seconds := float(_argument(arguments, "--wait", "3.0"))
	var output_path := _argument(arguments, "--output", "user://render_terrain_height/report.json")
	var only_filter := _argument(arguments, "--only", "").to_lower()
	var require_all := arguments.has("--require-all")
	var manifest := _load_json(MANIFEST_PATH)
	if manifest.is_empty():
		quit(1)
		return
	var packed_scene: PackedScene = load(String(manifest.get("scene", "")))
	if packed_scene == null:
		push_error("TERRAIN_HEIGHT_PROBE: cannot load streaming scene")
		quit(1)
		return
	var world := packed_scene.instantiate() as Node3D
	if world == null:
		push_error("TERRAIN_HEIGHT_PROBE: streaming scene root is not Node3D")
		quit(1)
		return
	var camera := Camera3D.new()
	camera.current = true
	world.add_child(camera)
	var render_facade := world.get_node_or_null("WorldRenderFacade")
	if render_facade == null:
		push_error("TERRAIN_HEIGHT_PROBE: streaming scene has no WorldRenderFacade")
		quit(1)
		return
	render_facade.set("streaming_focus_source_path", NodePath("../%s" % camera.name))
	world.set("debug_streaming", false)
	get_root().add_child(world)
	await process_frame
	await process_frame

	var results: Array[Dictionary] = []
	for checkpoint_variant in manifest.get("checkpoints", []):
		if not (checkpoint_variant is Dictionary):
			continue
		var checkpoint: Dictionary = checkpoint_variant
		if not checkpoint.has("reference_wow_camera"):
			continue
		if not only_filter.is_empty() and not String(checkpoint.get("name", "")).to_lower().contains(only_filter):
			continue
		var camera_position := _vector3(checkpoint.get("camera", []))
		camera.global_position = camera_position
		render_facade.call("refresh_streaming_focus", true)
		await create_timer(maxf(0.1, wait_seconds)).timeout
		var typed_camera_position = GODOT_WORLD_POSITION_SCRIPT.new(
			camera_position.x,
			camera_position.y,
			camera_position.z
		)
		var terrain_sample: Dictionary = render_facade.call(
			"renderer_ground_query_snapshot",
			typed_camera_position
		)
		terrain_sample["name"] = checkpoint.get("name", "checkpoint")
		terrain_sample["camera_y"] = camera_position.y
		if terrain_sample.has("terrain_height"):
			terrain_sample["camera_clearance"] = camera_position.y - float(terrain_sample.terrain_height)
		results.append(terrain_sample)

	var report := {
		"schema_version": 1,
		"created_utc": Time.get_datetime_string_from_system(true, true),
		"wait_seconds": wait_seconds,
		"results": results,
	}
	if not _write_json(output_path, report):
		quit(1)
		return
	var sampled_count := 0
	for result in results:
		if result.has("terrain_height"):
			sampled_count += 1
		print("TERRAIN_HEIGHT name=%s status=%s camera_y=%.3f terrain=%s clearance=%s" % [
			result.get("name", "checkpoint"),
			result.get("status", "unknown"),
			float(result.get("camera_y", 0.0)),
			str(result.get("terrain_height", "n/a")),
			str(result.get("camera_clearance", "n/a")),
		])
	print("TERRAIN_HEIGHT_PROBE sampled=%d total=%d report=%s" % [sampled_count, results.size(), output_path])
	world.queue_free()
	var failed := sampled_count == 0 or (require_all and sampled_count != results.size())
	quit(1 if failed else 0)


func _vector3(value_variant) -> Vector3:
	if not (value_variant is Array) or value_variant.size() != 3:
		return Vector3.ZERO
	return Vector3(float(value_variant[0]), float(value_variant[1]), float(value_variant[2]))


func _argument(arguments: PackedStringArray, name: String, default_value: String) -> String:
	var index := arguments.find(name)
	if index >= 0 and index + 1 < arguments.size():
		return arguments[index + 1]
	return default_value


func _load_json(path: String) -> Dictionary:
	var file := FileAccess.open(path, FileAccess.READ)
	if file == null:
		return {}
	var parsed = JSON.parse_string(file.get_as_text())
	return parsed if parsed is Dictionary else {}


func _write_json(path: String, value: Dictionary) -> bool:
	var absolute_path := ProjectSettings.globalize_path(path)
	if DirAccess.make_dir_recursive_absolute(absolute_path.get_base_dir()) != OK:
		return false
	var file := FileAccess.open(absolute_path, FileAccess.WRITE)
	if file == null:
		return false
	file.store_string(JSON.stringify(value, "  "))
	return true
