extends Node

# Le filet du **vrai chemin**: menu → changement de scène → restaurant.
#
# Le premier filet montait le restaurant à la main. Il prouvait le pont, pas le
# parcours: `change_scene_to_file()` **détruit la scène courante**. Un contrôle
# qui vit dans cette scène meurt avec elle — la première version de ce fichier
# s'est effacée au premier changement et a planté sur un `get_tree()` nul.
#
# La sonde vit donc dans la **racine**, où rien ne l'emporte.
#
#   godot --headless --path big-egg-3d res://scenes/main/bridge_3d_flow_check.tscn

var failures := 0
var is_probe := false

func _check(condition: bool, message: String) -> void:
	if condition:
		return
	failures += 1
	push_error("PARCOURS 3D: %s" % message)

func _ready() -> void:
	if is_probe:
		return
	GameState.save_locked = true
	var probe: Node = Node.new()
	probe.set_script(get_script())
	probe.set("is_probe", true)
	probe.name = "FlowProbe"
	# Dans la racine, pas dans la scène: la scène va disparaître.
	get_tree().root.add_child.call_deferred(probe)
	probe.call_deferred("_boot")

func _boot() -> void:
	await get_tree().process_frame
	# On part du menu, comme le joueur.
	get_tree().change_scene_to_file("res://scenes/main/main_menu.tscn")
	for _step: int in 3:
		await get_tree().process_frame
	_check(get_tree().current_scene != null, "Le menu principal ne s'est pas chargé")
	# Le pont laisse le menu en 2D: il n'y a rien à mettre en volume.
	if get_tree().current_scene != null:
		_check(get_tree().current_scene.get_node_or_null("Bridge3D") == null,
			"Le pont s'accroche au menu, qui doit rester en 2D")

	# Puis on entre, comme le joueur.
	get_tree().change_scene_to_file("res://scenes/restaurant/restaurant.tscn")
	for _step: int in 8:
		await get_tree().process_frame
	var scene := get_tree().current_scene
	_check(scene != null and scene.scene_file_path.ends_with("restaurant.tscn"),
		"Le restaurant ne s'est pas chargé")
	if scene == null:
		_finish()
		return

	_check(scene.get_node_or_null("Bridge3D") != null,
		"Le pont ne s'est pas accroché après le changement de scène")
	_check(Bridge3D._avatars.size() >= 8,
		"Après le changement de scène, le pont n'a que %d avatars" % Bridge3D._avatars.size())

	var player: Player = scene.get("player")
	_check(player != null, "Le restaurant n'expose pas son héros")
	if player == null:
		_finish()
		return
	_check(player.controls_enabled, "Le héros n'a pas la main après le changement de scène")

	# --- Le geste réel: une touche, la physique, l'avatar ---------------------
	# `Input.get_vector()` lit l'état du singleton, pas un événement: c'est ce
	# qui explique qu'une manette réponde quand le clavier se tait. Le contrôle
	# éprouve donc les DEUX chemins, action et touche physique.
	var start: Vector2 = player.global_position
	Input.action_press("move_right")
	for _step: int in 14:
		await get_tree().physics_frame
	Input.action_release("move_right")
	await get_tree().process_frame
	var by_action := player.global_position.distance_to(start)
	_check(by_action > 20.0, "Le héros ne bouge pas sous l'action: %.1f px" % by_action)

	player.global_position = start
	# La vraie touche D, par sa position physique: c'est ce que le joueur presse.
	var key := InputEventKey.new()
	key.physical_keycode = KEY_D
	key.pressed = true
	Input.parse_input_event(key)
	for _step: int in 14:
		await get_tree().physics_frame
	var by_key := player.global_position.distance_to(start)
	var released := InputEventKey.new()
	released.physical_keycode = KEY_D
	released.pressed = false
	Input.parse_input_event(released)
	await get_tree().process_frame
	_check(by_key > 20.0, "Le héros ne bouge pas sous la touche physique D: %.1f px" % by_key)
	player.global_position = start

	var avatar: Node3D = Bridge3D._avatars.get(player)
	_check(avatar != null and is_instance_valid(avatar), "Le héros a perdu son avatar sur le vrai chemin")

	# Sans caméra 3D courante, rien de la scène 3D ne s'affiche: on ne verrait
	# que les feuilles 2D, et le jeu paraîtrait figé alors qu'il tourne.
	var camera: Camera3DRig = scene.get_node_or_null("Bridge3D/Camera3D")
	_check(camera != null, "Aucune caméra 3D après le changement de scène")
	if camera != null:
		_check(camera.current, "La caméra 3D n'est pas la caméra courante du viewport")
	GameState.clear_announcements()
	Stage.clear()
	GameState.enqueue_notification("Événement", "Première")
	GameState.enqueue_notification("Événement", "Deuxième")
	var tray := GameState.notification_tray
	tray.present_next()
	get_tree().paused = true
	var frozen: float = tray.elapsed
	for frame: int in 3:
		await get_tree().process_frame
	_check(is_equal_approx(tray.elapsed, frozen) and not tray.panel.visible, "La notification continue sous le menu pause")
	get_tree().paused = false
	get_tree().change_scene_to_file("res://scenes/main/main_menu.tscn")
	for frame: int in 4:
		await get_tree().process_frame
	_check(not tray.panel.visible and tray.current.get("body") == "Première", "La notification se perd ou reste au menu principal")
	_check(GameState.pending_announcements.size() == 1, "Le changement de scène efface la file")
	get_tree().change_scene_to_file("res://scenes/restaurant/restaurant.tscn")
	for frame: int in 4:
		await get_tree().process_frame
	_check(tray.panel.visible and tray.current.get("body") == "Première", "La notification ne reprend pas dans le restaurant")
	GameState.clear_announcements()
	_finish()

func _finish() -> void:
	if failures > 0:
		print("PARCOURS 3D ÉCHEC: %d contrôle(s)" % failures)
		get_tree().quit(1)
		return
	print("PARCOURS 3D OK: menu, changement de scène, pont réaccroché, clavier vivant")
	get_tree().quit(0)
