@tool
class_name DialogicSidebar extends Control

## Script that handles the editor sidebar.

signal content_item_activated(item_name)
signal show_sidebar(show: bool)

# References
@onready var editors_manager = get_parent().get_parent()
@onready var resource_tree: Tree = %ResourceTree

var current_resource_list: Array = []

enum GroupMode {
	NONE,
	TYPE,
	FOLDER,
	PATH,
}
var group_mode: GroupMode = GroupMode.TYPE


func _ready() -> void:
	if owner != null and owner.get_parent() is SubViewport:
		return
	if editors_manager is SubViewportContainer:
		return

	## CONNECTIONS
	editors_manager.resource_opened.connect(_on_editors_resource_opened)
	editors_manager.editor_changed.connect(_on_editors_editor_changed)

	resource_tree.item_activated.connect(_on_resources_tree_item_activated)
	resource_tree.item_mouse_selected.connect(_on_resources_tree_item_clicked)
	resource_tree.item_collapsed.connect(_on_resources_tree_item_collapsed)

	%ContentList.item_selected.connect(
		func(idx: int): content_item_activated.emit(%ContentList.get_item_text(idx))
	)

	%OpenButton.pressed.connect(_show_sidebar)
	%CloseButton.pressed.connect(_hide_sidebar)

	var editor_scale := DialogicUtil.get_editor_scale()

	## ICONS
	%Logo.texture = load("res://addons/dialogic/Editor/Images/dialogic-logo.svg")
	%Logo.custom_minimum_size.y = 30 * editor_scale
	%Search.right_icon = get_theme_icon("Search", "EditorIcons")
	%Options.icon = get_theme_icon("GuiTabMenuHl", "EditorIcons")
	%OptionsPanel.add_theme_stylebox_override("panel", get_theme_stylebox("PanelForeground", "EditorStyles"))
	%OptionsPopup.hide()

	%ContentList.add_theme_color_override(
		"font_hovered_color", get_theme_color("warning_color", "Editor")
	)
	%ContentList.add_theme_color_override(
		"font_selected_color", get_theme_color("property_color_z", "Editor")
	)

	## RIGHT CLICK MENU
	%RightClickMenu.clear()
	%RightClickMenu.add_icon_item(get_theme_icon("Remove", "EditorIcons"), "Remove From List", 1)
	%RightClickMenu.add_separator()
	%RightClickMenu.add_icon_item(get_theme_icon("ActionCopy", "EditorIcons"), "Copy Identifier", 4)
	%RightClickMenu.add_separator()
	%RightClickMenu.add_icon_item(
		get_theme_icon("Filesystem", "EditorIcons"), "Show in FileSystem", 2
	)
	%RightClickMenu.add_icon_item(
		get_theme_icon("ExternalLink", "EditorIcons"), "Open in External Program", 3
	)

	## SORT MENU
	%GroupingOptions.set_item_icon(0, get_theme_icon("AnimationTrackGroup", "EditorIcons"))
	%GroupingOptions.set_item_icon(1, get_theme_icon("Folder", "EditorIcons"))
	%GroupingOptions.set_item_icon(2, get_theme_icon("FolderBrowse", "EditorIcons"))
	%GroupingOptions.set_item_icon(3, get_theme_icon("AnimationTrackList", "EditorIcons"))
	%GroupingOptions.item_selected.connect(_on_grouping_changed)

	await get_tree().process_frame
	if DialogicUtil.get_editor_setting("sidebar_collapsed", false):
		_hide_sidebar()

	%MainVSplit.split_offset = DialogicUtil.get_editor_setting("sidebar_v_split", 0)
	group_mode = DialogicUtil.get_editor_setting("sidebar_group_mode", 0)
	%GroupingOptions.select(%GroupingOptions.get_item_index(group_mode))

	%FolderColors.button_pressed = DialogicUtil.get_editor_setting("sidebar_use_folder_colors", true)
	%TrimFolderPaths.button_pressed = DialogicUtil.get_editor_setting("sidebar_trim_folder_paths", true)

	update_resource_list()


func set_unsaved_indicator(saved: bool = true) -> void:
	if saved and %CurrentResource.text.ends_with("(*)"):
		%CurrentResource.text = %CurrentResource.text.trim_suffix("(*)")
	if not saved and not %CurrentResource.text.ends_with("(*)"):
		%CurrentResource.text = %CurrentResource.text + "(*)"


func _on_logo_gui_input(event: InputEvent) -> void:
	if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
		editors_manager.open_editor(editors_manager.editors["HomePage"].node)


#region SHOW/HIDE SIDEBAR
################################################################################

func _show_sidebar() -> void:
	%VBoxPrimary.show()
	%VBoxHidden.hide()
	DialogicUtil.set_editor_setting("sidebar_collapsed", false)
	show_sidebar.emit(true)


func _hide_sidebar() -> void:
	%VBoxPrimary.hide()
	%VBoxHidden.show()
	DialogicUtil.set_editor_setting("sidebar_collapsed", true)
	show_sidebar.emit(false)

#endregion


################################################################################
## 						RESOURCE LIST
################################################################################


func _on_editors_resource_opened(_resource: Resource) -> void:
	update_resource_list()


func _on_editors_editor_changed(_previous: DialogicEditor, current: DialogicEditor) -> void:
	%ContentListSection.visible = current.current_resource is DialogicTimeline
	update_resource_list()


## Cleans resources that have been deleted from the resource list
func clean_resource_list(resources_list: Array = []) -> PackedStringArray:
	return PackedStringArray(resources_list.filter(func(x): return ResourceLoader.exists(x)))


#region BULDING/FILTERING THE RESOURCE LIST

func update_resource_list(resources_list: PackedStringArray = []) -> void:
	var filter: String = %Search.text
	var current_file := ""
	if editors_manager.current_editor and editors_manager.current_editor.current_resource:
		current_file = editors_manager.current_editor.current_resource.resource_path

	var character_directory: Dictionary = DialogicResourceUtil.get_character_directory()
	var timeline_directory: Dictionary = DialogicResourceUtil.get_timeline_directory()
	if resources_list.is_empty():
		resources_list = DialogicUtil.get_editor_setting("last_resources", [])
		if not current_file in resources_list:
			resources_list.append(current_file)

	resources_list = clean_resource_list(resources_list)

	%CurrentResource.text = "No Resource"
	%CurrentResource.add_theme_color_override(
		"font_uneditable_color", get_theme_color("disabled_font_color", "Editor")
	)

	resource_tree.clear()

	var character_items: Array = get_directory_items.call(character_directory, filter, load("res://addons/dialogic/Editor/Images/Resources/character.svg"), resources_list)
	var timeline_items: Array = get_directory_items.call(timeline_directory, filter, get_theme_icon("TripleBar", "EditorIcons"), resources_list)
	var all_items := character_items + timeline_items

	# BUILD TREE
	var root: TreeItem = resource_tree.create_item()

	match group_mode:
		GroupMode.NONE:
			all_items.sort_custom(_sort_by_item_text)
			for item in all_items:
				add_item(item, root, current_file)


		GroupMode.TYPE:
			character_items.sort_custom(_sort_by_item_text)
			timeline_items.sort_custom(_sort_by_item_text)
			if character_items.size() > 0:
				var character_tree := add_folder_item("Characters", root)
				for item in character_items:
					add_item(item, character_tree, current_file)

			if timeline_items.size() > 0:
				var timeline_tree := add_folder_item("Timelines", root)
				for item in timeline_items:
					add_item(item, timeline_tree, current_file)


		GroupMode.FOLDER:
			var dirs := {}
			for item in all_items:
				var dir := item.get_parent_directory() as String
				if not dirs.has(dir):
					dirs[dir] = []
				dirs[dir].append(item)

			for dir in dirs:
				var dir_item := add_folder_item(dir, root)

				for item in dirs[dir]:
					add_item(item, dir_item, current_file)


		GroupMode.PATH:
			# Collect all different directories that contain resources
			var dirs := {}
			for item in all_items:
				var path := (item.metadata.get_base_dir() as String).trim_prefix("res://")
				if not dirs.has(path):
					dirs[path] = []
				dirs[path].append(item)

			# Sort them into ones with the same folder name
			var dir_names := {}
			for dir in dirs:
				var sliced: String = dir.get_slice("/", dir.get_slice_count("/")-1)
				if not sliced in dir_names:
					dir_names[sliced] = {"folders":[dir]}
				else:
					dir_names[sliced].folders.append(dir)

			# Create a dictionary mapping a unique name to each directory
			# If two have been found to have the same folder name, the parent directory is added
			var unique_folder_names := {}
			for dir_name in dir_names:
				if dir_names[dir_name].folders.size() > 1:
					for i in dir_names[dir_name].folders:
						if "/" in i:
							unique_folder_names[i.get_slice("/", i.get_slice_count("/")-2)+"/"+i.get_slice("/", i.get_slice_count("/")-1)] = i
						else:
							unique_folder_names[i] = i
				else:
					unique_folder_names[dir_name] = dir_names[dir_name].folders[0]

			# Sort the folder names by their folder name (not by the full path)
			var sorted_dir_keys := unique_folder_names.keys()
			sorted_dir_keys.sort_custom(
				func(x, y):
					return x.get_slice("/", x.get_slice_count("/")-1) < y.get_slice("/", y.get_slice_count("/")-1)
					)
			var folder_colors: Dictionary = ProjectSettings.get_setting("file_customization/folder_colors", {})

			for dir in sorted_dir_keys:
				var display_name: String = dir
				if not %TrimFolderPaths.button_pressed:
					display_name = unique_folder_names[dir]
				var dir_path: String = unique_folder_names[dir]
				var dir_color_path := ""
				var dir_color := Color.BLACK
				if %FolderColors.button_pressed:
					for path in folder_colors:
						if String("res://"+dir_path+"/").begins_with(path) and len(path) > len(dir_color_path):
							dir_color_path = path
							dir_color = folder_colors[path]

				var dir_item := add_folder_item(display_name, root, dir_color, dir_path)

				for item in dirs[dir_path]:
					add_item(item, dir_item, current_file)


	if %CurrentResource.text != "No Resource":
		%CurrentResource.add_theme_color_override(
			"font_uneditable_color", get_theme_color("font_color", "Editor")
		)

	DialogicUtil.set_editor_setting("last_resources", resources_list)


func add_item(item:ResourceListItem, parent:TreeItem, current_file := "") -> TreeItem:
	var tree_item := resource_tree.create_item(parent)
	tree_item.set_text(0, item.text)
	tree_item.set_icon(0, item.icon)
	tree_item.set_metadata(0, item.metadata)
	tree_item.set_tooltip_text(0, item.tooltip)

	if item.metadata == current_file:
		%CurrentResource.text = item.metadata.get_file()
		resource_tree.set_selected(tree_item, 0)

	var bg_color := parent.get_custom_bg_color(0)
	if bg_color != get_theme_color("base_color", "Editor"):
		bg_color.a = 0.1
		tree_item.set_custom_bg_color(0, bg_color)

	return tree_item


func add_folder_item(label: String, parent:TreeItem, color:= Color.BLACK, tooltip:="") -> TreeItem:
	var folder_item := resource_tree.create_item(parent)
	folder_item.set_text(0, label)
	folder_item.set_icon(0, get_theme_icon("Folder", "EditorIcons"))
	folder_item.set_tooltip_text(0, tooltip)
	if color == Color.BLACK:
		folder_item.set_custom_bg_color(0, get_theme_color("base_color", "Editor"))
	else:
		color.a = 0.2
		folder_item.set_custom_bg_color(0, color)

	if label in DialogicUtil.get_editor_setting("resource_list_collapsed_info", []):
		folder_item.collapsed = true

	return folder_item


func get_directory_items(directory:Dictionary, filter:String, icon:Texture2D, resources_list:Array) -> Array:
	var items := []
	for item_name in directory:
		if (directory[item_name] in resources_list) and (filter.is_empty() or filter.to_lower() in item_name.to_lower()):
			var item := ResourceListItem.new()
			item.text = item_name
			item.icon = icon
			item.metadata = directory[item_name]
			item.tooltip = directory[item_name]
			items.append(item)
	return items


class ResourceListItem:
	extends Object

	var text: String
	var index: int = -1
	var icon: Texture
	var metadata: String
	var tooltip: String

	func _to_string() -> String:
		return JSON.stringify(
			{
				"text": text,
				"index": index,
				"icon": icon.resource_path,
				"metadata": metadata,
				"tooltip": tooltip,
				"parent_dir": get_parent_directory()
			},
			"\t",
			false
		)

	func get_parent_directory() -> String:
		return (metadata.get_base_dir() as String).split("/")[-1]


func _sort_by_item_text(a: ResourceListItem, b: ResourceListItem) -> bool:
	return a.text < b.text

#endregion


#region INTERACTING WITH RESOURCES


func _on_resources_tree_item_activated() -> void:
	if resource_tree.get_selected() == null:
		return
	var item := resource_tree.get_selected()
	if item.get_metadata(0) == null:
		return
	edit_resource(item.get_metadata(0))


func _on_resources_tree_item_clicked(_pos: Vector2, mouse_button_index: int) -> void:
	match mouse_button_index:
		MOUSE_BUTTON_LEFT:
			var selected_item := resource_tree.get_selected()
			if selected_item == null:
				return
			if selected_item.get_metadata(0) == null:
				return
			var resource_item := load(selected_item.get_metadata(0))
			call_deferred("edit_resource", resource_item)

		MOUSE_BUTTON_MIDDLE:
			remove_item_from_list(resource_tree.get_selected())

		MOUSE_BUTTON_RIGHT:
			if resource_tree.get_selected().get_metadata(0):
				%RightClickMenu.popup_on_parent(Rect2(get_global_mouse_position(), Vector2()))
				%RightClickMenu.set_meta("item_clicked", resource_tree.get_selected())


func _on_resources_tree_item_collapsed(item:TreeItem) -> void:
	var collapsed_info := DialogicUtil.get_editor_setting("resource_list_collapsed_info", [])
	if item.get_text(0) in collapsed_info:
		if not item.collapsed:
			collapsed_info.erase(item.get_text(0))
	else:
		if item.collapsed:
			collapsed_info.append(item.get_text(0))
	DialogicUtil.set_editor_setting("resource_list_collapsed_info", collapsed_info)


func edit_resource(resource_item: Variant) -> void:
	if resource_item is Resource:
		editors_manager.edit_resource(resource_item)
	else:
		editors_manager.edit_resource(load(resource_item))


func remove_item_from_list(item: TreeItem) -> void:
	var new_list := []
	for entry in DialogicUtil.get_editor_setting("last_resources", []):
		if entry != item.get_metadata(0):
			new_list.append(entry)
	DialogicUtil.set_editor_setting("last_resources", new_list)
	update_resource_list(new_list)


func _on_right_click_menu_id_pressed(id: int) -> void:
	match id:
		1:  # REMOVE ITEM FROM LIST
			remove_item_from_list(%RightClickMenu.get_meta("item_clicked"))
		2:  # OPEN IN FILESYSTEM
			EditorInterface.get_file_system_dock().navigate_to_path(
				%RightClickMenu.get_meta("item_clicked").get_metadata(0)
			)
		3:  # OPEN IN EXTERNAL EDITOR
			OS.shell_open(
				ProjectSettings.globalize_path(
					%RightClickMenu.get_meta("item_clicked").get_metadata(0)
				)
			)
		4:  # COPY IDENTIFIER
			DisplayServer.clipboard_set(
				DialogicResourceUtil.get_unique_identifier(
					%RightClickMenu.get_meta("item_clicked").get_metadata(0)
				)
			)
#endregion


#region FILTERING

func _on_search_text_changed(_new_text: String) -> void:
	update_resource_list()
	for item in resource_tree.get_root().get_children():
		if item.get_children().size() > 0:
			resource_tree.set_selected(item.get_child(0), 0)
			break


func _on_search_text_submitted(_new_text: String) -> void:
	if resource_tree.get_selected() == null:
		return
	var item := resource_tree.get_selected()
	if item.get_metadata(0) == null:
		return
	edit_resource(item.get_metadata(0))
	%Search.clear()

#endregion


#region CONTENT LIST

func update_content_list(list: PackedStringArray) -> void:
	var prev_selected := ""
	if %ContentList.is_anything_selected():
		prev_selected = %ContentList.get_item_text(%ContentList.get_selected_items()[0])
	%ContentList.clear()
	%ContentList.add_item("~ Top")
	for i in list:
		if i.is_empty():
			continue
		%ContentList.add_item(i)
		if i == prev_selected:
			%ContentList.select(%ContentList.item_count - 1)
	if list.is_empty():
		return

	var current_resource: Resource = editors_manager.get_current_editor().current_resource

	var timeline_directory := DialogicResourceUtil.get_timeline_directory()
	var label_directory := DialogicResourceUtil.get_label_cache()
	if current_resource != null:
		for i in timeline_directory:
			if timeline_directory[i] == current_resource.resource_path:
				label_directory[i] = list

	# also always store the current timelines labels for easy access
	label_directory[""] = list

	DialogicResourceUtil.set_label_cache(label_directory)

#endregion


#region RESOURCE LIST OPTIONS

func _on_options_pressed() -> void:
	%OptionsPopup.popup_on_parent(Rect2(%Options.global_position+%Options.size*Vector2(0,1), Vector2()))


func _on_grouping_changed(idx: int) -> void:
	var id: int = %GroupingOptions.get_item_id(idx)
	if (GroupMode as Dictionary).values().has(id):
		group_mode = (id as GroupMode)
		DialogicUtil.set_editor_setting("sidebar_group_mode", id)
		update_resource_list()

	%FolderColors.disabled = group_mode != GroupMode.PATH
	%TrimFolderPaths.disabled = group_mode != GroupMode.PATH


func _on_folder_colors_toggled(toggled_on: bool) -> void:
	DialogicUtil.set_editor_setting("sidebar_use_folder_colors", toggled_on)
	update_resource_list()


func _on_trim_folder_paths_toggled(toggled_on: bool) -> void:
	DialogicUtil.set_editor_setting("sidebar_trim_folder_paths", toggled_on)
	update_resource_list()

#endregion


func _on_main_v_split_dragged(offset: int) -> void:
	DialogicUtil.set_editor_setting("sidebar_v_split", offset)
