extends Node

const FN_PREFIX = "_on_"
const SIGNAL_PREFIX = "on_"

# Interaction Events
signal on_click(event: EventRay)
signal on_press_down(event: EventRay)
signal on_press_move(event: EventRay)
signal on_press_up(event: EventRay)
signal on_grab_down(event: EventRay)
signal on_grab_move(event: EventRay)
signal on_grab_up(event: EventRay)
signal on_ray_enter(event: EventRay)
signal on_ray_leave(event: EventRay)

signal on_key_down(event: EventKey)
signal on_key_up(event: EventKey)

signal on_focus_in(event: EventFocus)
signal on_focus_out(event: EventFocus)

var _active_node: Node = null

func emit(type: String, event: Event):
	if event is EventBubble:
		_bubble_call(type, event.target, event)
		if type == "press_down":
			_handle_focus(event)
	else:
		_root_call(type, event)

func is_focused(node: Node):
	return _active_node == node

func _handle_focus(event: EventRay):
	if event.target != null && event.target.is_in_group("ui_focus_skip"):
		return

	var event_focus = EventFocus.new()
	event_focus.previous_target = _active_node
	event_focus.target = event.target
	event_focus.ray = event.ray

	if _active_node != null && _active_node.has_method(FN_PREFIX + "focus_out"):
		_active_node.call(FN_PREFIX + "focus_out", event_focus)
		on_focus_out.emit(event_focus)

	if event.target == null || event.target.is_in_group("ui_focus") == false:
		_active_node = null
		return

	_active_node = event.target

	if _active_node != null && _active_node.has_method(FN_PREFIX + "focus_in"):
		_active_node.call(FN_PREFIX + "focus_in", event_focus)
		on_focus_in.emit(event_focus)

func _bubble_call(type: String, target: Variant, event: EventBubble):
	if target == null:
		return false

	if target.has_method(FN_PREFIX + type):
		var updated_event = target.call(FN_PREFIX + type, event)

		if updated_event is EventBubble:
			updated_event.merge(event)
			event = updated_event

		if event.bubbling == false:
			return false

	for child in target.get_children():
		if child is Function && child.has_method(FN_PREFIX + type):
			child.call(FN_PREFIX + type, event)

	var parent = target.get_parent()

	if parent != null && parent is Node:
		_bubble_call(type, parent, event)
	else:
		# in case the top has been reached
		_root_call(type, event)

	return true


func _root_call(type: String, event: Event):
	get(SIGNAL_PREFIX + type).emit(event)