class_name InfoBadge3D
extends Node3D

# Ce que l'entité a à dire, affiché au-dessus d'elle, en volume.
#
# Le jeu 2D dessine ces informations — commande d'un client, avancement d'une
# cuisson — **aux coordonnées du monde**. Sous une caméra 3D inclinée, ce point
# n'est plus au même endroit à l'écran: les rallumer telles quelles les
# afficherait à côté de ce qu'elles décrivent.
#
# Elles remontent donc dans la scène 3D, en panneau toujours face au joueur. Le
# badge ne décide de rien: il lit ce que l'entité publie déjà, et se tait quand
# il n'y a rien à dire.

const BAR_WIDTH := 64.0
const BAR_HEIGHT := 6.0
const CUSTOMER_PANEL_WIDTH := 210.0
const CUSTOMER_LINE_CHARACTERS := 30
# Hauteur du texte **en unités du monde**: `font_size × pixel_size`. L'écrire
# ainsi évite de régler deux nombres qui se multiplient sans qu'on voie le
# résultat — c'est comme ça qu'il s'est retrouvé cinq fois trop gros.
const TEXT_WORLD_HEIGHT := 13.0
const FONT_SIZE := 64

# Le nom, en retrait au-dessus du reste: discret par construction — plus petit,
# plus terne, et jamais devant l'information qui compte.
const NAME_WORLD_HEIGHT := 9.0

var _name: Label3D
var _label: Label3D
var _panel: MeshInstance3D
var _panel_mesh: BoxMesh
var _track: MeshInstance3D
var _fill: MeshInstance3D
var _fill_mesh: BoxMesh

func _ready() -> void:
	_panel_mesh = BoxMesh.new()
	_panel_mesh.size = Vector3(CUSTOMER_PANEL_WIDTH, 54.0, 1.0)
	_panel = MeshInstance3D.new()
	_panel.name = "BubblePanel"
	_panel.mesh = _panel_mesh
	var panel_material := StandardMaterial3D.new()
	panel_material.albedo_color = Color(0.055, 0.035, 0.08, 0.86)
	panel_material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
	panel_material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
	panel_material.billboard_mode = BaseMaterial3D.BILLBOARD_ENABLED
	panel_material.no_depth_test = true
	panel_material.render_priority = -1
	_panel.material_override = panel_material
	add_child(_panel)

	_label = Label3D.new()
	_label.billboard = BaseMaterial3D.BILLBOARD_ENABLED
	_label.no_depth_test = true
	_label.fixed_size = false
	_label.font_size = FONT_SIZE
	_label.pixel_size = TEXT_WORLD_HEIGHT / float(FONT_SIZE)
	_label.outline_size = 10
	_label.modulate = Color("f4f0f8")
	_label.outline_modulate = Color(0.05, 0.03, 0.08, 0.94)
	add_child(_label)

	_name = Label3D.new()
	_name.billboard = BaseMaterial3D.BILLBOARD_ENABLED
	_name.no_depth_test = true
	_name.font_size = FONT_SIZE
	_name.pixel_size = NAME_WORLD_HEIGHT / float(FONT_SIZE)
	_name.outline_size = 8
	_name.modulate = Color(0.82, 0.84, 0.92, 0.78)
	_name.outline_modulate = Color(0.05, 0.03, 0.08, 0.8)
	add_child(_name)

	_track = _bar(Color(0.22, 0.18, 0.27), BAR_WIDTH)
	add_child(_track)
	_fill_mesh = BoxMesh.new()
	_fill_mesh.size = Vector3(BAR_WIDTH, BAR_HEIGHT, 1.0)
	_fill = MeshInstance3D.new()
	_fill.mesh = _fill_mesh
	var filled := StandardMaterial3D.new()
	filled.albedo_color = Color("4ade80")
	filled.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
	filled.billboard_mode = BaseMaterial3D.BILLBOARD_ENABLED
	filled.no_depth_test = true
	_fill.material_override = filled
	add_child(_fill)
	hide_all()

func _bar(tint: Color, width: float) -> MeshInstance3D:
	var mesh := BoxMesh.new()
	mesh.size = Vector3(width, BAR_HEIGHT, 1.0)
	var node := MeshInstance3D.new()
	node.mesh = mesh
	var material := StandardMaterial3D.new()
	material.albedo_color = tint
	material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
	material.billboard_mode = BaseMaterial3D.BILLBOARD_ENABLED
	material.no_depth_test = true
	node.material_override = material
	return node

func hide_all() -> void:
	_label.visible = false
	_panel.visible = false
	_track.visible = false
	_fill.visible = false

# Ce que le badge montre réellement, pour qui veut le vérifier. Lire `visible`
# sur le badge ne dirait rien: le noeud reste affiché, ce sont ses parties qui
# s'allument.
func shows_bar() -> bool:
	return _fill != null and _fill.visible

func shows_customer_bubble() -> bool:
	return _panel != null and _panel.visible

func displayed_text() -> String:
	return _label.text if _label != null and _label.visible else ""

# Le nom vit sa vie: il reste affiché même quand l'entité n'a rien à dire.
func show_name(text: String, elevation: float) -> void:
	position.y = elevation
	_name.visible = not text.is_empty()
	_name.text = text
	_name.position = Vector3(0.0, NAME_WORLD_HEIGHT, 0.0)

# `text` peut être vide, `ratio` négatif: chaque moitié se montre pour elle-même.
func show_info(text: String, ratio: float, tint: Color, elevation: float) -> void:
	position.y = elevation
	_panel.visible = false
	_label.visible = not text.is_empty()
	_label.text = text
	_track.visible = ratio >= 0.0
	_fill.visible = ratio >= 0.0
	_label.position = Vector3(0.0, TEXT_WORLD_HEIGHT if ratio >= 0.0 else 0.0, 0.0)
	_name.position.y = (TEXT_WORLD_HEIGHT * 2.0 if not text.is_empty() else 0.0) + NAME_WORLD_HEIGHT
	if ratio < 0.0:
		return
	var clamped := clampf(ratio, 0.0, 1.0)
	# La barre se remplit **depuis la gauche**: on rétrécit le pavé et on le
	# décale d'autant, sinon il grandirait des deux côtés à la fois.
	_fill_mesh.size = Vector3(maxf(0.5, BAR_WIDTH * clamped), BAR_HEIGHT, 1.0)
	_fill.position = Vector3(-BAR_WIDTH * 0.5 + BAR_WIDTH * clamped * 0.5, 0.0, 0.5)
	var material: StandardMaterial3D = _fill.material_override
	material.albedo_color = tint

# Commande, parole et patience partagent un seul fond et une seule verticale.
# Le texte spontané reste en haut, la commande en dessous et la jauge ferme la
# bulle: aucune des trois informations ne peut dériver indépendamment.
func show_customer_info(speech: String, order: String, ratio: float,
		tint: Color, elevation: float) -> void:
	var lines: Array[String] = []
	if not speech.strip_edges().is_empty():
		lines.append_array(_wrap_customer_text(speech.strip_edges()))
	if not order.strip_edges().is_empty():
		lines.append(order.strip_edges())
	var combined := "\n".join(lines)
	show_info(combined, ratio, tint, elevation)
	if combined.is_empty() and ratio < 0.0:
		return
	_panel.visible = true
	var line_count := maxi(1, lines.size())
	var bar_space := BAR_HEIGHT + 10.0 if ratio >= 0.0 else 0.0
	var text_height := float(line_count) * TEXT_WORLD_HEIGHT
	var bottom := -8.0
	var top := bar_space + text_height + 9.0
	_panel_mesh.size = Vector3(CUSTOMER_PANEL_WIDTH, top - bottom, 1.0)
	_panel.position = Vector3(0.0, (top + bottom) * 0.5, -0.5)
	_label.position = Vector3(0.0, bar_space + text_height * 0.5, 1.0)
	_track.position.y = 1.0
	_fill.position.y = 1.0
	_name.position.y = top + NAME_WORLD_HEIGHT

func _wrap_customer_text(text: String) -> Array[String]:
	var result: Array[String] = []
	for paragraph: String in text.split("\n", true):
		var current := ""
		for word: String in paragraph.split(" ", false):
			var candidate := word if current.is_empty() else "%s %s" % [current, word]
			if candidate.length() <= CUSTOMER_LINE_CHARACTERS:
				current = candidate
			else:
				if not current.is_empty():
					result.append(current)
				current = word
		if not current.is_empty():
			result.append(current)
	if result.is_empty() and not text.is_empty():
		result.append(text)
	return result
