// AST → IR lowering - converts parser output to engine IR

use super::{Element, Value, IRNode, ConditionalBranch, Props};
use hypen_parser::{ComponentSpecification, Value as ParserValue};
use crate::reactive::{parse_binding, extract_bindings_from_expression, Binding};
use std::collections::HashSet;

// Event handling is now done at the renderer level, not in the engine

/// Expand Tailwind classes into CSS properties
/// Returns a Vec of (prop_name, value) pairs
fn expand_tailwind_classes(classes: &str) -> Vec<(String, Value)> {
    let output = hypen_tailwind_parse::parse_classes(classes);
    let mut props = Vec::new();

    // Add base properties (no variant)
    for css_prop in &output.base {
        props.push((
            css_prop.property.clone(),
            Value::Static(serde_json::Value::String(css_prop.value.clone())),
        ));
    }

    // Add variant properties with suffix notation
    // e.g., "padding@md" for responsive, "backgroundColor:hover" for state
    for (variant_key, css_props) in &output.variants {
        for css_prop in css_props {
            let prop_key = format!("{}{}", css_prop.property, variant_key);
            props.push((
                prop_key,
                Value::Static(serde_json::Value::String(css_prop.value.clone())),
            ));
        }
    }

    props
}

/// Extract all ${state.xxx} and ${item.xxx} bindings from a template string
/// This handles both simple bindings like ${state.x} and expressions like ${state.x ? 'a' : 'b'}
/// Returns None if no bindings found, Some(bindings) if bindings exist
fn extract_bindings_from_template(s: &str) -> Option<Vec<Binding>> {
    let mut bindings = Vec::new();
    let mut seen_paths: HashSet<String> = HashSet::new();
    let mut pos = 0;

    // Look for ${ patterns and try to parse them
    while let Some(start) = s[pos..].find("${") {
        let abs_start = pos + start;
        if let Some(end) = s[abs_start..].find('}') {
            let abs_end = abs_start + end;
            // Extract the full binding string including ${...}
            let binding_str = &s[abs_start..=abs_end];

            // Try to parse as a simple binding first
            if let Some(binding) = parse_binding(binding_str) {
                let path = binding.full_path_with_source();
                if !seen_paths.contains(&path) {
                    seen_paths.insert(path);
                    bindings.push(binding);
                }
            } else {
                // Not a simple binding - extract bindings from the expression
                let expr_content = &s[abs_start + 2..abs_end];
                for binding in extract_bindings_from_expression(expr_content) {
                    let path = binding.full_path_with_source();
                    if !seen_paths.contains(&path) {
                        seen_paths.insert(path);
                        bindings.push(binding);
                    }
                }
            }
            pos = abs_end + 1;
        } else {
            break;
        }
    }

    if bindings.is_empty() {
        None
    } else {
        Some(bindings)
    }
}

/// Convert parser AST to engine IR
pub fn ast_to_ir(component: &ComponentSpecification) -> Element {
    let mut element = Element::new(&component.name);
    
    // Debug: Log the component structure
    // #[cfg(target_arch = "wasm32")]
    // web_sys::console::log_1(&format!("🔍 Parsing component: {} with {} applicators", component.name, component.applicators.len()).into());

    // Convert arguments to props
    for (i, arg) in component.arguments.arguments.iter().enumerate() {
        let (key, value) = match arg {
            hypen_parser::Argument::Named { key, value } => {
                (key.clone(), parser_value_to_ir(value))
            }
            hypen_parser::Argument::Positioned { value, .. } => {
                // Positional args are mapped to numbered keys
                (i.to_string(), parser_value_to_ir(value))
            }
        };
        
        // All arguments become props - event handling is done at renderer level
        
        element.props.insert(key, value);
    }

    // Convert applicators to props
    for applicator in &component.applicators {
        // Special handling for .tw() - expand Tailwind classes to CSS properties
        if applicator.name == "tw" {
            // Get the first argument (the class string)
            if let Some(arg) = applicator.arguments.arguments.first() {
                let class_string = match arg {
                    hypen_parser::Argument::Named { value, .. } |
                    hypen_parser::Argument::Positioned { value, .. } => {
                        if let ParserValue::String(s) = value {
                            s.trim_matches('"').to_string()
                        } else {
                            continue;
                        }
                    }
                };
                // Expand Tailwind classes and insert as individual props
                for (prop_key, prop_value) in expand_tailwind_classes(&class_string) {
                    element.props.insert(prop_key, prop_value);
                }
            }
            continue;
        }

        // All other applicators become props - event handling is done at renderer level
        for (i, arg) in applicator.arguments.arguments.iter().enumerate() {
            let (key, value) = match arg {
                hypen_parser::Argument::Named { key, value } => {
                    (format!("{}.{}", applicator.name, key), parser_value_to_ir(value))
                }
                hypen_parser::Argument::Positioned { value, .. } => {
                    (format!("{}.{}", applicator.name, i), parser_value_to_ir(value))
                }
            };
            element.props.insert(key, value);
        }
    }

    // Optional key from first positional string argument or component id
    if let Some(first_arg) = component.arguments.arguments.first() {
        if let hypen_parser::Argument::Positioned { value, .. } = first_arg {
            if let ParserValue::String(s) = value {
                element.key = Some(s.clone());
            }
        }
    }

    // Convert children recursively
    for child in &component.children {
        element.children.push_back(std::sync::Arc::new(ast_to_ir(child)));
    }

    element
}

/// Convert parser AST to engine IRNode (first-class control flow constructs)
/// This detects ForEach, When, and If components and converts them to the appropriate IRNode variants
pub fn ast_to_ir_node(component: &ComponentSpecification) -> IRNode {
    match component.name.as_str() {
        "ForEach" => convert_foreach(component),
        "When" => convert_when(component),
        "If" => convert_if(component),
        _ => {
            // Regular element - convert children to IRNodes recursively
            let mut element = Element::new(&component.name);

            // Convert arguments to props
            for (i, arg) in component.arguments.arguments.iter().enumerate() {
                let (key, value) = match arg {
                    hypen_parser::Argument::Named { key, value } => {
                        (key.clone(), parser_value_to_ir(value))
                    }
                    hypen_parser::Argument::Positioned { value, .. } => {
                        (i.to_string(), parser_value_to_ir(value))
                    }
                };
                element.props.insert(key, value);
            }

            // Convert applicators to props
            for applicator in &component.applicators {
                // Special handling for .tw() - expand Tailwind classes to CSS properties
                if applicator.name == "tw" {
                    if let Some(arg) = applicator.arguments.arguments.first() {
                        let class_string = match arg {
                            hypen_parser::Argument::Named { value, .. } |
                            hypen_parser::Argument::Positioned { value, .. } => {
                                if let ParserValue::String(s) = value {
                                    s.trim_matches('"').to_string()
                                } else {
                                    continue;
                                }
                            }
                        };
                        for (prop_key, prop_value) in expand_tailwind_classes(&class_string) {
                            element.props.insert(prop_key, prop_value);
                        }
                    }
                    continue;
                }

                for (i, arg) in applicator.arguments.arguments.iter().enumerate() {
                    let (key, value) = match arg {
                        hypen_parser::Argument::Named { key, value } => {
                            (format!("{}.{}", applicator.name, key), parser_value_to_ir(value))
                        }
                        hypen_parser::Argument::Positioned { value, .. } => {
                            (format!("{}.{}", applicator.name, i), parser_value_to_ir(value))
                        }
                    };
                    element.props.insert(key, value);
                }
            }

            // Optional key from first positional string argument
            if let Some(first_arg) = component.arguments.arguments.first() {
                if let hypen_parser::Argument::Positioned { value, .. } = first_arg {
                    if let ParserValue::String(s) = value {
                        element.key = Some(s.clone());
                    }
                }
            }

            // Convert children recursively as IRNodes
            // Check if this element should be treated as a list (has binding prop "0" and children)
            let has_binding_prop = element.props.get("0").map_or(false, |v| matches!(v, Value::Binding(_)));
            if has_binding_prop && !component.children.is_empty() {
                // This is the legacy List pattern - convert to ForEach
                if let Some(Value::Binding(binding)) = element.props.get("0").cloned() {
                    let mut props = element.props.clone();
                    props.remove("0"); // Remove the array binding prop (uses COW)

                    let template: Vec<IRNode> = component.children.iter()
                        .map(|child| ast_to_ir_node(child))
                        .collect();

                    return IRNode::ForEach {
                        source: binding,
                        item_name: "item".to_string(),
                        key_path: None,
                        template,
                        props,
                    };
                }
            }

            // Regular element with children (Arc-wrapped for im::Vector<Arc<Element>>)
            element.children = component.children.iter()
                .map(|child| std::sync::Arc::new(ast_to_ir(child)))
                .collect();

            IRNode::Element(element)
        }
    }
}

/// Convert ForEach component to IRNode::ForEach
/// Syntax: ForEach(items: ${state.todos}, as: "todo", key: "id") { ... }
fn convert_foreach(component: &ComponentSpecification) -> IRNode {
    let mut source: Option<Binding> = None;
    let mut item_name = "item".to_string();
    let mut key_path: Option<String> = None;
    let mut props = Props::new();

    // Parse arguments
    for arg in &component.arguments.arguments {
        match arg {
            hypen_parser::Argument::Named { key, value } => {
                match key.as_str() {
                    "items" | "in" => {
                        // The source array binding
                        if let Value::Binding(binding) = parser_value_to_ir(value) {
                            source = Some(binding);
                        }
                    }
                    "as" => {
                        // The item variable name
                        if let ParserValue::String(s) = value {
                            item_name = s.trim_matches('"').to_string();
                        }
                    }
                    "key" => {
                        // The key path for reconciliation
                        if let ParserValue::String(s) = value {
                            key_path = Some(s.trim_matches('"').to_string());
                        }
                    }
                    _ => {
                        // Other props go to the container
                        props.insert(key.clone(), parser_value_to_ir(value));
                    }
                }
            }
            hypen_parser::Argument::Positioned { value, .. } => {
                // First positional arg is the source array
                if source.is_none() {
                    if let Value::Binding(binding) = parser_value_to_ir(value) {
                        source = Some(binding);
                    }
                }
            }
        }
    }

    // Convert applicators to props
    for applicator in &component.applicators {
        // Special handling for .tw() - expand Tailwind classes to CSS properties
        if applicator.name == "tw" {
            if let Some(arg) = applicator.arguments.arguments.first() {
                let class_string = match arg {
                    hypen_parser::Argument::Named { value, .. } |
                    hypen_parser::Argument::Positioned { value, .. } => {
                        if let ParserValue::String(s) = value {
                            s.trim_matches('"').to_string()
                        } else {
                            continue;
                        }
                    }
                };
                for (prop_key, prop_value) in expand_tailwind_classes(&class_string) {
                    props.insert(prop_key, prop_value);
                }
            }
            continue;
        }

        for (i, arg) in applicator.arguments.arguments.iter().enumerate() {
            let (key, value) = match arg {
                hypen_parser::Argument::Named { key, value } => {
                    (format!("{}.{}", applicator.name, key), parser_value_to_ir(value))
                }
                hypen_parser::Argument::Positioned { value, .. } => {
                    (format!("{}.{}", applicator.name, i), parser_value_to_ir(value))
                }
            };
            props.insert(key, value);
        }
    }

    // Convert children as template
    let template: Vec<IRNode> = component.children.iter()
        .map(|child| ast_to_ir_node(child))
        .collect();

    IRNode::ForEach {
        source: source.expect("ForEach requires an items binding"),
        item_name,
        key_path,
        template,
        props,
    }
}

/// Convert When component to IRNode::Conditional
/// Syntax: When(value: ${state.status}) { Case(match: "loading") {...} Else {...} }
fn convert_when(component: &ComponentSpecification) -> IRNode {
    let mut condition_value: Option<Value> = None;

    // Parse the condition value
    for arg in &component.arguments.arguments {
        match arg {
            hypen_parser::Argument::Named { key, value } => {
                if key == "value" || key == "condition" {
                    condition_value = Some(parser_value_to_ir(value));
                }
            }
            hypen_parser::Argument::Positioned { value, .. } => {
                if condition_value.is_none() {
                    condition_value = Some(parser_value_to_ir(value));
                }
            }
        }
    }

    let mut branches = Vec::new();
    let mut fallback: Option<Vec<IRNode>> = None;

    // Parse children: Case and Else
    for child in &component.children {
        match child.name.as_str() {
            "Case" => {
                // Get the match pattern
                let pattern = child.arguments.arguments.iter()
                    .find_map(|arg| {
                        match arg {
                            hypen_parser::Argument::Named { key, value } if key == "match" => {
                                Some(parser_value_to_ir(value))
                            }
                            hypen_parser::Argument::Positioned { value, .. } => {
                                Some(parser_value_to_ir(value))
                            }
                            _ => None,
                        }
                    })
                    .unwrap_or(Value::Static(serde_json::Value::Null));

                let children: Vec<IRNode> = child.children.iter()
                    .map(|c| ast_to_ir_node(c))
                    .collect();

                branches.push(ConditionalBranch::new(pattern, children));
            }
            "Else" => {
                let children: Vec<IRNode> = child.children.iter()
                    .map(|c| ast_to_ir_node(c))
                    .collect();
                fallback = Some(children);
            }
            _ => {
                // Non-Case/Else children become part of a default branch
                // This handles the case where When children are direct elements
            }
        }
    }

    IRNode::Conditional {
        value: condition_value.expect("When requires a condition value"),
        branches,
        fallback,
    }
}

/// Convert If component to IRNode::Conditional (syntactic sugar over When with boolean matching)
/// Syntax: If(condition: ${state.loggedIn}) { ... Else { ... } }
fn convert_if(component: &ComponentSpecification) -> IRNode {
    let mut condition_value: Option<Value> = None;

    // Parse the condition
    for arg in &component.arguments.arguments {
        match arg {
            hypen_parser::Argument::Named { key, value } => {
                if key == "condition" || key == "when" {
                    condition_value = Some(parser_value_to_ir(value));
                }
            }
            hypen_parser::Argument::Positioned { value, .. } => {
                if condition_value.is_none() {
                    condition_value = Some(parser_value_to_ir(value));
                }
            }
        }
    }

    // Separate then-branch children from Else children
    let mut then_children: Vec<IRNode> = Vec::new();
    let mut else_children: Option<Vec<IRNode>> = None;

    for child in &component.children {
        if child.name == "Else" {
            else_children = Some(
                child.children.iter()
                    .map(|c| ast_to_ir_node(c))
                    .collect()
            );
        } else {
            then_children.push(ast_to_ir_node(child));
        }
    }

    // Create branches: true branch with then_children
    let branches = vec![
        ConditionalBranch::new(
            Value::Static(serde_json::json!(true)),
            then_children,
        )
    ];

    IRNode::Conditional {
        value: condition_value.expect("If requires a condition"),
        branches,
        fallback: else_children,
    }
}

/// Convert parser Value to engine Value
fn parser_value_to_ir(value: &ParserValue) -> Value {
    match value {
        ParserValue::String(s) => {
            // Remove surrounding quotes if present
            let trimmed = s.trim_matches('"');

            // Check if it's a pure binding (entire string is ${state.xxx} or ${item.xxx})
            if trimmed.starts_with("${") && trimmed.ends_with('}') && !trimmed[2..trimmed.len()-1].contains("${") {
                // Try to parse as a simple binding first
                match parse_binding(trimmed) {
                    Some(binding) => Value::Binding(binding),
                    None => {
                        // Not a simple binding - might be an expression like ${state.active ? 'a' : 'b'}
                        // Extract bindings from the expression for dependency tracking
                        let bindings = extract_bindings_from_template(trimmed).unwrap_or_default();
                        Value::TemplateString {
                            template: trimmed.to_string(),
                            bindings,
                        }
                    }
                }
            } else if trimmed.starts_with('@') {
                let action_ref = &trimmed[1..]; // Strip @ prefix
                // Strip "actions." prefix if present
                let action_name = if action_ref.starts_with("actions.") {
                    &action_ref[8..]
                } else {
                    action_ref
                };
                Value::Action(action_name.to_string())
            } else if trimmed.contains("${") {
                // Template string with embedded expressions or bindings
                // Extract any simple bindings we can find
                let bindings = extract_bindings_from_template(trimmed).unwrap_or_default();
                Value::TemplateString {
                    template: trimmed.to_string(),
                    bindings,
                }
            } else {
                Value::Static(serde_json::Value::String(trimmed.to_string()))
            }
        }
        ParserValue::Number(n) => Value::Static(serde_json::json!(*n)),
        ParserValue::Boolean(b) => Value::Static(serde_json::json!(*b)),
        ParserValue::List(items) => {
            let converted: Vec<serde_json::Value> = items
                .iter()
                .map(|v| match parser_value_to_ir(v) {
                    Value::Static(val) => val,
                    Value::Binding(binding) => serde_json::json!(format!("${{{}}}", binding.full_path())),
                    Value::TemplateString { template, .. } => serde_json::json!(template),
                    Value::Action(s) => serde_json::json!(format!("@{}", s)),
                })
                .collect();
            Value::Static(serde_json::json!(converted))
        }
        ParserValue::Map(map) => {
            let mut json_map = serde_json::Map::new();
            for (k, v) in map {
                // Handle all value types, not just Static values
                // This preserves bindings like ${item.id} inside maps
                match parser_value_to_ir(v) {
                    Value::Static(val) => {
                        json_map.insert(k.clone(), val);
                    }
                    Value::Binding(binding) => {
                        // Preserve binding as a string representation for later resolution
                        json_map.insert(k.clone(), serde_json::json!(format!("${{{}}}", binding.full_path_with_source())));
                    }
                    Value::TemplateString { template, .. } => {
                        json_map.insert(k.clone(), serde_json::json!(template));
                    }
                    Value::Action(s) => {
                        json_map.insert(k.clone(), serde_json::json!(format!("@actions.{}", s)));
                    }
                }
            }
            Value::Static(serde_json::Value::Object(json_map))
        }
        ParserValue::Reference(ref_str) => {
            // References like @state.user, @actions.login, @item, @item.name
            if ref_str.starts_with("state.") || ref_str.starts_with("item.") || ref_str == "item" {
                // Parse as binding, wrapping in ${}
                let binding_str = format!("${{{}}}", ref_str);
                match parse_binding(&binding_str) {
                    Some(binding) => Value::Binding(binding),
                    None => Value::Static(serde_json::Value::String(ref_str.clone()))
                }
            } else if ref_str.starts_with("actions.") {
                Value::Action(ref_str[8..].to_string()) // Strip "actions."
            } else {
                Value::Static(serde_json::Value::String(ref_str.clone()))
            }
        }
    }
}

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

    #[test]
    fn test_simple_conversion() {
        let input = r#"Text("Hello World")"#;
        let component = parse_component(input).unwrap();
        let element = ast_to_ir(&component);

        assert_eq!(element.element_type, "Text");
        assert_eq!(element.props.len(), 1);
    }

    #[test]
    fn test_binding_conversion() {
        let input = r#"Text("${state.user.name}")"#;
        let component = parse_component(input).unwrap();
        let element = ast_to_ir(&component);

        // Check that binding was parsed correctly
        let first_prop = element.props.values().next().unwrap();
        match first_prop {
            Value::Binding(binding) => {
                assert_eq!(binding.path, vec!["user", "name"]);
                assert_eq!(binding.full_path(), "user.name");
            },
            _ => panic!("Expected binding, got: {:?}", first_prop),
        }
    }

    #[test]
    fn test_children_conversion() {
        let input = r#"
            Column {
                Text("First")
                Text("Second")
            }
        "#;
        let component = parse_component(input).unwrap();
        let element = ast_to_ir(&component);

        assert_eq!(element.element_type, "Column");
        assert_eq!(element.children.len(), 2);
        assert_eq!(element.children[0].element_type, "Text");
        assert_eq!(element.children[1].element_type, "Text");
    }

    #[test]
    fn test_onclick_applicator_conversion() {
        let input = r#"Text("Click me").onClick("@actions.increment")"#;
        let component = parse_component(input).unwrap();
        let element = ast_to_ir(&component);

        assert_eq!(element.element_type, "Text");
        // onClick should now be in props, not events
        assert!(element.props.contains_key("onClick.0"));
    }

    #[test]
    fn test_button_onclick_argument() {
        let input = r#"Button(onClick: "@actions.submit") { Text("Submit") }"#;
        let component = parse_component(input).unwrap();
        let element = ast_to_ir(&component);

        assert_eq!(element.element_type, "Button");
        // onClick argument should be in props
        assert!(element.props.contains_key("onClick"));
        // Should have one child
        assert_eq!(element.children.len(), 1);
    }

    #[test]
    fn test_template_string_conversion() {
        let input = r#"Text("Count: ${state.count}")"#;
        let component = parse_component(input).unwrap();
        let element = ast_to_ir(&component);

        // Check that template string was parsed correctly
        let first_prop = element.props.values().next().unwrap();
        match first_prop {
            Value::TemplateString { template, bindings } => {
                assert_eq!(template, "Count: ${state.count}");
                assert_eq!(bindings.len(), 1);
                assert_eq!(bindings[0].full_path(), "count");
            },
            _ => panic!("Expected TemplateString, got: {:?}", first_prop),
        }
    }

    #[test]
    fn test_template_string_multiple_bindings() {
        let input = r#"Text("Hello ${state.user.name}, you have ${state.count} messages")"#;
        let component = parse_component(input).unwrap();
        let element = ast_to_ir(&component);

        let first_prop = element.props.values().next().unwrap();
        match first_prop {
            Value::TemplateString { template, bindings } => {
                assert_eq!(template, "Hello ${state.user.name}, you have ${state.count} messages");
                assert_eq!(bindings.len(), 2);
                assert_eq!(bindings[0].full_path(), "user.name");
                assert_eq!(bindings[1].full_path(), "count");
            },
            _ => panic!("Expected TemplateString, got: {:?}", first_prop),
        }
    }

    #[test]
    fn test_static_string_no_bindings() {
        let input = r#"Text("Hello World")"#;
        let component = parse_component(input).unwrap();
        let element = ast_to_ir(&component);

        let first_prop = element.props.values().next().unwrap();
        match first_prop {
            Value::Static(val) => {
                assert_eq!(val.as_str().unwrap(), "Hello World");
            },
            _ => panic!("Expected Static, got: {:?}", first_prop),
        }
    }

    #[test]
    fn test_item_reference_conversion() {
        // Test @item.name reference syntax
        let input = r#"Text(text: @item.name)"#;
        let component = parse_component(input).unwrap();
        let element = ast_to_ir(&component);

        let text_prop = element.props.get("text").unwrap();
        match text_prop {
            Value::Binding(binding) => {
                assert!(binding.is_item(), "Should be an item binding");
                assert_eq!(binding.path, vec!["name"]);
                assert_eq!(binding.full_path(), "name");
                assert_eq!(binding.full_path_with_source(), "item.name");
            },
            _ => panic!("Expected item Binding, got: {:?}", text_prop),
        }
    }

    #[test]
    fn test_item_reference_nested_path() {
        // Test @item.user.profile.name reference syntax
        let input = r#"Text(text: @item.user.profile.name)"#;
        let component = parse_component(input).unwrap();
        let element = ast_to_ir(&component);

        let text_prop = element.props.get("text").unwrap();
        match text_prop {
            Value::Binding(binding) => {
                assert!(binding.is_item(), "Should be an item binding");
                assert_eq!(binding.path, vec!["user", "profile", "name"]);
                assert_eq!(binding.full_path(), "user.profile.name");
            },
            _ => panic!("Expected item Binding, got: {:?}", text_prop),
        }
    }

    #[test]
    fn test_bare_item_reference() {
        // Test @item reference (the whole item object)
        let input = r#"Component(data: @item)"#;
        let component = parse_component(input).unwrap();
        let element = ast_to_ir(&component);

        let data_prop = element.props.get("data").unwrap();
        match data_prop {
            Value::Binding(binding) => {
                assert!(binding.is_item(), "Should be an item binding");
                assert!(binding.path.is_empty(), "Path should be empty for bare @item");
                assert_eq!(binding.full_path_with_source(), "item");
            },
            _ => panic!("Expected item Binding, got: {:?}", data_prop),
        }
    }

    #[test]
    fn test_item_binding_in_template_string() {
        // Test ${item.name} in a template string
        let input = r#"Text("Hello ${item.name}!")"#;
        let component = parse_component(input).unwrap();
        let element = ast_to_ir(&component);

        let first_prop = element.props.values().next().unwrap();
        match first_prop {
            Value::TemplateString { template, bindings } => {
                assert_eq!(template, "Hello ${item.name}!");
                assert_eq!(bindings.len(), 1);
                assert!(bindings[0].is_item(), "Should be an item binding");
                assert_eq!(bindings[0].full_path(), "name");
            },
            _ => panic!("Expected TemplateString with item binding, got: {:?}", first_prop),
        }
    }

    #[test]
    fn test_mixed_state_and_item_bindings() {
        // Test template with both ${state.xxx} and ${item.xxx}
        let input = r#"Text("${state.prefix}: ${item.name}")"#;
        let component = parse_component(input).unwrap();
        let element = ast_to_ir(&component);

        let first_prop = element.props.values().next().unwrap();
        match first_prop {
            Value::TemplateString { template, bindings } => {
                assert_eq!(template, "${state.prefix}: ${item.name}");
                assert_eq!(bindings.len(), 2);
                assert!(bindings[0].is_state(), "First should be state binding");
                assert_eq!(bindings[0].full_path(), "prefix");
                assert!(bindings[1].is_item(), "Second should be item binding");
                assert_eq!(bindings[1].full_path(), "name");
            },
            _ => panic!("Expected TemplateString, got: {:?}", first_prop),
        }
    }

    #[test]
    fn test_tw_applicator_expansion() {
        // Test that .tw() applicator expands to CSS properties
        let input = r#"Text("Hello").tw("p-4 text-blue-500")"#;
        let component = parse_component(input).unwrap();
        let element = ast_to_ir(&component);

        // Should have expanded padding and color
        assert!(element.props.contains_key("padding"), "Should have padding prop");
        assert!(element.props.contains_key("color"), "Should have color prop");

        // Check padding value
        if let Value::Static(val) = element.props.get("padding").unwrap() {
            assert_eq!(val.as_str().unwrap(), "1rem");
        } else {
            panic!("Expected static padding value");
        }

        // Check color value
        if let Value::Static(val) = element.props.get("color").unwrap() {
            assert_eq!(val.as_str().unwrap(), "#3b82f6");
        } else {
            panic!("Expected static color value");
        }
    }

    #[test]
    fn test_tw_applicator_with_variants() {
        // Test that .tw() applicator handles responsive variants
        let input = r#"Text("Hello").tw("p-4 md:p-8 hover:bg-white")"#;
        let component = parse_component(input).unwrap();
        let element = ast_to_ir(&component);

        // Should have base padding
        assert!(element.props.contains_key("padding"), "Should have padding prop");

        // Should have variant properties
        assert!(element.props.contains_key("padding@md"), "Should have padding@md prop");
        assert!(element.props.contains_key("background-color:hover"), "Should have background-color:hover prop");

        // Check values
        if let Value::Static(val) = element.props.get("padding@md").unwrap() {
            assert_eq!(val.as_str().unwrap(), "2rem");
        }
        if let Value::Static(val) = element.props.get("background-color:hover").unwrap() {
            assert_eq!(val.as_str().unwrap(), "#ffffff");
        }
    }

    #[test]
    fn test_tw_mixed_with_other_applicators() {
        // Test .tw() combined with other applicators
        let input = r#"Text("Hello").tw("p-4").fontSize(18)"#;
        let component = parse_component(input).unwrap();
        let element = ast_to_ir(&component);

        // Should have expanded tailwind
        assert!(element.props.contains_key("padding"));

        // Should also have regular applicator
        assert!(element.props.contains_key("fontSize.0"));
    }
}
