Transposer/InputManager.cs
namespace Sandbox.Transposer;

/// <summary>
/// Input wrapper that provides static held/just-pressed booleans
/// for the Transposer game controls.
///
/// Must be updated once per frame before entity updates — call
/// <see cref="Update"/> from the game entry point component.
///
/// Uses the engine action system (Input.Down / Input.Pressed) for WASD+Space
/// which gives proper held-state detection. Arrow keys are not bound by default
/// but can be added via the Input.config action bindings.
/// Analog stick (controller) input is also supported via Input.AnalogMove.
/// </summary>
public static class InputManager
{
	// Analog stick deadzone — below this magnitude the stick is treated as neutral.
	private const float ANALOG_DEADZONE = 0.3f;

	// Previous-frame analog directions, used to generate JustPressed edges.
	private static bool _prevAnalogLeft;
	private static bool _prevAnalogRight;
	private static bool _prevAnalogUp;
	private static bool _prevAnalogDown;

	// ── Analog speed scale (1.0 for keyboard, 0–1 proportional for controller) ──
	public static float MoveSpeedScale { get; private set; }

	// ── Held state ───────────────────────────────────────────────────────
	public static bool LeftPressed { get; private set; }
	public static bool RightPressed { get; private set; }
	public static bool UpPressed { get; private set; }
	public static bool DownPressed { get; private set; }
	public static bool SpacePressed { get; private set; }

	// ── Just-pressed edge detection ──────────────────────────────────────
	public static bool LeftJustPressed { get; private set; }
	public static bool RightJustPressed { get; private set; }
	public static bool UpJustPressed { get; private set; }
	public static bool DownJustPressed { get; private set; }
	public static bool SpaceJustPressed { get; private set; }

	/// <summary>
	/// Poll input and update all state.
	/// Uses the action system for WASD+Space, raw keyboard for arrow keys,
	/// and Input.AnalogMove for controller left stick.
	/// Call once per frame before any game logic runs.
	/// </summary>
	public static void Update()
	{
		// Controller left stick — map to cardinal directions via deadzone.
		var stick = Input.AnalogMove;
		bool analogLeft  = stick.y >  ANALOG_DEADZONE;
		bool analogRight = stick.y < -ANALOG_DEADZONE;
		bool analogUp    = stick.x >  ANALOG_DEADZONE;
		bool analogDown  = stick.x < -ANALOG_DEADZONE;

		// Speed scale: remap stick magnitude from [deadzone, 1] → [0, 1].
		// Keyboard always gives full speed; controller scales with stick pressure.
		bool anyAnalog = analogLeft || analogRight || analogUp || analogDown;
		if ( Input.UsingController && anyAnalog )
		{
			float raw = stick.Length;
			MoveSpeedScale = MathX.Clamp( (raw - ANALOG_DEADZONE) / (1f - ANALOG_DEADZONE), 0f, 1f );
		}
		else
		{
			MoveSpeedScale = 1f;
		}

		// Held state: action system (WASD) + raw keyboard (arrows) + analog stick.
		LeftPressed  = Input.Down( "Left" )     || Input.Keyboard.Down( "LEFTARROW" )  || analogLeft;
		RightPressed = Input.Down( "Right" )    || Input.Keyboard.Down( "RIGHTARROW" ) || analogRight;
		UpPressed    = Input.Down( "Up" )  || Input.Keyboard.Down( "UPARROW" )    || analogUp;
		DownPressed  = Input.Down( "Down" ) || Input.Keyboard.Down( "DOWNARROW" )  || analogDown;
		SpacePressed = Input.Down( "Start" );

		// Just-pressed: action system (WASD) + raw keyboard (arrows) + analog stick rising edge.
		LeftJustPressed  = Input.Pressed( "Left" )     || Input.Keyboard.Pressed( "LEFTARROW" )  || (analogLeft  && !_prevAnalogLeft);
		RightJustPressed = Input.Pressed( "Right" )    || Input.Keyboard.Pressed( "RIGHTARROW" ) || (analogRight && !_prevAnalogRight);
		UpJustPressed    = Input.Pressed( "Up" )  || Input.Keyboard.Pressed( "UPARROW" )    || (analogUp    && !_prevAnalogUp);
		DownJustPressed  = Input.Pressed( "Down" ) || Input.Keyboard.Pressed( "DOWNARROW" )  || (analogDown  && !_prevAnalogDown);
		SpaceJustPressed = Input.Pressed( "Start" );

		_prevAnalogLeft  = analogLeft;
		_prevAnalogRight = analogRight;
		_prevAnalogUp    = analogUp;
		_prevAnalogDown  = analogDown;
	}
}