/// EIS4 SysEx Protocol — manufacturer ID 0x7D (non-commercial)

pub const SYSEX_MFR: u8 = 0x7D;

/* ESP32 → Cue */
pub const RSP_SWEEP_START: u8 = 0x01;
pub const RSP_DATA_POINT: u8 = 0x02;
pub const RSP_SWEEP_END: u8 = 0x03;
pub const RSP_CONFIG: u8 = 0x04;
pub const RSP_LSV_START: u8 = 0x05;
pub const RSP_LSV_POINT: u8 = 0x06;
pub const RSP_LSV_END: u8 = 0x07;
pub const RSP_AMP_START: u8 = 0x08;
pub const RSP_AMP_POINT: u8 = 0x09;
pub const RSP_AMP_END: u8 = 0x0A;
pub const RSP_CL_START: u8 = 0x0B;
pub const RSP_CL_POINT: u8 = 0x0C;
pub const RSP_CL_RESULT: u8 = 0x0D;
pub const RSP_CL_END: u8 = 0x0E;
pub const RSP_PH_RESULT: u8 = 0x0F;
pub const RSP_TEMP: u8 = 0x10;
pub const RSP_REF_FRAME: u8 = 0x20;
pub const RSP_REF_LP_RANGE: u8 = 0x21;
pub const RSP_REFS_DONE: u8 = 0x22;
pub const RSP_REF_STATUS: u8 = 0x23;

/* Cue → ESP32 */
pub const CMD_SET_SWEEP: u8 = 0x10;
pub const CMD_SET_RTIA: u8 = 0x11;
pub const CMD_SET_RCAL: u8 = 0x12;
pub const CMD_START_SWEEP: u8 = 0x13;
pub const CMD_GET_CONFIG: u8 = 0x14;
pub const CMD_SET_ELECTRODE: u8 = 0x15;
pub const CMD_START_LSV: u8 = 0x20;
pub const CMD_START_AMP: u8 = 0x21;
pub const CMD_STOP_AMP: u8 = 0x22;

pub const CMD_GET_TEMP: u8 = 0x17;
pub const CMD_START_CL: u8 = 0x23;
pub const CMD_START_PH: u8 = 0x24;
pub const CMD_START_CLEAN: u8 = 0x25;
pub const CMD_START_REFS: u8 = 0x30;
pub const CMD_GET_REFS: u8 = 0x31;
pub const CMD_CLEAR_REFS: u8 = 0x32;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Rtia {
    R200, R1K, R5K, R10K, R20K, R40K, R80K, R160K, ExtDe0,
}

impl Rtia {
    pub fn from_byte(b: u8) -> Option<Self> {
        Some(match b {
            0 => Self::R200, 1 => Self::R1K, 2 => Self::R5K, 3 => Self::R10K,
            4 => Self::R20K, 5 => Self::R40K, 6 => Self::R80K, 7 => Self::R160K,
            8 => Self::ExtDe0,
            _ => return None,
        })
    }
    pub fn as_byte(self) -> u8 { self as u8 }
    pub fn label(self) -> &'static str {
        match self {
            Self::R200 => "200Ω", Self::R1K => "1kΩ", Self::R5K => "5kΩ",
            Self::R10K => "10kΩ", Self::R20K => "20kΩ", Self::R40K => "40kΩ",
            Self::R80K => "80kΩ", Self::R160K => "160kΩ", Self::ExtDe0 => "Ext 3kΩ (DE0)",
        }
    }
    pub const ALL: &[Self] = &[
        Self::R200, Self::R1K, Self::R5K, Self::R10K,
        Self::R20K, Self::R40K, Self::R80K, Self::R160K, Self::ExtDe0,
    ];
}

impl std::fmt::Display for Rtia {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(self.label())
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Rcal {
    R200, R3K,
}

impl Rcal {
    pub fn from_byte(b: u8) -> Option<Self> {
        Some(match b { 0 => Self::R200, 1 => Self::R3K, _ => return None })
    }
    pub fn as_byte(self) -> u8 { self as u8 }
    pub fn label(self) -> &'static str {
        match self { Self::R200 => "200Ω (RCAL0↔RCAL1)", Self::R3K => "3kΩ (RCAL0↔AIN0)" }
    }
    pub const ALL: &[Self] = &[Self::R200, Self::R3K];
}

impl std::fmt::Display for Rcal {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(self.label())
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Electrode {
    FourWire, ThreeWire,
}

impl Electrode {
    pub fn from_byte(b: u8) -> Option<Self> {
        Some(match b { 0 => Self::FourWire, 1 => Self::ThreeWire, _ => return None })
    }
    pub fn as_byte(self) -> u8 { self as u8 }
    pub fn label(self) -> &'static str {
        match self {
            Self::FourWire => "4-wire (AIN)",
            Self::ThreeWire => "3-wire (CE0/RE0/SE0)",
        }
    }
    pub const ALL: &[Self] = &[Self::FourWire, Self::ThreeWire];
}

impl std::fmt::Display for Electrode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(self.label())
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LpRtia {
    R200, R1K, R2K, R3K, R4K, R6K, R8K, R10K, R12K, R16K,
    R20K, R24K, R30K, R32K, R40K, R48K, R64K, R85K, R96K,
    R100K, R120K, R128K, R160K, R196K, R256K, R512K,
}

impl LpRtia {
    #[allow(dead_code)]
    pub fn from_byte(b: u8) -> Option<Self> {
        Some(match b {
            0 => Self::R200,  1 => Self::R1K,   2 => Self::R2K,   3 => Self::R3K,
            4 => Self::R4K,   5 => Self::R6K,   6 => Self::R8K,   7 => Self::R10K,
            8 => Self::R12K,  9 => Self::R16K,  10 => Self::R20K, 11 => Self::R24K,
            12 => Self::R30K, 13 => Self::R32K, 14 => Self::R40K, 15 => Self::R48K,
            16 => Self::R64K, 17 => Self::R85K, 18 => Self::R96K, 19 => Self::R100K,
            20 => Self::R120K, 21 => Self::R128K, 22 => Self::R160K, 23 => Self::R196K,
            24 => Self::R256K, 25 => Self::R512K,
            _ => return None,
        })
    }
    pub fn as_byte(self) -> u8 { self as u8 }
    pub fn label(self) -> &'static str {
        match self {
            Self::R200 => "200Ω",  Self::R1K => "1kΩ",    Self::R2K => "2kΩ",
            Self::R3K => "3kΩ",    Self::R4K => "4kΩ",    Self::R6K => "6kΩ",
            Self::R8K => "8kΩ",    Self::R10K => "10kΩ",  Self::R12K => "12kΩ",
            Self::R16K => "16kΩ",  Self::R20K => "20kΩ",  Self::R24K => "24kΩ",
            Self::R30K => "30kΩ",  Self::R32K => "32kΩ",  Self::R40K => "40kΩ",
            Self::R48K => "48kΩ",  Self::R64K => "64kΩ",  Self::R85K => "85kΩ",
            Self::R96K => "96kΩ",  Self::R100K => "100kΩ", Self::R120K => "120kΩ",
            Self::R128K => "128kΩ", Self::R160K => "160kΩ", Self::R196K => "196kΩ",
            Self::R256K => "256kΩ", Self::R512K => "512kΩ",
        }
    }
    pub const ALL: &[Self] = &[
        Self::R200, Self::R1K, Self::R2K, Self::R3K, Self::R4K,
        Self::R6K, Self::R8K, Self::R10K, Self::R12K, Self::R16K,
        Self::R20K, Self::R24K, Self::R30K, Self::R32K, Self::R40K,
        Self::R48K, Self::R64K, Self::R85K, Self::R96K, Self::R100K,
        Self::R120K, Self::R128K, Self::R160K, Self::R196K, Self::R256K, Self::R512K,
    ];
}

impl std::fmt::Display for LpRtia {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(self.label())
    }
}

#[derive(Debug, Clone)]
pub struct EisPoint {
    pub freq_hz: f32,
    pub mag_ohms: f32,
    pub phase_deg: f32,
    pub z_real: f32,
    pub z_imag: f32,
    pub rtia_mag_before: f32,
    pub rtia_mag_after: f32,
    pub rev_mag: f32,
    pub rev_phase: f32,
    pub pct_err: f32,
}

#[derive(Debug, Clone)]
pub struct LsvPoint {
    pub v_mv: f32,
    pub i_ua: f32,
}

#[derive(Debug, Clone)]
pub struct AmpPoint {
    pub t_ms: f32,
    pub i_ua: f32,
}

#[derive(Debug, Clone)]
pub struct ClPoint {
    pub t_ms: f32,
    pub i_ua: f32,
    pub phase: u8,
}

#[derive(Debug, Clone)]
pub struct ClResult {
    pub i_free_ua: f32,
    pub i_total_ua: f32,
}

#[derive(Debug, Clone)]
pub struct PhResult {
    pub v_ocp_mv: f32,
    pub ph: f32,
    pub temp_c: f32,
}

#[derive(Debug, Clone)]
pub struct EisConfig {
    pub freq_start: f32,
    pub freq_stop: f32,
    pub ppd: u16,
    pub rtia: Rtia,
    pub rcal: Rcal,
    pub electrode: Electrode,
}

#[derive(Debug, Clone)]
pub enum EisMessage {
    SweepStart { num_points: u16, freq_start: f32, freq_stop: f32 },
    DataPoint { _index: u16, point: EisPoint },
    SweepEnd,
    Config(EisConfig),
    LsvStart { num_points: u16, v_start: f32, v_stop: f32 },
    LsvPoint { _index: u16, point: LsvPoint },
    LsvEnd,
    AmpStart { v_hold: f32 },
    AmpPoint { _index: u16, point: AmpPoint },
    AmpEnd,
    ClStart { num_points: u16 },
    ClPoint { _index: u16, point: ClPoint },
    ClResult(ClResult),
    ClEnd,
    PhResult(PhResult),
    Temperature(f32),
    RefFrame { mode: u8, rtia_idx: u8 },
    RefLpRange { mode: u8, low_idx: u8, high_idx: u8 },
    RefsDone,
    RefStatus { has_refs: bool },
}

fn decode_u16(data: &[u8]) -> u16 {
    let b0 = data[1] | ((data[0] & 1) << 7);
    let b1 = data[2] | ((data[0] & 2) << 6);
    u16::from_le_bytes([b0, b1])
}

fn decode_float(data: &[u8]) -> f32 {
    let m = data[0];
    let b0 = data[1] | ((m & 1) << 7);
    let b1 = data[2] | ((m & 2) << 6);
    let b2 = data[3] | ((m & 4) << 5);
    let b3 = data[4] | ((m & 8) << 4);
    f32::from_le_bytes([b0, b1, b2, b3])
}

fn encode_float(val: f32) -> [u8; 5] {
    let p = val.to_le_bytes();
    [
        ((p[0] >> 7) & 1) | ((p[1] >> 6) & 2) | ((p[2] >> 5) & 4) | ((p[3] >> 4) & 8),
        p[0] & 0x7F, p[1] & 0x7F, p[2] & 0x7F, p[3] & 0x7F,
    ]
}

fn encode_u16(val: u16) -> [u8; 3] {
    let p = val.to_le_bytes();
    [((p[0] >> 7) & 1) | ((p[1] >> 6) & 2), p[0] & 0x7F, p[1] & 0x7F]
}

pub fn parse_sysex(data: &[u8]) -> Option<EisMessage> {
    if data.len() < 2 || data[0] != SYSEX_MFR { return None; }
    match data[1] {
        RSP_SWEEP_START if data.len() >= 15 => {
            let p = &data[2..];
            Some(EisMessage::SweepStart {
                num_points: decode_u16(&p[0..3]),
                freq_start: decode_float(&p[3..8]),
                freq_stop: decode_float(&p[8..13]),
            })
        }
        RSP_DATA_POINT if data.len() >= 30 => {
            let p = &data[2..];
            let ext = p.len() >= 53;
            Some(EisMessage::DataPoint {
                _index: decode_u16(&p[0..3]),
                point: EisPoint {
                    freq_hz: decode_float(&p[3..8]),
                    mag_ohms: decode_float(&p[8..13]),
                    phase_deg: decode_float(&p[13..18]),
                    z_real: decode_float(&p[18..23]),
                    z_imag: decode_float(&p[23..28]),
                    rtia_mag_before: if ext { decode_float(&p[28..33]) } else { 0.0 },
                    rtia_mag_after:  if ext { decode_float(&p[33..38]) } else { 0.0 },
                    rev_mag:         if ext { decode_float(&p[38..43]) } else { 0.0 },
                    rev_phase:       if ext { decode_float(&p[43..48]) } else { 0.0 },
                    pct_err:         if ext { decode_float(&p[48..53]) } else { 0.0 },
                },
            })
        }
        RSP_SWEEP_END => Some(EisMessage::SweepEnd),
        RSP_CONFIG if data.len() >= 18 => {
            let p = &data[2..];
            Some(EisMessage::Config(EisConfig {
                freq_start: decode_float(&p[0..5]),
                freq_stop: decode_float(&p[5..10]),
                ppd: decode_u16(&p[10..13]),
                rtia: Rtia::from_byte(p[13]).unwrap_or(Rtia::R5K),
                rcal: Rcal::from_byte(p[14]).unwrap_or(Rcal::R3K),
                electrode: Electrode::from_byte(p[15]).unwrap_or(Electrode::FourWire),
            }))
        }
        RSP_LSV_START if data.len() >= 15 => {
            let p = &data[2..];
            Some(EisMessage::LsvStart {
                num_points: decode_u16(&p[0..3]),
                v_start: decode_float(&p[3..8]),
                v_stop: decode_float(&p[8..13]),
            })
        }
        RSP_LSV_POINT if data.len() >= 15 => {
            let p = &data[2..];
            Some(EisMessage::LsvPoint {
                _index: decode_u16(&p[0..3]),
                point: LsvPoint {
                    v_mv: decode_float(&p[3..8]),
                    i_ua: decode_float(&p[8..13]),
                },
            })
        }
        RSP_LSV_END => Some(EisMessage::LsvEnd),
        RSP_AMP_START if data.len() >= 7 => {
            let p = &data[2..];
            Some(EisMessage::AmpStart { v_hold: decode_float(&p[0..5]) })
        }
        RSP_AMP_POINT if data.len() >= 15 => {
            let p = &data[2..];
            Some(EisMessage::AmpPoint {
                _index: decode_u16(&p[0..3]),
                point: AmpPoint {
                    t_ms: decode_float(&p[3..8]),
                    i_ua: decode_float(&p[8..13]),
                },
            })
        }
        RSP_AMP_END => Some(EisMessage::AmpEnd),
        RSP_CL_START if data.len() >= 5 => {
            let p = &data[2..];
            Some(EisMessage::ClStart { num_points: decode_u16(&p[0..3]) })
        }
        RSP_CL_POINT if data.len() >= 16 => {
            let p = &data[2..];
            Some(EisMessage::ClPoint {
                _index: decode_u16(&p[0..3]),
                point: ClPoint {
                    t_ms: decode_float(&p[3..8]),
                    i_ua: decode_float(&p[8..13]),
                    phase: p[13],
                },
            })
        }
        RSP_CL_RESULT if data.len() >= 12 => {
            let p = &data[2..];
            Some(EisMessage::ClResult(ClResult {
                i_free_ua: decode_float(&p[0..5]),
                i_total_ua: decode_float(&p[5..10]),
            }))
        }
        RSP_CL_END => Some(EisMessage::ClEnd),
        RSP_TEMP if data.len() >= 7 => {
            let p = &data[2..];
            Some(EisMessage::Temperature(decode_float(&p[0..5])))
        }
        RSP_PH_RESULT if data.len() >= 17 => {
            let p = &data[2..];
            Some(EisMessage::PhResult(PhResult {
                v_ocp_mv: decode_float(&p[0..5]),
                ph: decode_float(&p[5..10]),
                temp_c: decode_float(&p[10..15]),
            }))
        }
        RSP_REF_FRAME if data.len() >= 4 => {
            Some(EisMessage::RefFrame { mode: data[2], rtia_idx: data[3] })
        }
        RSP_REF_LP_RANGE if data.len() >= 5 => {
            Some(EisMessage::RefLpRange { mode: data[2], low_idx: data[3], high_idx: data[4] })
        }
        RSP_REFS_DONE => Some(EisMessage::RefsDone),
        RSP_REF_STATUS if data.len() >= 3 => {
            Some(EisMessage::RefStatus { has_refs: data[2] != 0 })
        }
        _ => None,
    }
}

pub fn build_sysex_set_sweep(freq_start: f32, freq_stop: f32, ppd: u16) -> Vec<u8> {
    let mut sx = vec![0xF0, SYSEX_MFR, CMD_SET_SWEEP];
    sx.extend_from_slice(&encode_float(freq_start));
    sx.extend_from_slice(&encode_float(freq_stop));
    sx.extend_from_slice(&encode_u16(ppd));
    sx.push(0xF7);
    sx
}

pub fn build_sysex_set_rtia(rtia: Rtia) -> Vec<u8> {
    vec![0xF0, SYSEX_MFR, CMD_SET_RTIA, rtia.as_byte(), 0xF7]
}

pub fn build_sysex_set_rcal(rcal: Rcal) -> Vec<u8> {
    vec![0xF0, SYSEX_MFR, CMD_SET_RCAL, rcal.as_byte(), 0xF7]
}

pub fn build_sysex_set_electrode(e: Electrode) -> Vec<u8> {
    vec![0xF0, SYSEX_MFR, CMD_SET_ELECTRODE, e.as_byte(), 0xF7]
}

pub fn build_sysex_start_sweep() -> Vec<u8> {
    vec![0xF0, SYSEX_MFR, CMD_START_SWEEP, 0xF7]
}

pub fn build_sysex_get_config() -> Vec<u8> {
    vec![0xF0, SYSEX_MFR, CMD_GET_CONFIG, 0xF7]
}

pub fn build_sysex_start_lsv(v_start: f32, v_stop: f32, scan_rate: f32, lp_rtia: LpRtia) -> Vec<u8> {
    let mut sx = vec![0xF0, SYSEX_MFR, CMD_START_LSV];
    sx.extend_from_slice(&encode_float(v_start));
    sx.extend_from_slice(&encode_float(v_stop));
    sx.extend_from_slice(&encode_float(scan_rate));
    sx.push(lp_rtia.as_byte());
    sx.push(0xF7);
    sx
}

pub fn build_sysex_start_amp(v_hold: f32, interval_ms: f32, duration_s: f32, lp_rtia: LpRtia) -> Vec<u8> {
    let mut sx = vec![0xF0, SYSEX_MFR, CMD_START_AMP];
    sx.extend_from_slice(&encode_float(v_hold));
    sx.extend_from_slice(&encode_float(interval_ms));
    sx.extend_from_slice(&encode_float(duration_s));
    sx.push(lp_rtia.as_byte());
    sx.push(0xF7);
    sx
}

pub fn build_sysex_stop_amp() -> Vec<u8> {
    vec![0xF0, SYSEX_MFR, CMD_STOP_AMP, 0xF7]
}

pub fn build_sysex_start_cl(
    v_cond: f32, t_cond_ms: f32, v_free: f32, v_total: f32,
    t_dep_ms: f32, t_meas_ms: f32, lp_rtia: LpRtia,
) -> Vec<u8> {
    let mut sx = vec![0xF0, SYSEX_MFR, CMD_START_CL];
    sx.extend_from_slice(&encode_float(v_cond));
    sx.extend_from_slice(&encode_float(t_cond_ms));
    sx.extend_from_slice(&encode_float(v_free));
    sx.extend_from_slice(&encode_float(v_total));
    sx.extend_from_slice(&encode_float(t_dep_ms));
    sx.extend_from_slice(&encode_float(t_meas_ms));
    sx.push(lp_rtia.as_byte());
    sx.push(0xF7);
    sx
}

pub fn build_sysex_get_temp() -> Vec<u8> {
    vec![0xF0, SYSEX_MFR, CMD_GET_TEMP, 0xF7]
}

pub fn build_sysex_start_ph(stabilize_s: f32) -> Vec<u8> {
    let mut sx = vec![0xF0, SYSEX_MFR, CMD_START_PH];
    sx.extend_from_slice(&encode_float(stabilize_s));
    sx.push(0xF7);
    sx
}

pub fn build_sysex_start_clean(v_mv: f32, duration_s: f32) -> Vec<u8> {
    let mut sx = vec![0xF0, SYSEX_MFR, CMD_START_CLEAN];
    sx.extend_from_slice(&encode_float(v_mv));
    sx.extend_from_slice(&encode_float(duration_s));
    sx.push(0xF7);
    sx
}

pub fn build_sysex_start_refs() -> Vec<u8> {
    vec![0xF0, SYSEX_MFR, CMD_START_REFS, 0xF7]
}

pub fn build_sysex_get_refs() -> Vec<u8> {
    vec![0xF0, SYSEX_MFR, CMD_GET_REFS, 0xF7]
}

pub fn build_sysex_clear_refs() -> Vec<u8> {
    vec![0xF0, SYSEX_MFR, CMD_CLEAR_REFS, 0xF7]
}
