//! Per-modulation parameter structs for `SET_CONFIG` (`PROTOCOL.md §10`).
//!
//! `SET_CONFIG`'s payload is a one-byte modulation identifier followed
//! by a modulation-specific struct. Each struct is fixed-layout
//! little-endian; FSK/GFSK alone carries a variable-length sync word at
//! its tail.
//!
//! The crate models the whole universe of DongLoRa Protocol modulations even on
//! devices that physically can't drive them. Firmware ultimately returns
//! `EMODULATION` for anything the chip doesn't support, but the wire
//! codec stays uniform across all 1.0-compliant implementations.

use crate::{MAX_SYNC_WORD_LEN, ModulationEncodeError, ModulationParseError};

// ── Modulation identifier ───────────────────────────────────────────

/// One-byte modulation selector (`PROTOCOL.md §10`). Prefixes every
/// `SET_CONFIG` payload.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum ModulationId {
    LoRa = 0x01,
    FskGfsk = 0x02,
    LrFhss = 0x03,
    Flrc = 0x04,
}

impl ModulationId {
    pub const fn as_u8(self) -> u8 {
        self as u8
    }

    pub const fn from_u8(v: u8) -> Option<Self> {
        Some(match v {
            0x01 => Self::LoRa,
            0x02 => Self::FskGfsk,
            0x03 => Self::LrFhss,
            0x04 => Self::Flrc,
            _ => return None,
        })
    }
}

// ── LoRa enums ──────────────────────────────────────────────────────

/// LoRa signal bandwidth enum (`PROTOCOL.md §10.1`). The small integer
/// is what goes on the wire, NOT the kHz value.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum LoRaBandwidth {
    /// 7.81 kHz — sub-GHz chips only.
    Khz7 = 0,
    /// 10.42 kHz — sub-GHz chips only.
    Khz10 = 1,
    /// 15.63 kHz — sub-GHz chips only.
    Khz15 = 2,
    /// 20.83 kHz — sub-GHz chips only.
    Khz20 = 3,
    /// 31.25 kHz — sub-GHz chips only.
    Khz31 = 4,
    /// 41.67 kHz — sub-GHz chips only.
    Khz41 = 5,
    /// 62.5 kHz — sub-GHz chips only.
    Khz62 = 6,
    /// 125 kHz — all LoRa chips.
    Khz125 = 7,
    /// 250 kHz — all LoRa chips.
    Khz250 = 8,
    /// 500 kHz — all LoRa chips.
    Khz500 = 9,
    /// 200 kHz — SX128x (2.4 GHz) only.
    Khz200 = 10,
    /// 400 kHz — SX128x (2.4 GHz) only.
    Khz400 = 11,
    /// 800 kHz — SX128x (2.4 GHz) only.
    Khz800 = 12,
    /// 1600 kHz — SX128x (2.4 GHz) only.
    Khz1600 = 13,
}

impl LoRaBandwidth {
    pub const fn as_u8(self) -> u8 {
        self as u8
    }

    pub const fn from_u8(v: u8) -> Option<Self> {
        Some(match v {
            0 => Self::Khz7,
            1 => Self::Khz10,
            2 => Self::Khz15,
            3 => Self::Khz20,
            4 => Self::Khz31,
            5 => Self::Khz41,
            6 => Self::Khz62,
            7 => Self::Khz125,
            8 => Self::Khz250,
            9 => Self::Khz500,
            10 => Self::Khz200,
            11 => Self::Khz400,
            12 => Self::Khz800,
            13 => Self::Khz1600,
            _ => return None,
        })
    }

    /// Nominal bandwidth in Hz. Useful for the auto-LDRO rule
    /// `(2^SF)/BW_Hz > 16 ms` and for display.
    pub const fn as_hz(self) -> u32 {
        match self {
            Self::Khz7 => 7_810,
            Self::Khz10 => 10_420,
            Self::Khz15 => 15_630,
            Self::Khz20 => 20_830,
            Self::Khz31 => 31_250,
            Self::Khz41 => 41_670,
            Self::Khz62 => 62_500,
            Self::Khz125 => 125_000,
            Self::Khz250 => 250_000,
            Self::Khz500 => 500_000,
            Self::Khz200 => 200_000,
            Self::Khz400 => 400_000,
            Self::Khz800 => 800_000,
            Self::Khz1600 => 1_600_000,
        }
    }
}

/// LoRa coding rate enum (`PROTOCOL.md §10.1`).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum LoRaCodingRate {
    /// 4/5.
    Cr4_5 = 0,
    /// 4/6.
    Cr4_6 = 1,
    /// 4/7.
    Cr4_7 = 2,
    /// 4/8.
    Cr4_8 = 3,
}

impl LoRaCodingRate {
    pub const fn as_u8(self) -> u8 {
        self as u8
    }

    pub const fn from_u8(v: u8) -> Option<Self> {
        Some(match v {
            0 => Self::Cr4_5,
            1 => Self::Cr4_6,
            2 => Self::Cr4_7,
            3 => Self::Cr4_8,
            _ => return None,
        })
    }
}

/// LoRa header mode (`PROTOCOL.md §10.1`).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum LoRaHeaderMode {
    Explicit = 0,
    Implicit = 1,
}

impl LoRaHeaderMode {
    pub const fn as_u8(self) -> u8 {
        self as u8
    }

    pub const fn from_u8(v: u8) -> Option<Self> {
        Some(match v {
            0 => Self::Explicit,
            1 => Self::Implicit,
            _ => return None,
        })
    }
}

/// LoRa configuration payload (15 bytes after `modulation_id`).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct LoRaConfig {
    pub freq_hz: u32,
    /// Spreading factor 5–12 (chip-dependent: SX127x is 6–12).
    pub sf: u8,
    pub bw: LoRaBandwidth,
    pub cr: LoRaCodingRate,
    pub preamble_len: u16,
    pub sync_word: u16,
    pub tx_power_dbm: i8,
    pub header_mode: LoRaHeaderMode,
    /// Payload CRC: `false` disabled, `true` enabled.
    pub payload_crc: bool,
    /// IQ inversion: `false` normal, `true` inverted.
    pub iq_invert: bool,
}

impl LoRaConfig {
    /// Fixed wire size of the per-modulation params.
    pub const WIRE_SIZE: usize = 15;

    pub fn encode(&self, buf: &mut [u8]) -> Result<usize, ModulationEncodeError> {
        if buf.len() < Self::WIRE_SIZE {
            return Err(ModulationEncodeError::BufferTooSmall);
        }
        buf[0..4].copy_from_slice(&self.freq_hz.to_le_bytes());
        buf[4] = self.sf;
        buf[5] = self.bw.as_u8();
        buf[6] = self.cr.as_u8();
        buf[7..9].copy_from_slice(&self.preamble_len.to_le_bytes());
        buf[9..11].copy_from_slice(&self.sync_word.to_le_bytes());
        buf[11] = self.tx_power_dbm as u8;
        buf[12] = self.header_mode.as_u8();
        buf[13] = u8::from(self.payload_crc);
        buf[14] = u8::from(self.iq_invert);
        Ok(Self::WIRE_SIZE)
    }

    pub fn decode(buf: &[u8]) -> Result<Self, ModulationParseError> {
        if buf.len() != Self::WIRE_SIZE {
            return Err(ModulationParseError::WrongLength {
                expected: Self::WIRE_SIZE,
                actual: buf.len(),
            });
        }
        let freq_hz = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]);
        let sf = buf[4];
        let bw = LoRaBandwidth::from_u8(buf[5]).ok_or(ModulationParseError::InvalidField)?;
        let cr = LoRaCodingRate::from_u8(buf[6]).ok_or(ModulationParseError::InvalidField)?;
        let preamble_len = u16::from_le_bytes([buf[7], buf[8]]);
        let sync_word = u16::from_le_bytes([buf[9], buf[10]]);
        let tx_power_dbm = buf[11] as i8;
        let header_mode =
            LoRaHeaderMode::from_u8(buf[12]).ok_or(ModulationParseError::InvalidField)?;
        let payload_crc = match buf[13] {
            0 => false,
            1 => true,
            _ => return Err(ModulationParseError::InvalidField),
        };
        let iq_invert = match buf[14] {
            0 => false,
            1 => true,
            _ => return Err(ModulationParseError::InvalidField),
        };
        Ok(Self {
            freq_hz,
            sf,
            bw,
            cr,
            preamble_len,
            sync_word,
            tx_power_dbm,
            header_mode,
            payload_crc,
            iq_invert,
        })
    }
}

// ── FSK / GFSK ──────────────────────────────────────────────────────

/// FSK / GFSK configuration payload (`PROTOCOL.md §10.2`).
///
/// Wire size is `16 + sync_word_len` bytes. `sync_word_len` is 0–8 and
/// indicates how many leading bytes of the `sync_word` array are
/// transmitted (MSB-first, per FSK convention). Extra trailing bytes
/// are ignored.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct FskConfig {
    pub freq_hz: u32,
    pub bitrate_bps: u32,
    pub freq_dev_hz: u32,
    /// Chip-specific RX-filter bandwidth index.
    pub rx_bw: u8,
    pub preamble_len: u16,
    pub sync_word_len: u8,
    pub sync_word: [u8; MAX_SYNC_WORD_LEN],
}

impl FskConfig {
    /// Fixed part of the payload preceding the variable-length sync word.
    pub const FIXED_WIRE_SIZE: usize = 16;

    /// Total wire size for the given `sync_word_len`. `sync_word_len`
    /// larger than 8 is rejected by `encode`.
    pub const fn wire_size_for(sync_word_len: u8) -> usize {
        Self::FIXED_WIRE_SIZE + sync_word_len as usize
    }

    pub fn encode(&self, buf: &mut [u8]) -> Result<usize, ModulationEncodeError> {
        if self.sync_word_len as usize > MAX_SYNC_WORD_LEN {
            return Err(ModulationEncodeError::SyncWordTooLong);
        }
        let total = Self::wire_size_for(self.sync_word_len);
        if buf.len() < total {
            return Err(ModulationEncodeError::BufferTooSmall);
        }
        buf[0..4].copy_from_slice(&self.freq_hz.to_le_bytes());
        buf[4..8].copy_from_slice(&self.bitrate_bps.to_le_bytes());
        buf[8..12].copy_from_slice(&self.freq_dev_hz.to_le_bytes());
        buf[12] = self.rx_bw;
        buf[13..15].copy_from_slice(&self.preamble_len.to_le_bytes());
        buf[15] = self.sync_word_len;
        let n = self.sync_word_len as usize;
        buf[16..16 + n].copy_from_slice(&self.sync_word[..n]);
        Ok(total)
    }

    pub fn decode(buf: &[u8]) -> Result<Self, ModulationParseError> {
        if buf.len() < Self::FIXED_WIRE_SIZE {
            return Err(ModulationParseError::TooShort);
        }
        let sync_word_len = buf[15];
        if sync_word_len as usize > MAX_SYNC_WORD_LEN {
            return Err(ModulationParseError::InvalidField);
        }
        let expected = Self::wire_size_for(sync_word_len);
        if buf.len() != expected {
            return Err(ModulationParseError::WrongLength {
                expected,
                actual: buf.len(),
            });
        }
        let freq_hz = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]);
        let bitrate_bps = u32::from_le_bytes([buf[4], buf[5], buf[6], buf[7]]);
        let freq_dev_hz = u32::from_le_bytes([buf[8], buf[9], buf[10], buf[11]]);
        let rx_bw = buf[12];
        let preamble_len = u16::from_le_bytes([buf[13], buf[14]]);
        let mut sync_word = [0u8; MAX_SYNC_WORD_LEN];
        let n = sync_word_len as usize;
        sync_word[..n].copy_from_slice(&buf[16..16 + n]);
        Ok(Self {
            freq_hz,
            bitrate_bps,
            freq_dev_hz,
            rx_bw,
            preamble_len,
            sync_word_len,
            sync_word,
        })
    }
}

// ── LR-FHSS ─────────────────────────────────────────────────────────

/// LR-FHSS occupied-bandwidth enum (`PROTOCOL.md §10.3`).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum LrFhssBandwidth {
    /// 39.06 kHz.
    Khz39 = 0,
    /// 85.94 kHz.
    Khz85 = 1,
    /// 136.72 kHz.
    Khz136 = 2,
    /// 183.59 kHz.
    Khz183 = 3,
    /// 335.94 kHz.
    Khz335 = 4,
    /// 386.72 kHz.
    Khz386 = 5,
    /// 722.66 kHz.
    Khz722 = 6,
    /// 1523.44 kHz.
    Khz1523 = 7,
}

impl LrFhssBandwidth {
    pub const fn as_u8(self) -> u8 {
        self as u8
    }
    pub const fn from_u8(v: u8) -> Option<Self> {
        Some(match v {
            0 => Self::Khz39,
            1 => Self::Khz85,
            2 => Self::Khz136,
            3 => Self::Khz183,
            4 => Self::Khz335,
            5 => Self::Khz386,
            6 => Self::Khz722,
            7 => Self::Khz1523,
            _ => return None,
        })
    }
}

/// LR-FHSS coding rate enum (`PROTOCOL.md §10.3`).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum LrFhssCodingRate {
    /// 5/6.
    Cr5_6 = 0,
    /// 2/3.
    Cr2_3 = 1,
    /// 1/2.
    Cr1_2 = 2,
    /// 1/3.
    Cr1_3 = 3,
}

impl LrFhssCodingRate {
    pub const fn as_u8(self) -> u8 {
        self as u8
    }
    pub const fn from_u8(v: u8) -> Option<Self> {
        Some(match v {
            0 => Self::Cr5_6,
            1 => Self::Cr2_3,
            2 => Self::Cr1_2,
            3 => Self::Cr1_3,
            _ => return None,
        })
    }
}

/// LR-FHSS grid selection (`PROTOCOL.md §10.3`).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum LrFhssGrid {
    /// 25.39 kHz grid.
    Khz25 = 0,
    /// 3.9 kHz grid.
    Khz3_9 = 1,
}

impl LrFhssGrid {
    pub const fn as_u8(self) -> u8 {
        self as u8
    }
    pub const fn from_u8(v: u8) -> Option<Self> {
        Some(match v {
            0 => Self::Khz25,
            1 => Self::Khz3_9,
            _ => return None,
        })
    }
}

/// LR-FHSS configuration payload (10 bytes after `modulation_id`).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct LrFhssConfig {
    pub freq_hz: u32,
    pub bw: LrFhssBandwidth,
    pub cr: LrFhssCodingRate,
    pub grid: LrFhssGrid,
    pub hopping: bool,
    pub tx_power_dbm: i8,
}

impl LrFhssConfig {
    pub const WIRE_SIZE: usize = 10;

    pub fn encode(&self, buf: &mut [u8]) -> Result<usize, ModulationEncodeError> {
        if buf.len() < Self::WIRE_SIZE {
            return Err(ModulationEncodeError::BufferTooSmall);
        }
        buf[0..4].copy_from_slice(&self.freq_hz.to_le_bytes());
        buf[4] = self.bw.as_u8();
        buf[5] = self.cr.as_u8();
        buf[6] = self.grid.as_u8();
        buf[7] = u8::from(self.hopping);
        buf[8] = self.tx_power_dbm as u8;
        buf[9] = 0; // reserved
        Ok(Self::WIRE_SIZE)
    }

    pub fn decode(buf: &[u8]) -> Result<Self, ModulationParseError> {
        if buf.len() != Self::WIRE_SIZE {
            return Err(ModulationParseError::WrongLength {
                expected: Self::WIRE_SIZE,
                actual: buf.len(),
            });
        }
        let freq_hz = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]);
        let bw = LrFhssBandwidth::from_u8(buf[4]).ok_or(ModulationParseError::InvalidField)?;
        let cr = LrFhssCodingRate::from_u8(buf[5]).ok_or(ModulationParseError::InvalidField)?;
        let grid = LrFhssGrid::from_u8(buf[6]).ok_or(ModulationParseError::InvalidField)?;
        let hopping = match buf[7] {
            0 => false,
            1 => true,
            _ => return Err(ModulationParseError::InvalidField),
        };
        let tx_power_dbm = buf[8] as i8;
        // Reserved byte MUST be zero per §10.3.
        if buf[9] != 0 {
            return Err(ModulationParseError::InvalidField);
        }
        Ok(Self {
            freq_hz,
            bw,
            cr,
            grid,
            hopping,
            tx_power_dbm,
        })
    }
}

// ── FLRC ────────────────────────────────────────────────────────────

/// FLRC bitrate enum (`PROTOCOL.md §10.4`).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum FlrcBitrate {
    /// 2600 kbps.
    Kbps2600 = 0,
    /// 2080 kbps.
    Kbps2080 = 1,
    /// 1300 kbps.
    Kbps1300 = 2,
    /// 1040 kbps.
    Kbps1040 = 3,
    /// 650 kbps.
    Kbps650 = 4,
    /// 520 kbps.
    Kbps520 = 5,
    /// 325 kbps.
    Kbps325 = 6,
    /// 260 kbps.
    Kbps260 = 7,
}

impl FlrcBitrate {
    pub const fn as_u8(self) -> u8 {
        self as u8
    }
    pub const fn from_u8(v: u8) -> Option<Self> {
        Some(match v {
            0 => Self::Kbps2600,
            1 => Self::Kbps2080,
            2 => Self::Kbps1300,
            3 => Self::Kbps1040,
            4 => Self::Kbps650,
            5 => Self::Kbps520,
            6 => Self::Kbps325,
            7 => Self::Kbps260,
            _ => return None,
        })
    }
}

/// FLRC coding rate enum (`PROTOCOL.md §10.4`).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum FlrcCodingRate {
    /// 1/2.
    Cr1_2 = 0,
    /// 3/4.
    Cr3_4 = 1,
    /// 1/1 (no coding).
    Cr1_1 = 2,
}

impl FlrcCodingRate {
    pub const fn as_u8(self) -> u8 {
        self as u8
    }
    pub const fn from_u8(v: u8) -> Option<Self> {
        Some(match v {
            0 => Self::Cr1_2,
            1 => Self::Cr3_4,
            2 => Self::Cr1_1,
            _ => return None,
        })
    }
}

/// FLRC Gaussian BT-product enum (`PROTOCOL.md §10.4`).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum FlrcBt {
    /// Gaussian filtering off.
    Off = 0,
    /// BT = 0.5.
    Bt0_5 = 1,
    /// BT = 1.0.
    Bt1_0 = 2,
}

impl FlrcBt {
    pub const fn as_u8(self) -> u8 {
        self as u8
    }
    pub const fn from_u8(v: u8) -> Option<Self> {
        Some(match v {
            0 => Self::Off,
            1 => Self::Bt0_5,
            2 => Self::Bt1_0,
            _ => return None,
        })
    }
}

/// FLRC preamble length enum (`PROTOCOL.md §10.4`).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum FlrcPreambleLen {
    Bits8 = 0,
    Bits12 = 1,
    Bits16 = 2,
    Bits20 = 3,
    Bits24 = 4,
    Bits28 = 5,
    Bits32 = 6,
}

impl FlrcPreambleLen {
    pub const fn as_u8(self) -> u8 {
        self as u8
    }
    pub const fn from_u8(v: u8) -> Option<Self> {
        Some(match v {
            0 => Self::Bits8,
            1 => Self::Bits12,
            2 => Self::Bits16,
            3 => Self::Bits20,
            4 => Self::Bits24,
            5 => Self::Bits28,
            6 => Self::Bits32,
            _ => return None,
        })
    }
}

/// FLRC configuration payload (13 bytes after `modulation_id`).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct FlrcConfig {
    pub freq_hz: u32,
    pub bitrate: FlrcBitrate,
    pub cr: FlrcCodingRate,
    pub bt: FlrcBt,
    pub preamble_len: FlrcPreambleLen,
    /// 32-bit sync word, transmitted MSB-first on air.
    pub sync_word: u32,
    pub tx_power_dbm: i8,
}

impl FlrcConfig {
    pub const WIRE_SIZE: usize = 13;

    pub fn encode(&self, buf: &mut [u8]) -> Result<usize, ModulationEncodeError> {
        if buf.len() < Self::WIRE_SIZE {
            return Err(ModulationEncodeError::BufferTooSmall);
        }
        buf[0..4].copy_from_slice(&self.freq_hz.to_le_bytes());
        buf[4] = self.bitrate.as_u8();
        buf[5] = self.cr.as_u8();
        buf[6] = self.bt.as_u8();
        buf[7] = self.preamble_len.as_u8();
        buf[8..12].copy_from_slice(&self.sync_word.to_le_bytes());
        buf[12] = self.tx_power_dbm as u8;
        Ok(Self::WIRE_SIZE)
    }

    pub fn decode(buf: &[u8]) -> Result<Self, ModulationParseError> {
        if buf.len() != Self::WIRE_SIZE {
            return Err(ModulationParseError::WrongLength {
                expected: Self::WIRE_SIZE,
                actual: buf.len(),
            });
        }
        let freq_hz = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]);
        let bitrate = FlrcBitrate::from_u8(buf[4]).ok_or(ModulationParseError::InvalidField)?;
        let cr = FlrcCodingRate::from_u8(buf[5]).ok_or(ModulationParseError::InvalidField)?;
        let bt = FlrcBt::from_u8(buf[6]).ok_or(ModulationParseError::InvalidField)?;
        let preamble_len =
            FlrcPreambleLen::from_u8(buf[7]).ok_or(ModulationParseError::InvalidField)?;
        let sync_word = u32::from_le_bytes([buf[8], buf[9], buf[10], buf[11]]);
        let tx_power_dbm = buf[12] as i8;
        Ok(Self {
            freq_hz,
            bitrate,
            cr,
            bt,
            preamble_len,
            sync_word,
            tx_power_dbm,
        })
    }
}

// ── Modulation sum type ─────────────────────────────────────────────

/// Any of the four modulation configs, tagged by `ModulationId`.
///
/// This is the type carried inside `Command::SetConfig`. Encoding writes
/// the `modulation_id` byte first, then delegates to the per-modulation
/// `encode`. Decoding dispatches on the leading byte and calls the
/// matching `decode`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Modulation {
    LoRa(LoRaConfig),
    FskGfsk(FskConfig),
    LrFhss(LrFhssConfig),
    Flrc(FlrcConfig),
}

impl Modulation {
    pub const fn id(&self) -> ModulationId {
        match self {
            Self::LoRa(_) => ModulationId::LoRa,
            Self::FskGfsk(_) => ModulationId::FskGfsk,
            Self::LrFhss(_) => ModulationId::LrFhss,
            Self::Flrc(_) => ModulationId::Flrc,
        }
    }

    /// Encode as `[modulation_id][params]` into `buf`. Returns the total
    /// number of bytes written.
    pub fn encode(&self, buf: &mut [u8]) -> Result<usize, ModulationEncodeError> {
        if buf.is_empty() {
            return Err(ModulationEncodeError::BufferTooSmall);
        }
        buf[0] = self.id().as_u8();
        let n = match self {
            Self::LoRa(c) => c.encode(&mut buf[1..])?,
            Self::FskGfsk(c) => c.encode(&mut buf[1..])?,
            Self::LrFhss(c) => c.encode(&mut buf[1..])?,
            Self::Flrc(c) => c.encode(&mut buf[1..])?,
        };
        Ok(1 + n)
    }

    /// Decode from `[modulation_id][params]`. The full slice is the
    /// `SET_CONFIG` payload; length errors are propagated as
    /// `ModulationParseError::WrongLength`.
    pub fn decode(buf: &[u8]) -> Result<Self, ModulationParseError> {
        if buf.is_empty() {
            return Err(ModulationParseError::TooShort);
        }
        let id = ModulationId::from_u8(buf[0]).ok_or(ModulationParseError::UnknownModulation)?;
        let params = &buf[1..];
        Ok(match id {
            ModulationId::LoRa => Self::LoRa(LoRaConfig::decode(params)?),
            ModulationId::FskGfsk => Self::FskGfsk(FskConfig::decode(params)?),
            ModulationId::LrFhss => Self::LrFhss(LrFhssConfig::decode(params)?),
            ModulationId::Flrc => Self::Flrc(FlrcConfig::decode(params)?),
        })
    }
}

#[cfg(test)]
#[allow(clippy::panic, clippy::unwrap_used)]
mod tests {
    use super::*;

    fn sample_lora() -> LoRaConfig {
        LoRaConfig {
            freq_hz: 868_100_000,
            sf: 7,
            bw: LoRaBandwidth::Khz125,
            cr: LoRaCodingRate::Cr4_5,
            preamble_len: 8,
            sync_word: 0x1424,
            tx_power_dbm: 14,
            header_mode: LoRaHeaderMode::Explicit,
            payload_crc: true,
            iq_invert: false,
        }
    }

    #[test]
    fn lora_wire_size() {
        assert_eq!(LoRaConfig::WIRE_SIZE, 15);
    }

    #[test]
    fn lora_roundtrip() {
        let cfg = sample_lora();
        let mut buf = [0u8; 32];
        let n = cfg.encode(&mut buf).unwrap();
        assert_eq!(n, 15);
        let decoded = LoRaConfig::decode(&buf[..n]).unwrap();
        assert_eq!(decoded, cfg);
    }

    #[test]
    fn lora_appendix_c23_bytes() {
        // PROTOCOL.md §C.2.3 — EU868 SF7 BW125 CR4/5, preamble=8, sync=0x1424,
        // power=14 dBm, explicit, crc on, iq normal.
        let cfg = sample_lora();
        let mut buf = [0u8; 15];
        let n = cfg.encode(&mut buf).unwrap();
        assert_eq!(n, 15);
        let expected: [u8; 15] = [
            0xA0, 0x27, 0xBE, 0x33, // freq_hz = 868_100_000 LE
            0x07, // sf
            0x07, // bw = Khz125
            0x00, // cr = 4/5
            0x08, 0x00, // preamble_len = 8
            0x24, 0x14, // sync_word = 0x1424
            0x0E, // tx_power_dbm = 14
            0x00, // header explicit
            0x01, // payload_crc on
            0x00, // iq normal
        ];
        assert_eq!(buf, expected);
    }

    #[test]
    fn lora_rejects_wrong_length() {
        assert!(matches!(
            LoRaConfig::decode(&[0u8; 14]),
            Err(ModulationParseError::WrongLength { .. })
        ));
        assert!(matches!(
            LoRaConfig::decode(&[0u8; 16]),
            Err(ModulationParseError::WrongLength { .. })
        ));
    }

    #[test]
    fn lora_rejects_bad_enum_values() {
        let mut buf = [0u8; 15];
        sample_lora().encode(&mut buf).unwrap();

        let mut bad = buf;
        bad[5] = 14; // BW 14 is undefined
        assert!(LoRaConfig::decode(&bad).is_err());

        let mut bad = buf;
        bad[6] = 4; // CR 4 is undefined
        assert!(LoRaConfig::decode(&bad).is_err());

        let mut bad = buf;
        bad[12] = 2; // header_mode 2 undefined
        assert!(LoRaConfig::decode(&bad).is_err());

        let mut bad = buf;
        bad[13] = 2; // payload_crc must be 0 or 1
        assert!(LoRaConfig::decode(&bad).is_err());

        let mut bad = buf;
        bad[14] = 2; // iq_invert must be 0 or 1
        assert!(LoRaConfig::decode(&bad).is_err());
    }

    #[test]
    fn fsk_roundtrip_empty_sync() {
        let cfg = FskConfig {
            freq_hz: 868_000_000,
            bitrate_bps: 9_600,
            freq_dev_hz: 5_000,
            rx_bw: 0x0B,
            preamble_len: 16,
            sync_word_len: 0,
            sync_word: [0u8; MAX_SYNC_WORD_LEN],
        };
        let mut buf = [0u8; 32];
        let n = cfg.encode(&mut buf).unwrap();
        assert_eq!(n, 16);
        let decoded = FskConfig::decode(&buf[..n]).unwrap();
        assert_eq!(decoded, cfg);
    }

    #[test]
    fn fsk_roundtrip_with_sync() {
        let mut sync = [0u8; MAX_SYNC_WORD_LEN];
        sync[..4].copy_from_slice(&[0x12, 0x34, 0x56, 0x78]);
        let cfg = FskConfig {
            freq_hz: 868_000_000,
            bitrate_bps: 50_000,
            freq_dev_hz: 25_000,
            rx_bw: 0x1A,
            preamble_len: 32,
            sync_word_len: 4,
            sync_word: sync,
        };
        let mut buf = [0u8; 32];
        let n = cfg.encode(&mut buf).unwrap();
        assert_eq!(n, 20);
        let decoded = FskConfig::decode(&buf[..n]).unwrap();
        assert_eq!(decoded, cfg);
    }

    #[test]
    fn fsk_rejects_oversized_sync() {
        let mut cfg = FskConfig {
            freq_hz: 0,
            bitrate_bps: 0,
            freq_dev_hz: 0,
            rx_bw: 0,
            preamble_len: 0,
            sync_word_len: 9,
            sync_word: [0u8; MAX_SYNC_WORD_LEN],
        };
        let mut buf = [0u8; 32];
        assert!(cfg.encode(&mut buf).is_err());

        // Decoder rejects the same: sync_word_len > 8.
        cfg.sync_word_len = 0;
        cfg.encode(&mut buf).unwrap();
        let mut bad = [0u8; 16];
        bad.copy_from_slice(&buf[..16]);
        bad[15] = 9;
        assert!(FskConfig::decode(&bad).is_err());
    }

    #[test]
    fn lr_fhss_roundtrip() {
        let cfg = LrFhssConfig {
            freq_hz: 915_000_000,
            bw: LrFhssBandwidth::Khz136,
            cr: LrFhssCodingRate::Cr2_3,
            grid: LrFhssGrid::Khz25,
            hopping: true,
            tx_power_dbm: 14,
        };
        let mut buf = [0u8; 10];
        let n = cfg.encode(&mut buf).unwrap();
        assert_eq!(n, 10);
        let decoded = LrFhssConfig::decode(&buf[..n]).unwrap();
        assert_eq!(decoded, cfg);
        assert_eq!(buf[9], 0, "reserved byte must serialize as 0");
    }

    #[test]
    fn lr_fhss_rejects_nonzero_reserved() {
        let cfg = LrFhssConfig {
            freq_hz: 0,
            bw: LrFhssBandwidth::Khz39,
            cr: LrFhssCodingRate::Cr1_3,
            grid: LrFhssGrid::Khz25,
            hopping: false,
            tx_power_dbm: 0,
        };
        let mut buf = [0u8; 10];
        cfg.encode(&mut buf).unwrap();
        buf[9] = 1;
        assert!(LrFhssConfig::decode(&buf).is_err());
    }

    #[test]
    fn flrc_roundtrip() {
        let cfg = FlrcConfig {
            freq_hz: 2_400_000_000,
            bitrate: FlrcBitrate::Kbps1300,
            cr: FlrcCodingRate::Cr3_4,
            bt: FlrcBt::Bt0_5,
            preamble_len: FlrcPreambleLen::Bits24,
            sync_word: 0x1234_5678,
            tx_power_dbm: 10,
        };
        let mut buf = [0u8; 13];
        let n = cfg.encode(&mut buf).unwrap();
        assert_eq!(n, 13);
        let decoded = FlrcConfig::decode(&buf[..n]).unwrap();
        assert_eq!(decoded, cfg);
    }

    #[test]
    fn modulation_sum_roundtrip() {
        let m = Modulation::LoRa(sample_lora());
        let mut buf = [0u8; 64];
        let n = m.encode(&mut buf).unwrap();
        // id byte + LoRa wire size
        assert_eq!(n, 1 + LoRaConfig::WIRE_SIZE);
        assert_eq!(buf[0], ModulationId::LoRa.as_u8());
        let decoded = Modulation::decode(&buf[..n]).unwrap();
        assert_eq!(decoded, m);
    }

    #[test]
    fn modulation_id_rejects_unknown() {
        assert!(matches!(
            Modulation::decode(&[0x05, 0, 0, 0]),
            Err(ModulationParseError::UnknownModulation)
        ));
        assert!(matches!(
            Modulation::decode(&[]),
            Err(ModulationParseError::TooShort)
        ));
    }

    #[test]
    fn lora_bandwidth_hz_table() {
        // Cross-check §10.1 table values.
        assert_eq!(LoRaBandwidth::Khz125.as_hz(), 125_000);
        assert_eq!(LoRaBandwidth::Khz500.as_hz(), 500_000);
        assert_eq!(LoRaBandwidth::Khz7.as_hz(), 7_810);
        assert_eq!(LoRaBandwidth::Khz1600.as_hz(), 1_600_000);
    }
}
