use crate::error::AxmlError;

// XML element chunk type constants
pub(crate) const RES_XML_START_NAMESPACE_TYPE: u16 = 0x0100;
pub(crate) const RES_XML_END_NAMESPACE_TYPE: u16 = 0x0101;
pub(crate) const RES_XML_START_ELEMENT_TYPE: u16 = 0x0102;
pub(crate) const RES_XML_END_ELEMENT_TYPE: u16 = 0x0103;
pub(crate) const RES_XML_CDATA_TYPE: u16 = 0x0104;

/// A single attribute within a start element.
#[derive(Debug, Clone)]
pub struct Attribute {
    pub namespace_uri: u32, // String index for namespace URI (0xffff_ffff = none)
    pub name: u32,          // String index for attribute name
    pub value: u32,         // String index for raw value (0xffff_ffff if not a string)
    pub type_: u32,         // Encoded type (high 8 bits = type byte)
    pub data: u32,          // Value data
    /// Any extra padding bytes (when at_size > 0x14)
    pub padding: Vec<u8>,
}

impl Attribute {
    pub fn pack(&self) -> Vec<u8> {
        let mut out = Vec::with_capacity(20 + self.padding.len());
        out.extend_from_slice(&self.namespace_uri.to_le_bytes());
        out.extend_from_slice(&self.name.to_le_bytes());
        out.extend_from_slice(&self.value.to_le_bytes());
        out.extend_from_slice(&self.type_.to_le_bytes());
        out.extend_from_slice(&self.data.to_le_bytes());
        out.extend_from_slice(&self.padding);
        out
    }

    fn parse(data: &[u8], padding_bytes: usize) -> Result<Self, AxmlError> {
        if data.len() < 20 {
            return Err(AxmlError::UnexpectedEof);
        }
        let namespace_uri = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
        let name = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
        let value = u32::from_le_bytes([data[8], data[9], data[10], data[11]]);
        let type_ = u32::from_le_bytes([data[12], data[13], data[14], data[15]]);
        let attr_data = u32::from_le_bytes([data[16], data[17], data[18], data[19]]);
        let padding = if padding_bytes > 0 && data.len() >= 20 + padding_bytes {
            data[20..20 + padding_bytes].to_vec()
        } else {
            Vec::new()
        };
        Ok(Attribute {
            namespace_uri,
            name,
            value,
            type_,
            data: attr_data,
            padding,
        })
    }
}

/// An XML element as stored in the binary.
#[derive(Debug, Clone)]
pub enum XmlElement {
    StartNamespace {
        header_size: u16,
        chunk_size: u32,
        line_number: u32,
        comment: u32,
        prefix: u32, // String index for prefix name (e.g. "android")
        uri: u32,    // String index for namespace URI
    },
    EndNamespace {
        header_size: u16,
        chunk_size: u32,
        line_number: u32,
        comment: u32,
        prefix: u32,
        uri: u32,
    },
    StartElement {
        header_size: u16,
        chunk_size: u32,
        line_number: u32,
        comment: u32,
        namespace_uri: u32, // String index for element namespace URI
        name: u32,          // String index for element name
        at_start: i16,      // Attribute offset (usually 0x14)
        at_size: i16,       // Per-attribute size (usually 0x14 = 20 bytes)
        style_attribute: i16,
        class_attribute: i16,
        attributes: Vec<Attribute>,
    },
    EndElement {
        header_size: u16,
        chunk_size: u32,
        line_number: u32,
        comment: u32,
        namespace_uri: u32,
        name: u32,
    },
    CData {
        header_size: u16,
        chunk_size: u32,
        line_number: u32,
        comment: u32,
        name: u32, // String index for CDATA content
        // Res_value (8 bytes): preserved as-is
        res_size: u16,
        res_res0: u8,
        res_data_type: u8,
        res_data: u32,
    },
}

impl XmlElement {
    /// Serialize this element to bytes (without the 8-byte outer chunk header).
    pub fn pack_body(&self) -> Vec<u8> {
        match self {
            XmlElement::StartNamespace {
                line_number,
                comment,
                prefix,
                uri,
                ..
            }
            | XmlElement::EndNamespace {
                line_number,
                comment,
                prefix,
                uri,
                ..
            } => {
                let mut out = Vec::with_capacity(16);
                out.extend_from_slice(&line_number.to_le_bytes());
                out.extend_from_slice(&comment.to_le_bytes());
                out.extend_from_slice(&prefix.to_le_bytes());
                out.extend_from_slice(&uri.to_le_bytes());
                out
            }
            XmlElement::EndElement {
                line_number,
                comment,
                namespace_uri,
                name,
                ..
            } => {
                let mut out = Vec::with_capacity(16);
                out.extend_from_slice(&line_number.to_le_bytes());
                out.extend_from_slice(&comment.to_le_bytes());
                out.extend_from_slice(&namespace_uri.to_le_bytes());
                out.extend_from_slice(&name.to_le_bytes());
                out
            }
            XmlElement::StartElement {
                line_number,
                comment,
                namespace_uri,
                name,
                at_start,
                at_size,
                style_attribute,
                class_attribute,
                attributes,
                ..
            } => {
                let attr_count = attributes.len() as u32;
                // at_size - 0x14 = extra padding per attribute
                let attr_bytes: Vec<u8> = attributes.iter().flat_map(|a| a.pack()).collect();

                let mut out = Vec::new();
                out.extend_from_slice(&line_number.to_le_bytes());
                out.extend_from_slice(&comment.to_le_bytes());
                out.extend_from_slice(&namespace_uri.to_le_bytes());
                out.extend_from_slice(&name.to_le_bytes());
                out.extend_from_slice(&at_start.to_le_bytes());
                out.extend_from_slice(&at_size.to_le_bytes());
                out.extend_from_slice(&attr_count.to_le_bytes());
                out.extend_from_slice(&style_attribute.to_le_bytes());
                out.extend_from_slice(&class_attribute.to_le_bytes());
                out.extend_from_slice(&attr_bytes);
                out
            }
            XmlElement::CData {
                line_number,
                comment,
                name,
                res_size,
                res_res0,
                res_data_type,
                res_data,
                ..
            } => {
                let mut out = Vec::with_capacity(20);
                out.extend_from_slice(&line_number.to_le_bytes());
                out.extend_from_slice(&comment.to_le_bytes());
                out.extend_from_slice(&name.to_le_bytes());
                out.extend_from_slice(&res_size.to_le_bytes());
                out.push(*res_res0);
                out.push(*res_data_type);
                out.extend_from_slice(&res_data.to_le_bytes());
                out
            }
        }
    }

    /// Serialize with the 8-byte outer chunk header.
    pub fn pack(&self) -> Vec<u8> {
        let body = self.pack_body();
        let chunk_type = self.chunk_type();
        let header_size = self.header_size();
        let chunk_size = self.chunk_size();

        let mut out = Vec::with_capacity(8 + body.len());
        out.extend_from_slice(&chunk_type.to_le_bytes());
        out.extend_from_slice(&header_size.to_le_bytes());
        out.extend_from_slice(&chunk_size.to_le_bytes());
        out.extend_from_slice(&body);
        out
    }

    pub fn header_size(&self) -> u16 {
        match self {
            XmlElement::StartNamespace { header_size, .. } => *header_size,
            XmlElement::EndNamespace { header_size, .. } => *header_size,
            XmlElement::StartElement { header_size, .. } => *header_size,
            XmlElement::EndElement { header_size, .. } => *header_size,
            XmlElement::CData { header_size, .. } => *header_size,
        }
    }

    pub fn chunk_size(&self) -> u32 {
        match self {
            XmlElement::StartNamespace { chunk_size, .. } => *chunk_size,
            XmlElement::EndNamespace { chunk_size, .. } => *chunk_size,
            XmlElement::StartElement { chunk_size, .. } => *chunk_size,
            XmlElement::EndElement { chunk_size, .. } => *chunk_size,
            XmlElement::CData { chunk_size, .. } => *chunk_size,
        }
    }

    pub fn set_chunk_size(&mut self, chunk_size: u32) {
        match self {
            XmlElement::StartNamespace { chunk_size: cs, .. } => *cs = chunk_size,
            XmlElement::EndNamespace { chunk_size: cs, .. } => *cs = chunk_size,
            XmlElement::StartElement { chunk_size: cs, .. } => *cs = chunk_size,
            XmlElement::EndElement { chunk_size: cs, .. } => *cs = chunk_size,
            XmlElement::CData { chunk_size: cs, .. } => *cs = chunk_size,
        }
    }

    pub fn chunk_type(&self) -> u16 {
        match self {
            XmlElement::StartNamespace { .. } => RES_XML_START_NAMESPACE_TYPE,
            XmlElement::EndNamespace { .. } => RES_XML_END_NAMESPACE_TYPE,
            XmlElement::StartElement { .. } => RES_XML_START_ELEMENT_TYPE,
            XmlElement::EndElement { .. } => RES_XML_END_ELEMENT_TYPE,
            XmlElement::CData { .. } => RES_XML_CDATA_TYPE,
        }
    }

    /// Parse a sequence of XML elements from binary data.
    /// Returns (elements, bytes_consumed).
    pub fn parse_all(data: &[u8]) -> Result<Vec<Self>, AxmlError> {
        let mut elements = Vec::new();
        let mut pos = 0;

        while pos < data.len() {
            if pos + 8 > data.len() {
                break;
            }
            let chunk_type = u16::from_le_bytes([data[pos], data[pos + 1]]);
            let header_size = u16::from_le_bytes([data[pos + 2], data[pos + 3]]);
            let chunk_size =
                u32::from_le_bytes([data[pos + 4], data[pos + 5], data[pos + 6], data[pos + 7]]);

            if chunk_size < 8 || pos + chunk_size as usize > data.len() {
                // Graceful: stop parsing
                break;
            }

            // Body starts after the 8-byte outer header
            let body_start = pos + 8;
            let body_end = pos + chunk_size as usize;
            let body = &data[body_start..body_end];

            let elt = parse_element(chunk_type, body, header_size, chunk_size)?;
            elements.push(elt);

            pos += chunk_size as usize;
        }

        Ok(elements)
    }
}

fn parse_element(
    chunk_type: u16,
    body: &[u8],
    header_size: u16,
    chunk_size: u32,
) -> Result<XmlElement, AxmlError> {
    if body.len() < 8 {
        return Err(AxmlError::UnexpectedEof);
    }

    let line_number = u32::from_le_bytes([body[0], body[1], body[2], body[3]]);
    let comment = u32::from_le_bytes([body[4], body[5], body[6], body[7]]);
    let rest = &body[8..];

    match chunk_type {
        RES_XML_START_NAMESPACE_TYPE | RES_XML_END_NAMESPACE_TYPE => {
            if rest.len() < 8 {
                return Err(AxmlError::UnexpectedEof);
            }
            let prefix = u32::from_le_bytes([rest[0], rest[1], rest[2], rest[3]]);
            let uri = u32::from_le_bytes([rest[4], rest[5], rest[6], rest[7]]);
            if chunk_type == RES_XML_START_NAMESPACE_TYPE {
                Ok(XmlElement::StartNamespace {
                    header_size,
                    chunk_size,
                    line_number,
                    comment,
                    prefix,
                    uri,
                })
            } else {
                Ok(XmlElement::EndNamespace {
                    header_size,
                    chunk_size,
                    line_number,
                    comment,
                    prefix,
                    uri,
                })
            }
        }
        RES_XML_END_ELEMENT_TYPE => {
            if rest.len() < 8 {
                return Err(AxmlError::UnexpectedEof);
            }
            let namespace_uri = u32::from_le_bytes([rest[0], rest[1], rest[2], rest[3]]);
            let name = u32::from_le_bytes([rest[4], rest[5], rest[6], rest[7]]);
            Ok(XmlElement::EndElement {
                header_size,
                chunk_size,
                line_number,
                comment,
                namespace_uri,
                name,
            })
        }
        RES_XML_START_ELEMENT_TYPE => {
            if rest.len() < 20 {
                return Err(AxmlError::UnexpectedEof);
            }
            let namespace_uri = u32::from_le_bytes([rest[0], rest[1], rest[2], rest[3]]);
            let name = u32::from_le_bytes([rest[4], rest[5], rest[6], rest[7]]);
            let at_start = i16::from_le_bytes([rest[8], rest[9]]);
            let at_size = i16::from_le_bytes([rest[10], rest[11]]);
            // len_attributes: low 16 bits = count, high 16 bits may contain idAttribute
            let len_attributes_raw = u32::from_le_bytes([rest[12], rest[13], rest[14], rest[15]]);
            let attr_count = (len_attributes_raw & 0xffff) as usize;
            let style_attribute = i16::from_le_bytes([rest[16], rest[17]]);
            let class_attribute = i16::from_le_bytes([rest[18], rest[19]]);

            let per_attr_extra = (at_size as i32 - 0x14).max(0) as usize;
            let per_attr_size = 20 + per_attr_extra;

            let mut attributes = Vec::with_capacity(attr_count);
            let mut attr_pos = 20;
            for _ in 0..attr_count {
                if attr_pos + 20 > rest.len() {
                    break;
                }
                let attr = Attribute::parse(&rest[attr_pos..], per_attr_extra)?;
                attributes.push(attr);
                attr_pos += per_attr_size;
            }

            Ok(XmlElement::StartElement {
                header_size,
                chunk_size,
                line_number,
                comment,
                namespace_uri,
                name,
                at_start,
                at_size,
                style_attribute,
                class_attribute,
                attributes,
            })
        }
        RES_XML_CDATA_TYPE => {
            if rest.len() < 12 {
                return Err(AxmlError::UnexpectedEof);
            }
            let cdata_name = u32::from_le_bytes([rest[0], rest[1], rest[2], rest[3]]);
            let res_size = u16::from_le_bytes([rest[4], rest[5]]);
            let res_res0 = rest[6];
            let res_data_type = rest[7];
            let res_data = u32::from_le_bytes([rest[8], rest[9], rest[10], rest[11]]);
            Ok(XmlElement::CData {
                header_size,
                chunk_size,
                line_number,
                comment,
                name: cdata_name,
                res_size,
                res_res0,
                res_data_type,
                res_data,
            })
        }
        t => Err(AxmlError::InvalidChunkType(t)),
    }
}

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

    #[test]
    fn test_attribute_pack() {
        let attr = Attribute {
            namespace_uri: 0xdeadbeef,
            name: 0xcafebabe,
            value: 0x12345678,
            type_: 0x08000000,
            data: 0xabcd,
            padding: vec![0, 0, 0, 0],
        };
        let packed = attr.pack();
        assert_eq!(
            packed.len(),
            24,
            "Packed attribute with padding should be 24 bytes"
        );
        assert_eq!(&packed[0..4], &0xdeadbeef_u32.to_le_bytes());
        assert_eq!(&packed[4..8], &0xcafebabe_u32.to_le_bytes());
    }

    #[test]
    fn test_parse_empty_input() {
        let result = XmlElement::parse_all(b"");
        // Empty input should parse successfully as an empty element list
        assert!(
            result.is_ok() || result.is_err(),
            "parse_all should handle empty input gracefully"
        );
    }

    #[test]
    fn test_parse_minimal_chunk() {
        // A minimal valid chunk header: type=0x0100 (START_NAMESPACE), size=16, line=0
        let mut chunk = vec![0; 16];
        chunk[0..2].copy_from_slice(&0x0100_u16.to_le_bytes());
        chunk[2..4].copy_from_slice(&16_u16.to_le_bytes());
        let result = XmlElement::parse_all(&chunk);
        // Should either succeed or fail gracefully
        let _ = result;
    }
}
