namespace RioJoy.Core.Protocol;

/// <summary>What a <see cref="PacketParser"/> emitted for an input byte.</summary>
public enum RioRxEventKind
{
    /// <summary>A complete packet was framed (see <see cref="RioRxEvent.Packet"/>).</summary>
    Packet,

    /// <summary>
    /// A single byte arrived outside packet framing: a control character
    /// (ACK/NAK/RESTART/IDLE) or stray garbage. See <see cref="RioRxEvent.Byte"/>.
    /// </summary>
    ControlByte,

    /// <summary>
    /// A byte with the high bit set arrived mid-packet — a framing error. The
    /// in-progress packet was aborted and the parser resynchronized. Port of the
    /// <c>(ch &amp; 0x80)</c> abort in <c>ReadCommBlock</c> (riovjoy2.cpp#L871).
    /// </summary>
    FramingError,
}

/// <summary>
/// One event produced by <see cref="PacketParser"/>. A given input byte yields
/// at most one event.
/// </summary>
public readonly struct RioRxEvent
{
    public RioRxEventKind Kind { get; }

    /// <summary>The framed packet (valid when <see cref="Kind"/> is <see cref="RioRxEventKind.Packet"/>).</summary>
    public RioPacket Packet { get; }

    /// <summary>
    /// Whether the received checksum matched. Valid when <see cref="Kind"/> is
    /// <see cref="RioRxEventKind.Packet"/>. The legacy receive path force-accepts
    /// regardless (see docs/PROTOCOL.md §2 ⚠️); this flag exposes the real result
    /// so callers can choose to NAK.
    /// </summary>
    public bool ChecksumValid { get; }

    /// <summary>
    /// The single byte (valid when <see cref="Kind"/> is
    /// <see cref="RioRxEventKind.ControlByte"/> or
    /// <see cref="RioRxEventKind.FramingError"/>).
    /// </summary>
    public byte Byte { get; }

    private RioRxEvent(RioRxEventKind kind, RioPacket packet, bool checksumValid, byte b)
    {
        Kind = kind;
        Packet = packet;
        ChecksumValid = checksumValid;
        Byte = b;
    }

    internal static RioRxEvent ForPacket(RioPacket packet, bool checksumValid) =>
        new(RioRxEventKind.Packet, packet, checksumValid, 0);

    internal static RioRxEvent ForControl(byte b) =>
        new(RioRxEventKind.ControlByte, default, false, b);

    internal static RioRxEvent ForFramingError(byte b) =>
        new(RioRxEventKind.FramingError, default, false, b);

    /// <summary>True if this is a control byte equal to <paramref name="control"/>.</summary>
    public bool IsControl(RioControl control) =>
        Kind == RioRxEventKind.ControlByte && Byte == (byte)control;
}
