#if !NET40 // ViGEmBus is Win10+ only; the XP flavor uses RioGamepadXP (PLAN.md §8B)
using Nefarius.ViGEm.Client;
using Nefarius.ViGEm.Client.Targets;
using Nefarius.ViGEm.Client.Targets.Xbox360;
using RioJoy.Core.Calibration;
using RioJoy.Core.Mapping;

namespace RioJoy.Core.Output;

/// <summary>
/// <see cref="IJoystickSink"/> backed by a <b>signed ViGEmBus</b> virtual Xbox 360
/// controller. Properly signed — installs with no test-signing / Secure-Boot
/// changes — at the cost of the XInput layout: 11 buttons, two sticks + two
/// triggers, and the D-pad for the hat. RIO joystick buttons beyond
/// <see cref="MappableButtonCount"/> are dropped (map those to the keyboard).
/// </summary>
public sealed class ViGEmJoystickSink : IJoystickSink, IDisposable
{
    // RIO joystick button (1-based) -> Xbox 360 button, in a natural order.
    private static readonly Xbox360Button[] ButtonMap =
    {
        Xbox360Button.A, Xbox360Button.B, Xbox360Button.X, Xbox360Button.Y,
        Xbox360Button.LeftShoulder, Xbox360Button.RightShoulder,
        Xbox360Button.Back, Xbox360Button.Start,
        Xbox360Button.LeftThumb, Xbox360Button.RightThumb,
        Xbox360Button.Guide,
    };

    /// <summary>Display names for the mappable buttons, aligned with <see cref="ButtonMap"/>.</summary>
    public static IList<string> ButtonNames { get; } = new[]
    {
        "A", "B", "X", "Y", "LB", "RB", "Back", "Start", "L3", "R3", "Guide",
    };

    /// <summary>How many RIO joystick buttons map onto the Xbox 360 pad (11).</summary>
    public static int MappableButtonCount => ButtonMap.Length;

    private readonly ViGEmClient _client;
    private readonly IXbox360Controller _pad;
    private readonly object _gate = new();

    private ViGEmJoystickSink(ViGEmClient client, IXbox360Controller pad)
    {
        _client = client;
        _pad = pad;
    }

    /// <summary>
    /// Try to connect to ViGEmBus and create a virtual Xbox 360 controller. Returns
    /// <see langword="false"/> if ViGEmBus is not installed, so callers can fall back
    /// to another joystick sink.
    /// </summary>
    public static bool TryCreate(out ViGEmJoystickSink? sink)
    {
        sink = null;
        try
        {
            var client = new ViGEmClient();
            IXbox360Controller pad = client.CreateXbox360Controller();
            pad.AutoSubmitReport = false; // submit once per logical update
            pad.Connect();
            sink = new ViGEmJoystickSink(client, pad);
            return true;
        }
        catch
        {
            // VigemBusNotFoundException (driver absent) or any connect failure.
            return false;
        }
    }

    public void SetButton(int button, bool pressed)
    {
        int index = button - 1; // RIO joystick buttons are 1-based
        if (index < 0 || index >= ButtonMap.Length)
            return; // beyond the Xbox 360 layout — bind these to the keyboard instead

        lock (_gate)
        {
            _pad.SetButtonState(ButtonMap[index], pressed);
            _pad.SubmitReport();
        }
    }

    public void SetHat(RioHat position)
    {
        lock (_gate)
        {
            _pad.SetButtonState(Xbox360Button.Up, position == RioHat.Up);
            _pad.SetButtonState(Xbox360Button.Right, position == RioHat.Right);
            _pad.SetButtonState(Xbox360Button.Down, position == RioHat.Down);
            _pad.SetButtonState(Xbox360Button.Left, position == RioHat.Left);
            _pad.SubmitReport();
        }
    }

    public void SetAxis(JoyAxis axis, int value)
    {
        lock (_gate)
        {
            switch (axis)
            {
                case JoyAxis.X: _pad.SetAxisValue(Xbox360Axis.LeftThumbX, ToThumb(value)); break;
                case JoyAxis.Y: _pad.SetAxisValue(Xbox360Axis.LeftThumbY, ToThumb(value)); break;
                case JoyAxis.Rx: _pad.SetAxisValue(Xbox360Axis.RightThumbX, ToThumb(value)); break;
                case JoyAxis.Ry: _pad.SetAxisValue(Xbox360Axis.RightThumbY, ToThumb(value)); break;
                case JoyAxis.Z: _pad.SetSliderValue(Xbox360Slider.LeftTrigger, ToTrigger(value)); break;
                case JoyAxis.Rz: _pad.SetSliderValue(Xbox360Slider.RightTrigger, ToTrigger(value)); break;
                default: return;
            }
            _pad.SubmitReport();
        }
    }

    // RIO axis 0..32766 (centre 16383) -> Xbox thumb short -32768..32767.
    private static short ToThumb(int value) =>
        (short)RioJoy.Core.Compat.Net48Math.Clamp((value - AxisOutputs.Center) * 2, short.MinValue, short.MaxValue);

    // RIO axis 0..32766 -> Xbox trigger byte 0..255.
    private static byte ToTrigger(int value) =>
        (byte)RioJoy.Core.Compat.Net48Math.Clamp(value * 255 / AxisOutputs.Max, 0, 255);

    public void Dispose()
    {
        try { _pad.Disconnect(); } catch { /* already disconnected / bus gone */ }
        _client.Dispose();
    }
}
#endif
