use crate::style::{Color, Style};
use crate::text::Span;

/// Trait for chaining style methods on strings.
///
/// Returns [`Span`] objects that can be collected into [`Line`](crate::text::Line)
/// and [`Text`](crate::text::Text).
///
/// ```rust
/// use lv_tui::stylize::Stylize;
/// let s = "hello".red().bold();
/// assert!(s.style.bold);
/// ```
pub trait Stylize {
    fn style(self, style: Style) -> Span;
    fn fg(self, color: Color) -> Span;
    fn bg(self, color: Color) -> Span;

    fn red(self) -> Span where Self: Sized { self.fg(Color::Red) }
    fn green(self) -> Span where Self: Sized { self.fg(Color::Green) }
    fn blue(self) -> Span where Self: Sized { self.fg(Color::Blue) }
    fn cyan(self) -> Span where Self: Sized { self.fg(Color::Cyan) }
    fn yellow(self) -> Span where Self: Sized { self.fg(Color::Yellow) }
    fn magenta(self) -> Span where Self: Sized { self.fg(Color::Magenta) }
    fn white(self) -> Span where Self: Sized { self.fg(Color::White) }
    fn gray(self) -> Span where Self: Sized { self.fg(Color::Gray) }
    fn black(self) -> Span where Self: Sized { self.fg(Color::Black) }

    fn on_red(self) -> Span where Self: Sized { self.bg(Color::Red) }
    fn on_green(self) -> Span where Self: Sized { self.bg(Color::Green) }
    fn on_blue(self) -> Span where Self: Sized { self.bg(Color::Blue) }
    fn on_cyan(self) -> Span where Self: Sized { self.bg(Color::Cyan) }
    fn on_yellow(self) -> Span where Self: Sized { self.bg(Color::Yellow) }
    fn on_magenta(self) -> Span where Self: Sized { self.bg(Color::Magenta) }
    fn on_white(self) -> Span where Self: Sized { self.bg(Color::White) }
    fn on_gray(self) -> Span where Self: Sized { self.bg(Color::Gray) }
    fn on_black(self) -> Span where Self: Sized { self.bg(Color::Black) }

    fn rgb(self, r: u8, g: u8, b: u8) -> Span where Self: Sized { self.fg(Color::Rgb(r, g, b)) }
    fn on_rgb(self, r: u8, g: u8, b: u8) -> Span where Self: Sized { self.bg(Color::Rgb(r, g, b)) }
    fn hex(self, hex: &str) -> Span where Self: Sized {
        self.fg(Color::hex(hex).unwrap_or(Color::White))
    }
    fn on_hex(self, hex: &str) -> Span where Self: Sized {
        self.bg(Color::hex(hex).unwrap_or(Color::White))
    }

    fn bold(self) -> Span where Self: Sized;
    fn italic(self) -> Span where Self: Sized;
    fn underline(self) -> Span where Self: Sized;
}

impl Stylize for &str {
    fn style(self, style: Style) -> Span { Span { text: self.to_string(), style } }
    fn fg(self, color: Color) -> Span { Span { text: self.to_string(), style: Style::default().fg(color) } }
    fn bg(self, color: Color) -> Span { Span { text: self.to_string(), style: Style::default().bg(color) } }
    fn bold(self) -> Span { Span { text: self.to_string(), style: Style::default().bold() } }
    fn italic(self) -> Span { Span { text: self.to_string(), style: Style::default().italic() } }
    fn underline(self) -> Span { Span { text: self.to_string(), style: Style::default().underline() } }
}

impl Stylize for String {
    fn style(self, style: Style) -> Span { Span { text: self, style } }
    fn fg(self, color: Color) -> Span { Span { text: self, style: Style::default().fg(color) } }
    fn bg(self, color: Color) -> Span { Span { text: self, style: Style::default().bg(color) } }
    fn bold(self) -> Span { Span { text: self, style: Style::default().bold() } }
    fn italic(self) -> Span { Span { text: self, style: Style::default().italic() } }
    fn underline(self) -> Span { Span { text: self, style: Style::default().underline() } }
}

/// Merge styles for chained calls like `.red().bold()`.
impl Stylize for Span {
    fn style(mut self, style: Style) -> Span {
        self.style = crate::style_parser::merge_styles(self.style, &style);
        self
    }
    fn fg(self, color: Color) -> Span { self.style(Style::default().fg(color)) }
    fn bg(self, color: Color) -> Span { self.style(Style::default().bg(color)) }
    fn bold(self) -> Span { self.style(Style::default().bold()) }
    fn italic(self) -> Span { self.style(Style::default().italic()) }
    fn underline(self) -> Span { self.style(Style::default().underline()) }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_red() {
        let s = "hello".red();
        assert_eq!(s.text, "hello");
        assert_eq!(s.style.fg, Some(Color::Red));
    }

    #[test]
    fn test_red_bold() {
        let s = "hello".red().bold();
        assert_eq!(s.text, "hello");
        assert_eq!(s.style.fg, Some(Color::Red));
        assert!(s.style.bold);
    }

    #[test]
    fn test_on_blue_white() {
        let s = "text".on_blue().white();
        assert_eq!(s.style.bg, Some(Color::Blue));
        assert_eq!(s.style.fg, Some(Color::White));
    }

    #[test]
    fn test_chained_style() {
        let s = "x".red().on_black().bold().italic();
        assert_eq!(s.style.fg, Some(Color::Red));
        assert_eq!(s.style.bg, Some(Color::Black));
        assert!(s.style.bold);
        assert!(s.style.italic);
    }

    #[test]
    fn test_string_impl() {
        let s = String::from("hi").green();
        assert_eq!(s.text, "hi");
        assert_eq!(s.style.fg, Some(Color::Green));
    }

    #[test]
    fn test_gray() {
        let s = "dim".gray();
        assert_eq!(s.style.fg, Some(Color::Gray));
    }

    #[test]
    fn test_bg_methods() {
        let s = "x".on_red().on_green();
        assert_eq!(s.style.bg, Some(Color::Green));
    }
}
