extends Node3D

const Interactable = preload("res://src/interactable.gd")
const EnterSignals: Array[String] = ["area_entered", "body_entered"]
const ExitSignals: Array[String] = ["area_exited", "body_exited"]

@export var area: Area3D

class InteractableNode:
    var _body: CollisionObject3D
    var _interactable: Interactable

    var global_position: Vector3:
        get: return _body.global_position

    func _init(body: CollisionObject3D, interactable: Interactable):
        _body = body
        _interactable = interactable

    func interact(interactor: Node):
        _interactable.interact(interactor)

    func squared_distance_to(b: InteractableNode) -> float:
        return global_position.distance_squared_to(b.global_position)

var interactables: Array[InteractableNode] = []
var _sorted: bool = false

func _ready() -> void:
    _connect_many(area, EnterSignals, _on_object_entered)
    _connect_many(area, ExitSignals, _on_object_exited)

func _connect_many(node: Node, signals: Array[String], fn: Callable):
    for signal_name in signals:
        node.connect(signal_name, fn)

func _on_object_entered(object: CollisionObject3D):
    _sorted = false
    var child = NodeExt.find_child_variant(object, Interactable)
    if child.is_some():
        interactables.append(InteractableNode.new(object, child.unwrap() as Interactable))

func is_same_object(object: CollisionObject3D, node: InteractableNode) -> bool:
    return object == node._body

func _on_object_exited(object: CollisionObject3D):
    var index = interactables.find_custom(
        func(node): return is_same_object(object, node)
    )

    if index >= 0:
        interactables.remove_at(index)

func _distance_from(node: InteractableNode) -> float:
    return global_position.distance_squared_to(node.global_position)

func _distance_compare(a: InteractableNode, b: InteractableNode):
    return _distance_from(a) > _distance_from(b)

func _sort_by_distance():
    interactables.sort_custom(_distance_compare)
    _sorted = true

func interact(interactable: InteractableNode):
    interactable.interact(self)

func interact_nearest():
    if not _sorted:
        _sort_by_distance()

    if len(interactables) > 0:
        return interact(interactables[0])
