use std::fmt;
use std::fmt::Display;

pub(crate) struct State<'fmt, 'fmt2> {
    indent_level: usize,
    output: &'fmt mut fmt::Formatter<'fmt2>,
}

impl<'a, 'b> State<'a, 'b> {
    pub(crate) fn new(output: &'a mut fmt::Formatter<'b>) -> State<'a, 'b> {
        Self {
            indent_level: 0,
            output,
        }
    }

    pub(crate) fn indent_level(&self) -> usize {
        self.indent_level
    }

    pub(crate) fn write<T: Display>(&mut self, value: T) -> fmt::Result {
        write!(self.output, "{value}")
    }

    pub(crate) fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result {
        self.output.write_fmt(args)
    }

    pub(crate) fn new_line(&mut self) -> fmt::Result {
        self.write("\n")?;
        for _ in 0..self.indent_level {
            self.write("  ")?
        }
        Ok(())
    }

    pub(crate) fn indent_no_new_line(&mut self) {
        self.indent_level += 1;
    }

    pub(crate) fn indent(&mut self) -> fmt::Result {
        self.indent_no_new_line();
        self.new_line()
    }

    pub(crate) fn dedent(&mut self) -> fmt::Result {
        self.indent_level -= 1;
        self.new_line()
    }
}

pub(crate) fn write_indented_lines<T>(
    state: &mut State<'_, '_>,
    values: &[T],
    mut write_line: impl FnMut(&mut State<'_, '_>, &T) -> fmt::Result,
) -> fmt::Result {
    if !values.is_empty() {
        state.indent_no_new_line();
        for value in values {
            state.new_line()?;
            write_line(state, value)?;
        }
        state.dedent()?;
    }
    Ok(())
}

pub(crate) struct DisplaySlice<'a, T>(pub(crate) &'a [T]);

impl<T: Display> Display for DisplaySlice<'_, T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "[")?;
        let mut iter = self.0.iter();
        if let Some(item) = iter.next() {
            write!(f, "{item}")?;
        }
        iter.try_for_each(|item| write!(f, ", {item}"))?;
        write!(f, "]")
    }
}

pub(crate) struct DisplayOption<T>(pub(crate) Option<T>);

impl<T> DisplayOption<T> {
    pub(crate) fn new(inner: &Option<T>) -> DisplayOption<&T> {
        DisplayOption(inner.as_ref())
    }
}

impl<T: Display> Display for DisplayOption<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self.0 {
            Some(item) => write!(f, "Some({item})"),
            None => write!(f, "None"),
        }
    }
}
