@tool
extends CodeEdit


enum CompletionKind {
	IGNORE,
	FORMATTING,
	COLOR,
	COMMAND,
	CLASS_REFERENCE,
	REFERENCE_START,
	REFERENCE_END,
	REFERENCING_TAG,
	ANNOTATION,
}


const Completions = preload("res://addons/bbcode_edit.editor/completions_db/completions.gd")
const Scraper = preload("res://addons/bbcode_edit.editor/editor_interface_scraper.gd")

const ACTION_TOGGLE_BOLD = &"bbcode_edit/toggle_bold"
const ACTION_TOGGLE_ITALIC = &"bbcode_edit/toggle_italic"
const ACTION_TOGGLE_UNDERLINE = &"bbcode_edit/toggle_underline"
const ACTION_TOGGLE_STRIKE = &"bbcode_edit/toggle_strike"

const TOGGLING_ACTIONS = {
	ACTION_TOGGLE_BOLD: "b",
	ACTION_TOGGLE_ITALIC: "i",
	ACTION_TOGGLE_UNDERLINE: "u",
	ACTION_TOGGLE_STRIKE: "s",
}

const BBCODE_COMPLETION_ICON = preload("res://addons/bbcode_edit.editor/bbcode_completion_icon.svg")
const COLOR_PICKER_CONTAINER_PATH = ^"_BBCodeEditColorPicker"
const COLOR_PICKER_PATH = ^"_BBCodeEditColorPicker/ColorPicker"

const MALFORMED = "MALFORMED"
const COMMAND_PREFIX_CHAR = "\u0001"
const ORDER_PREFIX = "\ufffe"
const CLASS_REFERENCE_PREFIX_CHAR = "\uffff"
const REFERENCE_START_SUFFIX_CHAR = "\uffff"
const REFERENCE_END_SUFFIX_CHAR = "\ufffe"
const ANNOTATION_SUFFIX_CHAR = "\ufff0"
const _COMMAND_COLOR_PICKER = "color_picker"

const CLASS_DOC_ENDERS: Array[String] = [
	"##",
	"signal",
	"enum",
	"const",
	"@export",
	"var",
	"@onready",
	"static",
	"func",
	"class ",
]

static var REGEX_PARENTHESES = RegEx.create_from_string(r"\(([^)]+)\)")


func _init() -> void:
	set_process_input(true)
	if has_meta(&"initialized"):
		return
	code_completion_requested.connect(add_completion_options)
	text_changed.connect(_on_text_changed)
	code_completion_prefixes += ["["] # Use assignation because append don't work
	set_meta(&"initialized", true)


func add_completion_options() -> void:
	#print("Code completion options requested")
	var line_i: int = get_caret_line()
	var line: String = get_line(line_i)
	var column_i: int = get_caret_column()
	var comment_i: int = is_in_comment(line_i, column_i)
	
	if comment_i == -1 or get_delimiter_start_key(comment_i) != "##":
		if line[column_i-1] == "[":
			#print_rich("[color=red]Emergency cancel[/color]")
			cancel_code_completion()
		#print_rich("[color=red]not in bbcode completion[/color]")
		return
	#print_rich("[color=red]in bbcode completion[/color]")
	
	var to_test: String = trim_doc_comment_start(line.left(column_i))
	var line_only: String = to_test
	
	if line_only[0] == "@" and not (
		line_only.begins_with("@tutorial: ")
		or line_only.begins_with("@deprecated: ")
		or line_only.begins_with("@experimental: ")
	):
		add_code_completion_option(
			CodeEdit.KIND_PLAIN_TEXT,
			"@deprecated" + ANNOTATION_SUFFIX_CHAR,
			"deprecated\n## ",
			get_theme_color(&"font-color"),
			Scraper.get_icon(&"StatusError"),
		)
		add_code_completion_option(
			CodeEdit.KIND_PLAIN_TEXT,
			"@deprecated: Some explaination" + ANNOTATION_SUFFIX_CHAR,
			"deprecated: ",
			get_theme_color(&"font-color"),
			Scraper.get_icon(&"StatusError"),
		)
		add_code_completion_option(
			CodeEdit.KIND_PLAIN_TEXT,
			"@experimental" + ANNOTATION_SUFFIX_CHAR,
			"experimental\n## ",
			get_theme_color(&"font-color"),
			Scraper.get_icon(&"NodeWarning"),
		)
		add_code_completion_option(
			CodeEdit.KIND_PLAIN_TEXT,
			"@experimental: Some explaination" + ANNOTATION_SUFFIX_CHAR,
			"experimental: ",
			get_theme_color(&"font-color"),
			Scraper.get_icon(&"NodeWarning"),
		)
		
		var class_comment_end_line: int = 0
		while not _is_line_class_doc_ender(get_line(class_comment_end_line)):
			class_comment_end_line += 1
		while get_line(class_comment_end_line).begins_with("##"):
			class_comment_end_line += 1
		if _is_line_class_doc_ender(get_line(class_comment_end_line)):
			class_comment_end_line = -1
		
		if line_i < class_comment_end_line:
			add_code_completion_option(
				CodeEdit.KIND_PLAIN_TEXT,
				"@tutorial: https://example.com" + ANNOTATION_SUFFIX_CHAR,
				"tutorial: https://",
				get_theme_color(&"font-color"),
				Scraper.get_icon(&"ExternalLink"),
			)
			add_code_completion_option(
				CodeEdit.KIND_PLAIN_TEXT,
				"@tutorial(Title): https://example.com" + ANNOTATION_SUFFIX_CHAR,
				"tutorial(|): https://",
				get_theme_color(&"font-color"),
				Scraper.get_icon(&"ExternalLink"),
			)
		
		update_code_completion_options(true)
		return
	
	var prev_line_i: int = line_i - 1
	var prev_line: String = get_line(prev_line_i).strip_edges(true, false)
	while prev_line.begins_with("##"):
		to_test = prev_line.trim_prefix("##").strip_edges() + " " + to_test
		prev_line_i -= 1
		prev_line = get_line(prev_line_i).strip_edges(true, false)
	
	to_test = to_test.split("]")[-1]#.split("=")[-1]
	#print_rich("to_test:[color=magenta][code] ", to_test)
	
	if "[" not in to_test:
		#print("No BRACKET")
		update_code_completion_options(true)
		return
	
	var describes_i: int = line_i
	while is_in_comment(describes_i) != -1:
		describes_i += 1
	var describes: String = get_line(describes_i)
	
	if check_parameter_completions(to_test, describes_i, describes):
		return
	
	var font_color: Color = get_theme_color(&"font_color")
	
	if line_only[0] == "[":
		add_code_completion_option(
			CodeEdit.KIND_PLAIN_TEXT,
			"Note:",
			"b]Note:[/b] ",
			font_color,
			Scraper.get_icon(&"TextMesh"),
		)
		add_code_completion_option(
			CodeEdit.KIND_PLAIN_TEXT,
			"Warning:",
			"b]Warning:[/b] ",
			font_color,
			Scraper.get_icon(&"TextMesh"),
		)
		add_code_completion_option(
			CodeEdit.KIND_PLAIN_TEXT,
			"Example:",
			"b]Example:[/b] ",
			font_color,
			Scraper.get_icon(&"TextMesh"),
		)
	
	# TODO only propose valid tags
	var completions: Array[String] = (
		Completions.TAGS_UNIVERSAL
		+ Completions.TAGS_DOC_COMMENT_FORMATTING
		# TODO MAYBE: I have to refactor everything related to tag availability.
		#+ Completions.TAGS_RICH_TEXT_LABEL
	)
	var displays: Array[String] = []
	displays.assign(completions.map(_bracket))
	
	#print("First completion is: ", completions[0])
	
	for i in completions.size():
		add_code_completion_option(
			CodeEdit.KIND_PLAIN_TEXT,
			displays[i].replace("|", ""),
			completions[i],
			font_color,
			BBCODE_COMPLETION_ICON,
		)
	
	var reference_completions: Array[String] = Completions.TAGS_DOC_COMMENT_REFERENCE
	var reference_displays: Array[String] = []
	for completion in reference_completions:
		reference_displays.append(_bracket(completion.trim_suffix("|") + "Class.name"))
	
	var reference_icon: Texture2D = Scraper.get_reference_icon()
	for i in reference_completions.size():
		add_code_completion_option(
			CodeEdit.KIND_PLAIN_TEXT,
			reference_displays[i].replace("|", ""),
			reference_completions[i],
			font_color,
			reference_icon,
		)
	
	if describes.begins_with("func "):
		add_code_completion_option(
			CodeEdit.KIND_PLAIN_TEXT,
			"[param name]",
			"param |",
			font_color,
			reference_icon,
		)
	
	var class_completions := Completions.get_class_completions()
	for i in len(class_completions.names):
		var name_: String = class_completions.names[i]
		add_code_completion_option(
			CodeEdit.KIND_CLASS,
			CLASS_REFERENCE_PREFIX_CHAR + "[" + name_ + "]",
			name_ + "||",
			font_color,
			class_completions.icons[i],
		)
	
	update_code_completion_options(true) # NEEDED so that `[` triggers popup


func _is_line_class_doc_ender(line: String) -> bool:
	for doc_ender in CLASS_DOC_ENDERS:
		if line.begins_with(doc_ender):
			return true
	return false


func _bracket(string: String) -> String:
	return "[" + string + "]"


func trim_doc_comment_start(line: String) -> String:
	return line.strip_edges(true, false).trim_prefix("##").strip_edges(true, false)


func check_parameter_completions(to_test: String, describes_i: int, describes: String) -> bool:
	to_test = to_test.split("[")[-1]
	var parts: PackedStringArray = to_test.split(" ", false)
	
	var parameters: PackedStringArray = PackedStringArray()
	var values: PackedStringArray = PackedStringArray()
	for part in parts:
		# TODO MAYBE impleement sub parameter handling ? (e.g. [font otv="wght=200,wdth=400"])
		var split: PackedStringArray = part.split("=", 1)
		parameters.append(split[0])
		values.append(split[1] if split.size() == 2 else MALFORMED)
	
	#print_rich("Parameters:[color=magenta] ", parameters)
	#print_rich("Values:[color=magenta] ", values)
	
	if parameters.is_empty():
		return false
	
	if parameters.size() == 1 and values[0] != "MALFORMED":
		var value: String = values[0]
		match parameters[0]:
			"color":
				if value.begins_with(HEX_PREFIX) and value.substr(HEX_PREFIX.length()).is_valid_html_color():
					add_hex_color(value.substr(HEX_PREFIX.length()), true)
				elif value.is_valid_html_color():
					if value.is_valid_int():
						insert_text(HEX_PREFIX, get_caret_line(), get_caret_column()-value.length())
						request_code_completion.call_deferred(true)
					add_hex_color(value)
					
				add_color_completions(value.length())
				return true
	
	match parameters[0]:
		"param":
			if not describes.begins_with("func "):
				return false
			
			if ")" not in describes:
				var next_line_i: int = describes_i + 1
				var next_line: String = get_line(next_line_i)
				while ")" not in next_line:
					describes += next_line
					next_line_i += 1
					next_line = get_line(next_line_i)
				describes += next_line
			#print_rich("Describes: [color=purple][code]", describes)
			
			for part in (
				REGEX_PARENTHESES.search(describes).get_string().trim_prefix("(").trim_suffix(")").split(",")
			):
				var param_parts := part.split(":", true, 1)
				var parameter: String = param_parts[0].strip_edges()
				
				add_code_completion_option(
					CodeEdit.KIND_PLAIN_TEXT,
					parameter + REFERENCE_END_SUFFIX_CHAR,
					parameter + "||",
					get_theme_color(&"font_color"),
					Scraper.get_icon(&"Variant")
					if param_parts.size() == 1 else
					Scraper.try_get_icon(param_parts[1].split("=", true, 1)[0].strip_edges(), &"Variant")
				)
			
			update_code_completion_options(true)
			return true
		"member":
			if parameters.size() >= 2:
				var path: PackedStringArray = parameters[1].split(".")
				if path.size() >= 2:
					if ClassDB.class_exists(path[0]):
						add_member_completion_from_class_name(path[0])
					else:
						for other_class_ in ProjectSettings.get_global_class_list():
							if other_class_["class"] == path[0]:
								add_member_completion_from_script(load(other_class_["path"]))
								break
					update_code_completion_options(true)
					return true
			add_member_completion_from_script(EditorInterface.get_script_editor().get_current_script())
			add_classes_completion()
			return true
		"method":
			if parameters.size() >= 2:
				var path: PackedStringArray = parameters[1].split(".")
				if path.size() >= 2:
					if ClassDB.class_exists(path[0]):
						add_method_completion_from_class_name(path[0])
					else:
						for other_class_ in ProjectSettings.get_global_class_list():
							if other_class_["class"] == path[0]:
								add_method_completion_from_script(load(other_class_["path"]))
								break
					update_code_completion_options(true)
					return true
			add_method_completion_from_script(EditorInterface.get_script_editor().get_current_script())
			add_classes_completion()
			return true
		"constant":
			if parameters.size() >= 2:
				var path: PackedStringArray = parameters[1].split(".")
				if path.size() >= 2:
					if ClassDB.class_exists(path[0]):
						add_constant_completion_from_class_name(path[0])
					else:
						for other_class_ in ProjectSettings.get_global_class_list():
							if other_class_["class"] == path[0]:
								add_constant_completion_from_script(load(other_class_["path"]))
								break
					update_code_completion_options(true)
					return true
			add_constant_completion_from_script(EditorInterface.get_script_editor().get_current_script())
			add_classes_completion()
			return true
		"signal":
			if parameters.size() >= 2:
				var path: PackedStringArray = parameters[1].split(".")
				if path.size() >= 2:
					if ClassDB.class_exists(path[0]):
						add_signal_completion_from_class_name(path[0])
					else:
						for other_class_ in ProjectSettings.get_global_class_list():
							if other_class_["class"] == path[0]:
								add_signal_completion_from_script(load(other_class_["path"]))
								break
					update_code_completion_options(true)
					return true
			add_signal_completion_from_script(EditorInterface.get_script_editor().get_current_script())
			add_classes_completion()
			return true
		"enum":
			if parameters.size() >= 2:
				var path: PackedStringArray = parameters[1].split(".")
				if path.size() >= 2:
					if ClassDB.class_exists(path[0]):
						add_enum_completion_from_class_name(path[0])
					else:
						for other_class_ in ProjectSettings.get_global_class_list():
							if other_class_["class"] == path[0]:
								add_enum_completion_from_script(load(other_class_["path"]))
								break
					update_code_completion_options(true)
					return true
			add_enum_completion_from_script(EditorInterface.get_script_editor().get_current_script())
			add_classes_completion()
			return true
	
	return false


func substr_clamped_start(string: String, from: int, len: int) -> String:
	if from < 0:
		if len != -1:
			len += from
			if len < 0:
				len = 0
		from = 0
	
	return string.substr(from, len)


const HEX_PREFIX = "0x"
func add_hex_color(hex: String, include_prefix: bool = false) -> void:
	print_rich("Valid color: ", hex, " [color=", hex, "]██████")
	add_code_completion_option(
		CodeEdit.KIND_PLAIN_TEXT,
		HEX_PREFIX + hex + " ",
		HEX_PREFIX + hex if include_prefix else hex,
		get_theme_color(&"font_color"),
		Scraper.get_color_icon(),
		Color.html(hex),
	)


func add_color_completions(chars_typed: int) -> void:
	var icon = Scraper.get_color_icon()
	for color in Completions.COLORS:
		add_code_completion_option(
			CodeEdit.KIND_PLAIN_TEXT,
			color,
			color,
			get_theme_color(&"font_color"),
			icon,
			Color.from_string(color, Color.RED),
		)
	
	add_code_completion_option(
		CodeEdit.KIND_PLAIN_TEXT,
		COMMAND_PREFIX_CHAR + "Bring color picker",
		_COMMAND_COLOR_PICKER + "," + str(chars_typed),
		get_theme_color(&"font_color"),
		EditorInterface.get_base_control().get_theme_icon("ColorPicker", "EditorIcons"),
	)
	update_code_completion_options(true)


## Add completion for classes WITH SUBSCRIPT (aka it will add a dot at the end)
func add_classes_completion() -> void:
	var class_completions := Completions.get_class_completions()
	for i in len(class_completions.names):
		var name_: String = class_completions.names[i]
		add_code_completion_option(
			CodeEdit.KIND_CLASS,
			ORDER_PREFIX + name_ + "." + REFERENCE_START_SUFFIX_CHAR,
			name_ + ".",
			get_theme_color(&"font_color"),
			class_completions.icons[i],
		)
	update_code_completion_options(true)


func add_member_completion_from_script(class_: Script) -> void:
	add_members(class_.get_script_property_list())
	# Don't show inherited things, because the reference won't work.
	#add_member_completion_from_class_name(class_.get_instance_base_type())



func add_member_completion_from_class_name(class_: StringName) -> void:
	add_members(ClassDB.class_get_property_list(class_, true))


func add_members(members: Array[Dictionary]) -> void:
	for member in members:
		if member["usage"] & (
			PROPERTY_USAGE_INTERNAL
			| PROPERTY_USAGE_CATEGORY
			| PROPERTY_USAGE_GROUP
			| PROPERTY_USAGE_SUBGROUP
		):
			continue
		
		add_code_completion_option(
			CodeEdit.KIND_MEMBER,
			member["name"] + REFERENCE_END_SUFFIX_CHAR,
			member["name"] + "||",
			get_theme_color(&"font-color"),
			get_icon_for_member(member),
		)


func get_icon_for_member(member: Dictionary, fallback: StringName = &"MemberProperty") -> Texture2D:
	if member["type"] == TYPE_OBJECT:
		return Scraper.get_class_icon(member["class_name"], fallback)
	
	return Scraper.get_builtin_type_icon(member["type"], fallback)


func add_method_completion_from_script(class_: Script) -> void:
	add_methods(class_.get_method_list())
	# Don't show inherited things, because the reference won't work.
	#add_method_completion_from_class_name(class_.get_instance_base_type())


func add_method_completion_from_class_name(class_: StringName) -> void:
	add_methods(ClassDB.class_get_method_list(class_, true))


func add_methods(methods: Array[Dictionary]) -> void:
	for method in methods:
		add_code_completion_option(
			CodeEdit.KIND_FUNCTION,
			method["name"] + REFERENCE_END_SUFFIX_CHAR,
			method["name"] + "||",
			get_theme_color(&"font-color"),
			get_icon_for_method(method),
		)


func get_icon_for_method(method: Dictionary) -> Texture2D:
	var returned: Dictionary = method["return"]
	
	if returned["type"] == TYPE_NIL:
		return Scraper.get_icon(&"MemberMethod")
	
	return get_icon_for_member(returned, &"Function")


func add_constant_completion_from_script(class_: Script) -> void:
	add_constants(class_.get_script_constant_map())
	# Don't show inherited things, because the reference won't work.
	#add_constant_completion_from_class_name(class_.get_instance_base_type())


func add_constant_completion_from_class_name(class_: StringName) -> void:
	for constant_name in ClassDB.class_get_integer_constant_list(class_, true):
		add_constants({constant_name: ClassDB.class_get_integer_constant(class_, constant_name)})


func add_constants(constants: Dictionary) -> void:
	for constant in constants:
		var value: Variant = constants[constant]
		var type: int = typeof(value)
		
		if type == TYPE_OBJECT and value is Script and value.resource_path.is_empty():
			# Inner classes don't work in class
			continue
		
		var display_text: String = constant
		if type != TYPE_COLOR:
			var repr: String = var_to_str(value)
			if repr.is_empty():
				if type == TYPE_OBJECT:
					repr = value.to_string()
				else:
					repr = str(repr)
			if repr.length() > 32:
				repr = repr.left(32) + "..."
			display_text += " (" + repr + ")"
		display_text += REFERENCE_END_SUFFIX_CHAR
		
		add_code_completion_option(
			CodeEdit.KIND_CONSTANT,
			display_text,
			constant + "||",
			get_theme_color(&"font-color"),
			Scraper.get_type_icon(value, &"MemberConstant"),
			value
		)


func add_signal_completion_from_script(class_: Script) -> void:
	add_signals(class_.get_script_signal_list())
	# Don't show inherited things, because the reference won't work.
	#add_signal_completion_from_class_name(class_.get_instance_base_type())


func add_signal_completion_from_class_name(class_: StringName) -> void:
	add_signals(ClassDB.class_get_signal_list(class_, true))


func add_signals(signals: Array[Dictionary]) -> void:
	var icon: Texture2D = Scraper.get_icon(&"MemberSignal")
	for signal_ in signals:
		add_code_completion_option(
			CodeEdit.KIND_SIGNAL,
			signal_["name"] + REFERENCE_END_SUFFIX_CHAR,
			signal_["name"] + "||",
			get_theme_color(&"font-color"),
			icon,
		)


func add_enum_completion_from_script(class_: Script) -> void:
	var map := class_.get_script_constant_map()
	var probable_enums: PackedStringArray
	
	for constant in map:
		if typeof(map[constant]) == TYPE_DICTIONARY:
			var candidate: Dictionary = map[constant]
			if candidate.values().all(_is_int):
				probable_enums.append(constant)
	
	if probable_enums:
		add_enums(probable_enums)
	
	# Don't show inherited things, because the reference won't work.
	#add_enum_completion_from_class_name(class_.get_instance_base_type())


static func _is_int(value: Variant) -> bool:
	return typeof(value) == TYPE_INT


func add_enum_completion_from_class_name(class_: StringName) -> void:
	add_enums(ClassDB.class_get_enum_list(class_, true))


func add_enums(enums: PackedStringArray) -> void:
	var icon: Texture2D = Scraper.get_icon(&"Enum")
	for enum_ in enums:
		add_code_completion_option(
			CodeEdit.KIND_ENUM,
			enum_ + REFERENCE_END_SUFFIX_CHAR,
			enum_ + "||",
			get_theme_color(&"font-color"),
			icon,
		)


func toggle_tag(tag: String) -> void:
	var prefix: String = "[" + tag + "]"
	var prefix_len: int = prefix.length()
	var suffix: String = "[/" + tag + "]"
	var suffix_len: int = suffix.length()
	
	var main_selection_from_column: int = get_selection_from_column()
	var main_selection_from_line: int = get_selection_from_line()
	var main_selection_to_column: int = get_selection_to_column()
	var main_selection_to_line: int = get_selection_to_line()
	var main_selection_end_line: String = get_line(main_selection_to_line)
	
	if (
		main_selection_from_column > prefix_len
		and get_line(main_selection_from_line).substr(
			main_selection_from_column - prefix_len,
			prefix_len
		) == prefix
		and main_selection_to_column <= main_selection_end_line.length() - suffix_len
		and main_selection_end_line.substr(
			main_selection_to_column,
			suffix_len
		) == suffix
	):
		begin_complex_operation()
		begin_multicaret_edit()
		
		for caret in get_caret_count():
			if multicaret_edit_ignore_caret(caret):
				continue
			
			var initial_text: String = get_selected_text(caret)
			var initial_start_column: int = get_selection_from_column(caret)
			var initial_end_column: int = get_selection_to_column(caret)
			
			select(
				get_selection_from_line(caret),
				initial_start_column - prefix_len,
				get_selection_to_line(caret),
				initial_end_column + suffix_len,
				caret
			)
			insert_text_at_caret(initial_text, caret)
			select(
				get_selection_from_line(caret),
				initial_start_column - prefix_len,
				get_selection_to_line(caret),
				initial_end_column - prefix_len,
				caret
			)
		
		end_multicaret_edit()
		end_complex_operation()
		return
	
	begin_complex_operation()
	begin_multicaret_edit()
	
	for caret in get_caret_count():
		if multicaret_edit_ignore_caret(caret):
			continue
		
		var initial_start_column: int = get_selection_from_column(caret)
		var initial_end_column: int = get_selection_to_column(caret)
		
		insert_text_at_caret(prefix + get_selected_text(caret) + suffix, caret)
		
		select(
			get_selection_from_line(caret),
			initial_start_column + prefix_len,
			get_selection_to_line(caret),
			initial_end_column + prefix_len,
			caret
		)
	
	end_multicaret_edit()
	end_complex_operation()


func _confirm_code_completion(replace: bool = false) -> void:
	var selected_completion: Dictionary = get_code_completion_option(get_code_completion_selected_index())
	var display_text: String = selected_completion["display_text"]
	var prefix: String = display_text[0]
	
	if prefix == COMMAND_PREFIX_CHAR:
		var parts: PackedStringArray = selected_completion["insert_text"].split(",")
		var chars_to_remove: int = int(parts[1]) if parts.size() >= 2 else 0
		if chars_to_remove:
			for caret in get_caret_count():
				var line_i: int = get_caret_line(caret)
				var line: String = get_line(line_i)
				var column_i: int = get_caret_column(caret)
				set_line(line_i, line.left(column_i - chars_to_remove) + line.substr(column_i))
				set_caret_column(column_i - chars_to_remove, false, caret)
		
		match parts[0]:
			_COMMAND_COLOR_PICKER:
				if not has_node(^"BBCODE_EDIT_COLOR_PICKER"):
					add_child(preload("res://addons/bbcode_edit.editor/color_picker.tscn").instantiate())
				var container: PopupPanel = get_node(COLOR_PICKER_CONTAINER_PATH)
				var picker: ColorPicker = get_node(COLOR_PICKER_PATH)
				
				container.position = Vector2(get_pos_at_line_column(get_caret_line(), get_caret_column())) + global_position + Vector2(0, get_line_height())
				container.add_theme_stylebox_override(&"panel", EditorInterface.get_base_control().get_theme_stylebox(&"Content", &"EditorStyles"))
				picker.color_changed.connect(_on_color_picker_color_changed)
		
		cancel_code_completion()
		return
	
	var icon: Texture2D = selected_completion["icon"]
	var suffix: String = display_text[-1]
	var kind: CompletionKind = (
		CompletionKind.FORMATTING if icon == BBCODE_COMPLETION_ICON else
		CompletionKind.COLOR if icon == Scraper.get_color_icon() else
		CompletionKind.CLASS_REFERENCE if prefix == CLASS_REFERENCE_PREFIX_CHAR else
		CompletionKind.REFERENCE_START if suffix == REFERENCE_START_SUFFIX_CHAR else
		CompletionKind.REFERENCE_END if suffix == REFERENCE_END_SUFFIX_CHAR else
		CompletionKind.ANNOTATION if suffix == ANNOTATION_SUFFIX_CHAR else
		CompletionKind.REFERENCING_TAG if icon == Scraper.get_reference_icon() else
		0
	)
	
	#print_rich("Kind is [color=red][code]", CompletionKind.find_key(kind))
	
	begin_complex_operation()
	
	# Don't use the following code, it's a dev crime.
	# Oops, I just did...
	# This code block allows to call the code that is meant to be executed
	# when the virtual method isn't implemented.
	var script: GDScript = get_script()
	set_script(null)
	super.confirm_code_completion(replace)
	set_script(script)
	
	if (
		kind == CompletionKind.FORMATTING
		or kind == CompletionKind.CLASS_REFERENCE
		or kind == CompletionKind.REFERENCING_TAG
	):
		for caret in get_caret_count():
			var line: String = get_line(get_caret_line(caret)) + " " # Add space so that column is in range
			var column: int = get_caret_column(caret)
			if not line[column] == "]":
				insert_text_at_caret("]", caret)
				# Replace caret at it's previous column
				set_caret_column(column, false, caret)
	
	if kind == CompletionKind.COLOR:
		var line_i: int = get_caret_line()
		var line: String = get_line(line_i)
		var column: int = get_caret_column()
		var color_start: int = column - selected_completion["display_text"].length() + 1
		if line.substr(color_start).begins_with(HEX_PREFIX):
			set_line(
				line_i,
				line.left(color_start) + line.substr(color_start + HEX_PREFIX.length())
			)
			set_caret_column(column - HEX_PREFIX.length())
		
		for caret in get_caret_count():
			set_caret_column(get_caret_column(caret) + 1, false, caret) 
	elif kind:
		for caret in get_caret_count():
			var line_i: int = get_caret_line(caret)
			var line: String = get_line(line_i)
			var first_pipe: int = line.find("|")
			if first_pipe == -1: # Just in case
				continue
			var pipe_end: int = first_pipe + 1
			while pipe_end < line.length() and line[pipe_end] == "|":
				pipe_end += 1
			set_line(line_i, line.left(first_pipe) + line.substr(pipe_end))
			set_caret_column(pipe_end-1, false, caret)
	
	end_complex_operation()
	
	if kind == CompletionKind.REFERENCING_TAG:
		var prefixes := code_completion_prefixes
		code_completion_prefixes += [" "]
		request_code_completion()
		code_completion_prefixes = prefixes
	elif kind and kind != CompletionKind.ANNOTATION:
		request_code_completion()


func _gui_input(event: InputEvent) -> void:
	if not event.is_pressed() or event.is_echo():
		return
	
	if has_node(COLOR_PICKER_CONTAINER_PATH):
		if event is InputEventKey or event is InputEventMouseButton:
			get_node(COLOR_PICKER_CONTAINER_PATH).free()
	
	for action in TOGGLING_ACTIONS:
		if is_action(event, action):
			toggle_tag(TOGGLING_ACTIONS[action])


func is_action(event: InputEvent, action: StringName) -> bool:
	return InputMap.has_action(action) and event.is_action(action, true)


func _on_text_changed() -> void:
	var line_i: int = get_caret_line()
	var column_i: int = get_caret_column()
	var line: String = get_line(get_caret_line())
	if (
		is_in_comment(line_i, column_i) == -1
		and is_in_string(line_i, column_i) == -1
		and line
		and line[column_i-1] == "["
	):
		cancel_code_completion() # Prevent completing when typing array fast


func _on_color_picker_color_changed(color: Color) -> void:
	var hex: String = color.to_html(color.a8 != 255)
	
	for caret in get_caret_count():
		var line_i: int = get_caret_line(caret)
		var line: String = get_line(line_i)
		var column_i: int = get_caret_column(caret)
		
		if line[column_i] != "]" and line[column_i] != " ":
			var to_scan: String = line.substr(column_i)
			var end: int = to_scan.find("]")
			
			if end == -1:
				end = to_scan.find(" ")
			else:
				var other: int = to_scan.find(" ")
				if other != -1 and other < end:
					end = other
			
			set_line(line_i, line.left(column_i) + to_scan.substr(end))
		
		insert_text_at_caret(hex, caret)
		set_caret_column(column_i, false, caret)
