class_name Dish
extends Node2D

enum State { FILLED, SERVING, DIRTY }

var recipe_id := "fried_egg"
var state := State.FILLED
# Qui porte l'assiette. Le joueur ou un employé: le suivi, le halo et les
# libérations sont les mêmes, seul le type était trop étroit.
var carrier: Node2D
var bob_time := 0.0
var pending_payout := 0
var pending_tip := 0
var table_index := -1
var served_customer_id := 0
var payment_claimed := false
# Un plat raffiné. Deux chemins y mènent — l'épicerie, à coup sûr; le dressage
# d'un bon cuisinier, au hasard — et un seul drapeau les représente.
var superior := false
var visuals: VisualHolder
var _visual_state := ""

func _ready() -> void:
	add_to_group("dish")
	add_to_group("interactable")
	_build_visual_holder()
	queue_redraw()

func _build_visual_holder() -> void:
	if visuals == null:
		visuals = VisualHolder.new()
		visuals.name = "Visuals"
		add_child(visuals)
	visuals.bind("recipes", recipe_id)
	_visual_state = ""
	_refresh_visual_state()

# Une assiette sale n'est plus un plat: elle retombe sur le dessin, qui sait déjà
# montrer la vaisselle. Un état vide se résout en « aucune image ».
func _current_visual_state() -> String:
	return "" if state == State.DIRTY else "plated"

func _refresh_visual_state() -> void:
	if visuals == null or not is_instance_valid(visuals):
		return
	var wanted := _current_visual_state()
	if wanted == _visual_state:
		return
	_visual_state = wanted
	visuals.show_state(wanted)

# Le halo de visée et le liseré « raffiné » restent dessinés: ils informent, ils
# n'habillent pas.
func should_draw_body() -> bool:
	return visuals == null or not visuals.is_showing()

func setup(id: String) -> void:
	if not DataCatalog.RECIPES.has(id):
		push_error("Unknown dish recipe: " + id)
		return
	recipe_id = id
	state = State.FILLED
	_build_visual_holder()
	queue_redraw()

# Vrai quand c'est cette assiette-là que le joueur prendrait. Redessinée au
# changement seulement: un halo qui clignote coûterait plus qu'il ne rapporte.
var targeted := false

func _process(delta: float) -> void:
	# L etat de l assiette change par appels (salir, laver, restaurer) et non par
	# une machine continue: le rafraichissement se fait ici, une fois pour toutes.
	_refresh_visual_state()
	var player := get_tree().get_first_node_in_group("player") as Player
	var now_targeted := player != null and is_instance_valid(player) and player.current_target == self
	if now_targeted != targeted:
		targeted = now_targeted
		queue_redraw()
	if carrier == null or not is_instance_valid(carrier):
		return
	bob_time += delta
	global_position = carrier.global_position + Vector2(0, -34.0 + sin(bob_time * 7.0) * 2.0)

func interact(player: Player) -> void:
	# A plate a customer is eating stays put, and a carried plate is released
	# through a station rather than here: plates never go back on the floor.
	if state == State.SERVING or carrier != null:
		return
	pick_up(player)

func pick_up(player: Player) -> void:
	if not player.pick_up_dish(self):
		return
	carrier = player
	z_index = 10
	var label := Loc.ui("ui.plate.dirty") if state == State.DIRTY else DataCatalog.recipe_name(recipe_id)
	GameState.toast_requested.emit(Loc.ui("ui.plate.picked_up") % label)
	if state == State.DIRTY:
		var level := get_tree().get_first_node_in_group("level")
		# La table se libère au ramassage, l'encaissement reste au lavage.
		if level != null and level.has_method("release_table_plate"):
			level.release_table_plate(self)
		if not payment_claimed and level != null and level.has_method("claim_dirty_plate_payment"):
			level.claim_dirty_plate_payment(self)

# Prise par un employé. Même effet que le geste du joueur, sans le message ni le
# passage par `pick_up_dish`: un employé n'a pas de main unique à réserver.
func carry_by(bearer: Node2D) -> void:
	if carrier != null and is_instance_valid(carrier):
		return
	carrier = bearer
	z_index = 10
	if state == State.DIRTY:
		var level := get_tree().get_first_node_in_group("level")
		# La table se libère au ramassage, exactement comme pour le joueur.
		if level != null and level.has_method("release_table_plate"):
			level.release_table_plate(self)

func place_on(target_position: Vector2) -> void:
	if carrier != null and is_instance_valid(carrier) and carrier.has_method("clear_held_dish"):
		carrier.clear_held_dish(self)
	carrier = null
	global_position = target_position
	z_index = 0
	queue_redraw()

func to_save_dictionary() -> Dictionary:
	# Le mangeur traverse le changement de scène lui aussi: l'assiette reste
	# donc en cours de repas. Elle ne retombe à « sale » que si personne ne la
	# réclame au retour, ce que le restaurant tranche à la restauration.
	return {
		"recipe_id": recipe_id,
		"state": int(state),
		"position_x": global_position.x,
		"position_y": global_position.y,
		"table_index": table_index,
		"pending_payout": pending_payout,
		"pending_tip": pending_tip,
		"payment_claimed": payment_claimed,
		"superior": superior,
	}

func apply_save_dictionary(entry: Dictionary) -> void:
	recipe_id = str(entry.get("recipe_id", recipe_id))
	state = int(entry.get("state", int(State.FILLED))) as State
	# Le plat restaure choisit son visuel apres avoir repris recette et etat.
	_build_visual_holder()
	global_position = Vector2(float(entry.get("position_x", 0.0)), float(entry.get("position_y", 0.0)))
	table_index = int(entry.get("table_index", -1))
	pending_payout = int(entry.get("pending_payout", 0))
	pending_tip = int(entry.get("pending_tip", 0))
	payment_claimed = bool(entry.get("payment_claimed", false))
	superior = bool(entry.get("superior", false))
	served_customer_id = 0
	carrier = null
	z_index = 0
	if not is_in_group("interactable"):
		add_to_group("interactable")
	queue_redraw()

func place_for_customer(customer: Customer, payout: int, tip: int, table_position: Vector2) -> void:
	if carrier != null and is_instance_valid(carrier) and carrier.has_method("clear_held_dish"):
		carrier.clear_held_dish(self)
	carrier = null
	state = State.SERVING
	table_index = customer.table_index
	served_customer_id = customer.get_instance_id()
	pending_payout = payout
	pending_tip = tip
	payment_claimed = false
	global_position = table_position
	z_index = 1
	remove_from_group("interactable")
	queue_redraw()

func make_dirty() -> void:
	state = State.DIRTY
	if not is_in_group("interactable"):
		add_to_group("interactable")
	queue_redraw()

func wash() -> Dictionary:
	if carrier != null and is_instance_valid(carrier) and carrier.has_method("clear_held_dish"):
		carrier.clear_held_dish(self)
	carrier = null
	var result := {"payout": pending_payout, "tip": pending_tip, "table_index": table_index}
	queue_free()
	return result

# Jeter un plat ne jette pas l'assiette: elle reste dans les mains du joueur et
# devient sale. Ce qui disparaît, c'est le repas et tout ce qu'il rapportait.
func discard_meal() -> void:
	pending_payout = 0
	pending_tip = 0
	payment_claimed = false
	served_customer_id = 0
	table_index = -1
	superior = false
	make_dirty()

# Une assiette libérée d'office — remise à zéro, changement de scène — laissait
# le joueur avec une référence morte et le HUD lisait une instance libérée. Elle
# se désinscrit elle-même plutôt que de compter sur l'appelant, comme Customer.
func _exit_tree() -> void:
	if carrier != null and is_instance_valid(carrier) and carrier.has_method("clear_held_dish"):
		carrier.clear_held_dish(self)
	carrier = null

func consume() -> void:
	if carrier != null and is_instance_valid(carrier) and carrier.has_method("clear_held_dish"):
		carrier.clear_held_dish(self)
	carrier = null
	queue_free()

func get_prompt() -> String:
	if state == State.DIRTY:
		return Loc.ui("ui.plate.pick_dirty")
	if state == State.SERVING:
		return Loc.ui("ui.plate.being_eaten")
	return Loc.ui("ui.plate.pick_up") % DataCatalog.recipe_name(recipe_id)

func _draw() -> void:
	var filling_color := Color("ffd166")
	var recipe: Dictionary = DataCatalog.RECIPES.get(recipe_id, {})
	var ingredients: Dictionary = recipe.get("ingredients", {})
	if not ingredients.is_empty():
		var ingredient_id := str(ingredients.keys()[0])
		var ingredient: Dictionary = DataCatalog.INGREDIENTS.get(ingredient_id, {})
		var configured_color: Variant = ingredient.get("color", Color("ffd166"))
		filling_color = configured_color if configured_color is Color else Color(str(configured_color))
	# Le halo se dessine sous l'assiette: on voit lequel des plats posés partira.
	if targeted and carrier == null:
		draw_circle(Vector2.ZERO, 27.0, Color(1.0, 0.86, 0.4, 0.35))
		draw_circle(Vector2.ZERO, 27.0, Color("ffd166"), false, 3.0)
	if should_draw_body():
		draw_circle(Vector2.ZERO, 20.0, Color("f8f4e3"))
	# Un plat raffiné se reconnaît d'un coup d'œil, sur l'étagère comme en main.
	if superior and state != State.DIRTY:
		draw_arc(Vector2.ZERO, 23.0, 0.0, TAU, 24, Color("ffd166"), 3.0)
	if not should_draw_body():
		return
	if state == State.DIRTY:
		draw_arc(Vector2.ZERO, 13.0, 0.2, 4.9, 16, Color("7b5636"), 5.0)
	elif state == State.SERVING or state == State.FILLED:
		draw_circle(Vector2.ZERO, 14.0, filling_color)
	draw_circle(Vector2.ZERO, 20.0, Color("4d4058"), false, 2.0)
