use core::{fmt::Write as _, mem};

use sval::{Index, Label, Stream, Tag};
use sval_buffer::TextBuf;

pub(crate) trait LabelStream<'sval>: Stream<'sval> {
    fn take(&mut self) -> LabelBuf<'sval>;
}

impl<'a, 'sval, S: LabelStream<'sval> + ?Sized> LabelStream<'sval> for &'a mut S {
    fn take(&mut self) -> LabelBuf<'sval> {
        (**self).take()
    }
}

#[derive(Debug)]
pub(crate) enum LabelBuf<'sval> {
    Empty,
    Text(TextBuf<'sval>),
    I128(i128),
    U128(u128),
    F64(f64),
}

impl<'sval> Default for LabelBuf<'sval> {
    fn default() -> Self {
        LabelBuf::Empty
    }
}

impl<'sval> LabelBuf<'sval> {
    pub(crate) fn new() -> Self {
        LabelBuf::Empty
    }

    pub(crate) fn from_index(index: &Index) -> sval::Result<Self> {
        let mut buf = LabelBuf::new();
        buf.index(index)?;

        Ok(buf)
    }

    pub(crate) fn i128(&mut self, v: impl TryInto<i128>) -> sval::Result {
        *self = LabelBuf::I128(v.try_into().map_err(|_| sval::Error::new())?);
        Ok(())
    }

    pub(crate) fn u128(&mut self, v: impl TryInto<u128>) -> sval::Result {
        *self = LabelBuf::U128(v.try_into().map_err(|_| sval::Error::new())?);
        Ok(())
    }

    pub(crate) fn f64(&mut self, v: impl TryInto<f64>) -> sval::Result {
        *self = LabelBuf::F64(v.try_into().map_err(|_| sval::Error::new())?);
        Ok(())
    }

    pub(crate) fn null(&mut self) -> sval::Result {
        self.text_fragment("null")
    }

    pub(crate) fn bool(&mut self, v: bool) -> sval::Result {
        self.text_fragment(if v { "true" } else { "false" })
    }

    pub(crate) fn label(&mut self, label: &Label) -> sval::Result {
        if let Some(label) = label.as_static_str() {
            self.text_fragment(label)
        } else {
            self.text_fragment_computed(label.as_str())
        }
    }

    pub(crate) fn index(&mut self, index: &Index) -> sval::Result {
        if let Some(index) = index.to_isize() {
            self.i128(index)
        } else if let Some(index) = index.to_usize() {
            self.u128(index)
        } else {
            let buf = self.text_buf()?;
            write!(buf, "{}", index).map_err(|_| sval::Error::new())
        }
    }

    pub(crate) fn text_fragment(&mut self, fragment: &'sval str) -> sval::Result {
        self.text_buf()?
            .push_fragment(fragment)
            .map_err(|_| sval::Error::new())
    }

    pub(crate) fn text_fragment_computed(&mut self, fragment: &str) -> sval::Result {
        self.text_buf()?
            .push_fragment_computed(fragment)
            .map_err(|_| sval::Error::new())
    }

    pub(crate) fn text_buf(&mut self) -> sval::Result<&mut TextBuf<'sval>> {
        match self {
            LabelBuf::Text(buf) => Ok(buf),
            _ => {
                *self = LabelBuf::Text(TextBuf::new());
                if let LabelBuf::Text(buf) = self {
                    Ok(buf)
                } else {
                    unreachable!()
                }
            }
        }
    }

    pub(crate) fn with_label(&self, f: impl FnOnce(&Label) -> sval::Result) -> sval::Result {
        match self {
            LabelBuf::Empty => f(&Label::new_computed("")),
            LabelBuf::Text(text) => f(&Label::new_computed(text.as_str())),
            LabelBuf::I128(v) => {
                let mut buf = itoa::Buffer::new();
                let r = f(&Label::new_computed(buf.format(*v)));
                r
            }
            LabelBuf::U128(v) => {
                let mut buf = itoa::Buffer::new();
                let r = f(&Label::new_computed(buf.format(*v)));
                r
            }
            LabelBuf::F64(v) => {
                let mut buf = ryu::Buffer::new();
                let r = f(&Label::new_computed(buf.format(*v)));
                r
            }
        }
    }
}

impl<'sval> LabelStream<'sval> for LabelBuf<'sval> {
    fn take(&mut self) -> LabelBuf<'sval> {
        mem::take(self)
    }
}

impl<'sval> Stream<'sval> for LabelBuf<'sval> {
    fn null(&mut self) -> sval::Result {
        self.null()
    }

    fn bool(&mut self, value: bool) -> sval::Result {
        self.bool(value)
    }

    fn text_begin(&mut self, _: Option<usize>) -> sval::Result {
        Ok(())
    }

    fn text_fragment(&mut self, fragment: &'sval str) -> sval::Result {
        self.text_fragment(fragment)
    }

    fn text_fragment_computed(&mut self, fragment: &str) -> sval::Result {
        self.text_fragment_computed(fragment)
    }

    fn text_end(&mut self) -> sval::Result {
        Ok(())
    }

    fn u64(&mut self, value: u64) -> sval::Result {
        self.u128(value)
    }

    fn u128(&mut self, value: u128) -> sval::Result {
        self.u128(value)
    }

    fn i64(&mut self, value: i64) -> sval::Result {
        self.i128(value)
    }

    fn i128(&mut self, value: i128) -> sval::Result {
        self.i128(value)
    }

    fn f64(&mut self, value: f64) -> sval::Result {
        self.f64(value)
    }

    fn seq_begin(&mut self, _: Option<usize>) -> sval::Result {
        Ok(())
    }

    fn seq_value_begin(&mut self) -> sval::Result {
        Ok(())
    }

    fn seq_value_end(&mut self) -> sval::Result {
        Ok(())
    }

    fn seq_end(&mut self) -> sval::Result {
        Ok(())
    }

    fn tag(
        &mut self,
        _: Option<&Tag>,
        label: Option<&Label>,
        index: Option<&Index>,
    ) -> sval::Result {
        if let Some(label) = label {
            self.label(label)
        } else if let Some(index) = index {
            self.index(index)
        } else {
            self.null()
        }
    }
}

pub(crate) struct Empty;

impl<'sval> LabelStream<'sval> for Empty {
    fn take(&mut self) -> LabelBuf<'sval> {
        Default::default()
    }
}

impl<'sval> Stream<'sval> for Empty {
    fn null(&mut self) -> sval::Result {
        Ok(())
    }

    fn bool(&mut self, _: bool) -> sval::Result {
        Ok(())
    }

    fn text_begin(&mut self, _: Option<usize>) -> sval::Result {
        Ok(())
    }

    fn text_fragment_computed(&mut self, _: &str) -> sval::Result {
        Ok(())
    }

    fn text_end(&mut self) -> sval::Result {
        Ok(())
    }

    fn i64(&mut self, _: i64) -> sval::Result {
        Ok(())
    }

    fn f64(&mut self, _: f64) -> sval::Result {
        Ok(())
    }

    fn seq_begin(&mut self, _: Option<usize>) -> sval::Result {
        Ok(())
    }

    fn seq_value_begin(&mut self) -> sval::Result {
        Ok(())
    }

    fn seq_value_end(&mut self) -> sval::Result {
        Ok(())
    }

    fn seq_end(&mut self) -> sval::Result {
        Ok(())
    }
}
