## The remapping dialog. 
extends MarginContainer

signal closed(applied_config:GUIDERemappingConfig)

const Utils = preload("../utils.gd")

# Input
@export var keyboard_context:GUIDEMappingContext
@export var controller_context:GUIDEMappingContext
@export var binding_keyboard_context:GUIDEMappingContext
@export var binding_controller_context:GUIDEMappingContext
@export var close_dialog:GUIDEAction
@export var switch_to_controller:GUIDEAction
@export var switch_to_keyboard:GUIDEAction
@export var previous_tab:GUIDEAction
@export var next_tab:GUIDEAction

# UI 
@export var binding_row_scene:PackedScene
@export var binding_section_scene:PackedScene

@onready var _keyboard_bindings:Container = %KeyboardBindings
@onready var _controller_bindings:Container = %ControllerBindings
@onready var _press_prompt:Control = %PressPrompt
@onready var _controller_invert_horizontal:CheckBox = %ControllerInvertHorizontal
@onready var _controller_invert_vertical:CheckBox = %ControllerInvertVertical
@onready var _tab_container:TabContainer = %TabContainer

## The input detector for detecting new input
@onready var _input_detector:GUIDEInputDetector = %GUIDEInputDetector

## The remapper, helps us quickly remap inputs.
var _remapper:GUIDERemapper = GUIDERemapper.new()

## The config we're currently working on
var _remapping_config:GUIDERemappingConfig

## The last control that was focused when we started input detection.
## Used to restore focus afterwards.
var _focused_control:Control = null

func _ready():
	# connect the actions that the remapping dialog uses
	close_dialog.triggered.connect(_on_close_dialog)
	switch_to_controller.triggered.connect(_switch.bind(binding_controller_context))
	switch_to_keyboard.triggered.connect(_switch.bind(binding_keyboard_context))
	previous_tab.triggered.connect(_switch_tab.bind(-1))
	next_tab.triggered.connect(_switch_tab.bind(1))
	

func open():
	# switch the tab to the scheme that is currently enabled
	# to make life a bit easier for the player, and also
	# enable the correct mapping context for the binding dialog
	if GUIDE.is_mapping_context_enabled(controller_context):
		_tab_container.current_tab = 1
		GUIDE.enable_mapping_context(binding_controller_context, true)
	else:
		_tab_container.current_tab = 0
		GUIDE.enable_mapping_context(binding_keyboard_context, true)
		
	# todo provide specific actions for the tab bar controller
	_tab_container.get_tab_bar().grab_focus()
	
	# Open the user's last edited remapping config, if it exists
	_remapping_config = Utils.load_remapping_config()
	
	# And initialize the remapper
	_remapper.initialize([keyboard_context, controller_context], _remapping_config)
	
	_clear(_keyboard_bindings)
	_clear(_controller_bindings)
	
	# fill the keyboard section
	_fill_remappable_items(keyboard_context, _keyboard_bindings)
	
	# fill the controller section
	_fill_remappable_items(controller_context, _controller_bindings)
	
	_controller_invert_horizontal.button_pressed = _remapper.get_custom_data("invert_horizontal", false)
	_controller_invert_vertical.button_pressed = _remapper.get_custom_data("invert_vertical", false)
	
	
	visible = true
	
	
## Fills remappable items and sub-sections into the given container	
func _fill_remappable_items(context:GUIDEMappingContext, root:Container):
	var items := _remapper.get_remappable_items(context)
	var section_name = ""
	for item in items:
		if item.display_category != section_name:
			section_name = item.display_category
			var section = binding_section_scene.instantiate()
			root.add_child(section)
			section.text = section_name
			
		var instance = binding_row_scene.instantiate()
		root.add_child(instance)
		
		# Show the current binding.
		instance.initialize(item, _remapper.get_bound_input_or_null(item))
		instance.rebind.connect(_rebind_item)



func _rebind_item(item:GUIDERemapper.ConfigItem):
	_focused_control = get_viewport().gui_get_focus_owner()
	_focused_control.release_focus()
	
	_press_prompt.visible = true

	# Limit the devices that we can detect based on which
	# mapping context we're currently working on. So 
	# for keyboard only keys can be bound and for controller
	# only controller buttons can be bound.
	var device := GUIDEInputDetector.DeviceType.KEYBOARD
	if item.context == controller_context:
		device = GUIDEInputDetector.DeviceType.JOY

	# detect a new input
	_input_detector.detect(item.value_type, [device])
	var input = await _input_detector.input_detected

	_press_prompt.visible = false

	_focused_control.grab_focus()

	# check if the detection was aborted.
	if input == null:
		return

	# check for collisions 
	var collisions := _remapper.get_input_collisions(item, input)
		
	# if any collision is from a non-bindable mapping, we cannot use this input
	if collisions.any(func(it:GUIDERemapper.ConfigItem): return not it.is_remappable):
		return
		
	# unbind the colliding entries.
	for collision in collisions:
		_remapper.set_bound_input(collision, null)
		
	# now bind the new input
	_remapper.set_bound_input(item, input)
			


func _clear(root:Container):
	for child in root.get_children():
		root.remove_child(child)
		child.queue_free()
		
		
func _on_abort_detection():
	_input_detector.abort_detection()

func _on_close_dialog():
	if _input_detector.is_detecting:
		return
	# same as pressing return to game	
	_on_return_to_game_pressed()

func _on_controller_invert_horizontal_toggled(toggled_on:bool):
	_remapper.set_custom_data(Utils.CUSTOM_DATA_INVERT_HORIZONTAL, toggled_on)


func _on_controller_invert_vertical_toggled(toggled_on:bool):
	_remapper.set_custom_data(Utils.CUSTOM_DATA_INVERT_VERTICAL, toggled_on)


func _on_return_to_game_pressed():
	# get the modified config
	var final_config := _remapper.get_mapping_config()
	# store it
	Utils.save_remapping_config(final_config)

	# restore main mapping context based on what is currently active
	if GUIDE.is_mapping_context_enabled(binding_keyboard_context):
		GUIDE.enable_mapping_context(keyboard_context, true)
	else:
		GUIDE.enable_mapping_context(controller_context, true)

	# and close the dialog
	visible = false
	closed.emit(final_config)
	
	
func _switch_tab(index:int):
	_tab_container.current_tab = posmod(_tab_container.current_tab + index, 2)	

func _switch(context:GUIDEMappingContext):
	# only do this when the dialog is visible
	if not visible:
		return
		
	GUIDE.enable_mapping_context(context, true)
