// Copyright © 2026 Stephan Kunz
//! Implementation of the `XmlReader`.

use crate::{
    error::Error,
    xml_element::{find_bytes, parse_element_at, skip_whitespace, XmlElement},
};

/// Lazily parses XML from a borrowed source string.
///
/// No DOM tree is built.  Navigation is done on demand through [`XmlElement`].
pub struct XmlReader<'xml> {
    xml_text: &'xml str,
}

impl<'xml> XmlReader<'xml> {
    /// Wraps `xml_doc` for lazy parsing.  No work is done here.
    pub fn parse(xml_doc: &'xml str) -> XmlReader<'xml> {
        XmlReader { xml_text: xml_doc }
    }

    /// Returns the document's root element.
    ///
    /// Processing instructions, comments, and the XML declaration that precede
    /// the root are skipped automatically.
    ///
    /// # Errors
    /// - [`Error::NoRoot`] – source contains no element.
    /// - [`Error::MalformedXml`] – a PI, comment, or element tag is unclosed.
    pub fn root_element(&self) -> Result<XmlElement<'xml>, Error> {
        let b = self.xml_text.as_bytes();
        let len = b.len();
        let mut pos = 0;

        while pos < len {
            pos = skip_whitespace(self.xml_text, pos);
            if pos >= len {
                break;
            }
            if b[pos] != b'<' {
                pos += 1;
                continue;
            }

            // PI / XML declaration: <?…?>
            if b.get(pos + 1) == Some(&b'?') {
                pos = find_bytes(b, pos + 2, b"?>").ok_or(Error::MalformedXml)? + 2;
                continue;
            }

            // Comment: <!--…-->
            if b.get(pos + 1..pos + 4) == Some(b"!--") {
                pos = find_bytes(b, pos + 4, b"-->").ok_or(Error::MalformedXml)? + 3;
                continue;
            }

            // DOCTYPE declaration: <!DOCTYPE…>
            if b.get(pos + 1..pos + 9) == Some(b"!DOCTYPE")
                || b.get(pos + 1..pos + 9) == Some(b"!doctype")
            {
                pos = b[pos..].iter().position(|&c| c == b'>').ok_or(Error::MalformedXml)?
                    + pos
                    + 1;
                continue;
            }

            return parse_element_at(self.xml_text, pos, len).ok_or(Error::MalformedXml);
        }

        Err(Error::NoRoot)
    }
}

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

    const fn is_normal<T: Sized + Send + Sync>() {}

    #[test]
    const fn normal_types() {
        is_normal::<XmlReader>();
        is_normal::<&XmlReader>();
    }
}
