//! Conversion from CSV cell strings to the integer values under check.
//! Supports single columns (dec / hex / 0x-prefixed / integer-valued doubles)
//! and fixed-byte-width multi-column values combined as LE or BE.

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Radix {
    Dec,
    Hex,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Endian {
    Le,
    Be,
}

/// Largest integer an f64 can represent exactly (2^53). Double notation
/// beyond this is rejected because the intended value would be ambiguous.
const F64_EXACT_INT_MAX: f64 = 9_007_199_254_740_992.0;

/// Interprets a single cell as one value.
/// Dec: an i128 integer, or integer-valued float notation ("3.0", "1e3", ...).
/// Hex: unsigned hexadecimal with an optional "0x" prefix.
pub fn parse_single(cell: &str, radix: Radix) -> Result<i128, String> {
    let s = cell.trim();
    if s.is_empty() {
        return Err("empty cell".into());
    }
    match radix {
        Radix::Dec => parse_dec(s),
        Radix::Hex => {
            let v = parse_hex_cell(s)?;
            i128::try_from(v).map_err(|_| format!("hex value {s:?} exceeds i128 range"))
        }
    }
}

fn parse_dec(s: &str) -> Result<i128, String> {
    if let Ok(v) = s.parse::<i128>() {
        return Ok(v);
    }
    let f: f64 = s
        .parse()
        .map_err(|_| format!("invalid decimal value {s:?}"))?;
    if !f.is_finite() || f.fract() != 0.0 {
        return Err(format!("non-integer value {s:?}"));
    }
    if f.abs() > F64_EXACT_INT_MAX {
        return Err(format!("value {s:?} loses integer precision as double"));
    }
    Ok(f as i128)
}

fn parse_hex_cell(s: &str) -> Result<u128, String> {
    let digits = s
        .strip_prefix("0x")
        .or_else(|| s.strip_prefix("0X"))
        .unwrap_or(s);
    if digits.is_empty() || !digits.bytes().all(|b| b.is_ascii_hexdigit()) {
        return Err(format!("invalid hex value {s:?}"));
    }
    u128::from_str_radix(digits, 16).map_err(|_| format!("hex value {s:?} exceeds 128 bits"))
}

/// Infers the byte width from the apparent digit count of a hex cell
/// ("1A2B" → 2). Always 1 for Dec.
pub fn infer_col_bytes(cell: &str, radix: Radix) -> u32 {
    match radix {
        Radix::Dec => 1,
        Radix::Hex => {
            let s = cell.trim();
            let digits = s
                .strip_prefix("0x")
                .or_else(|| s.strip_prefix("0X"))
                .unwrap_or(s);
            (digits.len().max(1) as u32).div_ceil(2)
        }
    }
}

/// Combines multiple cells of fixed byte width `col_bytes` into one value
/// according to `endian`. Every cell must be unsigned and fit in `col_bytes`.
pub fn parse_multi(
    cells: &[&str],
    radix: Radix,
    endian: Endian,
    col_bytes: u32,
) -> Result<i128, String> {
    debug_assert!(col_bytes >= 1);
    let mut acc: u128 = 0;
    let ordered: Box<dyn Iterator<Item = &&str>> = match endian {
        Endian::Be => Box::new(cells.iter()),
        Endian::Le => Box::new(cells.iter().rev()),
    };
    for cell in ordered {
        let v = parse_cell_unsigned(cell, radix)?;
        if col_bytes < 16 && v >= 1u128 << (col_bytes * 8) {
            return Err(format!("cell {cell:?} does not fit in {col_bytes} byte(s)"));
        }
        acc = acc
            .checked_shl(col_bytes * 8)
            .filter(|_| acc >> (128 - col_bytes * 8) == 0)
            .map(|shifted| shifted | v)
            .ok_or_else(|| "combined value exceeds 128 bits".to_string())?;
    }
    i128::try_from(acc).map_err(|_| "combined value exceeds i128 range".to_string())
}

fn parse_cell_unsigned(cell: &str, radix: Radix) -> Result<u128, String> {
    let s = cell.trim();
    if s.is_empty() {
        return Err("empty cell".into());
    }
    match radix {
        Radix::Hex => parse_hex_cell(s),
        Radix::Dec => s
            .parse::<u128>()
            .map_err(|_| format!("invalid unsigned decimal value {s:?}")),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn dec_parses_integers() {
        assert_eq!(parse_single("42", Radix::Dec), Ok(42));
        assert_eq!(parse_single("-5", Radix::Dec), Ok(-5));
        assert_eq!(parse_single(" 7 ", Radix::Dec), Ok(7));
    }

    #[test]
    fn dec_parses_integer_valued_doubles() {
        assert_eq!(parse_single("3.0", Radix::Dec), Ok(3));
        assert_eq!(parse_single("-2.000", Radix::Dec), Ok(-2));
        assert_eq!(parse_single("1e3", Radix::Dec), Ok(1000));
    }

    #[test]
    fn dec_rejects_fractional_and_junk() {
        assert!(parse_single("3.5", Radix::Dec).is_err());
        assert!(parse_single("abc", Radix::Dec).is_err());
        assert!(parse_single("", Radix::Dec).is_err());
        assert!(parse_single("NaN", Radix::Dec).is_err());
    }

    #[test]
    fn dec_rejects_imprecise_doubles() {
        assert!(parse_single("1e30", Radix::Dec).is_err());
    }

    #[test]
    fn hex_parses_with_and_without_prefix() {
        assert_eq!(parse_single("ff", Radix::Hex), Ok(255));
        assert_eq!(parse_single("0xFF", Radix::Hex), Ok(255));
        assert_eq!(parse_single("0X1a", Radix::Hex), Ok(26));
    }

    #[test]
    fn hex_rejects_invalid() {
        assert!(parse_single("0x", Radix::Hex).is_err());
        assert!(parse_single("zz", Radix::Hex).is_err());
        assert!(parse_single("-1", Radix::Hex).is_err());
    }

    #[test]
    fn multi_hex_combines_big_endian() {
        assert_eq!(
            parse_multi(&["12", "34"], Radix::Hex, Endian::Be, 1),
            Ok(0x1234)
        );
    }

    #[test]
    fn multi_hex_combines_little_endian() {
        assert_eq!(
            parse_multi(&["12", "34"], Radix::Hex, Endian::Le, 1),
            Ok(0x3412)
        );
    }

    #[test]
    fn multi_hex_two_byte_columns() {
        assert_eq!(
            parse_multi(&["0001", "0203"], Radix::Hex, Endian::Be, 2),
            Ok(0x0001_0203)
        );
    }

    #[test]
    fn multi_dec_combines_as_bytes() {
        assert_eq!(parse_multi(&["1", "0"], Radix::Dec, Endian::Be, 1), Ok(256));
        assert_eq!(parse_multi(&["1", "0"], Radix::Dec, Endian::Le, 1), Ok(1));
    }

    #[test]
    fn multi_rejects_cell_exceeding_width() {
        assert!(parse_multi(&["1FF", "00"], Radix::Hex, Endian::Be, 1).is_err());
        assert!(parse_multi(&["256", "0"], Radix::Dec, Endian::Be, 1).is_err());
    }

    #[test]
    fn multi_rejects_negative_dec() {
        assert!(parse_multi(&["-1", "0"], Radix::Dec, Endian::Be, 1).is_err());
    }

    #[test]
    fn infer_col_bytes_from_hex_digits() {
        assert_eq!(infer_col_bytes("1A2B", Radix::Hex), 2);
        assert_eq!(infer_col_bytes("0xAB", Radix::Hex), 1);
        assert_eq!(infer_col_bytes("A", Radix::Hex), 1);
        assert_eq!(infer_col_bytes("123", Radix::Dec), 1);
    }
}
