//! Tests for src/ir/expand.rs - AST to IR conversion
//!
//! Tests conversion from parser output to engine intermediate representation

use hypen_engine::ir::Value;
use hypen_parser::parse_component;
use serde_json::json;

mod common;

// ============================================================================
// Value Conversion for All Types (7 tests)
// ============================================================================

#[test]
fn test_convert_string_value() {
    // GIVEN: Parser string value
    let input = r#"Text("Hello World")"#;
    let component = parse_component(input).unwrap();

    // WHEN: Convert to IR
    let element = hypen_engine::ir::expand::ast_to_ir(&component);

    // THEN: String becomes Static value
    assert_eq!(element.element_type, "Text");
    match element.props.get("0").unwrap() {
        Value::Static(v) => assert_eq!(v, &json!("Hello World")),
        _ => panic!("Expected Static value"),
    }
}

#[test]
fn test_convert_number_value() {
    // GIVEN: Parser number value
    let input = r#"Box(width: 100, height: 50.5)"#;
    let component = parse_component(input).unwrap();

    // WHEN: Convert to IR
    let element = hypen_engine::ir::expand::ast_to_ir(&component);

    // THEN: Numbers become Static values
    assert_eq!(element.element_type, "Box");
    match element.props.get("width").unwrap() {
        Value::Static(v) => assert_eq!(v, &json!(100.0)),
        _ => panic!("Expected Static value"),
    }
    match element.props.get("height").unwrap() {
        Value::Static(v) => assert_eq!(v, &json!(50.5)),
        _ => panic!("Expected Static value"),
    }
}

#[test]
fn test_convert_boolean_value() {
    // GIVEN: Parser boolean value
    let input = r#"Input(enabled: true, readonly: false)"#;
    let component = parse_component(input).unwrap();

    // WHEN: Convert to IR
    let element = hypen_engine::ir::expand::ast_to_ir(&component);

    // THEN: Booleans become Static values
    match element.props.get("enabled").unwrap() {
        Value::Static(v) => assert_eq!(v, &json!(true)),
        _ => panic!("Expected Static value"),
    }
    match element.props.get("readonly").unwrap() {
        Value::Static(v) => assert_eq!(v, &json!(false)),
        _ => panic!("Expected Static value"),
    }
}

#[test]
fn test_convert_list_value() {
    // GIVEN: Parser list value
    let input = r#"Box(tags: ["primary", "action"])"#;
    let component = parse_component(input).unwrap();

    // WHEN: Convert to IR
    let element = hypen_engine::ir::expand::ast_to_ir(&component);

    // THEN: List becomes Static JSON array
    match element.props.get("tags").unwrap() {
        Value::Static(v) => {
            assert_eq!(v, &json!(["primary", "action"]));
        }
        _ => panic!("Expected Static value"),
    }
}

#[test]
fn test_convert_map_value() {
    // GIVEN: Parser map value
    let input = r#"Box(config: {width: 100, height: 200})"#;
    let component = parse_component(input).unwrap();

    // WHEN: Convert to IR
    let element = hypen_engine::ir::expand::ast_to_ir(&component);

    // THEN: Map becomes Static JSON object
    match element.props.get("config").unwrap() {
        Value::Static(v) => {
            assert_eq!(v["width"], json!(100.0));
            assert_eq!(v["height"], json!(200.0));
        }
        _ => panic!("Expected Static value"),
    }
}

#[test]
fn test_convert_binding_value() {
    // GIVEN: Parser string with binding syntax
    let input = r#"Text("${state.user.name}")"#;
    let component = parse_component(input).unwrap();

    // WHEN: Convert to IR
    let element = hypen_engine::ir::expand::ast_to_ir(&component);

    // THEN: Becomes Binding value
    match element.props.get("0").unwrap() {
        Value::Binding(binding) => {
            assert_eq!(binding.path, vec!["user", "name"]);
            assert_eq!(binding.full_path(), "user.name");
        }
        _ => panic!("Expected Binding value"),
    }
}

#[test]
fn test_convert_action_value() {
    // GIVEN: Parser string with action syntax
    let input = r#"Button(onClick: "@actions.submit") { Text("Submit") }"#;
    let component = parse_component(input).unwrap();

    // WHEN: Convert to IR
    let element = hypen_engine::ir::expand::ast_to_ir(&component);

    // THEN: Becomes Action value (strips @actions. prefix)
    match element.props.get("onClick").unwrap() {
        Value::Action(action) => {
            assert_eq!(action, "submit");
        }
        _ => panic!("Expected Action value"),
    }
}

// ============================================================================
// Binding Parsing Edge Cases (4 tests)
// ============================================================================

#[test]
fn test_invalid_binding_becomes_template_string() {
    // GIVEN: Invalid binding syntax (missing "state." or "item." prefix)
    // This will be evaluated at runtime by exprimo as an expression
    let input = r#"Text("${user.name}")"#;
    let component = parse_component(input).unwrap();

    // WHEN: Convert to IR
    let element = hypen_engine::ir::expand::ast_to_ir(&component);

    // THEN: Invalid binding becomes a TemplateString that will be evaluated at runtime
    // exprimo will try to resolve "user.name" from the context
    match element.props.get("0").unwrap() {
        Value::TemplateString { template, bindings } => {
            assert_eq!(template, "${user.name}");
            assert!(bindings.is_empty(), "No state/item bindings extracted");
        }
        v => panic!("Expected TemplateString value for expression, got {:?}", v),
    }
}

#[test]
fn test_reference_with_state_prefix() {
    // GIVEN: Reference value with state prefix
    let input = r#"Text(text: @state.message)"#;
    let component = parse_component(input).unwrap();

    // WHEN: Convert to IR
    let element = hypen_engine::ir::expand::ast_to_ir(&component);

    // THEN: Becomes Binding value
    match element.props.get("text").unwrap() {
        Value::Binding(binding) => {
            assert_eq!(binding.path, vec!["message"]);
        }
        _ => panic!("Expected Binding value"),
    }
}

#[test]
fn test_reference_with_actions_prefix() {
    // GIVEN: Reference value with actions prefix
    let input = r#"Button(onClick: @actions.increment)"#;
    let component = parse_component(input).unwrap();

    // WHEN: Convert to IR
    let element = hypen_engine::ir::expand::ast_to_ir(&component);

    // THEN: Becomes Action value
    match element.props.get("onClick").unwrap() {
        Value::Action(action) => {
            assert_eq!(action, "increment");
        }
        _ => panic!("Expected Action value"),
    }
}

#[test]
fn test_binding_in_list_preserved_as_string() {
    // GIVEN: List with binding inside
    let input = r#"Box(items: ["${state.first}", "static"])"#;
    let component = parse_component(input).unwrap();

    // WHEN: Convert to IR
    let element = hypen_engine::ir::expand::ast_to_ir(&component);

    // THEN: Binding in list preserved as string representation (strips "state." prefix)
    match element.props.get("items").unwrap() {
        Value::Static(v) => {
            // List items with bindings are converted to their string representation
            assert!(v.is_array());
            // Note: Binding path "state.first" becomes "${first}" in string representation
            assert_eq!(v[0], json!("${first}"));
            assert_eq!(v[1], json!("static"));
        }
        _ => panic!("Expected Static value"),
    }
}

// ============================================================================
// Key Extraction and Generation (4 tests)
// ============================================================================

#[test]
fn test_key_from_first_positional_string() {
    // GIVEN: Component with first positional string argument
    let input = r#"Text("Item1")"#;
    let component = parse_component(input).unwrap();

    // WHEN: Convert to IR
    let element = hypen_engine::ir::expand::ast_to_ir(&component);

    // THEN: First positional string becomes key (parser preserves quotes)
    assert_eq!(element.key, Some("\"Item1\"".to_string()));
}

#[test]
fn test_no_key_for_named_argument() {
    // GIVEN: Component with only named arguments
    let input = r#"Box(width: 100, height: 200)"#;
    let component = parse_component(input).unwrap();

    // WHEN: Convert to IR
    let element = hypen_engine::ir::expand::ast_to_ir(&component);

    // THEN: No key generated
    assert_eq!(element.key, None);
}

#[test]
fn test_no_key_for_number_positional() {
    // GIVEN: Component with first positional number
    let input = r#"Box(100)"#;
    let component = parse_component(input).unwrap();

    // WHEN: Convert to IR
    let element = hypen_engine::ir::expand::ast_to_ir(&component);

    // THEN: No key (only string values become keys)
    assert_eq!(element.key, None);
}

#[test]
fn test_key_extraction_with_multiple_positional() {
    // GIVEN: Component with multiple positional arguments
    let input = r#"Text("Key", 100, true)"#;
    let component = parse_component(input).unwrap();

    // WHEN: Convert to IR
    let element = hypen_engine::ir::expand::ast_to_ir(&component);

    // THEN: First positional string becomes key (parser preserves quotes)
    assert_eq!(element.key, Some("\"Key\"".to_string()));
    // All arguments become props
    assert_eq!(element.props.len(), 3);
}

// ============================================================================
// Additional Edge Cases
// ============================================================================

#[test]
fn test_applicators_become_namespaced_props() {
    // GIVEN: Component with applicators
    let input = r#"Text("Styled").fontSize(18).color("blue")"#;
    let component = parse_component(input).unwrap();

    // WHEN: Convert to IR
    let element = hypen_engine::ir::expand::ast_to_ir(&component);

    // THEN: Applicators become namespaced props
    assert!(element.props.contains_key("fontSize.0"));
    assert!(element.props.contains_key("color.0"));

    match element.props.get("fontSize.0").unwrap() {
        Value::Static(v) => assert_eq!(v, &json!(18.0)),
        _ => panic!("Expected Static value"),
    }
    match element.props.get("color.0").unwrap() {
        Value::Static(v) => assert_eq!(v, &json!("blue")),
        _ => panic!("Expected Static value"),
    }
}

#[test]
fn test_nested_children_conversion() {
    // GIVEN: Deeply nested component tree
    let input = r#"
        Column {
            Row {
                Text("A")
                Text("B")
            }
            Text("C")
        }
    "#;
    let component = parse_component(input).unwrap();

    // WHEN: Convert to IR
    let element = hypen_engine::ir::expand::ast_to_ir(&component);

    // THEN: Tree structure preserved
    assert_eq!(element.element_type, "Column");
    assert_eq!(element.children.len(), 2);
    assert_eq!(element.children[0].element_type, "Row");
    assert_eq!(element.children[0].children.len(), 2);
    assert_eq!(element.children[1].element_type, "Text");
}

#[test]
fn test_action_short_syntax() {
    // GIVEN: Action with @ prefix only (no "actions.")
    let input = r#"Button(onClick: "@submit")"#;
    let component = parse_component(input).unwrap();

    // WHEN: Convert to IR
    let element = hypen_engine::ir::expand::ast_to_ir(&component);

    // THEN: Action value created without "actions." prefix
    match element.props.get("onClick").unwrap() {
        Value::Action(action) => {
            assert_eq!(action, "submit");
        }
        _ => panic!("Expected Action value"),
    }
}

#[test]
fn test_empty_component() {
    // GIVEN: Component with no arguments or children
    let input = r#"Divider()"#;
    let component = parse_component(input).unwrap();

    // WHEN: Convert to IR
    let element = hypen_engine::ir::expand::ast_to_ir(&component);

    // THEN: Element created with no props or children
    assert_eq!(element.element_type, "Divider");
    assert_eq!(element.props.len(), 0);
    assert_eq!(element.children.len(), 0);
    assert_eq!(element.key, None);
}
