extends VBoxContainer

const COLOR_NORMAL = Color("3c3c46")
const COLOR_HOVERED = Color("50505a")
const COLOR_PRESSED = Color("32323c")

export(PackedScene) var ChannelCard_scene: PackedScene = load("res://scenes/custom_nodes/channel_card/channel_card.tscn")
export(PackedScene) var ButtonContainer_scene: PackedScene = load("res://scenes/custom_nodes/button_container/button_container.tscn")

var _initialized: bool = false
var _following_changed: bool = false
var _last_search_text: String = ""
# Right margin offset that should be applied so that the scroll bar hugs the
# edge of the window
var _channel_list_scroll_container_offset: int = 0
# State this scene should be initialized with
var _init_state: Dictionary = {}
var _async_helper := AsyncHelper.new(self)
onready var _search_bar: LineEdit = find_node("SearchBar")
onready var _channel_list_node: VBoxContainer = find_node("ChannelList")
onready var _channel_list_margin_container: MarginContainer = \
		find_node("ChannelListMarginContainer")
onready var _channel_list_scroll_container: ScrollContainer = \
		find_node("ChannelListScrollContainer")


func _ready() -> void:
	connect("visibility_changed", self, "_on_visibility_changed")
	_search_bar.connect("text_entered", self, "_on_SearchBar_text_entered")
	_channel_list_scroll_container.get_v_scrollbar().connect(
			"visibility_changed", self,
			"_on_ChannelListScrollContainer_v_scrollbar_visibility_changed")
	LBRYActions.connect(
			"followed_channel", self, "_on_LBRYActions_channel_followed")
	LBRYActions.connect(
			"unfollowed_channel", self, "_on_LBRYActions_channel_unfollowed")
	
	var tracker: Dictionary = _async_helper.connect_wrapped(
			SDK.wait_until_ready(), "completed", false)
	yield(_async_helper.wait_until_finished([tracker]), "completed")
	
	tracker = _async_helper.connect_wrapped(
			LBRYActions.get_followed_channels(), "completed")
	yield(_async_helper.wait_until_finished([tracker]), "completed")
	var followed_channels: Array = tracker["result"]
	_display_followed_channels(followed_channels)
	_make_scroll_bar_hug_window_edge()
	
	if not _init_state.empty():
		_setup_state()


func get_tab_title() -> String:
	return "Following"


func get_state() -> Dictionary:
	"""
	Returns:
		The data needed to restore the main changes made to this scene
	"""
	var state: Dictionary = {
		"_last_search_text": _last_search_text,
		"ChannelListScrollContainer_v_scroll_position": \
				_channel_list_scroll_container.get_v_scrollbar().get_value(),
	}
	return state


func set_state(state: Dictionary) -> void:
	"""
	Call before the node enters the SceneTree to initialize it with the given
	state
	"""
	_init_state = state


func _setup_state() -> void:
	""" Sets up the scene using the state description in `_init_state` """
	_search_bar.set_text(_init_state["_last_search_text"])
	_apply_search(_init_state["_last_search_text"])
	
	var tracker: Dictionary = _async_helper.connect_wrapped(
			get_tree(), "idle_frame", false)
	yield(_async_helper.wait_until_finished([tracker]), "completed")
	_channel_list_scroll_container.get_v_scrollbar().set_value(
			_init_state["ChannelListScrollContainer_v_scroll_position"])


func _display_followed_channels(channel_urls: Array) -> void:
	if len(channel_urls) > 0:
		for channel_url in channel_urls:
			var button_container: ButtonContainer = \
					ButtonContainer_scene.instance()
			var channel_card: ChannelCard = ChannelCard_scene.instance()
			channel_card.init(channel_url)
			channel_card.connect("is_setup", self, "_on_ChannelCard_is_setup",
					[channel_card], CONNECT_ONESHOT)
			
			button_container.connect(
					"pressed", self, "_open_channel_window", [channel_url])
			button_container.set_normal_color(COLOR_NORMAL)
			button_container.set_hovered_color(COLOR_HOVERED)
			button_container.set_pressed_color(COLOR_PRESSED)
			button_container.add_child(channel_card)
			_channel_list_node.add_child(button_container)
	else:
		var label := Label.new()
		label.set_text("Not following any channels")
		_channel_list_node.add_child(label)


func _apply_search(search_text: String) -> void:
	"""
	Hides channels that don't match the search term, unless the search term is
	empty in which case all channels are shown
	"""
	_last_search_text = search_text
	for child in _channel_list_node.get_children():
		if child.get_child_count() > 0 and child.get_child(0) is ChannelCard:
			var channel_card: ChannelCard = child.get_child(0)
			# Don't hide channels that aren't setup because they will not have
			# the correct channel information to search for. This check should
			# be performed again when they are setup
			if (
					_channel_matches_search(channel_card, search_text)
					or not channel_card.is_setup()
			):
				child.set_visible(true)
				child.set_disabled(false)
			else:
				child.set_visible(false)
				child.set_disabled(true)


func _channel_matches_search(channel_card: ChannelCard, search_text: String
		) -> bool:
	"""
	Returns:
		`true` if `search_text` matches the channel or is an empty string,
		`false` otherwise.
	"""
	var name_search_term: String = \
			channel_card.get_channel_name().to_lower().trim_prefix("@")
	var title_search_term: String = \
			channel_card.get_channel_title().to_lower()
	
	if (
			search_text == ""
			or search_text.to_lower() in name_search_term
			or search_text.to_lower() in title_search_term
	):
		return true
	else:
		return false


func _clear() -> void:
	for child in _channel_list_node.get_children():
		child.queue_free()


func _make_scroll_bar_hug_window_edge() -> void:
	var scroll_container_right_edge: float = (
			_channel_list_scroll_container.get_global_position().x
			+ _channel_list_scroll_container.get_size().x
	)
	var diff := int(OS.get_window_size().x - scroll_container_right_edge)
	_channel_list_scroll_container_offset = -diff
	if _channel_list_scroll_container.get_v_scrollbar().is_visible_in_tree():
		_channel_list_margin_container.add_constant_override(
				"margin_right", _channel_list_scroll_container_offset)


func _open_channel_window(channel_url: String) -> void:
	get_parent().set_window("ChannelWindow", "init", [channel_url])


func _on_visibility_changed() -> void:
	if is_visible_in_tree() and _following_changed:
		_following_changed = false
		var tracker: Dictionary = _async_helper.connect_wrapped(
				LBRYActions.get_followed_channels(), "completed")
		_clear()
		yield(_async_helper.wait_until_finished([tracker]), "completed")
		var followed_channels: Array = tracker["result"]
		_display_followed_channels(followed_channels)


func _on_SearchBar_text_entered(new_text: String) -> void:
	_apply_search(new_text)


func _on_ChannelCard_is_setup(channel_card: ChannelCard) -> void:
	""" Hide the channel if it should be hidden """
	for child in _channel_list_node.get_children():
		if (
				child.get_child_count() > 0
				and child.get_child(0) == channel_card
		):
			if _channel_matches_search(channel_card, _last_search_text):
				child.set_visible(true)
				child.set_disabled(false)
			else:
				child.set_visible(false)
				child.set_disabled(true)
			return


func _on_ChannelListScrollContainer_v_scrollbar_visibility_changed() -> void:
	""" Ensure the margin is maintained when the scroll bar is not visible """
	var right_margin: int = (
			_channel_list_scroll_container_offset
			if _channel_list_scroll_container \
					.get_v_scrollbar() \
					.is_visible_in_tree()
			else 0
	)
	_channel_list_margin_container.add_constant_override(
			"margin_right", right_margin)


func _on_LBRYActions_channel_followed(_channel_url: String) -> void:
	_following_changed = true


func _on_LBRYActions_channel_unfollowed(_channel_url: String) -> void:
	_following_changed = true
