# Unified Input Action Mapping & Rebinding Reference

This module covers input device abstraction, action rebind mapping, analog stick deadzone filtering, and multi-input state handling for Ebitengine.

---

## 1. Action Mapping Abstraction Layer

Decouple raw physical keys/gamepad buttons from logical game actions (`ActionJump`, `ActionAttack`, `ActionPause`):

```go
type Action int

const (
	ActionMoveLeft Action = iota
	ActionMoveRight
	ActionJump
	ActionAttack
	ActionPause
)

type Binding struct {
	Keys     []ebiten.Key
	Buttons  []ebiten.StandardGamepadButton
	Mouses   []ebiten.MouseButton
}

type InputManager struct {
	Bindings map[Action]Binding
}

func NewInputManager() *InputManager {
	im := &InputManager{
		Bindings: make(map[Action]Binding),
	}
	// Default Bindings
	im.Bindings[ActionMoveLeft] = Binding{Keys: []ebiten.Key{ebiten.KeyA, ebiten.KeyLeft}}
	im.Bindings[ActionMoveRight] = Binding{Keys: []ebiten.Key{ebiten.KeyD, ebiten.KeyRight}}
	im.Bindings[ActionJump] = Binding{
		Keys: []ebiten.Key{ebiten.KeySpace, ebiten.KeyW, ebiten.KeyUp},
		Buttons: []ebiten.StandardGamepadButton{ebiten.StandardGamepadButtonSouth},
	}
	im.Bindings[ActionAttack] = Binding{
		Keys: []ebiten.Key{ebiten.KeyJ, ebiten.KeyZ},
		Mouses: []ebiten.MouseButton{ebiten.MouseButtonLeft},
		Buttons: []ebiten.StandardGamepadButton{ebiten.StandardGamepadButtonWest},
	}
	return im
}
```

---

## 2. Action State Evaluation

Query whether an action is currently held, just pressed, or just released across all assigned physical inputs:

```go
func (im *InputManager) IsActionPressed(action Action) bool {
	binding, ok := im.Bindings[action]
	if !ok { return false }

	for _, k := range binding.Keys {
		if ebiten.IsKeyPressed(k) { return true }
	}
	for _, m := range binding.Mouses {
		if ebiten.IsMouseButtonPressed(m) { return true }
	}

	// Gamepad support
	gamepads := ebiten.AppendGamepadIDs(nil)
	for _, id := range gamepads {
		for _, b := range binding.Buttons {
			if ebiten.IsStandardGamepadButtonPressed(id, b) { return true }
		}
	}
	return false
}

func (im *InputManager) IsActionJustPressed(action Action) bool {
	binding, ok := im.Bindings[action]
	if !ok { return false }

	for _, k := range binding.Keys {
		if inpututil.IsKeyJustPressed(k) { return true }
	}
	for _, m := range binding.Mouses {
		if inpututil.IsMouseButtonJustPressed(m) { return true }
	}

	gamepads := ebiten.AppendGamepadIDs(nil)
	for _, id := range gamepads {
		for _, b := range binding.Buttons {
			if inpututil.IsStandardGamepadButtonJustPressed(id, b) { return true }
		}
	}
	return false
}
```

---

## 3. Gamepad Analog Stick Radial Deadzone Filtering

Analog thumbsticks on gamepads exhibit hardware drift near center. Apply radial deadzone filtering:

```go
func ApplyRadialDeadzone(axisX, axisY, deadzone float64) (float64, float64) {
	magnitude := math.Sqrt(axisX*axisX + axisY*axisY)
	if magnitude < deadzone {
		return 0, 0
	}
	// Rescale remaining range [deadzone..1.0] to [0.0..1.0]
	normalizedMag := (magnitude - deadzone) / (1.0 - deadzone)
	if normalizedMag > 1.0 { normalizedMag = 1.0 }

	nx := (axisX / magnitude) * normalizedMag
	ny := (axisY / magnitude) * normalizedMag
	return nx, ny
}
```

---

## 4. Input Rebinding & Persistence

Serialize custom keybindings to disk as JSON:

```go
type SerializableBindings map[string][]string

func (im *InputManager) SaveBindings(filepath string) error {
	data, err := json.MarshalIndent(im.Bindings, "", "  ")
	if err != nil { return err }
	return os.WriteFile(filepath, data, 0644)
}
```
