use beef::lean::Cow;
use quick_xml::events::BytesDecl;
use quick_xml::events::BytesEnd;
use quick_xml::events::BytesStart;
use quick_xml::events::Event;
use quick_xml::name::QName;
use quick_xml::Reader;

use crate::UnexpectedTokenSnafu;
use crate::XmlError;
use crate::XmlResult;

pub(crate) struct XmlReader<'a> {
    inner: Reader<&'a [u8]>,

    peeked: Option<Event<'a>>,
}

impl<'a> XmlReader<'a> {
    pub(crate) fn from_str(document: &'a str) -> Self {
        let mut inner = quick_xml::Reader::from_str(document);

        inner.expand_empty_elements(true).trim_text(true);

        Self {
            inner,
            peeked: None,
        }
    }
}

impl<'a> XmlReader<'a> {
    pub(crate) fn expect_boolean(&mut self) -> XmlResult<bool> {
        let event = self.read_event()?;
        let Event::Text(text) = event else { todo!() };

        match &*text.unescape()? {
            "true" => Ok(true),
            "false" => Ok(false),
            _ => todo!(),
        }
    }

    pub(crate) fn expect_element_end(
        &mut self,
        start: BytesStart,
    ) -> Result<BytesEnd<'a>, XmlError> {
        match self.read_event()? {
            Event::End(end) if end.name() == start.name() => Ok(end),
            received => {
                let received = received.into_owned();

                UnexpectedTokenSnafu {
                    expected: format!("element end for {start:?}"),
                    received,
                }
                .fail()
            }
        }
    }

    pub(crate) fn expect_element_start(&mut self, tag: &str) -> Result<BytesStart<'a>, XmlError> {
        match self.read_event()? {
            Event::Start(start) if start.name().into_inner() == tag.as_bytes() => Ok(start),

            received => {
                let received = received.into_owned();

                UnexpectedTokenSnafu {
                    expected: format!("element start for {tag:?}"),
                    received,
                }
                .fail()
            }
        }
    }

    pub(crate) fn expect_start_tag(&mut self) -> Result<BytesStart<'a>, XmlError> {
        match self.read_event()? {
            Event::Start(start) => Ok(start),

            received => {
                let received = received.into_owned();

                UnexpectedTokenSnafu {
                    expected: "start tag",
                    received,
                }
                .fail()
            }
        }
    }

    pub(crate) fn expect_text(&mut self) -> XmlResult<Cow<'a, str>> {
        let event = self.read_event()?;
        let Event::Text(text) = event else { todo!() };

        text.unescape().map(Cow::from).map_err(XmlError::from)
    }

    pub(crate) fn parse_element<T, F>(&mut self, tag: &str, body: F) -> XmlResult<T>
    where
        F: FnOnce(&mut Self, &BytesStart<'a>) -> XmlResult<T>,
    {
        let start = self.expect_element_start(tag)?;

        let element = body(self, &start)?;

        self.expect_element_end(start)?;

        Ok(element)
    }

    pub(crate) fn parse_list<T, F>(&mut self, tag: &str, mut body: F) -> XmlResult<Vec<T>>
    where
        F: FnMut(&mut Self, &BytesStart<'a>) -> XmlResult<T>,
    {
        let mut result = Vec::new();
        loop {
            let Event::Start(start) = self.peek_event()? else {
                break;
            };

            if start.name().as_ref() != tag.as_bytes() {
                break;
            }

            let Ok(Event::Start(start)) = self.read_event() else {
                unreachable!()
            };

            let element = body(self, &start)?;

            self.expect_element_end(start)?;

            result.push(element);
        }

        Ok(result)
    }

    pub(crate) fn parse_optional_element<T, F>(
        &mut self,
        tag: &str,
        parser: F,
    ) -> XmlResult<Option<T>>
    where
        F: FnOnce(&mut Self, &BytesStart<'a>) -> XmlResult<T>,
    {
        let Event::Start(start) = self.peek_event()? else {
            return Ok(None);
        };

        if start.name().as_ref() != tag.as_bytes() {
            return Ok(None);
        }

        let Ok(Event::Start(start)) = self.read_event() else {
            unreachable!()
        };

        let element = parser(self, &start)?;

        self.expect_element_end(start)?;

        Ok(Some(element))
    }

    pub(crate) fn peek_event(&mut self) -> XmlResult<&Event<'a>> {
        let peeked = if let Some(ref peeked) = self.peeked {
            peeked
        } else {
            let event = self.inner.read_event()?;
            let peeked = self.peeked.insert(event);
            peeked
        };

        Ok(peeked)
    }

    pub(crate) fn peek_start_name(&mut self) -> XmlResult<QName> {
        let Event::Start(start) = self.peek_event()? else {
            todo!();
        };

        Ok(start.name())
    }

    pub(crate) fn read_event(&mut self) -> XmlResult<Event<'a>> {
        if let Some(event) = self.peeked.take() {
            Ok(event)
        } else {
            self.inner.read_event().map_err(XmlError::from)
        }
    }

    pub(crate) fn try_declaration(&mut self) -> XmlResult<Option<BytesDecl>> {
        let Event::Decl(_) = self.peek_event()? else {
            return Ok(None);
        };

        let Ok(Event::Decl(decl)) = self.read_event() else {
            unreachable!()
        };

        Ok(Some(decl))
    }
}

impl<'a> XmlReader<'a> {
    pub fn inner(&self) -> &Reader<&'a [u8]> {
        &self.inner
    }
}