class_name ControlBinding extends CheckerContainer
## Input for rebinding an action.

const ACTION_KEY_FMT := "ACTION_{0}"
const LISTENING_TEXT := "UI_LISTEN"

const MOTION_THRESHOLD := 0.5

@export var key: StringName

var listening: bool = false:
	set(value):
		if button:
			button.disabled = value
		listening = value

@onready var action: Label = %Action
@onready var button: Button = %Button


func _get_settings_key() -> String:
	return "input/%s" % key


func _ready() -> void:
	# gdlint:ignore = private-method-call
	super._ready()

	# Set action label text
	var loc_action := tr(ACTION_KEY_FMT.format([key]))
	# Fall back to just the key if no localization exists
	@warning_ignore("incompatible_ternary")
	action.text = loc_action if loc_action else key

	# Set the binding label
	_set_label_from_binding()


func _set_label_from_binding() -> void:
	var actions := InputMap.action_get_events(key)
	if actions:
		var primary := actions[0]
		button.text = PromptMap.from_event(primary)
		if button.text == PromptMap.UNKNOWN_INPUT_SYMBOL:
			print_debug("No mapping for input event: ", primary)
		# Special case: Can't rebind things bound to ESC
		if primary is InputEventKey and (primary as InputEventKey).physical_keycode == KEY_ESCAPE:
			button.disabled = true


func _input(event: InputEvent) -> void:
	if not listening:
		return

	if event is InputEventKey:
		var key_event: InputEventKey = event
		if (
			key_event.physical_keycode in [KEY_CTRL, KEY_ALT, KEY_SHIFT, KEY_META]
			and key_event.pressed
		):
			# Ignore modifier key until release
			return
		if key_event.physical_keycode == KEY_ESCAPE:
			get_viewport().set_input_as_handled()
			cancel_rebinding()
			return

	if event is InputEventJoypadMotion:
		var motion_event: InputEventJoypadMotion = event
		if abs(motion_event.axis_value) < MOTION_THRESHOLD:
			# Ignore axis motion unless it's over our threshold
			return

	if (
		event is InputEventKey
		or event is InputEventMouseButton
		or event is InputEventJoypadButton
		or event is InputEventJoypadMotion
	):
		get_viewport().set_input_as_handled()
		rebind(event)


func start_listening() -> void:
	button.text = LISTENING_TEXT
	listening = true


func cancel_rebinding() -> void:
	_set_label_from_binding()
	listening = false


func rebind(event: InputEvent) -> void:
	# Clear previous binding
	InputMap.action_erase_events(key)
	# Add new binding
	InputMap.action_add_event(key, event)
	# Update label
	_set_label_from_binding()

	listening = false


func apply() -> void:
	var setting: Dictionary = ProjectSettings.get_setting(_get_settings_key())
	setting["events"] = InputMap.action_get_events(key)
	ProjectSettings.set_setting(_get_settings_key(), setting)
