use crate::{dyn_styles::StyleFlags, Style, Styled};
use core::{
    fmt::{self, Display},
    marker::PhantomData,
};
#[cfg(feature = "alloc")]
extern crate alloc;
mod sealed {
    pub trait IsStyled {
        type Inner: core::fmt::Display;
        fn style(&self) -> &crate::Style;
        fn inner(&self) -> &Self::Inner;
    }
}
use sealed::IsStyled;
impl<T: IsStyled> IsStyled for &T {
    type Inner = T::Inner;
    fn style(&self) -> &Style {
        <T as IsStyled>::style(*self)
    }
    fn inner(&self) -> &Self::Inner {
        <T as IsStyled>::inner(*self)
    }
}
impl<T: Display> IsStyled for Styled<T> {
    type Inner = T;
    fn style(&self) -> &Style {
        &self.style
    }
    fn inner(&self) -> &T {
        &self.target
    }
}
pub struct StyledList<T, U>(pub T, PhantomData<fn(U)>)
where
    T: AsRef<[U]>,
    U: IsStyled;
impl<T, U> From<T> for StyledList<T, U>
where
    T: AsRef<[U]>,
    U: IsStyled,
{
    fn from(list: T) -> Self {
        Self(list, PhantomData)
    }
}
impl<T, U> Display for StyledList<T, U>
where
    T: AsRef<[U]>,
    U: IsStyled,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let first_item = match self.0.as_ref().first() {
            Some(s) => s,
            None => return Ok(()),
        };
        first_item.style().fmt_prefix(f)?;
        write!(f, "{}", first_item.inner())?;
        for window in self.0.as_ref().windows(2) {
            let prev = &window[0];
            let current = &window[1];
            write!(
                f,
                "{}{}",
                current.style().transition_from(prev.style()),
                current.inner()
            )?;
        }
        self.0.as_ref().last().unwrap().style().fmt_suffix(f)
    }
}
impl<'a> Style {
    fn transition_from(&'a self, from: &Style) -> Transition<'a> {
        if self == from {
            return Transition::Noop;
        }
        if (from.fg.is_some() && self.fg.is_none())
            || (from.bg.is_some() && self.bg.is_none())
            || (from.bold && !self.bold)
            || (!self.style_flags.0 & from.style_flags.0) != 0
        {
            return Transition::FullReset(self);
        }
        let fg = match (self.fg, from.fg) {
            (Some(fg), Some(from_fg)) if fg != from_fg => Some(fg),
            (Some(fg), None) => Some(fg),
            _ => None,
        };
        let bg = match (self.bg, from.bg) {
            (Some(bg), Some(from_bg)) if bg != from_bg => Some(bg),
            (Some(bg), None) => Some(bg),
            _ => None,
        };
        let new_style = Style {
            fg,
            bg,
            bold: from.bold ^ self.bold,
            style_flags: StyleFlags(self.style_flags.0 ^ from.style_flags.0),
        };
        Transition::Style(new_style)
    }
}
#[cfg_attr(test, derive(Debug, PartialEq))]
enum Transition<'a> {
    Noop,
    FullReset(&'a Style),
    Style(Style),
}
impl fmt::Display for Transition<'_> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Transition::Noop => Ok(()),
            Transition::FullReset(style) => {
                write!(f, "\x1B[0m")?;
                style.fmt_prefix(f)
            }
            Transition::Style(style) => style.fmt_prefix(f),
        }
    }
}
#[cfg(feature = "alloc")]
pub type StyledVec<T> = StyledList<alloc::vec::Vec<Styled<T>>, Styled<T>>;
#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn test_styled_list() {
        let list = &[
            Style::new().red().style("red"),
            Style::new().green().italic().style("green italic"),
            Style::new().red().bold().style("red bold"),
        ];
        let list = StyledList::from(list);
        assert_eq!(
            format!("{}", list),
            "\x1b[31mred\x1b[32;3mgreen italic\x1b[0m\x1b[31;1mred bold\x1b[0m"
        );
    }
    #[test]
    fn test_styled_final_plain() {
        let list = &[
            Style::new().red().style("red"),
            Style::new().green().italic().style("green italic"),
            Style::new().style("plain"),
        ];
        let list = StyledList::from(list);
        assert_eq!(
            format!("{}", list),
            "\x1b[31mred\x1b[32;3mgreen italic\x1b[0mplain"
        );
    }
    #[test]
    fn test_transition_from_noop() {
        let style_current = Style::new().italic().red();
        let style_prev = Style::new().italic().red();
        assert_eq!(style_current.transition_from(&style_prev), Transition::Noop);
    }
    #[test]
    fn test_transition_from_full_reset() {
        let style_current = Style::new().italic().red();
        let style_prev = Style::new().italic().dimmed().red();
        assert_eq!(
            style_current.transition_from(&style_prev),
            Transition::FullReset(&style_current)
        );
        let style_current = Style::new();
        let style_prev = Style::new().red();
        assert_eq!(
            style_current.transition_from(&style_prev),
            Transition::FullReset(&style_current)
        );
        let style_current = Style::new();
        let style_prev = Style::new().bold();
        assert_eq!(
            style_current.transition_from(&style_prev),
            Transition::FullReset(&style_current)
        );
    }
    #[test]
    fn test_transition_from_style() {
        let style_current = Style::new().italic().dimmed().red();
        let style_prev = Style::new().italic().red();
        assert_eq!(
            style_current.transition_from(&style_prev),
            Transition::Style(Style::new().dimmed())
        );
        let style_current = Style::new().red().on_green();
        let style_prev = Style::new().red().on_bright_cyan();
        assert_eq!(
            style_current.transition_from(&style_prev),
            Transition::Style(Style::new().on_green())
        );
        let style_current = Style::new().bold().blue();
        let style_prev = Style::new().bold();
        assert_eq!(
            style_current.transition_from(&style_prev),
            Transition::Style(Style::new().blue())
        );
    }
}