extends ScrollContainer

# Emitted when the button causing the scrolling is released
signal horizontal_scroll_ended
signal vertical_scroll_ended
# Emitted when the scrollbar stops scrolling and it is at either end of its
# range, provided the previous time it stopped scrolling it was not at the same
# end
signal h_scrollbar_reached_start
signal v_scrollbar_reached_start
signal h_scrollbar_reached_end
signal v_scrollbar_reached_end

var _h_scrollbar_pressed: bool = false
var _v_scrollbar_pressed: bool = false
var _h_scroll_started_at: float = -INF
var _v_scroll_started_at: float = -INF


func _ready() -> void:
	connect("horizontal_scroll_ended", self, "_on_horizontal_scroll_ended")
	connect("vertical_scroll_ended", self, "_on_vertical_scroll_ended")
	# Signal `scroll_ended` doesn't work, so implement it with `gui_input`
	# See: https://github.com/godotengine/godot/issues/22936
	connect("gui_input", self, "_on_gui_input")
	get_h_scrollbar().connect("gui_input", self, "_on_h_scrollbar_gui_input")
	get_v_scrollbar().connect("gui_input", self, "_on_v_scrollbar_gui_input")


func is_h_scrollbar_at_start() -> bool:
	var distance_to_start: float = (
			get_h_scrollbar().get_min()
			- get_h_scrollbar().get_value())
	return is_zero_approx(distance_to_start)


func is_v_scrollbar_at_start() -> bool:
	var distance_to_start: float = (
			get_v_scrollbar().get_min()
			- get_v_scrollbar().get_value())
	return is_zero_approx(distance_to_start)


func is_h_scrollbar_at_end() -> bool:
	var distance_to_end: float = (
			get_h_scrollbar().get_max()
			- get_h_scrollbar().get_page()
			- get_h_scrollbar().get_value())
	return is_zero_approx(distance_to_end)


func is_v_scrollbar_at_end() -> bool:
	var distance_to_end: float = (
			get_v_scrollbar().get_max() 
			- get_v_scrollbar().get_page() 
			- get_v_scrollbar().get_value())
	return is_zero_approx(distance_to_end)


func _on_gui_input(event: InputEvent) -> void:
	if (
			event is InputEventMouseButton
			and event.get_button_index() in
				[BUTTON_WHEEL_UP, BUTTON_WHEEL_DOWN]
	):
		# Pass through to the vertical scrollbar
		_on_v_scrollbar_gui_input(event)


func _on_h_scrollbar_gui_input(event: InputEvent) -> void:
	"""
	Checks if scrolling has started or stopped and emits a signal if it has
	stopped
	"""
	if (
			event is InputEventMouseButton 
			and event.get_button_index() == BUTTON_LEFT
	):
		if event.is_pressed():
			_h_scrollbar_pressed = true
			_h_scroll_started_at = get_h_scrollbar().get_value()
		else:
			if _h_scrollbar_pressed:
				emit_signal("horizontal_scroll_ended")
			_h_scrollbar_pressed = false


func _on_v_scrollbar_gui_input(event: InputEvent) -> void:
	"""
	Checks if scrolling has started or stopped and emits a signal if it has
	stopped
	"""
	if (
			event is InputEventMouseButton 
			and event.get_button_index() in
				[BUTTON_LEFT, BUTTON_WHEEL_UP, BUTTON_WHEEL_DOWN]
	):
		if event.is_pressed():
			_v_scrollbar_pressed = true
			_v_scroll_started_at = get_v_scrollbar().get_value()
		else:
			if _v_scrollbar_pressed:
				emit_signal("vertical_scroll_ended")
			_v_scrollbar_pressed = false


func _on_horizontal_scroll_ended() -> void:
	"""
	Emit a signal notifying that the scrollbar has reached the start or end of
	its range if it has stopped scrolling and the previous time it stopped
	scrolling it was not at the same end
	"""
	var at_start: bool = is_h_scrollbar_at_start()
	var at_end: bool = is_h_scrollbar_at_end()
	var h_scrollbar_value: float = get_h_scrollbar().get_value()
	
	if not is_equal_approx(_h_scroll_started_at, h_scrollbar_value):
		if at_start:
			emit_signal("h_scrollbar_reached_start")
		elif at_end:
			emit_signal("h_scrollbar_reached_end")


func _on_vertical_scroll_ended() -> void:
	"""
	Emit a signal notifying that the scrollbar has reached the start or end of
	its range if it has stopped scrolling and the previous time it stopped
	scrolling it was not at the same end
	"""
	var at_start: bool = is_v_scrollbar_at_start()
	var at_end: bool = is_v_scrollbar_at_end()
	var v_scrollbar_value: float = get_v_scrollbar().get_value()
	
	if not is_equal_approx(_v_scroll_started_at, v_scrollbar_value):
		if at_start:
			emit_signal("v_scrollbar_reached_start")
		elif at_end:
			emit_signal("v_scrollbar_reached_end")
