//! Types for styling terminal cells.
//!
//! Terminal styling is controlled by [`Sgr`] commands, the `CSI ... m` escape sequences that set
//! foreground color, background color, intensity, underline, and related text attributes. This
//! module provides those low-level SGR attribute types and a small [`StyleExt`] convenience trait
//! for formatting styled text.
//!
//! # Examples
//!
//! ```
//! use termina::style::StyleExt as _;
//!
//! # termina::style::Stylized::force_ansi_color(true);
//! let warning = "warning".red().bold();
//! assert_eq!(warning.to_string(), "\x1b[0;31;1mwarning\x1b[m");
//! ```
//!
//! # Implementation Notes
//!
//! Styling support is shared almost fairly between crossterm and TermWiz: SGR property types like
//! [`Underline`], [`CursorStyle`], and [`Intensity`] are adapted from [termwiz styling], while
//! [`StyleExt`] follows the shape of [crossterm styling] helpers.
//!
//! [termwiz styling]: https://docs.rs/termwiz/latest/termwiz/
//! [crossterm styling]: https://docs.rs/crossterm/latest/crossterm/style/index.html

use std::{
    borrow::Cow,
    fmt::{self, Display},
    str::FromStr,
    sync::atomic::{AtomicBool, Ordering},
};

use crate::escape::{
    self,
    csi::{Csi, Sgr},
};

/// Styling of a cell's underline according to the [Kitty underline extension].
///
/// Single and double underlines are widely understood SGR attributes. Curly, dotted, dashed, and
/// colored underlines are extensions and depend on terminal support.
///
/// ```
/// use termina::{
///     escape::csi::{Csi, Sgr},
///     style::Underline,
/// };
///
/// assert_eq!(Csi::Sgr(Sgr::Underline(Underline::Curly)).to_string(), "\x1b[4:3m");
/// ```
///
/// [kitty underline extension]: https://sw.kovidgoyal.net/kitty/underlines/
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum Underline {
    /// No underline
    #[default]
    None = 0,

    /// Straight underline
    Single = 1,

    /// Two underlines stacked on top of one another
    Double = 2,

    /// Curly / "squiggly" / "wavy" underline
    Curly = 3,

    /// Dotted underline
    Dotted = 4,

    /// Dashed underline
    Dashed = 5,
}

/// Cursor shape values for [DECSCUSR].
///
/// DECSCUSR is the DEC-style cursor shape setting used by many modern terminals. The numeric
/// values select the terminal default, block, underline, or vertical-bar cursor, with blinking and
/// steady variants where the protocol defines both.
///
/// ```
/// use termina::{
///     escape::csi::{Csi, Cursor},
///     style::CursorStyle,
/// };
///
/// let cursor = Csi::Cursor(Cursor::CursorStyle(CursorStyle::SteadyBar));
/// assert_eq!(cursor.to_string(), "\x1b[6 q");
/// ```
///
/// [DECSCUSR]: https://vt100.net/docs/vt510-rm/DECSCUSR.html
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum CursorStyle {
    /// DECSCUSR value 0: use the terminal's configured cursor style.
    #[default]
    Default = 0,
    /// DECSCUSR value 1: a blinking block cursor.
    BlinkingBlock = 1,
    /// DECSCUSR value 2: a steady block cursor.
    SteadyBlock = 2,
    /// DECSCUSR value 3: a blinking underline cursor.
    BlinkingUnderline = 3,
    /// DECSCUSR value 4: a steady underline cursor.
    SteadyUnderline = 4,
    /// DECSCUSR value 5: a blinking vertical bar cursor.
    BlinkingBar = 5,
    /// DECSCUSR value 6: a steady vertical bar cursor.
    SteadyBar = 6,
}

impl Display for CursorStyle {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", *self as u8)
    }
}

/// An 8-bit "256-color".
///
/// Colors 0-15 are the same as [`AnsiColor`] values (0-7 being normal colors and 8-15 being
/// "bright").
/// Colors 16-231 make up a 6x6x6 "color cube"; 232-255 define a dark-to-light grayscale.
///
/// These are also known as "web-safe colors" or "X11 colors" historically, although the actual
/// colors varied somewhat between historical usages; see the [ANSI 8-bit color] table.
///
/// Convert a `WebColor` into [`ColorSpec`] when building SGR colors:
///
/// ```
/// use termina::style::{ColorSpec, WebColor};
///
/// let orange: ColorSpec = WebColor(208).into();
/// assert_eq!(orange, ColorSpec::PaletteIndex(208));
/// ```
///
/// [ANSI 8-bit color]: https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WebColor(pub u8);

/// Red, green, and blue color with 8-bit channels.
///
/// Use [`Self::new`] for byte channels, [`Self::new_f32`] for normalized floating-point channels,
/// or [`str::parse`] for xterm-style color strings:
///
/// ```
/// use termina::style::{ColorSpec, RgbColor};
///
/// let from_bytes = RgbColor::new(40, 80, 120);
/// let from_floats = RgbColor::new_f32(0.5, 0.0, 1.0);
/// let from_hex: RgbColor = "#285078".parse().unwrap();
///
/// assert_eq!(from_bytes, from_hex);
/// assert_eq!(from_floats, RgbColor::new(127, 0, 255));
///
/// let color_spec: ColorSpec = from_bytes.into();
/// assert!(matches!(color_spec, ColorSpec::TrueColor(_)));
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RgbColor {
    /// Red channel.
    pub red: u8,
    /// Green channel.
    pub green: u8,
    /// Blue channel.
    pub blue: u8,
}

impl RgbColor {
    /// Creates a new RGB color from 8-bit channel values.
    pub const fn new(red: u8, green: u8, blue: u8) -> Self {
        Self { red, green, blue }
    }

    /// Creates a new RGB color from floating-point channel values.
    ///
    /// Values are multiplied by 255 and cast to `u8`. Rust float-to-integer casts round toward
    /// zero, so fractional values lose their fractional part after scaling. `NaN` and values below
    /// `0.0` become `0`; values above `1.0` and positive infinity become `255`.
    pub fn new_f32(red: f32, green: f32, blue: f32) -> Self {
        let red = (red * 255.) as u8;
        let green = (green * 255.) as u8;
        let blue = (blue * 255.) as u8;
        Self { red, green, blue }
    }

    fn channel_from_hex(s: &str) -> Result<u8, InvalidFormatError> {
        if s.is_empty() || s.len() > 4 {
            return Err(InvalidFormatError);
        }
        let color: u16 = u16::from_str_radix(s, 16).map_err(|_| InvalidFormatError)?;
        let divisor: usize = match s.len() {
            1 => 0xf,
            2 => 0xff,
            3 => 0xfff,
            4 => 0xffff,
            _ => return Err(InvalidFormatError),
        };
        Ok(((color as usize) * 0xff / divisor) as u8)
    }
}

/// Error returned when parsing a red, green, and blue color string fails.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InvalidFormatError;

impl FromStr for RgbColor {
    type Err = InvalidFormatError;

    // See `man xparsecolor`. This parses colors according to some of the formats accepted by
    // xterm's `XParseColor` function.
    //
    // 1. rgb:<red>/<green>/<blue>
    //    <red>, <green>, <blue> := h | hh | hhh | hhhh
    //    h := single hexadecimal digits (case insignificant)
    // 2. #RGB, #RRGGBB, #RRRGGGBBB, #RRRRGGGGBBBB
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if let Some(rgb) = s.strip_prefix("rgb:") {
            let mut parts = rgb.split('/').map(Self::channel_from_hex);
            let Some(r) = parts.next().transpose()? else {
                return Err(InvalidFormatError);
            };
            let Some(g) = parts.next().transpose()? else {
                return Err(InvalidFormatError);
            };
            let Some(b) = parts.next().transpose()? else {
                return Err(InvalidFormatError);
            };
            Ok(Self::new(r, g, b))
        } else if let Some(hex) = s.strip_prefix('#') {
            let (r, g, b) = match hex.len() {
                3 => (
                    Self::channel_from_hex(&hex[0..1])?,
                    Self::channel_from_hex(&hex[1..2])?,
                    Self::channel_from_hex(&hex[2..3])?,
                ),
                6 => (
                    Self::channel_from_hex(&hex[0..2])?,
                    Self::channel_from_hex(&hex[2..4])?,
                    Self::channel_from_hex(&hex[4..6])?,
                ),
                9 => (
                    Self::channel_from_hex(&hex[0..3])?,
                    Self::channel_from_hex(&hex[3..6])?,
                    Self::channel_from_hex(&hex[6..9])?,
                ),
                12 => (
                    Self::channel_from_hex(&hex[0..4])?,
                    Self::channel_from_hex(&hex[4..8])?,
                    Self::channel_from_hex(&hex[8..12])?,
                ),
                _ => return Err(InvalidFormatError),
            };
            Ok(Self::new(r, g, b))
        } else {
            Err(InvalidFormatError)
        }
    }
}

/// Red, green, blue, and alpha color with 8-bit channels.
///
/// Alpha defaults to fully opaque when converting from [`RgbColor`]. Converting back to
/// [`RgbColor`] drops the alpha channel because standard terminal foreground/background colors are
/// RGB values.
///
/// ```
/// use termina::style::{RgbColor, RgbaColor};
///
/// let rgb = RgbColor::new(10, 20, 30);
/// let rgba = RgbaColor::from(rgb);
/// assert_eq!(rgba.alpha, 255);
/// assert_eq!(RgbColor::from(rgba), rgb);
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RgbaColor {
    /// Red channel.
    pub red: u8,
    /// Green channel.
    pub green: u8,
    /// Blue channel.
    pub blue: u8,
    /// Also known as "opacity"
    pub alpha: u8,
}

impl From<RgbaColor> for RgbColor {
    fn from(color: RgbaColor) -> Self {
        Self {
            red: color.red,
            green: color.green,
            blue: color.blue,
        }
    }
}

impl From<RgbColor> for RgbaColor {
    fn from(color: RgbColor) -> Self {
        Self {
            red: color.red,
            green: color.green,
            blue: color.blue,
            alpha: 255,
        }
    }
}

/// Named ANSI colors used by standard 16-color palettes.
///
/// The numeric SGR assignments are stable, but names are not fully consistent across terminal
/// libraries. In particular, SGR 37 may be called white, gray, or silver, while SGR 97 may be
/// called bright white, light white, or white. Termina names the base 37 color [`Self::White`] and
/// the bright 97 color [`Self::BrightWhite`]. Ratatui documents the same naming ambiguity in its
/// [ANSI color table].
///
/// `AnsiColor` converts to [`ColorSpec`] through the indexed palette path:
///
/// ```
/// use termina::style::{AnsiColor, ColorSpec};
///
/// let red: ColorSpec = AnsiColor::Red.into();
/// assert_eq!(red, ColorSpec::RED);
/// ```
///
/// [ANSI color table]: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
/// [Ratatui color docs]: https://docs.rs/ratatui/latest/ratatui/style/enum.Color.html
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AnsiColor {
    /// The standard black palette entry.
    Black = 0,
    /// The standard red palette entry.
    Red,
    /// The standard green palette entry.
    Green,
    /// The standard yellow palette entry.
    Yellow,
    /// The standard blue palette entry.
    Blue,
    /// The standard magenta palette entry.
    Magenta,
    /// The standard cyan palette entry.
    Cyan,
    /// The standard white palette entry.
    ///
    /// This is SGR 37. Some terminal libraries call this gray or silver and reserve white for the
    /// bright SGR 97 color.
    White,
    /// Bright black.
    ///
    /// This is SGR 90. Some terminal libraries call this dark gray, gray, light black, or bright
    /// black.
    BrightBlack,
    /// The bright red palette entry.
    BrightRed,
    /// The bright green palette entry.
    BrightGreen,
    /// The bright yellow palette entry.
    BrightYellow,
    /// The bright blue palette entry.
    BrightBlue,
    /// The bright magenta palette entry.
    BrightMagenta,
    /// The bright cyan palette entry.
    BrightCyan,
    /// The bright white palette entry.
    ///
    /// This is SGR 97. Some terminal libraries call this white, bright white, or light white.
    BrightWhite,
}

/// Index into the terminal's 256-color palette.
pub type PaletteIndex = u8;

/// Terminal color specification for [`Sgr`] color commands.
///
/// This is the common color input type for foreground, background, and underline colors. Named
/// ANSI colors and 256-color palette values use [`Self::PaletteIndex`]; true-color values use
/// [`Self::TrueColor`]; [`Self::Reset`] returns the terminal color to its default.
///
/// ```
/// use termina::{
///     escape::csi::{Csi, Sgr},
///     style::{ColorSpec, RgbColor},
/// };
///
/// assert_eq!(Csi::Sgr(Sgr::Foreground(ColorSpec::RED)).to_string(), "\x1b[31m");
///
/// let blue = ColorSpec::from(RgbColor::new(0, 0, 255));
/// assert_eq!(Csi::Sgr(Sgr::Foreground(blue)).to_string(), "\x1b[38;2;0;0;255m");
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ColorSpec {
    /// Reset the color back to the terminal default.
    ///
    /// For SGR foreground and background colors this formats as the standard reset color codes
    /// rather than as an indexed palette color.
    Reset,

    /// Use an indexed palette color.
    ///
    /// Values 0-15 address the standard ANSI palette. Values 16-255 address the 256-color
    /// extension palette described by [`WebColor`].
    PaletteIndex(PaletteIndex),

    /// Use a true-color red, green, blue, and alpha value.
    ///
    /// Red, green, and blue values format using SGR true-color parameters. Non-opaque alpha uses
    /// the colon form from ITU T.416-style color notation.
    TrueColor(RgbaColor),
}

impl ColorSpec {
    /// Standard black palette color.
    pub const BLACK: Self = Self::PaletteIndex(AnsiColor::Black as PaletteIndex);
    /// Standard red palette color.
    pub const RED: Self = Self::PaletteIndex(AnsiColor::Red as PaletteIndex);
    /// Standard green palette color.
    pub const GREEN: Self = Self::PaletteIndex(AnsiColor::Green as PaletteIndex);
    /// Standard yellow palette color.
    pub const YELLOW: Self = Self::PaletteIndex(AnsiColor::Yellow as PaletteIndex);
    /// Standard blue palette color.
    pub const BLUE: Self = Self::PaletteIndex(AnsiColor::Blue as PaletteIndex);
    /// Standard magenta palette color.
    pub const MAGENTA: Self = Self::PaletteIndex(AnsiColor::Magenta as PaletteIndex);
    /// Standard cyan palette color.
    pub const CYAN: Self = Self::PaletteIndex(AnsiColor::Cyan as PaletteIndex);
    /// Standard white palette color.
    pub const WHITE: Self = Self::PaletteIndex(AnsiColor::White as PaletteIndex);
    /// Bright black palette color.
    pub const BRIGHT_BLACK: Self = Self::PaletteIndex(AnsiColor::BrightBlack as PaletteIndex);
    /// Bright red palette color.
    pub const BRIGHT_RED: Self = Self::PaletteIndex(AnsiColor::BrightRed as PaletteIndex);
    /// Bright green palette color.
    pub const BRIGHT_GREEN: Self = Self::PaletteIndex(AnsiColor::BrightGreen as PaletteIndex);
    /// Bright yellow palette color.
    pub const BRIGHT_YELLOW: Self = Self::PaletteIndex(AnsiColor::BrightYellow as PaletteIndex);
    /// Bright blue palette color.
    pub const BRIGHT_BLUE: Self = Self::PaletteIndex(AnsiColor::BrightBlue as PaletteIndex);
    /// Bright magenta palette color.
    pub const BRIGHT_MAGENTA: Self = Self::PaletteIndex(AnsiColor::BrightMagenta as PaletteIndex);
    /// Bright cyan palette color.
    pub const BRIGHT_CYAN: Self = Self::PaletteIndex(AnsiColor::BrightCyan as PaletteIndex);
    /// Bright white palette color.
    pub const BRIGHT_WHITE: Self = Self::PaletteIndex(AnsiColor::BrightWhite as PaletteIndex);
}

impl From<AnsiColor> for ColorSpec {
    fn from(color: AnsiColor) -> Self {
        Self::PaletteIndex(color as u8)
    }
}

impl From<WebColor> for ColorSpec {
    fn from(color: WebColor) -> Self {
        Self::PaletteIndex(color.0)
    }
}

impl From<RgbColor> for ColorSpec {
    fn from(color: RgbColor) -> Self {
        Self::TrueColor(color.into())
    }
}

impl From<RgbaColor> for ColorSpec {
    fn from(color: RgbaColor) -> Self {
        Self::TrueColor(color)
    }
}

/// Text intensity for [`Sgr`].
///
/// Use this directly with [`Sgr::Intensity`] when building escape sequences, or through
/// [`StyleExt::bold`] for simple styled strings.
///
/// ```
/// use termina::{
///     escape::csi::{Csi, Sgr},
///     style::{Intensity, StyleExt as _},
/// };
///
/// assert_eq!(Csi::Sgr(Sgr::Intensity(Intensity::Bold)).to_string(), "\x1b[1m");
///
/// # termina::style::Stylized::force_ansi_color(true);
/// assert_eq!("warn".bold().to_string(), "\x1b[0;1mwarn\x1b[m");
/// ```
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum Intensity {
    /// SGR 22: normal text intensity.
    #[default]
    Normal,
    /// SGR 1: bold text intensity.
    Bold,
    /// SGR 2: dim text intensity.
    Dim,
}

/// Text blink mode for [`Sgr`].
///
/// Blink support is terminal-dependent. Some terminals ignore blink entirely or let users disable
/// it in their settings.
///
/// ```
/// use termina::{
///     escape::csi::{Csi, Sgr},
///     style::Blink,
/// };
///
/// assert_eq!(Csi::Sgr(Sgr::Blink(Blink::Slow)).to_string(), "\x1b[5m");
/// ```
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum Blink {
    /// SGR 25: disable blinking text.
    #[default]
    None,
    /// SGR 5: slow blinking text.
    Slow,
    /// SGR 6: rapid blinking text.
    Rapid,
}

/// Font selection for SGR parameters 10-19.
///
/// Alternate fonts are terminal-dependent and uncommon in modern terminal applications. The
/// variant value maps to SGR 11 through SGR 19; `Font::Alternate(1)` emits SGR 11.
///
/// ```
/// use termina::{
///     escape::csi::{Csi, Sgr},
///     style::Font,
/// };
///
/// assert_eq!(Csi::Sgr(Sgr::Font(Font::Alternate(1))).to_string(), "\x1b[11m");
/// ```
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum Font {
    /// SGR 10: use the default font.
    #[default]
    Default,

    /// SGR 11-19: select an alternate font.
    ///
    /// Valid values are 1-9, corresponding to SGR 11 through SGR 19.
    Alternate(u8),
}

/// Vertical alignment for [`Sgr`].
///
/// Superscript and subscript support is terminal-dependent.
///
/// ```
/// use termina::{
///     escape::csi::{Csi, Sgr},
///     style::VerticalAlign,
/// };
///
/// let superscript = Csi::Sgr(Sgr::VerticalAlign(VerticalAlign::SuperScript));
/// assert_eq!(superscript.to_string(), "\x1b[73m");
/// ```
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum VerticalAlign {
    /// SGR 75: baseline text alignment.
    #[default]
    BaseLine = 0,
    /// SGR 73: superscript text alignment.
    SuperScript = 1,
    /// SGR 74: subscript text alignment.
    SubScript = 2,
}

/// Styled text that renders by surrounding content with SGR escape sequences.
///
/// Use this for simple styled strings, for example a CLI help string. Code that already writes
/// structured terminal output can use [`crate::escape::csi::Sgr`] directly instead.
///
/// Instead of using this type directly, `use` the [`StyleExt`] trait and the helper functions
/// attached to strings:
///
/// ```
/// use termina::style::StyleExt as _;
///
/// # termina::style::Stylized::force_ansi_color(true);
/// assert_eq!(
///     "warning".red().bold().underlined().to_string(),
///     "\x1b[0;31;1;4mwarning\x1b[m",
/// );
/// ```
///
/// [`PlatformTerminal`]: crate::PlatformTerminal
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Stylized<'a> {
    /// The text rendered between the opening SGR sequence and reset sequence.
    pub content: Cow<'a, str>,
    styles: Vec<Sgr>,
}

static INITIALIZER: parking_lot::Once = parking_lot::Once::new();
static NO_COLOR: AtomicBool = AtomicBool::new(false);

impl Stylized<'_> {
    /// Checks whether ANSI color sequences were turned off in the environment.
    ///
    /// This follows the guidance on [no-color.org][no-color]: if the `NO_COLOR` environment
    /// variable is present and non-empty, color escape sequences will be omitted when rendering
    /// this struct. This behavior can be overridden with [Self::force_ansi_color].
    ///
    /// [no-color]: https://no-color.org/
    pub fn is_ansi_color_disabled() -> bool {
        // Guidance on disabling colors comes from the no-color.org recommendations.
        INITIALIZER.call_once(|| {
            NO_COLOR.store(
                std::env::var("NO_COLOR").is_ok_and(|e| !e.is_empty()),
                Ordering::SeqCst,
            );
        });
        NO_COLOR.load(Ordering::SeqCst)
    }

    /// Overrides detection of the `NO_COLOR` environment variable.
    ///
    /// Pass `true` to ensure that ANSI color codes are always included when displaying this type
    /// or `false` to ensure ANSI color codes are never included.
    pub fn force_ansi_color(enable_color: bool) {
        // Run the `Once` first so this override is not later overwritten by the `Once` fn.
        let _ = Self::is_ansi_color_disabled();
        NO_COLOR.store(!enable_color, Ordering::SeqCst);
    }
}

impl Display for Stylized<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let no_color = Self::is_ansi_color_disabled();
        let mut styles = self
            .styles
            .iter()
            .filter(|sgr| {
                !(no_color
                    && matches!(
                        sgr,
                        Sgr::Foreground(_) | Sgr::Background(_) | Sgr::UnderlineColor(_)
                    ))
            })
            .peekable();

        if styles.peek().is_none() {
            write!(f, "{}", self.content)?;
        } else {
            write!(f, "{}0", escape::CSI)?;
            for sgr in styles {
                write!(f, ";{sgr}")?;
            }
            write!(f, "m{}{}", self.content, Csi::Sgr(Sgr::Reset))?;
        }
        Ok(())
    }
}

/// Convenience methods for building [`Stylized`] text.
///
/// Methods that accept `impl Into<ColorSpec>` work with named ANSI colors, 256-color palette
/// values, and true-color RGB values:
///
/// ```
/// use termina::style::{AnsiColor, RgbColor, StyleExt as _, Stylized, WebColor};
///
/// Stylized::force_ansi_color(true);
///
/// assert_eq!("red".foreground(AnsiColor::Red).to_string(), "\x1b[0;31mred\x1b[m");
/// assert_eq!("orange".foreground(WebColor(208)).to_string(), "\x1b[0;38;5;208morange\x1b[m");
/// assert_eq!(
///     "blue".foreground(RgbColor::new(0, 0, 255)).bold().to_string(),
///     "\x1b[0;38;2;0;0;255;1mblue\x1b[m",
/// );
/// ```
pub trait StyleExt<'a>: Sized {
    /// Wraps this value in [`Stylized`] without adding styles.
    fn stylized(self) -> Stylized<'a>;

    /// Adds a foreground color.
    fn foreground(self, color: impl Into<ColorSpec>) -> Stylized<'a> {
        let mut this = self.stylized();
        this.styles.push(Sgr::Foreground(color.into()));
        this
    }
    /// Adds the standard red foreground color.
    fn red(self) -> Stylized<'a> {
        self.foreground(ColorSpec::RED)
    }
    /// Adds the standard yellow foreground color.
    fn yellow(self) -> Stylized<'a> {
        self.foreground(ColorSpec::YELLOW)
    }
    /// Adds the standard green foreground color.
    fn green(self) -> Stylized<'a> {
        self.foreground(ColorSpec::GREEN)
    }
    /// Adds a single underline.
    fn underlined(self) -> Stylized<'a> {
        let mut this = self.stylized();
        this.styles.push(Sgr::Underline(Underline::Single));
        this
    }
    /// Adds bold intensity.
    fn bold(self) -> Stylized<'a> {
        let mut this = self.stylized();
        this.styles.push(Sgr::Intensity(Intensity::Bold));
        this
    }
}

impl<'a> StyleExt<'a> for Cow<'a, str> {
    fn stylized(self) -> Stylized<'a> {
        Stylized {
            content: self,
            styles: Vec::with_capacity(2),
        }
    }
}

impl<'a> StyleExt<'a> for &'a str {
    fn stylized(self) -> Stylized<'a> {
        Cow::Borrowed(self).stylized()
    }
}

impl StyleExt<'static> for String {
    fn stylized(self) -> Stylized<'static> {
        Cow::<str>::Owned(self).stylized()
    }
}

// NOTE: this allows chaining like `"hello".green().bold()`.
impl<'a> StyleExt<'a> for Stylized<'a> {
    fn stylized(self) -> Stylized<'a> {
        self
    }
}

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

    #[test]
    fn parse_color() {
        assert_eq!("#282828".parse(), Ok(RgbColor::new(40, 40, 40)));
        assert_eq!("rgb:28/28/28".parse(), Ok(RgbColor::new(40, 40, 40)));
        assert_eq!("rgb:2828/2828/2828".parse(), Ok(RgbColor::new(40, 40, 40)));
    }
}
