//! Scalar value wrapper with proper escaping and style support.

use std::fmt;

#[cfg(feature = "base64")]
use base64::{engine::general_purpose, Engine as _};

/// Base64 encode bytes for binary data
#[cfg(feature = "base64")]
fn base64_encode(input: &[u8]) -> String {
    general_purpose::STANDARD.encode(input)
}

/// Base64 decode string back to bytes
#[cfg(feature = "base64")]
fn base64_decode(input: &str) -> Result<Vec<u8>, String> {
    general_purpose::STANDARD
        .decode(input.trim())
        .map_err(|e| format!("Base64 decode error: {}", e))
}

/// Style of scalar representation in YAML
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScalarStyle {
    /// Plain scalar (no quotes)
    Plain,
    /// Single-quoted scalar
    SingleQuoted,
    /// Double-quoted scalar
    DoubleQuoted,
    /// Literal scalar (|)
    Literal,
    /// Folded scalar (>)
    Folded,
}

/// Type of a scalar value
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ScalarType {
    /// String value
    String,
    /// Integer value
    Integer,
    /// Float value
    Float,
    /// Boolean value
    Boolean,
    /// Null value
    Null,
    /// Binary data (base64 encoded)
    #[cfg(feature = "base64")]
    Binary,
    /// Timestamp value
    Timestamp,
    /// Regular expression
    Regex,
}

/// A scalar value with metadata about its style and content
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ScalarValue {
    /// The actual value
    value: String,
    /// The style to use when rendering
    style: ScalarStyle,
    /// The type of the scalar
    scalar_type: ScalarType,
}

impl ScalarValue {
    /// Create a scalar value explicitly treating it as a string (no type auto-detection)
    ///
    /// This method always creates a `String` type scalar. The value will be properly
    /// quoted if needed when rendering to YAML, but no type detection is performed.
    ///
    /// # Examples
    ///
    /// ```
    /// use yaml_edit::ScalarValue;
    ///
    /// let scalar = ScalarValue::string("123");
    /// assert_eq!(scalar.scalar_type(), yaml_edit::ScalarType::String);
    /// // Renders with quotes to distinguish from integer
    /// assert_eq!(scalar.to_yaml_string(), "'123'");
    ///
    /// let scalar = ScalarValue::string("true");
    /// assert_eq!(scalar.scalar_type(), yaml_edit::ScalarType::String);
    /// // Renders with quotes to distinguish from boolean
    /// assert_eq!(scalar.to_yaml_string(), "'true'");
    ///
    /// let scalar = ScalarValue::string("hello");
    /// assert_eq!(scalar.scalar_type(), yaml_edit::ScalarType::String);
    /// // Plain strings don't need quotes
    /// assert_eq!(scalar.to_yaml_string(), "hello");
    /// ```
    ///
    /// For YAML-style type detection (parsing "123" as Integer, "true" as Boolean),
    /// use [`ScalarValue::parse()`] instead.
    pub fn string(value: impl Into<String>) -> Self {
        let value = value.into();
        let style = Self::detect_style(&value);
        // Detect the type - default to String for user-provided values
        let scalar_type = ScalarType::String;
        Self {
            value,
            style,
            scalar_type,
        }
    }

    /// Parse escape sequences in a double-quoted string
    pub fn parse_escape_sequences(text: &str) -> String {
        let mut result = String::with_capacity(text.len()); // Pre-allocate
        let mut chars = text.chars().peekable();

        while let Some(ch) = chars.next() {
            if ch == '\\' {
                if let Some(&escaped) = chars.peek() {
                    chars.next(); // consume the escaped character
                    match escaped {
                        // Standard escape sequences
                        'n' => result.push('\n'),
                        't' => result.push('\t'),
                        'r' => result.push('\r'),
                        'b' => result.push('\x08'),
                        'f' => result.push('\x0C'),
                        'a' => result.push('\x07'), // bell
                        'e' => result.push('\x1B'), // escape
                        'v' => result.push('\x0B'), // vertical tab
                        '0' => result.push('\0'),   // null
                        '\\' => result.push('\\'),
                        '"' => result.push('"'),
                        '\'' => result.push('\''),
                        '/' => result.push('/'),
                        // Line break escape (YAML specific)
                        ' ' => {
                            // Escaped space followed by line break - line folding
                            if let Some(&'\n') = chars.peek() {
                                chars.next(); // consume the newline
                                              // In YAML, escaped line breaks are folded to nothing
                                continue;
                            } else {
                                result.push(' ');
                            }
                        }
                        '\n' => {
                            // Escaped line break - removes the line break
                            continue;
                        }
                        // Unicode escapes
                        'x' => {
                            // \xNN - 2-digit hex
                            let mut hex_chars = [0u8; 2];
                            let mut count = 0;
                            for (i, ch) in chars.by_ref().take(2).enumerate() {
                                if let Some(digit) = ch.to_digit(16) {
                                    hex_chars[i] = digit as u8;
                                    count += 1;
                                } else {
                                    // Put back invalid char
                                    result.push('\\');
                                    result.push('x');
                                    for &hex_char in hex_chars.iter().take(count) {
                                        result.push(char::from_digit(hex_char as u32, 16).unwrap());
                                    }
                                    result.push(ch);
                                    break;
                                }
                            }
                            if count == 2 {
                                let code = hex_chars[0] * 16 + hex_chars[1];
                                result.push(code as char);
                            } else if count > 0 {
                                // Incomplete hex escape
                                result.push('\\');
                                result.push('x');
                                for &hex_char in hex_chars.iter().take(count) {
                                    result.push(char::from_digit(hex_char as u32, 16).unwrap());
                                }
                            }
                        }
                        'u' => {
                            // \uNNNN - 4-digit hex
                            let hex_digits: String = chars.by_ref().take(4).collect();
                            if hex_digits.len() == 4 {
                                if let Ok(code) = u16::from_str_radix(&hex_digits, 16) {
                                    if let Some(unicode_char) = char::from_u32(code as u32) {
                                        result.push(unicode_char);
                                    } else {
                                        // Invalid Unicode code point
                                        result.push('\\');
                                        result.push('u');
                                        result.push_str(&hex_digits);
                                    }
                                } else {
                                    // Invalid hex
                                    result.push('\\');
                                    result.push('u');
                                    result.push_str(&hex_digits);
                                }
                            } else {
                                // Incomplete hex escape
                                result.push('\\');
                                result.push('u');
                                result.push_str(&hex_digits);
                            }
                        }
                        'U' => {
                            // \UNNNNNNNN - 8-digit hex
                            let hex_digits: String = chars.by_ref().take(8).collect();
                            if hex_digits.len() == 8 {
                                if let Ok(code) = u32::from_str_radix(&hex_digits, 16) {
                                    if let Some(unicode_char) = char::from_u32(code) {
                                        result.push(unicode_char);
                                    } else {
                                        // Invalid Unicode code point
                                        result.push('\\');
                                        result.push('U');
                                        result.push_str(&hex_digits);
                                    }
                                } else {
                                    // Invalid hex
                                    result.push('\\');
                                    result.push('U');
                                    result.push_str(&hex_digits);
                                }
                            } else {
                                // Incomplete hex escape
                                result.push('\\');
                                result.push('U');
                                result.push_str(&hex_digits);
                            }
                        }
                        // Unknown escape sequence - preserve as literal
                        _ => {
                            result.push('\\');
                            result.push(escaped);
                        }
                    }
                } else {
                    // Backslash at end of string
                    result.push('\\');
                }
            } else {
                result.push(ch);
            }
        }

        result
    }

    /// Create a new scalar with a specific style
    pub fn with_style(value: impl Into<String>, style: ScalarStyle) -> Self {
        Self {
            value: value.into(),
            style,
            scalar_type: ScalarType::String,
        }
    }

    /// Create a plain scalar
    pub fn plain(value: impl Into<String>) -> Self {
        Self::with_style(value, ScalarStyle::Plain)
    }

    /// Create a single-quoted scalar
    pub fn single_quoted(value: impl Into<String>) -> Self {
        Self::with_style(value, ScalarStyle::SingleQuoted)
    }

    /// Create a double-quoted scalar
    pub fn double_quoted(value: impl Into<String>) -> Self {
        Self::with_style(value, ScalarStyle::DoubleQuoted)
    }

    /// Create a literal scalar
    pub fn literal(value: impl Into<String>) -> Self {
        Self::with_style(value, ScalarStyle::Literal)
    }

    /// Create a folded scalar
    pub fn folded(value: impl Into<String>) -> Self {
        Self::with_style(value, ScalarStyle::Folded)
    }

    /// Create a null scalar
    pub fn null() -> Self {
        Self {
            value: "null".to_string(),
            style: ScalarStyle::Plain,
            scalar_type: ScalarType::Null,
        }
    }

    /// Create a binary scalar from raw bytes
    #[cfg(feature = "base64")]
    pub fn binary(data: &[u8]) -> Self {
        let encoded = base64_encode(data);
        Self {
            value: encoded,
            style: ScalarStyle::Plain,
            scalar_type: ScalarType::Binary,
        }
    }

    /// Create a timestamp scalar
    pub fn timestamp(value: impl Into<String>) -> Self {
        Self {
            value: value.into(),
            style: ScalarStyle::Plain,
            scalar_type: ScalarType::Timestamp,
        }
    }

    /// Create a regex scalar
    pub fn regex(pattern: impl Into<String>) -> Self {
        Self {
            value: pattern.into(),
            style: ScalarStyle::Plain,
            scalar_type: ScalarType::Regex,
        }
    }

    /// Get the raw value
    pub fn value(&self) -> &str {
        &self.value
    }

    /// Get the style
    pub fn style(&self) -> ScalarStyle {
        self.style
    }

    /// Get the scalar type
    pub fn scalar_type(&self) -> ScalarType {
        self.scalar_type
    }

    /// Try to parse this scalar as an `i64`.
    ///
    /// Returns `None` if the scalar type is not `Integer`.
    pub fn to_i64(&self) -> Option<i64> {
        if self.scalar_type == ScalarType::Integer {
            Self::parse_integer(&self.value)
        } else {
            None
        }
    }

    /// Try to parse this scalar as an `f64`.
    ///
    /// Returns `None` if the scalar type is not `Float`.
    pub fn to_f64(&self) -> Option<f64> {
        if self.scalar_type == ScalarType::Float {
            self.value.trim().parse::<f64>().ok()
        } else {
            None
        }
    }

    /// Try to parse this scalar as a `bool`.
    ///
    /// Returns `None` if the scalar type is not `Boolean`.
    /// Recognizes: `true`, `false`, `yes`, `no`, `on`, `off` (case-insensitive).
    pub fn to_bool(&self) -> Option<bool> {
        if self.scalar_type == ScalarType::Boolean {
            match self.value.to_lowercase().as_str() {
                "true" | "yes" | "on" => Some(true),
                "false" | "no" | "off" => Some(false),
                _ => None,
            }
        } else {
            None
        }
    }

    /// Extract binary data if this is a binary scalar
    #[cfg(feature = "base64")]
    pub fn as_binary(&self) -> Option<Result<Vec<u8>, String>> {
        match self.scalar_type {
            ScalarType::Binary => Some(base64_decode(&self.value)),
            _ => None,
        }
    }

    /// Check if this is a binary scalar
    #[cfg(feature = "base64")]
    pub fn is_binary(&self) -> bool {
        self.scalar_type == ScalarType::Binary
    }

    /// Check if this is a timestamp scalar
    pub fn is_timestamp(&self) -> bool {
        self.scalar_type == ScalarType::Timestamp
    }

    /// Check if this is a regex scalar
    pub fn is_regex(&self) -> bool {
        self.scalar_type == ScalarType::Regex
    }

    /// Compile and return a Regex object if this is a regex scalar
    ///
    /// This method is only available when the `regex` feature is enabled.
    /// Returns None if this is not a regex scalar or if the pattern is invalid.
    ///
    /// # Example
    /// ```
    /// # #[cfg(feature = "regex")]
    /// # {
    /// use yaml_edit::ScalarValue;
    ///
    /// let scalar = ScalarValue::regex(r"\d{3}-\d{4}");
    /// let regex = scalar.as_regex().unwrap();
    /// assert!(regex.is_match("555-1234"));
    /// # }
    /// ```
    #[cfg(feature = "regex")]
    pub fn as_regex(&self) -> Option<regex::Regex> {
        if self.scalar_type == ScalarType::Regex {
            regex::Regex::new(&self.value).ok()
        } else {
            None
        }
    }

    /// Try to compile this scalar as a regex, regardless of its type
    ///
    /// This method is only available when the `regex` feature is enabled.
    /// This will attempt to compile the scalar value as a regex pattern,
    /// even if it's not marked with the !!regex tag.
    ///
    /// # Example
    /// ```
    /// # #[cfg(feature = "regex")]
    /// # {
    /// use yaml_edit::ScalarValue;
    ///
    /// let scalar = ScalarValue::string(r"\d+");  // Plain string scalar
    /// let regex = scalar.try_as_regex().unwrap();
    /// assert!(regex.is_match("123"));
    /// # }
    /// ```
    #[cfg(feature = "regex")]
    pub fn try_as_regex(&self) -> Result<regex::Regex, regex::Error> {
        regex::Regex::new(&self.value)
    }

    /// Try to coerce this scalar to the specified type
    pub fn coerce_to_type(&self, target_type: ScalarType) -> Option<ScalarValue> {
        if self.scalar_type == target_type {
            return Some(self.clone());
        }

        match target_type {
            ScalarType::String => Some(ScalarValue {
                value: self.value.clone(),
                style: ScalarStyle::Plain,
                scalar_type: ScalarType::String,
            }),
            ScalarType::Integer => Self::parse_integer(&self.value).map(ScalarValue::from),
            ScalarType::Float => self.value.parse::<f64>().ok().map(ScalarValue::from),
            ScalarType::Boolean => match self.value.to_lowercase().as_str() {
                "true" | "yes" | "on" | "1" => Some(ScalarValue::from(true)),
                "false" | "no" | "off" | "0" => Some(ScalarValue::from(false)),
                _ => None,
            },
            ScalarType::Null => match self.value.to_lowercase().as_str() {
                "null" | "~" | "" => Some(ScalarValue::null()),
                _ => None,
            },
            #[cfg(feature = "base64")]
            ScalarType::Binary => {
                // Try to decode as base64 to verify it's valid binary data
                if base64_decode(&self.value).is_ok() {
                    Some(ScalarValue {
                        value: self.value.clone(),
                        style: ScalarStyle::Plain,
                        scalar_type: ScalarType::Binary,
                    })
                } else {
                    None
                }
            }
            ScalarType::Timestamp => {
                // Basic timestamp format validation
                if self.is_valid_timestamp(&self.value) {
                    Some(ScalarValue::timestamp(&self.value))
                } else {
                    None
                }
            }
            ScalarType::Regex => {
                // For regex, just convert the value
                Some(ScalarValue::regex(&self.value))
            }
        }
    }

    /// Parse an integer with support for various formats
    /// Supports: decimal, hexadecimal (0x), binary (0b), octal (0o and legacy 0)
    pub(crate) fn parse_integer(value: &str) -> Option<i64> {
        let value = value.trim();

        // Handle negative numbers
        let (is_negative, value) = if let Some(stripped) = value.strip_prefix('-') {
            (true, stripped)
        } else if let Some(stripped) = value.strip_prefix('+') {
            (false, stripped)
        } else {
            (false, value)
        };

        let parsed = if let Some(hex_part) = value
            .strip_prefix("0x")
            .or_else(|| value.strip_prefix("0X"))
        {
            // Hexadecimal
            i64::from_str_radix(hex_part, 16).ok()
        } else if let Some(bin_part) = value
            .strip_prefix("0b")
            .or_else(|| value.strip_prefix("0B"))
        {
            // Binary
            i64::from_str_radix(bin_part, 2).ok()
        } else if let Some(oct_part) = value
            .strip_prefix("0o")
            .or_else(|| value.strip_prefix("0O"))
        {
            // Modern octal
            i64::from_str_radix(oct_part, 8).ok()
        } else if value.starts_with('0')
            && value.len() > 1
            && value.chars().all(|c| c.is_ascii_digit())
        {
            // Legacy octal (starts with 0 but not 0x, 0b, 0o)
            i64::from_str_radix(value, 8).ok()
        } else {
            // Decimal
            value.parse::<i64>().ok()
        };

        parsed.map(|n| if is_negative { -n } else { n })
    }

    /// Auto-detect the most appropriate scalar type from a string value
    pub fn auto_detect_type(value: &str) -> ScalarType {
        // Check for null values first
        match value.to_lowercase().as_str() {
            "null" | "~" | "" => return ScalarType::Null,
            _ => {}
        }

        // Check for boolean values
        match value.to_lowercase().as_str() {
            "true" | "false" | "yes" | "no" | "on" | "off" => return ScalarType::Boolean,
            _ => {}
        }

        // Check for numbers with various formats
        if Self::parse_integer(value).is_some() {
            return ScalarType::Integer;
        }
        if value.parse::<f64>().is_ok() {
            return ScalarType::Float;
        }

        // Check for timestamps (basic patterns)
        if Self::is_valid_timestamp_static(value) {
            return ScalarType::Timestamp;
        }

        // Check for binary data (base64)
        #[cfg(feature = "base64")]
        if Self::looks_like_base64(value) && base64_decode(value).is_ok() {
            return ScalarType::Binary;
        }

        // Default to string
        ScalarType::String
    }

    /// Parse a YAML scalar value with automatic type detection
    ///
    /// This method automatically detects the YAML type based on the content:
    /// - "123" → Integer
    /// - "3.14" → Float
    /// - "true" / "false" → Boolean
    /// - "null" / "~" → Null
    /// - etc.
    ///
    /// # Examples
    ///
    /// ```
    /// use yaml_edit::{ScalarValue, ScalarType};
    ///
    /// let scalar = ScalarValue::parse("123");
    /// assert_eq!(scalar.scalar_type(), ScalarType::Integer);
    /// assert_eq!(scalar.to_yaml_string(), "123");
    ///
    /// let scalar = ScalarValue::parse("true");
    /// assert_eq!(scalar.scalar_type(), ScalarType::Boolean);
    /// assert_eq!(scalar.to_yaml_string(), "true");
    ///
    /// let scalar = ScalarValue::parse("hello");
    /// assert_eq!(scalar.scalar_type(), ScalarType::String);
    /// assert_eq!(scalar.to_yaml_string(), "hello");
    /// ```
    ///
    /// To create a String-type scalar without auto-detection (e.g., to represent
    /// the string "123" rather than the integer 123), use [`ScalarValue::string()`] instead.
    pub fn parse(value: impl Into<String>) -> Self {
        let value = value.into();
        let scalar_type = Self::auto_detect_type(&value);
        // For non-string types, use Plain style (no quotes)
        // For string types, detect appropriate style
        let style = match scalar_type {
            ScalarType::String => Self::detect_style(&value),
            // All other types use plain style
            _ => ScalarStyle::Plain,
        };

        Self {
            value,
            style,
            scalar_type,
        }
    }

    /// Create a ScalarValue from a Scalar syntax node, preserving the type from the lexer
    ///
    /// This extracts type information directly from the token kind (INT, BOOL, FLOAT, etc.)
    /// rather than guessing based on heuristics. This is the correct way to convert
    /// parsed YAML into ScalarValue.
    pub fn from_scalar(scalar: &crate::yaml::Scalar) -> Self {
        use crate::lex::SyntaxKind;
        use rowan::ast::AstNode;

        let value = scalar.as_string();

        // Get the token kind from the first token in the scalar
        let syntax_node = scalar.syntax();
        let scalar_type = if let Some(token) = syntax_node.first_token() {
            match token.kind() {
                SyntaxKind::INT => ScalarType::Integer,
                SyntaxKind::FLOAT => ScalarType::Float,
                SyntaxKind::BOOL => ScalarType::Boolean,
                SyntaxKind::NULL => ScalarType::Null,
                SyntaxKind::STRING => ScalarType::String,
                _ => ScalarType::String, // fallback
            }
        } else {
            ScalarType::String
        };

        // Determine style based on the actual text (with quotes if present)
        let raw_text = scalar.value();
        let style = if raw_text.starts_with('"') && raw_text.ends_with('"') {
            ScalarStyle::DoubleQuoted
        } else if raw_text.starts_with('\'') && raw_text.ends_with('\'') {
            ScalarStyle::SingleQuoted
        } else {
            ScalarStyle::Plain
        };

        Self {
            value,
            style,
            scalar_type,
        }
    }

    /// Check if a string looks like base64 encoded data
    #[cfg(feature = "base64")]
    fn looks_like_base64(value: &str) -> bool {
        if value.is_empty() {
            return false;
        }

        // Must be reasonable length and contain only base64 characters
        // Also need to check that padding is only at the end
        if value.len() < 4 || value.len() % 4 != 0 {
            return false;
        }

        let padding_count = value.chars().filter(|&c| c == '=').count();
        if padding_count > 2 {
            return false;
        }

        // Check all characters are valid base64
        if !value
            .chars()
            .all(|c| matches!(c, 'A'..='Z' | 'a'..='z' | '0'..='9' | '+' | '/' | '='))
        {
            return false;
        }

        // Check that padding is only at the end
        if padding_count > 0 {
            let padding_start = value.len() - padding_count;
            if !value[padding_start..].chars().all(|c| c == '=') {
                return false;
            }
            // Check that non-padding part doesn't contain '='
            if value[..padding_start].contains('=') {
                return false;
            }
        }

        // Final validation: try to decode it to ensure it's actually valid base64
        // This will catch cases like "SGVs" which looks valid but isn't proper base64
        base64_decode(value).is_ok()
    }

    /// Basic timestamp format validation
    fn is_valid_timestamp(&self, value: &str) -> bool {
        Self::is_valid_timestamp_static(value)
    }

    /// Static version of timestamp validation
    fn is_valid_timestamp_static(value: &str) -> bool {
        // Basic patterns for common timestamp formats
        // ISO 8601: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS etc.
        if Self::matches_iso8601_pattern(value) {
            return true;
        }

        // Unix timestamp (seconds since epoch)
        if let Ok(timestamp) = value.parse::<u64>() {
            // Reasonable range: between 1970 and 2100
            return timestamp > 0 && timestamp < 4_102_444_800; // 2100-01-01
        }

        false
    }

    /// Simple pattern matching for ISO 8601 timestamps
    fn matches_iso8601_pattern(value: &str) -> bool {
        let chars: Vec<char> = value.chars().collect();

        // Must be at least YYYY-MM-DD (10 chars)
        if chars.len() < 10 {
            return false;
        }

        // Check YYYY-MM-DD pattern
        if !(chars[0..4].iter().all(|c| c.is_ascii_digit())
            && chars[4] == '-'
            && chars[5..7].iter().all(|c| c.is_ascii_digit())
            && chars[7] == '-'
            && chars[8..10].iter().all(|c| c.is_ascii_digit()))
        {
            return false;
        }

        // Validate month and day ranges (basic validation)
        let month_str: String = chars[5..7].iter().collect();
        let day_str: String = chars[8..10].iter().collect();

        if let (Ok(month), Ok(day)) = (month_str.parse::<u8>(), day_str.parse::<u8>()) {
            if !(1..=12).contains(&month) || !(1..=31).contains(&day) {
                return false;
            }
        } else {
            return false;
        }

        // If it's just YYYY-MM-DD, that's valid
        if chars.len() == 10 {
            return true;
        }

        // Check for time part: T or t or space followed by HH:MM:SS
        if chars.len() >= 19 {
            let sep = chars[10];
            if (sep == 'T' || sep == 't' || sep == ' ')
                && chars[11..13].iter().all(|c| c.is_ascii_digit())
                && chars[13] == ':'
                && chars[14..16].iter().all(|c| c.is_ascii_digit())
                && chars[16] == ':'
                && chars[17..19].iter().all(|c| c.is_ascii_digit())
            {
                // Validate hour, minute, second ranges
                let hour_str: String = chars[11..13].iter().collect();
                let minute_str: String = chars[14..16].iter().collect();
                let second_str: String = chars[17..19].iter().collect();

                if let (Ok(hour), Ok(minute), Ok(second)) = (
                    hour_str.parse::<u8>(),
                    minute_str.parse::<u8>(),
                    second_str.parse::<u8>(),
                ) {
                    if hour > 23 || minute > 59 || second > 59 {
                        return false;
                    }
                } else {
                    return false;
                }

                return true;
            }
        }

        false
    }

    /// Detect the appropriate style for a value
    fn detect_style(value: &str) -> ScalarStyle {
        // Check if value needs quoting
        if Self::needs_quoting(value) {
            // Prefer single quotes if no single quotes in value
            if !value.contains('\'') {
                ScalarStyle::SingleQuoted
            } else {
                ScalarStyle::DoubleQuoted
            }
        } else if value.contains('\n') {
            // Multi-line strings use literal style
            ScalarStyle::Literal
        } else {
            ScalarStyle::Plain
        }
    }

    /// Check if a value needs quoting when treated as a string
    fn needs_quoting(value: &str) -> bool {
        // Empty string needs quotes
        if value.is_empty() {
            return true;
        }

        // Check for YAML keywords that would be misinterpreted
        // These need quotes when we want them as strings
        if value.eq_ignore_ascii_case("true")
            || value.eq_ignore_ascii_case("false")
            || value.eq_ignore_ascii_case("yes")
            || value.eq_ignore_ascii_case("no")
            || value.eq_ignore_ascii_case("on")
            || value.eq_ignore_ascii_case("off")
            || value.eq_ignore_ascii_case("null")
            || value == "~"
        {
            return true;
        }

        // Also quote things that look like numbers to preserve them as strings
        if value.parse::<f64>().is_ok() || Self::parse_integer(value).is_some() {
            return true;
        }

        // Check if starts with special characters
        if value.starts_with(|ch: char| {
            matches!(ch, '-' | '?' | '[' | ']' | '{' | '}' | ',' | '>' | '<')
        }) {
            return true;
        }

        // Check for special characters that require quoting
        // : and # need context-aware checking (only ambiguous before whitespace or at end)
        let mut chars = value.chars().peekable();
        while let Some(ch) = chars.next() {
            match ch {
                '&' | '*' | '!' | '|' | '\'' | '"' | '%' => return true,
                ':' | '#' if chars.peek().map_or(true, |next| next.is_whitespace()) => {
                    return true;
                }
                _ => {}
            }
        }

        // Leading/trailing whitespace needs quotes
        if value != value.trim() {
            return true;
        }

        false
    }

    /// Render the scalar as a YAML string with proper escaping
    pub fn to_yaml_string(&self) -> String {
        // For special data types, always include the tag regardless of style
        let tag_prefix = match self.scalar_type {
            #[cfg(feature = "base64")]
            ScalarType::Binary => "!!binary ",
            ScalarType::Timestamp => "!!timestamp ",
            ScalarType::Regex => "!!regex ",
            _ => "",
        };

        let content = match self.style {
            ScalarStyle::Plain => {
                // Check if we need to quote based on type vs content
                match self.scalar_type {
                    ScalarType::String => {
                        // For strings, quote if the content looks like a special value
                        if Self::needs_quoting(&self.value) {
                            self.to_single_quoted()
                        } else {
                            self.value.clone()
                        }
                    }
                    // For non-strings, output as plain (unquoted)
                    ScalarType::Integer
                    | ScalarType::Float
                    | ScalarType::Boolean
                    | ScalarType::Null
                    | ScalarType::Timestamp
                    | ScalarType::Regex => self.value.clone(),
                    #[cfg(feature = "base64")]
                    ScalarType::Binary => self.value.clone(),
                }
            }
            ScalarStyle::SingleQuoted => self.to_single_quoted(),
            ScalarStyle::DoubleQuoted => self.to_double_quoted(),
            ScalarStyle::Literal => self.to_literal(),
            ScalarStyle::Folded => self.to_folded(),
        };

        format!("{}{}", tag_prefix, content)
    }

    /// Convert to single-quoted string
    fn to_single_quoted(&self) -> String {
        // Escape single quotes by doubling them
        let escaped = self.value.replace('\'', "''");
        format!("'{}'", escaped)
    }

    /// Convert to double-quoted string
    fn to_double_quoted(&self) -> String {
        let mut result = String::from("\"");
        for ch in self.value.chars() {
            match ch {
                '"' => result.push_str("\\\""),
                '\\' => result.push_str("\\\\"),
                '\n' => result.push_str("\\n"),
                '\r' => result.push_str("\\r"),
                '\t' => result.push_str("\\t"),
                '\x08' => result.push_str("\\b"),
                '\x0C' => result.push_str("\\f"),
                '\x07' => result.push_str("\\a"), // bell
                '\x1B' => result.push_str("\\e"), // escape
                '\x0B' => result.push_str("\\v"), // vertical tab
                '\0' => result.push_str("\\0"),   // null
                c if c.is_control() || (c as u32) > 0x7F => {
                    // Handle Unicode characters and control characters
                    let code_point = c as u32;
                    if code_point <= 0xFF {
                        result.push_str(&format!("\\x{:02X}", code_point));
                    } else if code_point <= 0xFFFF {
                        result.push_str(&format!("\\u{:04X}", code_point));
                    } else {
                        result.push_str(&format!("\\U{:08X}", code_point));
                    }
                }
                c => result.push(c),
            }
        }
        result.push('"');
        result
    }

    /// Convert to literal block scalar
    fn to_literal(&self) -> String {
        self.to_literal_with_indent(2)
    }

    /// Convert to folded block scalar
    fn to_folded(&self) -> String {
        self.to_folded_with_indent(2)
    }

    /// Convert to literal block scalar with specific indentation
    pub fn to_literal_with_indent(&self, indent: usize) -> String {
        let indent_str = " ".repeat(indent);

        // Detect the existing indentation of the content
        let existing_indent = self.detect_content_indentation();

        // If content already has consistent indentation, preserve it
        if existing_indent.is_some() {
            format!("|\n{}", self.value)
        } else {
            // Add consistent indentation
            let indented = self
                .value
                .lines()
                .map(|line| {
                    if line.trim().is_empty() {
                        String::new()
                    } else {
                        format!("{}{}", indent_str, line)
                    }
                })
                .collect::<Vec<_>>()
                .join("\n");
            format!("|\n{}", indented)
        }
    }

    /// Convert to folded block scalar with specific indentation
    pub fn to_folded_with_indent(&self, indent: usize) -> String {
        let indent_str = " ".repeat(indent);

        // Detect the existing indentation of the content
        let existing_indent = self.detect_content_indentation();

        // If content already has consistent indentation, preserve it
        if existing_indent.is_some() {
            format!(">\n{}", self.value)
        } else {
            // Add consistent indentation
            let indented = self
                .value
                .lines()
                .map(|line| {
                    if line.trim().is_empty() {
                        String::new()
                    } else {
                        format!("{}{}", indent_str, line)
                    }
                })
                .collect::<Vec<_>>()
                .join("\n");
            format!(">\n{}", indented)
        }
    }

    /// Detect the minimum indentation level of non-empty lines in the content
    fn detect_content_indentation(&self) -> Option<usize> {
        let non_empty_lines: Vec<&str> = self
            .value
            .lines()
            .filter(|line| !line.trim().is_empty())
            .collect();

        if non_empty_lines.is_empty() {
            return None;
        }

        let mut min_indent = None;
        let mut all_have_same_indent = true;

        for line in non_empty_lines {
            let indent = line.len() - line.trim_start().len();
            match min_indent {
                None => min_indent = Some(indent),
                Some(current_min) => {
                    if indent != current_min {
                        all_have_same_indent = false;
                    }
                    min_indent = Some(current_min.min(indent));
                }
            }
        }

        // Only preserve indentation if all lines have some consistent structure
        if all_have_same_indent && min_indent.unwrap_or(0) > 0 {
            min_indent
        } else {
            None
        }
    }
}

impl fmt::Display for ScalarValue {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.to_yaml_string())
    }
}

impl From<String> for ScalarValue {
    fn from(value: String) -> Self {
        Self::string(value)
    }
}

impl From<&str> for ScalarValue {
    fn from(value: &str) -> Self {
        Self::string(value)
    }
}

impl From<i32> for ScalarValue {
    fn from(value: i32) -> Self {
        Self {
            value: value.to_string(),
            style: ScalarStyle::Plain,
            scalar_type: ScalarType::Integer,
        }
    }
}

impl From<i64> for ScalarValue {
    fn from(value: i64) -> Self {
        Self {
            value: value.to_string(),
            style: ScalarStyle::Plain,
            scalar_type: ScalarType::Integer,
        }
    }
}

impl From<f32> for ScalarValue {
    fn from(value: f32) -> Self {
        Self {
            value: value.to_string(),
            style: ScalarStyle::Plain,
            scalar_type: ScalarType::Float,
        }
    }
}

impl From<f64> for ScalarValue {
    fn from(value: f64) -> Self {
        Self {
            value: value.to_string(),
            style: ScalarStyle::Plain,
            scalar_type: ScalarType::Float,
        }
    }
}

impl From<bool> for ScalarValue {
    fn from(value: bool) -> Self {
        Self {
            value: if value { "true" } else { "false" }.to_string(),
            style: ScalarStyle::Plain,
            scalar_type: ScalarType::Boolean,
        }
    }
}

impl From<crate::yaml::Scalar> for ScalarValue {
    fn from(scalar: crate::yaml::Scalar) -> Self {
        let value = scalar.as_string();
        ScalarValue::parse(&value)
    }
}

impl crate::AsYaml for ScalarValue {
    fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
        None
    }

    fn kind(&self) -> crate::as_yaml::YamlKind {
        crate::as_yaml::YamlKind::Scalar
    }

    fn build_content(
        &self,
        builder: &mut rowan::GreenNodeBuilder,
        _indent: usize,
        _flow_context: bool,
    ) -> bool {
        use crate::lex::SyntaxKind;
        // Render with proper YAML quoting/escaping. When the value needs
        // quotes (e.g. the string "null", or text containing ": "), the
        // rendered text starts with a quote and must be tokenized as a
        // STRING regardless of the semantic type.
        let text = self.to_yaml_string();
        let quoted = text.starts_with('\'') || text.starts_with('"');
        let token_kind = if quoted {
            SyntaxKind::STRING
        } else {
            match self.scalar_type() {
                ScalarType::Integer => SyntaxKind::INT,
                ScalarType::Float => SyntaxKind::FLOAT,
                ScalarType::Boolean => SyntaxKind::BOOL,
                ScalarType::Null => SyntaxKind::NULL,
                _ => SyntaxKind::STRING,
            }
        };
        builder.start_node(SyntaxKind::SCALAR.into());
        builder.token(token_kind.into(), &text);
        builder.finish_node();
        false
    }

    fn is_inline(&self) -> bool {
        true
    }
}

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

    #[test]
    fn test_plain_scalars() {
        let scalar = ScalarValue::string("simple");
        assert_eq!(scalar.to_yaml_string(), "simple");

        let scalar = ScalarValue::string("hello world");
        assert_eq!(scalar.to_yaml_string(), "hello world");
    }

    #[test]
    fn test_values_needing_quotes() {
        // Boolean-like values
        let scalar = ScalarValue::string("true");
        assert_eq!(scalar.to_yaml_string(), "'true'");

        let scalar = ScalarValue::string("false");
        assert_eq!(scalar.to_yaml_string(), "'false'");

        let scalar = ScalarValue::string("yes");
        assert_eq!(scalar.to_yaml_string(), "'yes'");

        let scalar = ScalarValue::string("no");
        assert_eq!(scalar.to_yaml_string(), "'no'");

        // Null-like values
        let scalar = ScalarValue::string("null");
        assert_eq!(scalar.to_yaml_string(), "'null'");

        let scalar = ScalarValue::string("~");
        assert_eq!(scalar.to_yaml_string(), "'~'");

        // Numbers
        let scalar = ScalarValue::string("123");
        assert_eq!(scalar.to_yaml_string(), "'123'");

        let scalar = ScalarValue::string("3.14");
        assert_eq!(scalar.to_yaml_string(), "'3.14'");

        // Special characters
        let scalar = ScalarValue::string("value: something");
        assert_eq!(scalar.to_yaml_string(), "'value: something'");

        let scalar = ScalarValue::string("# comment");
        assert_eq!(scalar.to_yaml_string(), "'# comment'");

        // Leading/trailing whitespace
        let scalar = ScalarValue::string("  spaces  ");
        assert_eq!(scalar.to_yaml_string(), "'  spaces  '");
    }

    #[test]
    fn test_single_quoted() {
        let scalar = ScalarValue::single_quoted("value with 'quotes'");
        assert_eq!(scalar.to_yaml_string(), "'value with ''quotes'''");
    }

    #[test]
    fn test_double_quoted() {
        let scalar = ScalarValue::double_quoted("value with \"quotes\" and \\backslash");
        assert_eq!(
            scalar.to_yaml_string(),
            "\"value with \\\"quotes\\\" and \\\\backslash\""
        );

        let scalar = ScalarValue::double_quoted("line1\nline2\ttab");
        assert_eq!(scalar.to_yaml_string(), "\"line1\\nline2\\ttab\"");
    }

    #[test]
    fn test_multiline() {
        let scalar = ScalarValue::string("line1\nline2\nline3");
        // Should auto-detect literal style for multiline
        assert_eq!(scalar.style(), ScalarStyle::Literal);
    }

    #[test]
    fn test_from_types() {
        let scalar = ScalarValue::from(42);
        assert_eq!(scalar.to_yaml_string(), "42");

        let scalar = ScalarValue::from(1.234);
        assert_eq!(scalar.to_yaml_string(), "1.234");

        let scalar = ScalarValue::from(true);
        assert_eq!(scalar.to_yaml_string(), "true");

        let scalar = ScalarValue::from(false);
        assert_eq!(scalar.to_yaml_string(), "false");
    }

    #[test]
    fn test_empty_string() {
        let scalar = ScalarValue::string("");
        assert_eq!(scalar.to_yaml_string(), "''");
    }

    #[test]
    fn test_special_start_chars() {
        let scalar = ScalarValue::string("-item");
        assert_eq!(scalar.to_yaml_string(), "'-item'");

        let scalar = ScalarValue::string("?key");
        assert_eq!(scalar.to_yaml_string(), "'?key'");

        let scalar = ScalarValue::string("[array]");
        assert_eq!(scalar.to_yaml_string(), "'[array]'");
    }

    #[test]
    fn test_null_scalar() {
        let scalar = ScalarValue::null();
        assert_eq!(scalar.to_yaml_string(), "null");
        assert_eq!(scalar.scalar_type, ScalarType::Null);
    }

    #[test]
    fn test_escape_sequences_basic() {
        // Test basic escape sequences
        assert_eq!(
            ScalarValue::parse_escape_sequences("hello\\nworld"),
            "hello\nworld"
        );
        assert_eq!(
            ScalarValue::parse_escape_sequences("tab\\there"),
            "tab\there"
        );
        assert_eq!(
            ScalarValue::parse_escape_sequences("quote\\\"test"),
            "quote\"test"
        );
        assert_eq!(
            ScalarValue::parse_escape_sequences("back\\\\slash"),
            "back\\slash"
        );
        assert_eq!(
            ScalarValue::parse_escape_sequences("return\\rtest"),
            "return\rtest"
        );
    }

    #[test]
    fn test_escape_sequences_control_chars() {
        // Test control character escapes
        assert_eq!(ScalarValue::parse_escape_sequences("bell\\a"), "bell\x07");
        assert_eq!(
            ScalarValue::parse_escape_sequences("backspace\\b"),
            "backspace\x08"
        );
        assert_eq!(
            ScalarValue::parse_escape_sequences("formfeed\\f"),
            "formfeed\x0C"
        );
        assert_eq!(
            ScalarValue::parse_escape_sequences("escape\\e"),
            "escape\x1B"
        );
        assert_eq!(ScalarValue::parse_escape_sequences("vtab\\v"), "vtab\x0B");
        assert_eq!(ScalarValue::parse_escape_sequences("null\\0"), "null\0");
        assert_eq!(ScalarValue::parse_escape_sequences("slash\\/"), "slash/");
    }

    #[test]
    fn test_escape_sequences_unicode_x() {
        // Test \xNN escape sequences
        assert_eq!(ScalarValue::parse_escape_sequences("\\x41"), "A"); // 0x41 = 'A'
        assert_eq!(ScalarValue::parse_escape_sequences("\\x7A"), "z"); // 0x7A = 'z'
        assert_eq!(ScalarValue::parse_escape_sequences("\\x20"), " "); // 0x20 = space
        assert_eq!(ScalarValue::parse_escape_sequences("\\xFF"), "\u{FF}"); // 0xFF = ÿ

        // Test invalid hex sequences
        assert_eq!(ScalarValue::parse_escape_sequences("\\xGH"), "\\xGH"); // Invalid hex
        assert_eq!(ScalarValue::parse_escape_sequences("\\x4"), "\\x4"); // Incomplete
    }

    #[test]
    fn test_escape_sequences_unicode_u() {
        // Test \uNNNN escape sequences
        assert_eq!(ScalarValue::parse_escape_sequences("\\u0041"), "A"); // 0x0041 = 'A'
        assert_eq!(ScalarValue::parse_escape_sequences("\\u03B1"), "α"); // Greek alpha
        assert_eq!(ScalarValue::parse_escape_sequences("\\u2603"), "☃"); // Snowman
        assert_eq!(ScalarValue::parse_escape_sequences("\\u4E2D"), "中"); // Chinese character

        // Test invalid sequences
        assert_eq!(ScalarValue::parse_escape_sequences("\\uGHIJ"), "\\uGHIJ"); // Invalid hex
        assert_eq!(ScalarValue::parse_escape_sequences("\\u041"), "\\u041"); // Incomplete
    }

    #[test]
    fn test_escape_sequences_unicode_capital_u() {
        // Test \UNNNNNNNN escape sequences
        assert_eq!(ScalarValue::parse_escape_sequences("\\U00000041"), "A"); // 0x00000041 = 'A'
        assert_eq!(ScalarValue::parse_escape_sequences("\\U0001F603"), "😃"); // Smiley emoji
        assert_eq!(ScalarValue::parse_escape_sequences("\\U0001F4A9"), "💩"); // Pile of poo emoji

        // Test invalid sequences
        assert_eq!(
            ScalarValue::parse_escape_sequences("\\UGHIJKLMN"),
            "\\UGHIJKLMN"
        ); // Invalid hex
        assert_eq!(
            ScalarValue::parse_escape_sequences("\\U0000004"),
            "\\U0000004"
        ); // Incomplete
        assert_eq!(
            ScalarValue::parse_escape_sequences("\\UFFFFFFFF"),
            "\\UFFFFFFFF"
        ); // Invalid code point
    }

    #[test]
    fn test_escape_sequences_line_folding() {
        // Test line folding with escaped spaces and newlines
        assert_eq!(
            ScalarValue::parse_escape_sequences("line\\ \nfolding"),
            "linefolding"
        );
        assert_eq!(
            ScalarValue::parse_escape_sequences("escaped\\nline\\nbreak"),
            "escaped\nline\nbreak"
        );
        assert_eq!(
            ScalarValue::parse_escape_sequences("remove\\\nline\\nbreak"),
            "removeline\nbreak"
        );
    }

    #[test]
    fn test_escape_sequences_mixed() {
        // Test mixed escape sequences
        let input = "Hello\\nWorld\\u0021\\x20\\U0001F44D";
        let expected = "Hello\nWorld! 👍";
        assert_eq!(ScalarValue::parse_escape_sequences(input), expected);

        // Test with quotes and backslashes
        let input = "Quote\\\"back\\\\slash\\ttab";
        let expected = "Quote\"back\\slash\ttab";
        assert_eq!(ScalarValue::parse_escape_sequences(input), expected);
    }

    #[test]
    fn test_escape_sequences_unknown() {
        // Test unknown escape sequences are preserved
        assert_eq!(ScalarValue::parse_escape_sequences("\\q"), "\\q");
        assert_eq!(ScalarValue::parse_escape_sequences("\\z"), "\\z");
        assert_eq!(ScalarValue::parse_escape_sequences("\\1"), "\\1");
    }

    #[test]
    fn test_indentation_preservation() {
        // Test preserving exact indentation in block scalars
        let content_with_indent = "  Line 1\n    Line 2 more indented\n  Line 3";
        let scalar = ScalarValue::literal(content_with_indent);

        // Should detect that content already has indentation and preserve it
        let yaml_output = scalar.to_literal_with_indent(2);
        assert_eq!(
            yaml_output,
            "|\n    Line 1\n      Line 2 more indented\n    Line 3"
        );
    }

    #[test]
    fn test_indentation_detection() {
        // Test content with consistent indentation
        let consistent_content = "  Line 1\n  Line 2\n  Line 3";
        let scalar1 = ScalarValue::literal(consistent_content);
        assert_eq!(scalar1.detect_content_indentation(), Some(2));

        // Test content with no indentation
        let no_indent_content = "Line 1\nLine 2\nLine 3";
        let scalar2 = ScalarValue::literal(no_indent_content);
        assert_eq!(scalar2.detect_content_indentation(), None);

        // Test content with inconsistent indentation
        let inconsistent_content = "  Line 1\n    Line 2\n Line 3";
        let scalar3 = ScalarValue::literal(inconsistent_content);
        assert_eq!(scalar3.detect_content_indentation(), None);

        // Test empty content
        let empty_content = "";
        let scalar4 = ScalarValue::literal(empty_content);
        assert_eq!(scalar4.detect_content_indentation(), None);

        // Test content with only whitespace lines
        let whitespace_content = "  Line 1\n\n  Line 3";
        let scalar5 = ScalarValue::literal(whitespace_content);
        assert_eq!(scalar5.detect_content_indentation(), Some(2));
    }

    #[test]
    fn test_literal_with_custom_indent() {
        // Test applying custom indentation to unindented content
        let content = "Line 1\nLine 2\nLine 3";
        let scalar = ScalarValue::literal(content);

        let yaml_4_spaces = scalar.to_literal_with_indent(4);
        assert_eq!(yaml_4_spaces, "|\n    Line 1\n    Line 2\n    Line 3");

        let yaml_1_space = scalar.to_literal_with_indent(1);
        assert_eq!(yaml_1_space, "|\n Line 1\n Line 2\n Line 3");
    }

    #[test]
    fn test_folded_with_custom_indent() {
        // Test applying custom indentation to folded scalars
        let content = "Line 1\nLine 2\nLine 3";
        let scalar = ScalarValue::folded(content);

        let yaml_3_spaces = scalar.to_folded_with_indent(3);
        assert_eq!(yaml_3_spaces, ">\n   Line 1\n   Line 2\n   Line 3");
    }

    #[test]
    fn test_mixed_empty_lines_preservation() {
        // Test handling of empty lines in block scalars
        let content_with_empty_lines = "Line 1\n\nLine 3\n\n\nLine 6";
        let scalar = ScalarValue::literal(content_with_empty_lines);

        let yaml_output = scalar.to_literal_with_indent(2);
        assert_eq!(yaml_output, "|\n  Line 1\n\n  Line 3\n\n\n  Line 6");

        // Empty lines should remain empty (no indentation added)
        // Input has 3 empty lines; they should appear unchanged in the output
        let lines: Vec<&str> = yaml_output.lines().collect();
        let empty_line_count = lines.iter().filter(|line| line.is_empty()).count();
        assert_eq!(empty_line_count, 3);
    }

    #[test]
    fn test_escape_sequences_edge_cases() {
        // Test edge cases
        assert_eq!(ScalarValue::parse_escape_sequences(""), "");
        assert_eq!(ScalarValue::parse_escape_sequences("\\"), "\\");
        assert_eq!(
            ScalarValue::parse_escape_sequences("no escapes"),
            "no escapes"
        );
        assert_eq!(ScalarValue::parse_escape_sequences("\\\\\\\\"), "\\\\");
    }

    #[test]
    fn test_double_quoted_with_escapes() {
        // Test that double-quoted scalars properly escape and unescape
        let original = "Hello\nWorld\t😃";
        let scalar = ScalarValue::double_quoted(original);
        let yaml_string = scalar.to_yaml_string();

        // Should contain escaped sequences
        assert_eq!(yaml_string, "\"Hello\\nWorld\\t\\U0001F603\"");

        // Parse it back
        let parsed = ScalarValue::parse_escape_sequences(&yaml_string[1..yaml_string.len() - 1]);
        assert_eq!(parsed, original);
    }

    #[test]
    fn test_unicode_output_formatting() {
        // Test that Unicode characters are properly formatted in output
        let scalar = ScalarValue::double_quoted("Hello 世界 🌍");
        let yaml_string = scalar.to_yaml_string();

        // Should escape non-ASCII characters
        assert_eq!(yaml_string, "\"Hello \\u4E16\\u754C \\U0001F30D\"");

        // But the internal value should remain unchanged
        assert_eq!(scalar.value(), "Hello 世界 🌍");
    }

    #[test]
    #[cfg(feature = "base64")]
    fn test_binary_data_encoding() {
        // Test creating binary scalar from raw bytes
        let data = b"Hello, World!";
        let scalar = ScalarValue::binary(data);

        assert!(scalar.is_binary());
        assert_eq!(scalar.scalar_type(), ScalarType::Binary);

        // Should produce valid base64
        let yaml_output = scalar.to_yaml_string();
        assert!(yaml_output.starts_with("!!binary "));

        // Should be able to decode back to original data
        if let Some(decoded_result) = scalar.as_binary() {
            let decoded = decoded_result.expect("Should decode successfully");
            assert_eq!(decoded, data);
        } else {
            panic!("Should be able to extract binary data");
        }
    }

    #[test]
    #[cfg(feature = "base64")]
    fn test_base64_encoding_decoding() {
        // Test various byte sequences
        let test_cases = [
            b"".as_slice(),
            b"A",
            b"AB",
            b"ABC",
            b"ABCD",
            b"Hello, World!",
            &[0, 1, 2, 3, 255, 254, 253],
        ];

        for data in test_cases {
            let encoded = base64_encode(data);
            let decoded = base64_decode(&encoded).expect("Should decode successfully");
            assert_eq!(decoded, data, "Failed for data: {:?}", data);
        }
    }

    #[test]
    fn test_timestamp_creation_and_validation() {
        // Test various timestamp formats
        let valid_timestamps = [
            "2023-12-25",
            "2023-12-25T10:30:45",
            "2023-12-25 10:30:45",
            "2023-12-25T10:30:45Z",
            "2001-12-14 21:59:43.10 -5", // Space-separated with timezone
            "2001-12-15T02:59:43.1Z",    // ISO 8601
            "2001-12-14t21:59:43.10-05:00", // Lowercase t
        ];

        for ts in valid_timestamps {
            let scalar = ScalarValue::timestamp(ts);
            assert!(scalar.is_timestamp());
            assert_eq!(scalar.scalar_type(), ScalarType::Timestamp);
            assert_eq!(scalar.value(), ts);

            let yaml_output = scalar.to_yaml_string();
            assert_eq!(yaml_output, format!("!!timestamp {}", ts));

            // Test auto-detection recognizes it as timestamp
            let auto_scalar = ScalarValue::parse(ts);
            assert_eq!(
                auto_scalar.scalar_type(),
                ScalarType::Timestamp,
                "Failed to auto-detect '{}' as timestamp",
                ts
            );
        }

        // Test invalid timestamps are not recognized
        let invalid_timestamps = [
            "not-a-date",
            "2023-13-01", // Invalid month
            "2023-12-32", // Invalid day
            "12:34:56",   // Time only (should be String)
            "2023/12/25", // Wrong separator
        ];

        for ts in invalid_timestamps {
            let auto_scalar = ScalarValue::parse(ts);
            assert_ne!(
                auto_scalar.scalar_type(),
                ScalarType::Timestamp,
                "'{}' should not be detected as timestamp",
                ts
            );
        }
    }

    #[test]
    fn test_regex_creation() {
        let pattern = r"^\d{3}-\d{2}-\d{4}$";
        let scalar = ScalarValue::regex(pattern);

        assert!(scalar.is_regex());
        assert_eq!(scalar.scalar_type(), ScalarType::Regex);
        assert_eq!(scalar.value(), pattern);

        let yaml_output = scalar.to_yaml_string();
        assert_eq!(yaml_output, format!("!!regex {}", pattern));
    }

    #[test]
    fn test_regex_edge_cases() {
        // Test empty pattern
        let empty_regex = ScalarValue::regex("");
        assert!(empty_regex.is_regex());
        assert_eq!(empty_regex.value(), "");
        assert_eq!(empty_regex.to_yaml_string(), "!!regex ");

        // Test pattern with special characters
        let special_chars = ScalarValue::regex(r"[.*+?^${}()|[\]\\]");
        assert!(special_chars.is_regex());
        assert_eq!(special_chars.value(), r"[.*+?^${}()|[\]\\]");

        // Test unicode patterns
        let unicode_regex = ScalarValue::regex(r"\p{L}+");
        assert!(unicode_regex.is_regex());
        assert_eq!(unicode_regex.value(), r"\p{L}+");

        // Test very long pattern
        let long_pattern = "a".repeat(1000);
        let long_regex = ScalarValue::regex(&long_pattern);
        assert!(long_regex.is_regex());
        assert_eq!(long_regex.value(), long_pattern);

        // Test pattern with quotes and escapes
        let quoted_regex = ScalarValue::regex(r#"'quoted' and "double quoted" with \\ backslash"#);
        assert!(quoted_regex.is_regex());
        assert_eq!(
            quoted_regex.value(),
            r#"'quoted' and "double quoted" with \\ backslash"#
        );
    }

    #[test]
    fn test_regex_type_coercion() {
        let regex_scalar = ScalarValue::regex(r"\d+");

        // Test coercing regex to string
        let string_scalar = regex_scalar.coerce_to_type(ScalarType::String).unwrap();
        assert_eq!(string_scalar.scalar_type(), ScalarType::String);
        assert_eq!(string_scalar.value(), r"\d+");
        assert!(!string_scalar.is_regex());

        // Test coercing string to regex
        let str_scalar = ScalarValue::string("test.*");
        let regex_from_string = str_scalar.coerce_to_type(ScalarType::Regex).unwrap();
        assert_eq!(regex_from_string.scalar_type(), ScalarType::Regex);
        assert_eq!(regex_from_string.value(), "test.*");
        assert!(regex_from_string.is_regex());

        // Test that regex cannot be coerced to number types
        assert!(regex_scalar.coerce_to_type(ScalarType::Integer).is_none());
        assert!(regex_scalar.coerce_to_type(ScalarType::Float).is_none());
        assert!(regex_scalar.coerce_to_type(ScalarType::Boolean).is_none());
    }

    #[test]
    #[cfg(feature = "regex")]
    fn test_regex_compilation() {
        // Test as_regex() with a regex scalar
        let regex_scalar = ScalarValue::regex(r"\d{3}-\d{4}");
        let compiled = regex_scalar.as_regex().unwrap();
        assert!(compiled.is_match("555-1234"));
        assert!(!compiled.is_match("not-a-phone"));

        // Test as_regex() with a non-regex scalar returns None
        let string_scalar = ScalarValue::string("not a regex");
        assert!(string_scalar.as_regex().is_none());

        // Test try_as_regex() with any scalar type
        let pattern_scalar = ScalarValue::string(r"^\w+@\w+\.\w+$");
        let email_regex = pattern_scalar.try_as_regex().unwrap();
        assert!(email_regex.is_match("test@example.com"));
        assert!(!email_regex.is_match("not-an-email"));

        // Test with invalid regex pattern
        let invalid_scalar = ScalarValue::regex(r"[invalid(");
        assert!(invalid_scalar.as_regex().is_none());

        // Test try_as_regex() with invalid pattern returns error
        let invalid_pattern = ScalarValue::string(r"[invalid(");
        assert!(invalid_pattern.try_as_regex().is_err());
    }

    #[test]
    #[cfg(feature = "regex")]
    fn test_regex_extraction_use_cases() {
        // Test extracting and using regex for validation
        let validation_rules = [
            ScalarValue::regex(r"^\d{5}$"),                 // ZIP code
            ScalarValue::regex(r"^[A-Z]{2}$"),              // State code
            ScalarValue::regex(r"^\(\d{3}\) \d{3}-\d{4}$"), // Phone number
        ];

        let test_values = ["12345", "CA", "(555) 123-4567"];

        for (rule, value) in validation_rules.iter().zip(test_values.iter()) {
            let regex = rule.as_regex().unwrap();
            assert!(regex.is_match(value), "Pattern should match {}", value);
        }

        // Test with complex regex patterns
        let email_regex = ScalarValue::regex(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$");
        let compiled = email_regex.as_regex().unwrap();
        assert!(compiled.is_match("user@example.com"));
        assert!(compiled.is_match("test.user+tag@sub.domain.org"));
        assert!(!compiled.is_match("invalid.email"));

        // Test extracting capture groups
        let version_regex = ScalarValue::regex(r"^v(\d+)\.(\d+)\.(\d+)$");
        let compiled = version_regex.as_regex().unwrap();
        if let Some(captures) = compiled.captures("v1.2.3") {
            assert_eq!(captures.get(1).unwrap().as_str(), "1");
            assert_eq!(captures.get(2).unwrap().as_str(), "2");
            assert_eq!(captures.get(3).unwrap().as_str(), "3");
        } else {
            panic!("Should have matched version string");
        }
    }

    #[test]
    fn test_type_coercion() {
        // Test coercing string to integer
        let str_scalar = ScalarValue::string("42");
        let int_scalar = str_scalar.coerce_to_type(ScalarType::Integer).unwrap();
        assert_eq!(int_scalar.scalar_type(), ScalarType::Integer);
        assert_eq!(int_scalar.value(), "42");

        // Test coercing string to boolean
        let bool_scalar = ScalarValue::string("true")
            .coerce_to_type(ScalarType::Boolean)
            .unwrap();
        assert_eq!(bool_scalar.scalar_type(), ScalarType::Boolean);
        assert_eq!(bool_scalar.value(), "true");

        // Test coercing boolean string variations
        let yes_scalar = ScalarValue::string("yes")
            .coerce_to_type(ScalarType::Boolean)
            .unwrap();
        assert_eq!(yes_scalar.value(), "true");

        let no_scalar = ScalarValue::string("no")
            .coerce_to_type(ScalarType::Boolean)
            .unwrap();
        assert_eq!(no_scalar.value(), "false");

        // Test failed coercion
        let str_scalar = ScalarValue::string("not_a_number");
        assert!(str_scalar.coerce_to_type(ScalarType::Integer).is_none());
    }

    #[test]
    fn test_auto_type_detection() {
        // Test various automatic type detections
        assert_eq!(ScalarValue::auto_detect_type("42"), ScalarType::Integer);
        assert_eq!(ScalarValue::auto_detect_type("3.14"), ScalarType::Float);
        assert_eq!(ScalarValue::auto_detect_type("true"), ScalarType::Boolean);
        assert_eq!(ScalarValue::auto_detect_type("false"), ScalarType::Boolean);
        assert_eq!(ScalarValue::auto_detect_type("yes"), ScalarType::Boolean);
        assert_eq!(ScalarValue::auto_detect_type("null"), ScalarType::Null);
        assert_eq!(ScalarValue::auto_detect_type("~"), ScalarType::Null);
        assert_eq!(ScalarValue::auto_detect_type(""), ScalarType::Null);
        assert_eq!(
            ScalarValue::auto_detect_type("2023-12-25"),
            ScalarType::Timestamp
        );
        assert_eq!(
            ScalarValue::auto_detect_type("2023-12-25T10:30:45"),
            ScalarType::Timestamp
        );
        #[cfg(feature = "base64")]
        assert_eq!(
            ScalarValue::auto_detect_type("SGVsbG8gV29ybGQ="),
            ScalarType::Binary
        );
        #[cfg(not(feature = "base64"))]
        assert_eq!(
            ScalarValue::auto_detect_type("SGVsbG8gV29ybGQ="),
            ScalarType::String
        );
        assert_eq!(
            ScalarValue::auto_detect_type("hello world"),
            ScalarType::String
        );
    }

    #[test]
    fn test_from_yaml_scalar_creation() {
        let int_scalar = ScalarValue::parse("123");
        assert_eq!(int_scalar.scalar_type(), ScalarType::Integer);

        let bool_scalar = ScalarValue::parse("true");
        assert_eq!(bool_scalar.scalar_type(), ScalarType::Boolean);

        let timestamp_scalar = ScalarValue::parse("2023-12-25");
        assert_eq!(timestamp_scalar.scalar_type(), ScalarType::Timestamp);

        let string_scalar = ScalarValue::parse("hello world");
        assert_eq!(string_scalar.scalar_type(), ScalarType::String);
    }

    #[test]
    fn test_timestamp_pattern_matching() {
        // Valid patterns
        assert!(ScalarValue::matches_iso8601_pattern("2023-12-25"));
        assert!(ScalarValue::matches_iso8601_pattern("2023-12-25T10:30:45"));
        assert!(ScalarValue::matches_iso8601_pattern("2023-12-25t10:30:45")); // Lowercase t
        assert!(ScalarValue::matches_iso8601_pattern("2023-12-25 10:30:45"));
        assert!(ScalarValue::matches_iso8601_pattern("2023-01-01T00:00:00"));
        assert!(ScalarValue::matches_iso8601_pattern(
            "2001-12-14t21:59:43.10-05:00"
        )); // Complex with lowercase t

        // Invalid patterns
        assert!(!ScalarValue::matches_iso8601_pattern("2023-13-25")); // Invalid month
        assert!(!ScalarValue::matches_iso8601_pattern("23-12-25")); // Wrong year format
        assert!(!ScalarValue::matches_iso8601_pattern("2023/12/25")); // Wrong separator
        assert!(!ScalarValue::matches_iso8601_pattern("not-a-date"));
        assert!(!ScalarValue::matches_iso8601_pattern("2023"));
    }

    #[test]
    #[cfg(feature = "base64")]
    fn test_base64_detection() {
        // Valid base64 strings
        assert!(ScalarValue::looks_like_base64("SGVsbG8=")); // "Hello"
        assert!(ScalarValue::looks_like_base64("V29ybGQ=")); // "World"
        assert!(ScalarValue::looks_like_base64("SGVsbG8gV29ybGQ=")); // "Hello World"
        assert!(ScalarValue::looks_like_base64("AAAA")); // All A's

        // Invalid base64 strings
        assert!(!ScalarValue::looks_like_base64("Hello")); // No padding, wrong chars
        assert!(!ScalarValue::looks_like_base64("SGVsbG8")); // Missing padding (7 chars, should be 8 with padding)
        assert!(!ScalarValue::looks_like_base64("")); // Empty
        assert!(!ScalarValue::looks_like_base64("SGV@")); // Invalid character
        assert!(!ScalarValue::looks_like_base64("SGVsbG8g===")); // Too much padding
    }

    #[test]
    #[cfg(feature = "base64")]
    fn test_binary_yaml_output_with_tags() {
        let data = b"Binary data here";
        let scalar = ScalarValue::binary(data);
        let yaml_output = scalar.to_yaml_string();

        assert!(yaml_output.starts_with("!!binary "));

        // Extract just the base64 part
        let base64_part = &yaml_output[9..]; // Remove "!!binary "
        let decoded = base64_decode(base64_part).expect("Should decode");
        assert_eq!(decoded, data);
    }

    #[test]
    #[cfg(feature = "base64")]
    fn test_special_data_types_with_different_styles() {
        // Binary with different styles should still include tag
        let data = b"test";
        let binary_scalar = ScalarValue::binary(data);

        // Even if we change style, binary type should maintain tag
        let mut styled_binary = binary_scalar;
        styled_binary.style = ScalarStyle::DoubleQuoted;

        // The to_yaml_string should still respect the scalar type for tagging
        assert_eq!(styled_binary.to_yaml_string(), "!!binary \"dGVzdA==\"");
    }

    #[test]
    fn test_type_checking_methods() {
        #[cfg(feature = "base64")]
        let binary_scalar = ScalarValue::binary(b"test");
        let timestamp_scalar = ScalarValue::timestamp("2023-12-25");
        let regex_scalar = ScalarValue::regex(r"\d+");
        let string_scalar = ScalarValue::string("hello");

        // Test type checking methods
        #[cfg(feature = "base64")]
        assert!(binary_scalar.is_binary());
        #[cfg(feature = "base64")]
        assert!(!binary_scalar.is_timestamp());
        #[cfg(feature = "base64")]
        assert!(!binary_scalar.is_regex());

        #[cfg(feature = "base64")]
        assert!(!timestamp_scalar.is_binary());
        assert!(timestamp_scalar.is_timestamp());
        assert!(!timestamp_scalar.is_regex());

        #[cfg(feature = "base64")]
        assert!(!regex_scalar.is_binary());
        assert!(!regex_scalar.is_timestamp());
        assert!(regex_scalar.is_regex());

        #[cfg(feature = "base64")]
        assert!(!string_scalar.is_binary());
        assert!(!string_scalar.is_timestamp());
        assert!(!string_scalar.is_regex());
    }

    #[test]
    fn test_binary_number_parsing() {
        // Test binary number parsing (0b prefix)
        assert_eq!(ScalarValue::parse_integer("0b1010"), Some(10));
        assert_eq!(ScalarValue::parse_integer("0b11111111"), Some(255));
        assert_eq!(ScalarValue::parse_integer("0B101"), Some(5)); // Uppercase B
        assert_eq!(ScalarValue::parse_integer("-0b1010"), Some(-10));
        assert_eq!(ScalarValue::parse_integer("+0b101"), Some(5));

        // Test auto-detection
        assert_eq!(ScalarValue::auto_detect_type("0b1010"), ScalarType::Integer);
        assert_eq!(
            ScalarValue::auto_detect_type("0B11111111"),
            ScalarType::Integer
        );

        // Test invalid binary
        assert_eq!(ScalarValue::parse_integer("0b1012"), None); // Contains invalid digit
        assert_eq!(ScalarValue::parse_integer("0b"), None); // Empty after prefix
    }

    #[test]
    fn test_modern_octal_number_parsing() {
        // Test modern octal number parsing (0o prefix)
        assert_eq!(ScalarValue::parse_integer("0o755"), Some(493)); // 7*64 + 5*8 + 5
        assert_eq!(ScalarValue::parse_integer("0o644"), Some(420)); // 6*64 + 4*8 + 4
        assert_eq!(ScalarValue::parse_integer("0O777"), Some(511)); // Uppercase O
        assert_eq!(ScalarValue::parse_integer("-0o755"), Some(-493));
        assert_eq!(ScalarValue::parse_integer("+0o644"), Some(420));

        // Test auto-detection
        assert_eq!(ScalarValue::auto_detect_type("0o755"), ScalarType::Integer);
        assert_eq!(ScalarValue::auto_detect_type("0O644"), ScalarType::Integer);

        // Test invalid octal
        assert_eq!(ScalarValue::parse_integer("0o789"), None); // Contains invalid digit
        assert_eq!(ScalarValue::parse_integer("0o"), None); // Empty after prefix
    }

    #[test]
    fn test_legacy_octal_number_parsing() {
        // Test legacy octal number parsing (0 prefix)
        assert_eq!(ScalarValue::parse_integer("0755"), Some(493));
        assert_eq!(ScalarValue::parse_integer("0644"), Some(420));
        assert_eq!(ScalarValue::parse_integer("0777"), Some(511));

        // Test auto-detection
        assert_eq!(ScalarValue::auto_detect_type("0755"), ScalarType::Integer);
        assert_eq!(ScalarValue::auto_detect_type("0644"), ScalarType::Integer);

        // Test edge cases
        assert_eq!(ScalarValue::parse_integer("0"), Some(0)); // Single zero
        assert_eq!(ScalarValue::parse_integer("00"), Some(0)); // Double zero

        // Numbers starting with 0 but containing 8 or 9 should fail as octal
        assert_eq!(ScalarValue::parse_integer("0789"), None);
        assert_eq!(ScalarValue::parse_integer("0128"), None);
    }

    #[test]
    fn test_hexadecimal_number_parsing() {
        // Test hexadecimal number parsing (0x prefix) - should still work
        assert_eq!(ScalarValue::parse_integer("0xFF"), Some(255));
        assert_eq!(ScalarValue::parse_integer("0x1A"), Some(26));
        assert_eq!(ScalarValue::parse_integer("0XFF"), Some(255)); // Uppercase X
        assert_eq!(ScalarValue::parse_integer("-0xFF"), Some(-255));
        assert_eq!(ScalarValue::parse_integer("+0x1A"), Some(26));

        // Test auto-detection
        assert_eq!(ScalarValue::auto_detect_type("0xFF"), ScalarType::Integer);
        assert_eq!(ScalarValue::auto_detect_type("0X1A"), ScalarType::Integer);
    }

    #[test]
    fn test_decimal_number_parsing() {
        // Test decimal number parsing (no prefix) - should still work
        assert_eq!(ScalarValue::parse_integer("42"), Some(42));
        assert_eq!(ScalarValue::parse_integer("123"), Some(123));
        assert_eq!(ScalarValue::parse_integer("-42"), Some(-42));
        assert_eq!(ScalarValue::parse_integer("+123"), Some(123));

        // Test auto-detection
        assert_eq!(ScalarValue::auto_detect_type("42"), ScalarType::Integer);
        assert_eq!(ScalarValue::auto_detect_type("-123"), ScalarType::Integer);
    }

    #[test]
    fn test_number_format_yaml_output() {
        // Test that different number formats are properly detected and output
        let binary_scalar = ScalarValue::parse("0b1010");
        assert_eq!(binary_scalar.scalar_type(), ScalarType::Integer);
        assert_eq!(binary_scalar.value(), "0b1010");

        let octal_scalar = ScalarValue::parse("0o755");
        assert_eq!(octal_scalar.scalar_type(), ScalarType::Integer);
        assert_eq!(octal_scalar.value(), "0o755");

        let hex_scalar = ScalarValue::parse("0xFF");
        assert_eq!(hex_scalar.scalar_type(), ScalarType::Integer);
        assert_eq!(hex_scalar.value(), "0xFF");

        let legacy_octal_scalar = ScalarValue::parse("0755");
        assert_eq!(legacy_octal_scalar.scalar_type(), ScalarType::Integer);
        assert_eq!(legacy_octal_scalar.value(), "0755");
    }

    #[test]
    fn test_unix_timestamp_bounds() {
        // Unix epoch boundary: 0 is rejected, 1 accepted, and the upper bound
        // (2100-01-01) is exclusive.
        assert!(!ScalarValue::is_valid_timestamp_static("0"));
        assert!(ScalarValue::is_valid_timestamp_static("1"));
        assert!(ScalarValue::is_valid_timestamp_static("4102444799"));
        assert!(!ScalarValue::is_valid_timestamp_static("4102444800"));

        // The instance method delegates to the static one.
        let s = ScalarValue::string("x");
        assert!(s.is_valid_timestamp("1"));
        assert!(!s.is_valid_timestamp("0"));
    }

    #[test]
    fn test_iso8601_time_component_bounds() {
        // Hour/minute/second upper bounds: 23:59:59 is valid, anything past is not.
        assert!(ScalarValue::matches_iso8601_pattern("2023-01-01T23:59:59"));
        assert!(!ScalarValue::matches_iso8601_pattern("2023-01-01T24:00:00"));
        assert!(!ScalarValue::matches_iso8601_pattern("2023-01-01T10:60:00"));
        assert!(!ScalarValue::matches_iso8601_pattern("2023-01-01T10:30:60"));

        // Day bounds: 31 is valid, 0 and 32 are not.
        assert!(ScalarValue::matches_iso8601_pattern("2023-01-31"));
        assert!(!ScalarValue::matches_iso8601_pattern("2023-01-00"));
        assert!(!ScalarValue::matches_iso8601_pattern("2023-01-32"));

        // A malformed time separator falls through to invalid.
        assert!(!ScalarValue::matches_iso8601_pattern("2023-01-01X10:30:45"));
        // Time present but seconds field non-numeric.
        assert!(!ScalarValue::matches_iso8601_pattern("2023-01-01T10:30:xx"));

        // The two date separators must each be '-'. A non-dash in either
        // position must reject even though the digit groups parse as valid
        // month/day. This pins the separator conjuncts in the format check.
        assert!(!ScalarValue::matches_iso8601_pattern("2023X12-25"));
        assert!(!ScalarValue::matches_iso8601_pattern("2023-12X25"));
    }

    #[test]
    #[cfg(feature = "base64")]
    fn test_base64_padding_bounds() {
        // Exactly two padding characters is the maximum allowed and must pass.
        assert!(ScalarValue::looks_like_base64("QQ==")); // "A"
                                                         // One padding character.
        assert!(ScalarValue::looks_like_base64("SGVsbG8=")); // "Hello"
                                                             // Padding in the middle is rejected even with a valid count.
        assert!(!ScalarValue::looks_like_base64("QQ=Q"));
        // Length not a multiple of 4.
        assert!(!ScalarValue::looks_like_base64("QQQ"));
    }

    #[test]
    fn test_coerce_to_null() {
        // Only the recognized null spellings coerce to Null.
        let s = ScalarValue::string("null");
        assert_eq!(
            s.coerce_to_type(ScalarType::Null).map(|v| v.scalar_type()),
            Some(ScalarType::Null)
        );
        assert!(ScalarValue::string("~")
            .coerce_to_type(ScalarType::Null)
            .is_some());
        assert!(ScalarValue::string("")
            .coerce_to_type(ScalarType::Null)
            .is_some());
        assert!(ScalarValue::string("notnull")
            .coerce_to_type(ScalarType::Null)
            .is_none());
    }

    #[test]
    fn test_parse_escape_single_quote() {
        // The \' escape decodes to a bare single quote.
        assert_eq!(ScalarValue::parse_escape_sequences("a\\'b"), "a'b");
    }

    #[test]
    fn test_parse_preserves_string_style() {
        // The String arm of parse() runs detect_style; a plain word stays Plain.
        let plain = ScalarValue::parse("hello");
        assert_eq!(plain.scalar_type(), ScalarType::String);
        assert_eq!(plain.style, ScalarStyle::Plain);

        // A multi-line string is given Literal style by detect_style, which only
        // runs in the String arm of parse().
        let multiline = ScalarValue::parse("multi line\nvalue");
        assert_eq!(multiline.scalar_type(), ScalarType::String);
        assert_eq!(multiline.style, ScalarStyle::Literal);
    }

    #[test]
    fn test_from_scalar_type_and_style() {
        use crate::yaml::Document;
        use rowan::ast::AstNode;
        use std::str::FromStr;

        // NULL token maps to Null type.
        let doc = Document::from_str("k: null\n").unwrap();
        let mapping = doc.as_mapping().unwrap();
        let null_node = mapping.get("k").unwrap();
        let null_scalar = crate::yaml::Scalar::cast(null_node.syntax().clone()).unwrap();
        let sv = ScalarValue::from_scalar(&null_scalar);
        assert_eq!(sv.scalar_type(), ScalarType::Null);
        assert_eq!(sv.style, ScalarStyle::Plain);

        // STRING token in double quotes maps to String type, DoubleQuoted style.
        let doc = Document::from_str("k: \"hi\"\n").unwrap();
        let node = doc.as_mapping().unwrap().get("k").unwrap();
        let scalar = crate::yaml::Scalar::cast(node.syntax().clone()).unwrap();
        let sv = ScalarValue::from_scalar(&scalar);
        assert_eq!(sv.scalar_type(), ScalarType::String);
        assert_eq!(sv.style, ScalarStyle::DoubleQuoted);

        // Single quoted string keeps SingleQuoted style.
        let doc = Document::from_str("k: 'hi'\n").unwrap();
        let node = doc.as_mapping().unwrap().get("k").unwrap();
        let scalar = crate::yaml::Scalar::cast(node.syntax().clone()).unwrap();
        let sv = ScalarValue::from_scalar(&scalar);
        assert_eq!(sv.style, ScalarStyle::SingleQuoted);
    }

    #[test]
    fn test_needs_quoting_special_chars() {
        // Leading-character based quoting and special embedded characters, plus
        // each boolean/null keyword spelling (so dropping any one keyword arm is
        // detected). Single quotes are preferred unless the value already
        // contains one, in which case double quotes are used.
        let cases = [
            ("&anchor", "'&anchor'"),
            ("*alias", "'*alias'"),
            ("!tag", "'!tag'"),
            ("a|b", "'a|b'"),
            ("it's", "\"it's\""),
            ("a\"b", "'a\"b'"),
            ("50%", "'50%'"),
            ("on", "'on'"),
            ("off", "'off'"),
            ("On", "'On'"),
            ("OFF", "'OFF'"),
            ("yes", "'yes'"),
            ("no", "'no'"),
            ("true", "'true'"),
            ("false", "'false'"),
            ("null", "'null'"),
        ];
        for (input, expected) in cases {
            assert_eq!(ScalarValue::string(input).to_yaml_string(), expected);
        }
        // A plain word that needs no quoting round-trips unchanged.
        assert_eq!(ScalarValue::string("plain").to_yaml_string(), "plain");
    }

    #[test]
    fn test_literal_and_folded_nonempty() {
        // Literal and folded styles render the block indicator on its own line
        // followed by the content indented by two spaces.
        let s = ScalarValue::with_style("line one\nline two", ScalarStyle::Literal);
        assert_eq!(s.to_yaml_string(), "|\n  line one\n  line two");

        let s = ScalarValue::with_style("line one\nline two", ScalarStyle::Folded);
        assert_eq!(s.to_yaml_string(), ">\n  line one\n  line two");
    }

    #[test]
    fn test_display_matches_yaml_string() {
        let s = ScalarValue::string("true");
        assert_eq!(format!("{}", s), s.to_yaml_string());
        assert_eq!(format!("{}", s), "'true'");
    }

    #[test]
    fn test_as_yaml_token_kind_per_type() {
        use crate::lex::SyntaxKind;
        use rowan::ast::AstNode;

        let cases = [
            (ScalarValue::parse("42"), SyntaxKind::INT),
            (ScalarValue::parse("3.14"), SyntaxKind::FLOAT),
            (ScalarValue::parse("true"), SyntaxKind::BOOL),
            (ScalarValue::parse("null"), SyntaxKind::NULL),
            (ScalarValue::string("hello"), SyntaxKind::STRING),
        ];
        for (value, expected) in cases {
            let mut builder = rowan::GreenNodeBuilder::new();
            crate::AsYaml::build_content(&value, &mut builder, 0, false);
            let green = builder.finish();
            let node = rowan::SyntaxNode::<crate::yaml::Lang>::new_root(green);
            let scalar = crate::yaml::Scalar::cast(node).unwrap();
            let kind = scalar
                .syntax()
                .children_with_tokens()
                .filter_map(|c| c.into_token())
                .next()
                .unwrap()
                .kind();
            assert_eq!(kind, expected, "value {:?}", value.value());
        }
    }

    #[test]
    fn test_as_yaml_quotes_string_that_looks_special() {
        use std::str::FromStr;

        // A string whose value reads as a null/bool/number must be quoted so it
        // round-trips as a string, not the special value.
        let doc = crate::yaml::Document::from_str("key: x\n").unwrap();
        doc.as_mapping()
            .unwrap()
            .set("key", ScalarValue::string("null"));
        assert_eq!(doc.to_string(), "key: 'null'\n");

        // A string containing ": " must be quoted or it produces invalid YAML.
        let doc = crate::yaml::Document::from_str("key: x\n").unwrap();
        doc.as_mapping()
            .unwrap()
            .set("key", ScalarValue::string("has: colon"));
        assert_eq!(doc.to_string(), "key: 'has: colon'\n");

        // An actual null scalar stays bare.
        let doc = crate::yaml::Document::from_str("key: x\n").unwrap();
        doc.as_mapping().unwrap().set("key", ScalarValue::null());
        assert_eq!(doc.to_string(), "key: null\n");
    }

    #[test]
    #[cfg(feature = "regex")]
    fn test_as_regex_type_gated() {
        // as_regex only compiles when the scalar is tagged as a regex.
        let re = ScalarValue::regex(r"\d+");
        assert!(re.as_regex().is_some());

        let not_re = ScalarValue::string(r"\d+");
        assert!(not_re.as_regex().is_none());

        // try_as_regex ignores the type and compiles the value directly.
        assert!(not_re.try_as_regex().is_ok());
        assert!(ScalarValue::string("(").try_as_regex().is_err());
    }
}
