@tool
class_name PersistentPocket
extends XRToolsSnapZone


## Persistent Pocket Node
##
## The [PersistentPocket] type holds persistent items managed by the
## persistence system. The [PersistentPocket] type extends from
## [XRToolsSnapZone] to allow [PersistentItem] objects to be snapped or
## removed by the player.


## Enumeration to control pocket behavior when the parent item is held
enum HeldBehavior {
	IGNORE,		## Ignore picked_up/dropped changes
	ENABLE,		## Enable when picked up
	DISABLE		## Disable when picked up
}


# Group for world-data properties
@export_group("World Data")

## This property specifies the unique ID of this pocket
@export var pocket_id : String

# Group for options
@export_group("Options")

## Pocket behavior when held
@export var held_behavior := HeldBehavior.ENABLE : set = _set_held_behavior


# Parent pickable body
var _parent_body : XRToolsPickable


# Add support for is_xr_class
func is_xr_class(p_name : String) -> bool:
	return p_name == "PersistentPocket" or super(p_name)


# Called when the node enters the scene tree for the first time.
func _ready():
	super()

	# Skip initialization if in editor
	if Engine.is_editor_hint():
		return

	# Search for an ancestor XRToolsPickable
	_parent_body = XRTools.find_xr_ancestor(self, "*", "XRToolsPickable")
	if _parent_body:
		_parent_body.picked_up.connect(_on_picked_up)
		_parent_body.dropped.connect(_on_dropped)

	# Update the held behavior
	_update_held_behavior()


# Get configuration warnings
func _get_configuration_warnings() -> PackedStringArray:
	var warnings := PackedStringArray()

	# Verify pocket ID is set
	if not pocket_id:
		warnings.append("Pocket ID not zet")

	# Verify pocket is in persistent group
	if not is_in_group("persistent"):
		warnings.append("Pocket not in 'persistent' group")

	# Return warnings
	return warnings


# Handle notifications
func _notification(what : int) -> void:
	# Ignore notifications on freeing objects
	if is_queued_for_deletion():
		return

	match what:
		Persistent.NOTIFICATION_LOAD_STATE:
			_load_state()

		Persistent.NOTIFICATION_SAVE_STATE:
			_save_state()

		Persistent.NOTIFICATION_DESTROY:
			_destroy()


# This method loads the pocket state from [PersistentWorld]. If the
# [PersistentWorld] indicates this pocket holds an item then the item is
# created and picked up by the pocket.
func _load_state() -> void:
	# Queue populating the pocket as new nodes cannot be created inside a
	# notification handler.
	_populate_pocket.call_deferred()


# This method saves the state of the pocket to [PersistentWorld].
func _save_state() -> void:
	# Handle pocket not holding on to PersistentItem
	if not picked_up_object is PersistentItem:
		# Save that the pocket is empty
		PersistentWorld.instance.clear_value(pocket_id)
		return

	# Get the item_id of the PersistentItem in the pocket
	var item_id : String = picked_up_object.item_id

	# Save that the pocket holds the item
	PersistentWorld.instance.set_value(pocket_id, item_id)


# This method destroys the pocket and any item inside it.
func _destroy() -> void:
	# Propagate destruction for anything we hold
	if is_instance_valid(picked_up_object):
		print(self, " propagating destroy to ", picked_up_object.name)
		picked_up_object.propagate_notification(Persistent.NOTIFICATION_DESTROY)
		picked_up_object.queue_free()


# Populate the contents of a pocket
func _populate_pocket() -> void:
	# Get the ID of the item in the pocket
	var item_id = PersistentWorld.instance.get_value(pocket_id)
	if not item_id is String:
		return

	# Construct the item for the pocket
	var zone = PersistentZone.find_instance(self)
	var item := zone.create_item_instance(item_id)
	if not item:
		return

	# Put the item in the pocket
	item.global_transform = global_transform
	pick_up_object.call_deferred(item)


# Called when the parent pickable body is picked up
func _on_picked_up(_pickable) -> void:
	_update_held_behavior()


# Called when the parent pickable body is dropped
func _on_dropped(_pickable) -> void:
	_update_held_behavior()


# Called when the held_behavior property has been modified
func _set_held_behavior(p_held_behavior : HeldBehavior) -> void:
	held_behavior = p_held_behavior
	if is_inside_tree() and _parent_body:
		_update_held_behavior()


# Update the pocket enable
func _update_held_behavior() -> void:
	# Skip if no valid parent body
	if not is_instance_valid(_parent_body):
		return

	# Test if the parent pickable is held
	var is_held := _parent_body.is_picked_up()

	# Update the enabled state based on whether the parent body is held and
	# the desired behavior
	match held_behavior:
		HeldBehavior.ENABLE:
			enabled = is_held

		HeldBehavior.DISABLE:
			enabled = not is_held
