// Typed value constants (Android TypedValue)
#![allow(dead_code)]
pub(crate) const TYPE_NULL: u8 = 0x00;
pub(crate) const TYPE_REFERENCE: u8 = 0x01;
pub(crate) const TYPE_ATTRIBUTE: u8 = 0x02;
pub(crate) const TYPE_STRING: u8 = 0x03;
pub(crate) const TYPE_FLOAT: u8 = 0x04;
pub(crate) const TYPE_DIMENSION: u8 = 0x05;
pub(crate) const TYPE_FRACTION: u8 = 0x06;
pub(crate) const TYPE_INT_DEC: u8 = 0x10;
pub(crate) const TYPE_INT_HEX: u8 = 0x11;
pub(crate) const TYPE_INT_BOOLEAN: u8 = 0x12;
pub(crate) const TYPE_INT_COLOR_ARGB8: u8 = 0x1c;
pub(crate) const TYPE_INT_COLOR_RGB8: u8 = 0x1d;
pub(crate) const TYPE_INT_COLOR_ARGB4: u8 = 0x1e;
pub(crate) const TYPE_INT_COLOR_RGB4: u8 = 0x1f;

use crate::string_pool::StringPool;

// Complex value constants
const COMPLEX_UNIT_SHIFT: u32 = 0;
const COMPLEX_UNIT_MASK: u32 = 0xf;
const COMPLEX_RADIX_SHIFT: u32 = 4;
const COMPLEX_RADIX_MASK: u32 = 0x3;
const COMPLEX_MANTISSA_SHIFT: u32 = 8;
const COMPLEX_MANTISSA_MASK: u32 = 0xff_ffff;

const DIMENSION_UNIT_STRS: &[&str] = &["px", "dip", "sp", "pt", "in", "mm"];
const FRACTION_UNIT_STRS: &[&str] = &["%", "%p"];

const RADIX_MULTS: [f64; 4] = [
    1.0 / 256.0,
    1.0 / 32768.0,      // 1 / (1 << 15) / 256
    1.0 / 8388608.0,    // 1 / (1 << 23) / 256
    1.0 / 2147483648.0, // 1 / (1 << 31) / 256
];

/// Extract the type byte from a packed type field (upper 8 bits)
pub(crate) fn get_type(t: u32) -> u8 {
    (t >> 24) as u8
}

/// Convert complex encoded integer to float value
pub(crate) fn complex_to_float(complex: u32) -> f64 {
    let mantissa = (complex >> COMPLEX_MANTISSA_SHIFT) & COMPLEX_MANTISSA_MASK;
    let radix = ((complex >> COMPLEX_RADIX_SHIFT) & COMPLEX_RADIX_MASK) as usize;
    (mantissa as f64) * RADIX_MULTS[radix]
}

/// Convert float+unit string to complex integer representation
pub(crate) fn float_to_complex(val: &str) -> Option<u32> {
    let (num_str, unit) = if let Some(s) = val.strip_suffix("dip") {
        (s, 1u32)
    } else if let Some(s) = val.strip_suffix("dp") {
        (s, 1u32)
    } else if let Some(s) = val.strip_suffix("sp") {
        (s, 2u32)
    } else if let Some(s) = val.strip_suffix("px") {
        (s, 0u32)
    } else if let Some(s) = val.strip_suffix("pt") {
        (s, 3u32)
    } else if let Some(s) = val.strip_suffix("in") {
        (s, 4u32)
    } else if let Some(s) = val.strip_suffix("mm") {
        (s, 5u32)
    } else if let Some(s) = val.strip_suffix('%') {
        (s, 0u32)
    } else {
        return None;
    };

    let f: f64 = num_str.parse().ok()?;

    let (base, radix) = if f.abs() < 1.0 {
        ((f * (1 << 23) as f64) as i64, 3u32)
    } else if f.abs() < 256.0 {
        ((f * (1 << 15) as f64) as i64, 2u32)
    } else if f.abs() < 65536.0 {
        ((f * (1 << 7) as f64) as i64, 1u32)
    } else {
        (f as i64, 0u32)
    };

    let base_u32 = base as u32;
    Some((base_u32 << 8) | (radix << 4) | unit)
}

/// Coerce a typed value to a displayable string.
pub(crate) fn coerce_to_string(type_byte: u8, data: u32) -> Option<String> {
    match type_byte {
        TYPE_NULL => Some("nil".to_string()),
        TYPE_REFERENCE => Some(format!("@{:x}", data)),
        TYPE_ATTRIBUTE => Some(format!("?{:x}", data)),
        TYPE_FLOAT => {
            let bits = data;
            let f = f32::from_bits(bits);
            Some(f.to_string())
        }
        TYPE_DIMENSION => {
            let val = complex_to_float(data);
            let unit_idx = ((data >> COMPLEX_UNIT_SHIFT) & COMPLEX_UNIT_MASK) as usize;
            let unit = DIMENSION_UNIT_STRS.get(unit_idx).unwrap_or(&"?");
            Some(format!("{}{}", val, unit))
        }
        TYPE_FRACTION => {
            let val = complex_to_float(data) * 100.0;
            let unit_idx = ((data >> COMPLEX_UNIT_SHIFT) & COMPLEX_UNIT_MASK) as usize;
            let unit = FRACTION_UNIT_STRS.get(unit_idx).unwrap_or(&"?");
            Some(format!("{}{}", val, unit))
        }
        TYPE_INT_HEX => Some(format!("0x{:x}", data)),
        TYPE_INT_BOOLEAN => Some(if data != 0 { "true" } else { "false" }.to_string()),
        t if (TYPE_INT_COLOR_ARGB8..=TYPE_INT_COLOR_RGB4).contains(&t) => {
            Some(format!("#{:x}", data))
        }
        t if (TYPE_INT_DEC..=0x1f).contains(&t) => {
            // Signed int
            Some((data as i32).to_string())
        }
        _ => None,
    }
}

/// Encode an attribute value string into (type_field, value_idx, data).
pub(crate) fn encode_attribute_value(
    val: &str,
    name: &str,
    pool: &mut StringPool,
) -> (u32, u32, u32) {
    if val == "true" {
        return (0x1200_0008, 0xffff_ffff, 1);
    }
    if val == "false" {
        return (0x1200_0008, 0xffff_ffff, 0);
    }

    if let Some(rest) = val.strip_prefix("@android:") {
        if let Ok(id) = u32::from_str_radix(rest, 16) {
            return (0x0100_0008, 0xffff_ffff, id);
        }
    }
    if let Some(rest) = val.strip_prefix('@') {
        if let Ok(id) = u32::from_str_radix(rest, 16) {
            return (0x0100_0008, 0xffff_ffff, id);
        }
    }
    if let Some(rest) = val.strip_prefix("0x") {
        if let Ok(v) = u32::from_str_radix(rest, 16) {
            return (0x1100_0000, 0xffff_ffff, v);
        }
    }
    if let Some(rest) = val.strip_prefix('#') {
        if let Ok(v) = u32::from_str_radix(rest, 16) {
            return (0x1c00_0008, 0xffff_ffff, v);
        }
    }
    if let Some(rest) = val.strip_prefix('?') {
        if let Ok(v) = u32::from_str_radix(rest, 16) {
            return (0x0200_0008, 0xffff_ffff, v);
        }
    }

    if let Some(complex) = float_to_complex(val) {
        return (0x0500_0008, 0xffff_ffff, complex);
    }

    // Special string attributes
    if name == "versionName" || name == "compileSdkVersionCodename" {
        let idx = pool.add(val);
        return (0x0300_0008, idx, idx);
    }

    if let Ok(v) = val.parse::<i64>() {
        return (0x1000_0008, 0xffff_ffff, v as u32);
    }

    if let Ok(f) = val.parse::<f32>() {
        return (0x0400_0008, 0xffff_ffff, f.to_bits());
    }

    let idx = pool.add(val);
    (0x0300_0008, idx, idx)
}

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

    #[test]
    fn test_get_type() {
        assert_eq!(get_type(0x1200_0008), 0x12);
        assert_eq!(get_type(0x0300_0008), 0x03);
        assert_eq!(get_type(0x0000_0000), 0x00);
    }

    #[test]
    fn test_coerce_null() {
        assert_eq!(coerce_to_string(TYPE_NULL, 0), Some("nil".to_string()));
    }

    #[test]
    fn test_coerce_reference() {
        assert_eq!(
            coerce_to_string(TYPE_REFERENCE, 0x7f04_0001),
            Some("@7f040001".to_string())
        );
    }

    #[test]
    fn test_coerce_boolean() {
        assert_eq!(
            coerce_to_string(TYPE_INT_BOOLEAN, 1),
            Some("true".to_string())
        );
        assert_eq!(
            coerce_to_string(TYPE_INT_BOOLEAN, 0),
            Some("false".to_string())
        );
    }

    #[test]
    fn test_coerce_hex() {
        assert_eq!(
            coerce_to_string(TYPE_INT_HEX, 0xdead_beef),
            Some("0xdeadbeef".to_string())
        );
    }

    #[test]
    fn test_coerce_dec() {
        assert_eq!(
            coerce_to_string(TYPE_INT_DEC, 42u32),
            Some("42".to_string())
        );
        // Signed: 0xffff_ffff = -1 as i32
        assert_eq!(
            coerce_to_string(TYPE_INT_DEC, 0xffff_ffff),
            Some("-1".to_string())
        );
    }

    #[test]
    fn test_coerce_unknown_returns_none() {
        // 0x07 is not a known type constant
        assert_eq!(coerce_to_string(0x07, 0), None);
    }

    #[test]
    fn test_float_to_complex_px() {
        let encoded = float_to_complex("16px");
        assert!(encoded.is_some(), "16px should encode");
        // Unit bits for px = 0
        assert_eq!(encoded.unwrap() & 0xf, 0);
    }

    #[test]
    fn test_float_to_complex_dp() {
        let encoded = float_to_complex("8dp");
        assert!(encoded.is_some(), "8dp should encode");
        // Unit bits for dip/dp = 1
        assert_eq!(encoded.unwrap() & 0xf, 1);
    }

    #[test]
    fn test_float_to_complex_none() {
        assert_eq!(
            float_to_complex("16em"),
            None,
            "unknown unit should return None"
        );
        assert_eq!(float_to_complex("notanumber"), None);
    }
}
