class_name ResourceStateRecord
extends RefCounted

signal changed(state: ResourceStateRecord)
signal depleted(state: ResourceStateRecord)

const SCHEMA_VERSION := 2
const LEGACY_SCHEMA_VERSION := 1

var data: Dictionary


func _init(record_data: Dictionary = {}) -> void:
	data = record_data.duplicate(true)


static func create_from_node(node: ResourceNode) -> ResourceStateRecord:
	return ResourceStateRecord.new(
		{
			"schema_version": SCHEMA_VERSION,
			"node_id": String(node.node_id),
			"action_id": String(node.action_id),
			"resource_id": String(node.resource_id),
			"amount_remaining": node.initial_amount,
			"yield_per_action": node.yield_per_action,
			"reserved_by": -1,
			"enabled": node.initial_enabled,
			"can_npcs_use": node.can_npcs_use,
			"can_player_use": node.can_player_use
		}
	)


static func from_dictionary(record_data: Dictionary) -> ResourceStateRecord:
	var version := int(record_data.get("schema_version", -1))
	if version == LEGACY_SCHEMA_VERSION:
		record_data = _migrate_v1(record_data)
	elif version != SCHEMA_VERSION:
		return null

	if not record_data.has_all(
		[
			"node_id",
			"action_id",
			"resource_id",
			"amount_remaining",
			"yield_per_action",
			"reserved_by",
			"enabled",
			"can_npcs_use",
			"can_player_use"
		]
	):
		return null
	if String(record_data["node_id"]).is_empty():
		return null
	var action_id := StringName(record_data["action_id"])
	if not action_id.is_empty() and SimulationDefinitions.get_action(action_id) == null:
		return null
	return ResourceStateRecord.new(record_data)


static func _migrate_v1(legacy_data: Dictionary) -> Dictionary:
	if not legacy_data.has_all(["node_id", "amount_remaining", "reserved_by", "enabled"]):
		return {}
	var migrated := legacy_data.duplicate(true)
	migrated["schema_version"] = SCHEMA_VERSION
	migrated["action_id"] = ""
	migrated["resource_id"] = ""
	migrated["yield_per_action"] = 0.0
	migrated["can_npcs_use"] = false
	migrated["can_player_use"] = false
	return migrated


func apply_definition(node: ResourceNode) -> bool:
	if node == null or node.node_id != get_node_id():
		return false

	var has_legacy_definition := get_action_id().is_empty()
	if has_legacy_definition:
		data["action_id"] = String(node.action_id)
		data["resource_id"] = String(node.resource_id)
		data["yield_per_action"] = node.yield_per_action
		data["can_npcs_use"] = node.can_npcs_use
		data["can_player_use"] = node.can_player_use
		data["schema_version"] = SCHEMA_VERSION
		return true

	return (
		get_action_id() == node.action_id
		and get_resource_id() == node.resource_id
		and is_equal_approx(get_yield_per_action(), node.yield_per_action)
		and can_npc_use() == node.can_npcs_use
		and can_player_use_resource() == node.can_player_use
	)


func get_node_id() -> StringName:
	return StringName(data["node_id"])


func get_action_id() -> StringName:
	return StringName(data["action_id"])


func get_resource_id() -> StringName:
	return StringName(data["resource_id"])


func get_amount_remaining() -> float:
	return float(data["amount_remaining"])


func get_yield_per_action() -> float:
	return float(data["yield_per_action"])


func get_reserved_by() -> int:
	return int(data["reserved_by"])


func is_enabled() -> bool:
	return bool(data["enabled"])


func can_npc_use() -> bool:
	return bool(data["can_npcs_use"])


func can_player_use_resource() -> bool:
	return bool(data["can_player_use"])


func is_depleted() -> bool:
	return get_amount_remaining() <= 0.0


func can_extract() -> bool:
	return is_enabled() and not is_depleted()


func is_available_for(agent_id: int) -> bool:
	return can_extract() and (get_reserved_by() == -1 or get_reserved_by() == agent_id)


func reserve(agent_id: int) -> bool:
	if not is_available_for(agent_id):
		return false
	data["reserved_by"] = agent_id
	changed.emit(self)
	return true


func release(agent_id: int) -> void:
	if get_reserved_by() != agent_id:
		return
	data["reserved_by"] = -1
	changed.emit(self)


func extract(requested_amount: float = -1.0) -> float:
	if not can_extract():
		return 0.0
	var amount := requested_amount
	if amount < 0.0:
		amount = get_yield_per_action()
	var extracted := minf(maxf(amount, 0.0), get_amount_remaining())
	data["amount_remaining"] = get_amount_remaining() - extracted
	if is_depleted():
		data["reserved_by"] = -1
	changed.emit(self)
	if is_depleted():
		depleted.emit(self)
	return extracted


func set_amount_remaining(amount: float) -> void:
	var was_depleted := is_depleted()
	data["amount_remaining"] = maxf(amount, 0.0)
	if is_depleted():
		data["reserved_by"] = -1
	changed.emit(self)
	if not was_depleted and is_depleted():
		depleted.emit(self)


func set_enabled(value: bool) -> void:
	data["enabled"] = value
	changed.emit(self)


func to_dictionary() -> Dictionary:
	return data.duplicate(true)
