use std::fmt;

#[derive(Debug, Clone, Copy)]
pub enum BarSegment<'s> {
    Dynamic(&'s str, u64),
    Static(&'s str),
}

impl<'s> BarSegment<'s> {
    fn char(&self) -> &'s str {
        match self {
            BarSegment::Dynamic(c, _) => c,
            BarSegment::Static(c) => c,
        }
    }

    fn length(&self) -> u64 {
        match self {
            BarSegment::Dynamic(_c, len) => *len,
            BarSegment::Static(c) => c.len() as u64,
        }
    }

    fn is_static(&self) -> bool {
        matches!(self, BarSegment::Static(_))
    }

    fn scaled(&self, new_len: u64) -> Self {
        match self {
            BarSegment::Dynamic(c, _) => BarSegment::Dynamic(c, new_len),
            BarSegment::Static(c) => BarSegment::Static(c),
        }
    }
}

#[derive(Debug)]
pub struct MultiBar<'s, const N: usize>(pub [BarSegment<'s>; N]);

impl<const N: usize> MultiBar<'_, N> {
    fn dynamic_length(&self) -> u64 {
        self.0
            .iter()
            .filter(|seg| !seg.is_static())
            .map(|seg| seg.length())
            .sum::<u64>()
    }

    fn static_length(&self) -> u64 {
        self.0
            .iter()
            .filter(|seg| seg.is_static())
            .map(|seg| seg.length())
            .sum::<u64>()
    }

    /// Transforms the bar to be of target size
    /// Static parts remain the same length, dynamic parts are scaled proportionally
    pub(crate) fn scale(&self, size: u64) -> Self {
        let static_len = self.static_length();
        let dynamic_len = std::cmp::max(self.dynamic_length(), 1);

        let available_size = size.saturating_sub(static_len);

        let mut prev_prop = 0;
        let mut curr_prop = 0;

        let inner = self.0.map(|seg| match seg {
            BarSegment::Static(_) => seg,
            BarSegment::Dynamic(c, len) => {
                curr_prop += len;
                let nb_chars = available_size * curr_prop / dynamic_len
                    - available_size * prev_prop / dynamic_len;
                prev_prop = curr_prop;
                BarSegment::Dynamic(c, nb_chars)
            }
        });

        Self(inner)
    }
}

impl<const N: usize> fmt::Display for MultiBar<'_, N> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        for seg in &self.0 {
            let (c, len) = match seg {
                BarSegment::Dynamic(c, len) => (c, *len),
                BarSegment::Static(c) => (c, c.len() as u64),
            };

            for _ in 0..len {
                f.write_str(c)?;
            }
        }

        Ok(())
    }
}
