using RioJoy.Core.Calibration;
using RioJoy.Core.Output;
using Xunit;

namespace RioJoy.Core.Tests.Output;

public class AxisRouterTests
{
    // --- Default routing reproduces the legacy hardcoded sink exactly --------

    [Theory]
    [InlineData(JoyAxis.X, PadTarget.LeftThumbX)]
    [InlineData(JoyAxis.Y, PadTarget.LeftThumbY)]
    [InlineData(JoyAxis.Rx, PadTarget.RightThumbX)]
    [InlineData(JoyAxis.Ry, PadTarget.RightThumbY)]
    public void Default_ThumbAxes_UseLegacyCenteredMath(JoyAxis axis, PadTarget expected)
    {
        var config = new AxisRoutingConfig();

        // Legacy ToThumb: (value - 16383) * 2, clamped to the short range.
        ResolvedAxis min = AxisRouter.Resolve(config, axis, 0);
        Assert.Equal(expected, min.Target);
        Assert.Equal(-32766, min.Value);

        Assert.Equal(0, AxisRouter.Resolve(config, axis, AxisOutputs.Center).Value);
        Assert.Equal(32766, AxisRouter.Resolve(config, axis, AxisOutputs.Max).Value);
    }

    [Theory]
    [InlineData(JoyAxis.Z, PadTarget.LeftTrigger)]
    [InlineData(JoyAxis.Rz, PadTarget.RightTrigger)]
    public void Default_TriggerAxes_UseLegacyTriggerMath(JoyAxis axis, PadTarget expected)
    {
        var config = new AxisRoutingConfig();

        // Legacy ToTrigger: value * 255 / 32766, clamped to the byte range.
        ResolvedAxis rest = AxisRouter.Resolve(config, axis, 0);
        Assert.Equal(expected, rest.Target);
        Assert.Equal(0, rest.Value);

        Assert.Equal(127, AxisRouter.Resolve(config, axis, AxisOutputs.Center).Value);
        Assert.Equal(255, AxisRouter.Resolve(config, axis, AxisOutputs.Max).Value);
    }

    [Fact]
    public void Default_CenteredThumb_ClampsToShortRange()
    {
        var config = new AxisRoutingConfig();

        // (40000 - 16383) * 2 = 47234 → clamped to short.MaxValue.
        Assert.Equal(32767, AxisRouter.Resolve(config, JoyAxis.X, 40000).Value);
        // (-2000 - 16383) * 2 = -36766 → clamped to short.MinValue.
        Assert.Equal(-32768, AxisRouter.Resolve(config, JoyAxis.X, -2000).Value);
    }

    // --- UnipolarPositive: clamp(value, 0, 32767) ----------------------------

    [Fact]
    public void UnipolarPositive_MapsCalibratedZeroToThumbCenter()
    {
        var config = new AxisRoutingConfig
        {
            Z = new AxisRoute { Target = PadTarget.RightThumbY, Mode = AxisOutputMode.UnipolarPositive },
        };

        Assert.Equal(0, AxisRouter.Resolve(config, JoyAxis.Z, 0).Value);          // detent → center
        Assert.Equal(16383, AxisRouter.Resolve(config, JoyAxis.Z, 16383).Value);  // half → half up
        Assert.Equal(32766, AxisRouter.Resolve(config, JoyAxis.Z, AxisOutputs.Max).Value); // full → max (32766 of 32767)
        Assert.Equal(0, AxisRouter.Resolve(config, JoyAxis.Z, -5).Value);         // clamps below
        Assert.Equal(32767, AxisRouter.Resolve(config, JoyAxis.Z, 40000).Value);  // clamps above
    }

    [Fact]
    public void UnipolarPositive_OnTriggerTarget_IsIgnored_TriggerMathApplies()
    {
        // Mode is meaningless for trigger targets — they keep the trigger byte math.
        var config = new AxisRoutingConfig
        {
            Z = new AxisRoute { Target = PadTarget.LeftTrigger, Mode = AxisOutputMode.UnipolarPositive },
        };

        Assert.Equal(255, AxisRouter.Resolve(config, JoyAxis.Z, AxisOutputs.Max).Value);
    }

    // --- None: the axis is not emitted ---------------------------------------

    [Fact]
    public void NoneTarget_EmitsNothing()
    {
        var config = new AxisRoutingConfig
        {
            Rx = new AxisRoute { Target = PadTarget.None },
        };

        ResolvedAxis r = AxisRouter.Resolve(config, JoyAxis.Rx, 12345);
        Assert.Equal(PadTarget.None, r.Target);
        Assert.Equal(0, r.Value);
    }

    // --- The Descent shape ----------------------------------------------------

    [Fact]
    public void DescentConfig_ThrottleToRightThumbY_TriggersUntargeted()
    {
        // profiles/descent-d1x.json: DXX's stock GameController defaults fire on
        // the LT/RT axis-buttons, so no axis may land on a trigger.
        var config = new AxisRoutingConfig
        {
            Z = new AxisRoute { Target = PadTarget.RightThumbY, Mode = AxisOutputMode.UnipolarPositive },
            Rz = new AxisRoute { Target = PadTarget.RightThumbX },
            Rx = new AxisRoute { Target = PadTarget.None },
            Ry = new AxisRoute { Target = PadTarget.None },
        };

        ResolvedAxis z = AxisRouter.Resolve(config, JoyAxis.Z, 16383);
        Assert.Equal(PadTarget.RightThumbY, z.Target);
        Assert.Equal(16383, z.Value); // unipolar: half throttle → half deflection up

        ResolvedAxis rz = AxisRouter.Resolve(config, JoyAxis.Rz, AxisOutputs.Center);
        Assert.Equal(PadTarget.RightThumbX, rz.Target);
        Assert.Equal(0, rz.Value); // rudder stays bipolar: center → thumb 0

        foreach (JoyAxis axis in new[] { JoyAxis.X, JoyAxis.Y, JoyAxis.Z, JoyAxis.Rx, JoyAxis.Ry, JoyAxis.Rz })
        {
            PadTarget target = AxisRouter.Resolve(config, axis, AxisOutputs.Center).Target;
            Assert.NotEqual(PadTarget.LeftTrigger, target);
            Assert.NotEqual(PadTarget.RightTrigger, target);
        }
    }
}
