//! Expression evaluation using exprimo
//!
//! Supports JavaScript-like expressions in bindings:
//! - `${state.selected ? '#FFA7E1' : '#374151'}`
//! - `${item.count > 10 ? 'many' : 'few'}`
//! - `${state.user.name + ' (' + state.user.role + ')'}`

use exprimo::Evaluator;
use serde_json::Value;
use std::collections::{HashMap, HashSet};

use super::{Binding, BindingSource};
use crate::error::EngineError;

/// Evaluate an expression string with the given context
///
/// The context should contain flattened variables like:
/// - `state` -> the full state object
/// - `item` -> the current item (in list iteration)
///
/// # Example
/// ```
/// use serde_json::json;
/// use std::collections::HashMap;
/// use hypen_engine::reactive::evaluate_expression;
///
/// let mut context = HashMap::new();
/// context.insert("selected".to_string(), json!(true));
///
/// let result = evaluate_expression("selected ? 'yes' : 'no'", &context);
/// assert_eq!(result, Ok(json!("yes")));
/// ```
pub fn evaluate_expression(
    expr: &str,
    context: &HashMap<String, Value>,
) -> Result<Value, EngineError> {
    let evaluator = Evaluator::new(context.clone(), HashMap::new());
    evaluator
        .evaluate(expr)
        .map_err(|e| EngineError::ExpressionError(format!("{:?}", e)))
}

/// Build a context for expression evaluation from state and optional item
///
/// Flattens `state` and `item` objects so that expressions can access:
/// - `state.user.name` via path traversal
/// - `item.selected` via path traversal
/// - Direct access like `selected` if we inject item properties directly
pub fn build_expression_context(state: &Value, item: Option<&Value>) -> HashMap<String, Value> {
    let mut context = HashMap::new();

    // Add the full state object
    context.insert("state".to_string(), state.clone());

    // Add the full item object if present
    if let Some(item_value) = item {
        context.insert("item".to_string(), item_value.clone());
    }

    context
}

/// Extract all state and item bindings from an expression string
///
/// This scans the expression for `state.xxx` and `item.xxx` patterns and returns
/// Binding objects for dependency tracking.
///
/// # Example
/// ```
/// use hypen_engine::reactive::extract_bindings_from_expression;
///
/// let bindings = extract_bindings_from_expression("state.selected ? '#FFA7E1' : '#374151'");
/// assert_eq!(bindings.len(), 1);
/// assert_eq!(bindings[0].full_path(), "selected");
/// ```
pub fn extract_bindings_from_expression(expr: &str) -> Vec<Binding> {
    let mut bindings = Vec::new();
    let mut seen_paths: HashSet<String> = HashSet::new();

    // Find all state.xxx and item.xxx patterns
    for prefix in &["state.", "item."] {
        let source = if *prefix == "state." {
            BindingSource::State
        } else {
            BindingSource::Item
        };

        let mut search_pos = 0;
        while let Some(start) = expr[search_pos..].find(prefix) {
            let abs_start = search_pos + start;

            // Check that this isn't in the middle of another identifier
            // (e.g., "mystate.x" should not match)
            if abs_start > 0 {
                let prev_char = expr.chars().nth(abs_start - 1).unwrap_or(' ');
                if prev_char.is_ascii_alphanumeric() || prev_char == '_' {
                    search_pos = abs_start + prefix.len();
                    continue;
                }
            }

            // Extract the path after the prefix
            let path_start = abs_start + prefix.len();
            let mut path_end = path_start;

            // Consume valid path characters (alphanumeric, underscore, and dots)
            let chars: Vec<char> = expr.chars().collect();
            while path_end < chars.len() {
                let c = chars[path_end];
                if c.is_ascii_alphanumeric() || c == '_' || c == '.' {
                    path_end += 1;
                } else {
                    break;
                }
            }

            if path_end > path_start {
                let path_str: String = chars[path_start..path_end].iter().collect();
                // Remove trailing dots
                let path_str = path_str.trim_end_matches('.');

                if !path_str.is_empty() {
                    let full_path = format!("{}{}", prefix, path_str);
                    if !seen_paths.contains(&full_path) {
                        seen_paths.insert(full_path);
                        let path: Vec<String> =
                            path_str.split('.').map(|s| s.to_string()).collect();
                        bindings.push(Binding::new(source.clone(), path));
                    }
                }
            }

            search_pos = path_end.max(abs_start + prefix.len());
        }
    }

    bindings
}

/// Check if a string contains an expression (not just a simple path)
///
/// Simple paths: `state.user.name`, `item.selected`
/// Expressions: `state.selected ? 'a' : 'b'`, `item.count > 10`
pub fn is_expression(s: &str) -> bool {
    // Look for operators that indicate an expression
    s.contains('?')
        || s.contains("&&")
        || s.contains("||")
        || s.contains("==")
        || s.contains("!=")
        || s.contains(">=")
        || s.contains("<=")
        || s.contains('>') && !s.contains("->") // avoid matching arrow functions
        || s.contains('<') && !s.contains("<-")
        || s.contains('+')
        || s.contains('-') && !s.starts_with('-') // avoid negative numbers
        || s.contains('*')
        || s.contains('/')
        || s.contains('%')
        || s.contains('!')
}

/// Evaluate a template string that may contain expressions
///
/// Handles strings like:
/// - `"Hello ${state.user.name}"` - simple binding
/// - `"Color: ${state.selected ? '#FFA7E1' : '#374151'}"` - expression
/// - `"${state.count} items"` - mixed
pub fn evaluate_template_string(
    template: &str,
    state: &Value,
    item: Option<&Value>,
) -> Result<String, EngineError> {
    let context = build_expression_context(state, item);
    let mut result = template.to_string();
    let mut pos = 0;

    while let Some(start) = result[pos..].find("${") {
        let abs_start = pos + start;

        // Find matching closing brace, handling nested braces
        let mut depth = 1;
        let mut end_pos = abs_start + 2;
        let chars: Vec<char> = result.chars().collect();

        while end_pos < chars.len() && depth > 0 {
            match chars[end_pos] {
                '{' => depth += 1,
                '}' => depth -= 1,
                _ => {}
            }
            if depth > 0 {
                end_pos += 1;
            }
        }

        if depth != 0 {
            return Err(EngineError::ExpressionError(
                "Unclosed expression in template".to_string(),
            ));
        }

        // Extract the expression content
        let expr_content: String = chars[abs_start + 2..end_pos].iter().collect();

        // Evaluate the expression
        let value = evaluate_expression(&expr_content, &context)?;

        // Convert to string
        let replacement = match &value {
            Value::String(s) => s.clone(),
            Value::Number(n) => n.to_string(),
            Value::Bool(b) => b.to_string(),
            Value::Null => "null".to_string(),
            _ => serde_json::to_string(&value).unwrap_or_default(),
        };

        // Replace in result
        let pattern: String = chars[abs_start..=end_pos].iter().collect();
        result = result.replacen(&pattern, &replacement, 1);

        // Reset position to search from beginning (replacement might be shorter/longer)
        pos = 0;
    }

    Ok(result)
}

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

    #[test]
    fn test_simple_expression() {
        let mut context = HashMap::new();
        context.insert("x".to_string(), json!(5));
        context.insert("y".to_string(), json!(3));

        let result = evaluate_expression("x + y", &context).unwrap();
        // exprimo returns floats for arithmetic, so compare as f64
        assert_eq!(result.as_f64().unwrap(), 8.0);
    }

    #[test]
    fn test_ternary_expression() {
        let mut context = HashMap::new();
        context.insert("selected".to_string(), json!(true));

        let result = evaluate_expression("selected ? 'yes' : 'no'", &context).unwrap();
        assert_eq!(result, json!("yes"));
    }

    #[test]
    fn test_ternary_with_colors() {
        let mut context = HashMap::new();
        context.insert("selected".to_string(), json!(true));

        let result = evaluate_expression("selected ? '#FFA7E1' : '#374151'", &context).unwrap();
        assert_eq!(result, json!("#FFA7E1"));
    }

    #[test]
    fn test_comparison_expression() {
        let mut context = HashMap::new();
        context.insert("count".to_string(), json!(15));

        let result = evaluate_expression("count > 10", &context).unwrap();
        assert_eq!(result, json!(true));
    }

    #[test]
    fn test_state_object_access() {
        let context =
            build_expression_context(&json!({"user": {"name": "Alice", "age": 30}}), None);

        let result = evaluate_expression("state.user.name", &context).unwrap();
        assert_eq!(result, json!("Alice"));
    }

    #[test]
    fn test_item_object_access() {
        let context = build_expression_context(
            &json!({}),
            Some(&json!({"name": "Item 1", "selected": true})),
        );

        let result = evaluate_expression("item.name", &context).unwrap();
        assert_eq!(result, json!("Item 1"));
    }

    #[test]
    fn test_item_ternary() {
        let context = build_expression_context(&json!({}), Some(&json!({"selected": true})));

        let result =
            evaluate_expression("item.selected ? '#FFA7E1' : '#374151'", &context).unwrap();
        assert_eq!(result, json!("#FFA7E1"));
    }

    #[test]
    fn test_template_string_simple() {
        let state = json!({"user": {"name": "Alice"}});
        let result = evaluate_template_string("Hello ${state.user.name}!", &state, None).unwrap();
        assert_eq!(result, "Hello Alice!");
    }

    #[test]
    fn test_template_string_with_expression() {
        let state = json!({"selected": true});
        let result = evaluate_template_string(
            "Color: ${state.selected ? '#FFA7E1' : '#374151'}",
            &state,
            None,
        )
        .unwrap();
        assert_eq!(result, "Color: #FFA7E1");
    }

    #[test]
    fn test_template_string_multiple_expressions() {
        let state = json!({"name": "Alice", "count": 5});
        let result =
            evaluate_template_string("${state.name} has ${state.count} items", &state, None)
                .unwrap();
        assert_eq!(result, "Alice has 5 items");
    }

    #[test]
    fn test_template_with_item() {
        let state = json!({});
        let item = json!({"name": "Product", "price": 99});
        let result =
            evaluate_template_string("${item.name}: $${item.price}", &state, Some(&item)).unwrap();
        assert_eq!(result, "Product: $99");
    }

    #[test]
    fn test_is_expression() {
        // Simple paths - not expressions
        assert!(!is_expression("state.user.name"));
        assert!(!is_expression("item.selected"));

        // Expressions
        assert!(is_expression("selected ? 'a' : 'b'"));
        assert!(is_expression("count > 10"));
        assert!(is_expression("a && b"));
        assert!(is_expression("a || b"));
        assert!(is_expression("a == b"));
        assert!(is_expression("a + b"));
    }

    #[test]
    fn test_string_concatenation() {
        let mut context = HashMap::new();
        context.insert("first".to_string(), json!("Hello"));
        context.insert("second".to_string(), json!("World"));

        let result = evaluate_expression("first + ' ' + second", &context).unwrap();
        assert_eq!(result, json!("Hello World"));
    }

    #[test]
    fn test_logical_and() {
        let mut context = HashMap::new();
        context.insert("a".to_string(), json!(true));
        context.insert("b".to_string(), json!(false));

        let result = evaluate_expression("a && b", &context).unwrap();
        assert_eq!(result, json!(false));
    }

    #[test]
    fn test_logical_or() {
        let mut context = HashMap::new();
        context.insert("a".to_string(), json!(false));
        context.insert("b".to_string(), json!(true));

        let result = evaluate_expression("a || b", &context).unwrap();
        assert_eq!(result, json!(true));
    }

    #[test]
    fn test_complex_expression() {
        let context = build_expression_context(
            &json!({
                "user": {
                    "premium": true,
                    "age": 25
                }
            }),
            None,
        );

        let result = evaluate_expression(
            "state.user.premium && state.user.age >= 18 ? 'VIP Adult' : 'Standard'",
            &context,
        )
        .unwrap();
        assert_eq!(result, json!("VIP Adult"));
    }
}
