class_name ResourceNode
extends Node3D

signal amount_changed(node_id: StringName, amount_remaining: float)
signal depleted(node_id: StringName)
signal reservation_changed(node_id: StringName, agent_id: int)

static var _all: Array[ResourceNode] = []

@export var node_id: StringName
@export var action_id: StringName = SimulationIds.ACTION_GATHER_FOOD
@export var resource_id: StringName = SimulationIds.RESOURCE_FOOD
@export var initial_amount: float = 10.0
@export var yield_per_action: float = 2.0
@export var initial_enabled: bool = true
@export var can_npcs_use: bool = true
@export var can_player_use: bool = true
@export var hide_visual_when_depleted: bool = false

@onready var interaction_point: Marker3D = $InteractionPoint

var state: ResourceStateRecord


func _ready() -> void:
	if node_id.is_empty():
		push_error("ResourceNode at %s has empty node_id" % get_path())
		initial_enabled = false
		_update_presentation()
		return
	var existing := get_by_id(node_id)
	if existing != null:
		push_error(
			(
				"Duplicate ResourceNode node_id '%s' at %s; first registered at %s"
				% [node_id, get_path(), existing.get_path()]
			)
		)
		initial_enabled = false
		_update_presentation()
		return
	_all.append(self)
	add_to_group("resource_nodes")
	_try_register_with_simulation()
	_update_presentation()


func _exit_tree() -> void:
	_all.erase(self)
	if state != null and state.changed.is_connected(_on_state_changed):
		state.changed.disconnect(_on_state_changed)
	if state != null and state.depleted.is_connected(_on_state_depleted):
		state.depleted.disconnect(_on_state_depleted)
	state = null


func bind_state(resource_state: ResourceStateRecord) -> bool:
	if resource_state == null or resource_state.get_node_id() != node_id:
		return false
	if state != null and state.changed.is_connected(_on_state_changed):
		state.changed.disconnect(_on_state_changed)
	if state != null and state.depleted.is_connected(_on_state_depleted):
		state.depleted.disconnect(_on_state_depleted)
	state = resource_state
	if not state.changed.is_connected(_on_state_changed):
		state.changed.connect(_on_state_changed)
	if not state.depleted.is_connected(_on_state_depleted):
		state.depleted.connect(_on_state_depleted)
	_update_presentation()
	return true


func _try_register_with_simulation() -> void:
	var managers := get_tree().get_nodes_in_group("simulation_manager")
	if managers.size() != 1:
		return
	var manager := managers[0]
	if manager.has_method("register_resource_node"):
		manager.register_resource_node(self)


func get_amount_remaining() -> float:
	return state.get_amount_remaining() if state != null else initial_amount


func get_reserved_by() -> int:
	return state.get_reserved_by() if state != null else -1


func is_enabled() -> bool:
	return state.is_enabled() if state != null else initial_enabled


func is_depleted() -> bool:
	return state.is_depleted() if state != null else initial_amount <= 0.0


func can_extract() -> bool:
	return state.can_extract() if state != null else initial_enabled and not is_depleted()


func _on_state_changed(changed_state: ResourceStateRecord) -> void:
	if changed_state != state:
		return
	amount_changed.emit(node_id, state.get_amount_remaining())
	reservation_changed.emit(node_id, state.get_reserved_by())
	_update_presentation()


func _on_state_depleted(depleted_state: ResourceStateRecord) -> void:
	if depleted_state == state:
		depleted.emit(node_id)


func _update_presentation() -> void:
	if has_node("Visual"):
		$Visual.visible = not (hide_visual_when_depleted and is_depleted())
	if not has_node("DebugLabel"):
		return
	var label := $DebugLabel as Label3D
	var text := "%s\n%s %.1f" % [node_id, resource_id, get_amount_remaining()]
	if get_reserved_by() >= 0:
		text += "\nheld:%d" % get_reserved_by()
	if is_depleted():
		text += "\nDEPLETED"
	if state == null:
		text += "\nUNBOUND"
	label.text = text


static func get_by_id(search_id: StringName) -> ResourceNode:
	for node in _all:
		if node.node_id == search_id:
			return node
	return null


static func get_all() -> Array[ResourceNode]:
	return _all.duplicate()
