//! Syntax highlighting for Storybook DSL
//!
//! This module tokenizes source code and maps tokens to theme colors for
//! rendering.

use std::ops::Range;

use iced::Color;
use storybook::syntax::lexer::{
    Lexer,
    Token,
};

use crate::theme::*;

/// Tokenize source code into tokens with byte positions
pub fn tokenize(source: &str) -> Vec<(Token, Range<usize>)> {
    let lexer = Lexer::new(source);
    lexer
        .map(|(start, token, end)| (token, start..end))
        .collect()
}

/// Map a token to its display color based on context
/// Field values (identifiers after colons) get a different color than field
/// names
pub fn token_to_color_contextual(token: &Token, after_colon: bool) -> Color {
    match token {
        // Identifiers - different colors for field names vs field values
        | Token::Ident(_) => {
            if after_colon {
                // Field value - bright cyan/teal
                Color::from_rgb8(0x66, 0xd9, 0xef)
            } else {
                // Field name or other identifier - bright green
                Color::from_rgb8(0x50, 0xfa, 0x7b)
            }
        },
        // All other tokens use the standard coloring
        | _ => token_to_color(token),
    }
}

/// Map a token to its display color based on the theme
/// Using a vibrant, professional color scheme inspired by modern editors
pub fn token_to_color(token: &Token) -> Color {
    match token {
        // Declaration keywords (character, template, etc.) - Bright Purple/Magenta
        | Token::Character |
        Token::Template |
        Token::LifeArc |
        Token::Schedule |
        Token::Behavior |
        Token::Institution |
        Token::Relationship |
        Token::Location |
        Token::Species |
        Token::Enum |
        Token::State => Color::from_rgb8(0xc5, 0x86, 0xc0), // Vibrant purple

        // Control flow keywords - Bright Pink/Rose
        | Token::ForAll |
        Token::Exists |
        Token::In |
        Token::Where |
        Token::And |
        Token::Or |
        Token::Not |
        Token::On |
        Token::Enter => Color::from_rgb8(0xff, 0x79, 0xc6), // Hot pink

        // Import/module keywords - Bright Orange
        | Token::Use | Token::Include | Token::From => Color::from_rgb8(0xff, 0xb8, 0x6c), /* Bright orange */

        // Special keywords - Cyan/Aqua
        | Token::As |
        Token::SelfKw |
        Token::Other |
        Token::Remove |
        Token::Append |
        Token::Strict |
        Token::Is => Color::from_rgb8(0x8b, 0xe9, 0xfd), // Bright cyan

        // Boolean literals - Bright Orange
        | Token::True | Token::False => Color::from_rgb8(0xff, 0xb8, 0x6c),

        // Identifiers - Bright green for field names/identifiers
        | Token::Ident(_) => Color::from_rgb8(0x50, 0xfa, 0x7b), // Bright green

        // Number literals - Bright Purple
        | Token::NumberLit(_) | Token::DecimalLit(_) => Color::from_rgb8(0xbd, 0x93, 0xf9), /* Light purple */

        // Text literals - Vibrant Yellow/Gold
        | Token::TextLit(_) => Color::from_rgb8(0xf1, 0xfa, 0x8c), // Bright yellow

        // Time/duration literals - Bright Teal
        | Token::TimeLit(_) | Token::DurationLit(_) => Color::from_rgb8(0x66, 0xd9, 0xef), /* Bright teal */

        // Prose blocks - Soft pink/peach (to stand out as prose)
        | Token::ProseBlock(_) => Color::from_rgb8(0xff, 0xb8, 0x6c), // Peach/coral

        // Braces and brackets - Bright foreground
        | Token::LBrace |
        Token::RBrace |
        Token::LParen |
        Token::RParen |
        Token::LBracket |
        Token::RBracket => Color::from_rgb8(0xf8, 0xf8, 0xf2), // Bright white

        // Other punctuation - Dimmed
        | Token::Colon |
        Token::ColonColon |
        Token::Semicolon |
        Token::Comma |
        Token::Dot |
        Token::DotDot |
        Token::Star |
        Token::Question |
        Token::At => Color::from_rgb8(0x6d, 0x6d, 0x6d), // Medium gray

        // Operators - Bright Pink
        | Token::Gt | Token::Ge | Token::Lt | Token::Le | Token::Arrow => {
            Color::from_rgb8(0xff, 0x79, 0xc6)
        },

        // Prose marker - Comment gray
        | Token::ProseMarker => Color::from_rgb8(0x6d, 0x6d, 0x6d), // Gray

        // Errors - Bright Red
        | Token::Error => Color::from_rgb8(0xff, 0x55, 0x55), // Bright red
    }
}

/// Build a vector of text spans with syntax highlighting
/// Returns (text_content, color) pairs for rendering
pub fn build_highlighted_spans(
    source: &str,
    tokens: &[(Token, Range<usize>)],
) -> Vec<(String, Color)> {
    if tokens.is_empty() {
        return vec![(source.to_string(), Neutral::WARM_GRAY_100)];
    }

    let mut spans = Vec::new();
    let mut last_end = 0;

    for (token, range) in tokens {
        // Add any unhighlighted gap before this token (whitespace, comments)
        if range.start > last_end {
            let gap = &source[last_end..range.start];
            if !gap.is_empty() {
                spans.push((gap.to_string(), Neutral::WARM_GRAY_400));
            }
        }

        // Add the highlighted token
        let text = &source[range.clone()];
        let color = token_to_color(token);
        spans.push((text.to_string(), color));

        last_end = range.end;
    }

    // Add any remaining text after the last token
    if last_end < source.len() {
        let remaining = &source[last_end..];
        if !remaining.is_empty() {
            spans.push((remaining.to_string(), Neutral::WARM_GRAY_400));
        }
    }

    spans
}

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

    #[test]
    fn test_tokenize_basic() {
        let source = "character Martha { age: 34 }";
        let tokens = tokenize(source);

        assert!(!tokens.is_empty());
        assert!(matches!(tokens[0].0, Token::Character));
        assert_eq!(tokens[0].1, 0..9); // "character"
    }

    #[test]
    fn test_token_colors() {
        // Keywords should be gold
        assert_eq!(token_to_color(&Token::Character), Gold::_500);

        // Strings should be green
        assert_eq!(
            token_to_color(&Token::TextLit("test".to_string())),
            Semantic::SUCCESS
        );

        // Numbers should be gold (lighter)
        assert_eq!(token_to_color(&Token::NumberLit(42)), Gold::_400);

        // Identifiers should be warm gray
        assert_eq!(
            token_to_color(&Token::Ident("Martha".to_string())),
            Neutral::WARM_GRAY_100
        );
    }

    #[test]
    fn test_build_highlighted_spans() {
        let source = "character Martha";
        let tokens = tokenize(source);
        let spans = build_highlighted_spans(source, &tokens);

        // Should have at least 3 spans: "character", " " (whitespace), "Martha"
        assert!(spans.len() >= 3);

        // First span should be "character" in gold
        assert_eq!(spans[0].0, "character");
        assert_eq!(spans[0].1, Gold::_500);

        // Last span should be "Martha" in warm gray
        let last = spans.last().unwrap();
        assert_eq!(last.0, "Martha");
        assert_eq!(last.1, Neutral::WARM_GRAY_100);
    }
}
