extends Node

## Global keybinding manager (autoload `Controls`).
## Loads saved keyboard bindings on startup and applies them to InputMap so
## rebinds made in the intro menu persist into the game scene. Only the
## keyboard event of each action is overridden; joypad/mouse events are kept.

const SAVE_PATH := "user://controls.cfg"
const SECTION := "keybinds"

## Actions the options menu lets the player rebind (keyboard only).
const REBINDABLE_ACTIONS: PackedStringArray = [
	&"move_forward",
	&"move_back",
	&"move_left",
	&"move_right",
	&"ability_kick",
	&"ability_dash",
	&"ability_slam",
	&"ability_roll",
]


func _ready() -> void:
	load_saved()


## Whether to drive the game with the on-screen touch UI (joystick + buttons)
## instead of keyboard hints. DisplayServer.is_touchscreen_available() can't be
## used here: `emulate_touch_from_mouse` (on for desktop testing) makes it report
## true on every desktop. Gate on the actual runtime platform instead — real
## mobile/native and mobile web browsers — plus the DP force flag for testing.
func use_touch_ui() -> bool:
	if DP.b("force_touch_controls"):
		return true
	return (
		OS.has_feature("mobile")
		or OS.has_feature("web_android")
		or OS.has_feature("web_ios")
	)


## Returns the first keyboard event bound to an action, or null.
func get_key_event(action: StringName) -> InputEventKey:
	for event: InputEvent in InputMap.action_get_events(action):
		if event is InputEventKey:
			return event
	return null


## Human-readable label for the action's current key (e.g. "W", "Space").
func get_key_label(action: StringName) -> String:
	var event := get_key_event(action)
	if event == null:
		return "—"
	return event.as_text_physical_keycode()


## Replaces the primary keyboard event of `action` with `event`, keeping
## every other event (e.g. arrow-key alternates, joypad) in its place.
func rebind(action: StringName, event: InputEventKey) -> void:
	rebind_no_save(action, event)
	save()


func save() -> void:
	var cfg := ConfigFile.new()
	for action: StringName in REBINDABLE_ACTIONS:
		var event := get_key_event(action)
		if event != null:
			cfg.set_value(SECTION, String(action), event.physical_keycode)
	cfg.save(SAVE_PATH)


func load_saved() -> void:
	var cfg := ConfigFile.new()
	if cfg.load(SAVE_PATH) != OK:
		return
	for action: StringName in REBINDABLE_ACTIONS:
		var key := String(action)
		if not cfg.has_section_key(SECTION, key):
			continue
		var keycode: int = cfg.get_value(SECTION, key)
		var event := InputEventKey.new()
		event.physical_keycode = keycode as Key
		rebind_no_save(action, event)


## Rebuilds the action's event list, swapping the first keyboard event for
## `event` in place so ordering (and thus the "primary" key) is preserved.
func rebind_no_save(action: StringName, event: InputEventKey) -> void:
	var events := InputMap.action_get_events(action)
	InputMap.action_erase_events(action)
	var replaced := false
	for existing: InputEvent in events:
		if not replaced and existing is InputEventKey:
			InputMap.action_add_event(action, event)
			replaced = true
		else:
			InputMap.action_add_event(action, existing)
	if not replaced:
		InputMap.action_add_event(action, event)


func reset_defaults() -> void:
	InputMap.load_from_project_settings()
	if FileAccess.file_exists(SAVE_PATH):
		DirAccess.remove_absolute(SAVE_PATH)
