using RioJoy.Core.Mapping;

namespace RioJoy.Core.Tests.Mapping;

/// <summary>
/// Records every routed output as an ordered, human-readable log so tests can
/// assert both <i>what</i> happened and <i>in what order</i>.
/// </summary>
internal sealed class RecordingSink : IInputSink, IJoystickSink, ILampSink, IRioCommandSink
{
    private readonly List<string> _log = new();

    /// <summary>Direct access for single-threaded tests.</summary>
    public List<string> Log
    {
        get { lock (_log) return _log; }
    }

    /// <summary>Thread-safe copy for assertions while the link thread writes.</summary>
    public string[] Snapshot()
    {
        lock (_log) return _log.ToArray();
    }

    private void Add(string entry)
    {
        lock (_log) _log.Add(entry);
    }

    public void KeyDown(byte virtualKey, bool extended) => Add($"KeyDown(0x{virtualKey:X2},ext={extended})");

    public void KeyUp(byte virtualKey, bool extended) => Add($"KeyUp(0x{virtualKey:X2},ext={extended})");

    public void MouseMove(int dx, int dy) => Add($"MouseMove({dx},{dy})");

    public void MouseButton(MouseButton button, bool down) => Add($"MouseButton({button},{down})");

    public void SetButton(int button, bool pressed) => Add($"Joy({button},{pressed})");

    public void SetHat(RioHat position) => Add($"Hat({position})");

    public void SetAxis(RioJoy.Core.Calibration.JoyAxis axis, int value) => Add($"Axis({axis},{value})");

    public void SetLamp(int address, byte lampState) => Add($"Lamp(0x{address:X2},0x{lampState:X2})");

    public void Execute(RioCommandCode command) => Add($"Cmd({command})");
}
