/// WASM Integration Tests
///
/// These tests verify the WASM interface works correctly with the engine.
/// While they run natively (not in WASM), they test the same code paths
/// that WASM would use.
use hypen_engine::{
    dispatch::Action,
    ir::{ast_to_ir, ComponentRegistry, Element, Value},
    lifecycle::{Module, ModuleInstance},
    reactive::{DependencyGraph, Scheduler},
    reconcile::{create_tree, reconcile, InstanceTree, Patch},
};
use serde_json::json;
use std::collections::HashMap;

/// Mock render callback to capture patches
struct MockRenderer {
    patches: Vec<Patch>,
}

#[allow(dead_code)]
impl MockRenderer {
    fn new() -> Self {
        Self {
            patches: Vec::new(),
        }
    }

    fn capture_patch(&mut self, patch: Patch) {
        self.patches.push(patch);
    }

    fn clear(&mut self) {
        self.patches.clear();
    }

    fn patch_count(&self) -> usize {
        self.patches.len()
    }

    fn has_create_patch(&self, element_type: &str) -> bool {
        self.patches.iter().any(|p| match p {
            Patch::Create {
                element_type: et, ..
            } => et == element_type,
            _ => false,
        })
    }

    fn has_set_prop_patch(&self, prop_name: &str) -> bool {
        self.patches.iter().any(|p| match p {
            Patch::SetProp { name, .. } => name == prop_name,
            _ => false,
        })
    }
}

#[test]
fn test_wasm_render_simple_component() {
    // Simulates: WasmEngine.renderSource("Text('Hello')")
    let source = r#"Text("Hello")"#;
    let component = hypen_parser::parse_component(source).unwrap();
    let element = ast_to_ir(&component);

    let mut tree = InstanceTree::new();
    let mut dependencies = DependencyGraph::new();
    let mut renderer = MockRenderer::new();

    let mut patches = Vec::new();
    let state_json = serde_json::to_value(HashMap::<String, serde_json::Value>::new()).unwrap();
    let _root_id = create_tree(
        &mut tree,
        &element,
        None,
        &state_json,
        &mut patches,
        true,
        &mut dependencies,
    );

    for patch in patches {
        renderer.capture_patch(patch);
    }

    assert!(renderer.has_create_patch("Text"));
    assert!(renderer.patch_count() > 0);
}

#[test]
fn test_wasm_state_update_flow() {
    // Simulates: module with state, then updateState() call
    let source = r#"Text("Count: ${state.count}")"#;
    let component = hypen_parser::parse_component(source).unwrap();
    let element = ast_to_ir(&component);

    let mut tree = InstanceTree::new();
    let mut dependencies = DependencyGraph::new();
    let initial_state = HashMap::from([("count".to_string(), json!(0))]);

    // Initial render
    let mut patches = Vec::new();
    let state_json = serde_json::to_value(&initial_state).unwrap();
    let _root_id = create_tree(
        &mut tree,
        &element,
        None,
        &state_json,
        &mut patches,
        true,
        &mut dependencies,
    );
    assert!(!patches.is_empty());

    // Update state
    let _new_state = HashMap::from([("count".to_string(), json!(1))]);
    let changed_paths = vec!["count".to_string()];

    let mut scheduler = Scheduler::new();
    // Get affected nodes for each changed path
    for path in &changed_paths {
        let affected = dependencies.get_affected_nodes(path);
        for node_id in affected {
            scheduler.mark_dirty(node_id);
        }
    }

    // Reconcile affected nodes
    let dirty_nodes = scheduler.take_dirty();
    assert!(
        !dirty_nodes.is_empty(),
        "Should have dirty nodes after state change"
    );
}

#[test]
fn test_wasm_action_dispatch() {
    // Simulates: dispatchAction("increment", payload)
    let action = Action {
        name: "increment".to_string(),
        payload: Some(json!(1)),
        sender: None,
    };

    // Verify action structure is valid
    assert_eq!(action.name, "increment");
    assert_eq!(action.payload, Some(json!(1)));
    assert!(action.sender.is_none());
}

#[test]
fn test_wasm_component_registry() {
    // Simulates: registerComponent() calls
    use hypen_engine::ir::Component;
    let mut registry = ComponentRegistry::new();

    let component = Component::new("Button", |_props| Element::new("Text"));

    registry.register(component);

    assert!(registry.get("Button", None).is_some());
    assert!(registry.get("NonExistent", None).is_none());
}

#[test]
fn test_wasm_component_resolution() {
    // Simulates: component resolution with context path
    use hypen_engine::ir::Component;
    let mut registry = ComponentRegistry::new();

    let button = Component::new("Button", |_props| Element::new("Text"));

    registry.register(button);

    // Get component
    let resolved = registry.get("Button", None);
    assert!(resolved.is_some());
}

#[test]
fn test_wasm_parse_error_handling() {
    // Simulates: renderSource() with invalid syntax
    let invalid_sources = vec![
        "Text(",           // Incomplete
        "Text('unclosed)", // Unclosed string
        "123Invalid",      // Invalid start
        "",                // Empty
    ];

    for source in invalid_sources {
        let result = hypen_parser::parse_component(source);
        assert!(
            result.is_err(),
            "Should fail to parse invalid source: {}",
            source
        );
    }
}

#[test]
fn test_wasm_nested_component_expansion() {
    // Simulates: Component with nested children
    let source = r#"
        Column {
            Text("Title")
            Text("Subtitle")
        }
    "#;

    let component = hypen_parser::parse_component(source).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_wasm_state_binding_extraction() {
    // Simulates: State bindings in templates
    let source = r#"Text("User: ${state.user.name}, Age: ${state.user.age}")"#;

    let component = hypen_parser::parse_component(source).unwrap();
    let element = ast_to_ir(&component);

    let mut tree = InstanceTree::new();
    let mut dependencies = DependencyGraph::new();
    let state = HashMap::from([
        ("user.name".to_string(), json!("Alice")),
        ("user.age".to_string(), json!(30)),
    ]);

    let mut patches = Vec::new();
    let state_json = serde_json::to_value(&state).unwrap();
    let _ = create_tree(
        &mut tree,
        &element,
        None,
        &state_json,
        &mut patches,
        true,
        &mut dependencies,
    );

    // Verify dependencies were tracked
    let affected = dependencies.get_affected_nodes("user.name");
    assert!(!affected.is_empty(), "Should track user.name dependency");
}

#[test]
fn test_wasm_module_lifecycle() {
    // Simulates: Module registration and lifecycle
    let module = Module {
        name: "TestModule".to_string(),
        actions: vec!["increment".to_string()],
        state_keys: vec!["count".to_string()],
        persist: false,
        version: Some(1),
    };

    let initial_state = json!({ "count": 0 });
    let instance = ModuleInstance::new(module.clone(), initial_state.clone());

    assert_eq!(instance.module.name, "TestModule");
    assert_eq!(instance.get_state(), &initial_state);
}

#[test]
fn test_wasm_state_merging() {
    // Simulates: Deep state merging in modules
    let base_state = json!({
        "user": {
            "name": "Alice",
            "age": 30
        },
        "settings": {
            "theme": "dark"
        }
    });

    let updates = json!({
        "user": {
            "age": 31
        },
        "settings": {
            "notifications": true
        }
    });

    // Test that merge logic preserves nested structure
    // (This would be the actual merge implementation)
    assert!(base_state.is_object());
    assert!(updates.is_object());
}

#[test]
fn test_wasm_reconciliation_keyed_lists() {
    // Simulates: Keyed list reconciliation
    let old_source = r#"
        Column {
            Text(key: "a", "Item A")
            Text(key: "b", "Item B")
            Text(key: "c", "Item C")
        }
    "#;

    let new_source = r#"
        Column {
            Text(key: "b", "Item B")
            Text(key: "a", "Item A")
            Text(key: "d", "Item D")
        }
    "#;

    let old_component = hypen_parser::parse_component(old_source).unwrap();
    let new_component = hypen_parser::parse_component(new_source).unwrap();

    let old_element = ast_to_ir(&old_component);
    let new_element = ast_to_ir(&new_component);

    let mut tree = InstanceTree::new();
    let mut dependencies = DependencyGraph::new();
    let state: HashMap<String, serde_json::Value> = HashMap::new();

    // Initial render
    let mut patches = Vec::new();
    let state_json = serde_json::to_value(&state).unwrap();
    let root_id = create_tree(
        &mut tree,
        &old_element,
        None,
        &state_json,
        &mut patches,
        true,
        &mut dependencies,
    );

    // Reconcile with new tree
    let reconcile_patches = reconcile(
        &mut tree,
        &new_element,
        Some(root_id),
        &state_json,
        &mut dependencies,
    );

    // Should generate move/remove/create patches, not replace all
    let has_move = reconcile_patches
        .iter()
        .any(|p| matches!(p, Patch::Move { .. }));
    let has_remove = reconcile_patches
        .iter()
        .any(|p| matches!(p, Patch::Remove { .. }));
    let has_create = reconcile_patches
        .iter()
        .any(|p| matches!(p, Patch::Create { .. }));

    assert!(
        has_move || has_remove || has_create,
        "Should generate efficient patches for keyed list changes"
    );
}

#[test]
fn test_wasm_props_with_various_types() {
    // Simulates: Props with different value types
    let source = r#"
        Button(
            text: "Click",
            count: 42,
            enabled: true,
            data: {name: "test", value: 123},
            items: [1, 2, 3]
        )
    "#;

    let component = hypen_parser::parse_component(source).unwrap();
    let element = ast_to_ir(&component);

    // Verify all prop types are preserved
    assert!(element.props.contains_key("text"));
    assert!(element.props.contains_key("count"));
    assert!(element.props.contains_key("enabled"));
    assert!(element.props.contains_key("data"));
    assert!(element.props.contains_key("items"));
}

#[test]
fn test_wasm_applicators() {
    // Simulates: Applicators (style modifiers)
    let source = r#"
        Text("Styled")
            .fontSize(18)
            .color(blue)
            .padding(16)
    "#;

    let component = hypen_parser::parse_component(source).unwrap();
    let element = ast_to_ir(&component);

    // Applicators become props with format "applicatorName.index"
    assert!(element.props.contains_key("fontSize.0"));
    assert!(element.props.contains_key("color.0"));
    assert!(element.props.contains_key("padding.0"));
}

#[test]
fn test_wasm_action_references() {
    // Simulates: @actions.* references
    let source = r#"Button("@actions.handleClick")"#;

    let component = hypen_parser::parse_component(source).unwrap();
    let element = ast_to_ir(&component);

    // Check that action reference is preserved
    let first_arg = element.props.get("0");
    assert!(first_arg.is_some());

    match first_arg.unwrap() {
        Value::Action(action) => {
            assert_eq!(action, "handleClick");
        }
        _ => panic!("Expected Action"),
    }
}

#[test]
fn test_wasm_clear_tree() {
    // Simulates: clearTree() - resetting engine state
    let source = r#"Text("Hello")"#;
    let component = hypen_parser::parse_component(source).unwrap();
    let element = ast_to_ir(&component);

    let mut tree = InstanceTree::new();
    let mut dependencies = DependencyGraph::new();
    let state: HashMap<String, serde_json::Value> = HashMap::new();

    let mut patches = Vec::new();
    let state_json = serde_json::to_value(&state).unwrap();
    let root_id = create_tree(
        &mut tree,
        &element,
        None,
        &state_json,
        &mut patches,
        true,
        &mut dependencies,
    );
    tree.set_root(root_id);

    // Verify tree is not empty
    assert!(tree.root().is_some());

    // Clear (simulate)
    tree = InstanceTree::new();
    let _dependencies = DependencyGraph::new();

    // Verify tree is empty
    assert!(tree.root().is_none());
}

#[test]
fn test_wasm_revision_tracking() {
    // Simulates: Revision counter for remote UI
    let mut revision: u64 = 0;

    // Each render increments revision
    revision += 1;
    assert_eq!(revision, 1);

    revision += 1;
    assert_eq!(revision, 2);

    // Verify overflow doesn't panic (will wrap)
    let mut max_revision = u64::MAX;
    max_revision = max_revision.wrapping_add(1);
    assert_eq!(max_revision, 0);
}

#[test]
fn test_wasm_dependency_tracking_complex() {
    // Simulates: Complex nested state dependencies
    let source = r#"
        Column {
            Text("${state.user.profile.name}")
            Text("${state.user.profile.email}")
            Text("${state.settings.theme}")
        }
    "#;

    let component = hypen_parser::parse_component(source).unwrap();
    let element = ast_to_ir(&component);

    let mut tree = InstanceTree::new();
    let mut dependencies = DependencyGraph::new();
    let state = HashMap::from([
        ("user.profile.name".to_string(), json!("Alice")),
        ("user.profile.email".to_string(), json!("alice@example.com")),
        ("settings.theme".to_string(), json!("dark")),
    ]);

    let mut patches = Vec::new();
    let state_json = serde_json::to_value(&state).unwrap();
    create_tree(
        &mut tree,
        &element,
        None,
        &state_json,
        &mut patches,
        true,
        &mut dependencies,
    );

    // Test that changing user.profile affects both name and email nodes
    let affected = dependencies.get_affected_nodes("user.profile");
    assert!(
        affected.len() >= 2,
        "Changing user.profile should affect both name and email"
    );

    // Test that changing settings.theme only affects theme node
    let affected_theme = dependencies.get_affected_nodes("settings.theme");
    assert!(
        !affected_theme.is_empty(),
        "Changing settings.theme should affect theme node"
    );
}

#[test]
fn test_wasm_error_recovery() {
    // Simulates: Engine continues working after errors
    let mut tree = InstanceTree::new();

    // Invalid operation shouldn't crash subsequent valid operations
    let valid_source = r#"Text("Valid")"#;
    let component = hypen_parser::parse_component(valid_source).unwrap();
    let element = ast_to_ir(&component);

    let mut dependencies = DependencyGraph::new();
    let state: HashMap<String, serde_json::Value> = HashMap::new();

    let mut patches = Vec::new();
    let state_json = serde_json::to_value(&state).unwrap();
    create_tree(
        &mut tree,
        &element,
        None,
        &state_json,
        &mut patches,
        true,
        &mut dependencies,
    );
    assert!(!patches.is_empty(), "Should continue working after errors");
}

#[test]
fn test_wasm_large_tree_performance() {
    // Simulates: Rendering large component trees
    let mut source = String::from("Column {\n");
    for i in 0..100 {
        source.push_str(&format!("  Text(\"Item {}\")\n", i));
    }
    source.push('}');

    let component = hypen_parser::parse_component(&source).unwrap();
    let element = ast_to_ir(&component);

    assert_eq!(element.element_type, "Column");
    assert_eq!(element.children.len(), 100);

    let mut tree = InstanceTree::new();
    let mut dependencies = DependencyGraph::new();
    let state: HashMap<String, serde_json::Value> = HashMap::new();

    let mut patches = Vec::new();
    let state_json = serde_json::to_value(&state).unwrap();
    create_tree(
        &mut tree,
        &element,
        None,
        &state_json,
        &mut patches,
        true,
        &mut dependencies,
    );

    // Should generate patches for all elements
    assert!(
        patches.len() > 100,
        "Should create patches for all elements"
    );
}

#[test]
fn test_wasm_concurrent_state_paths() {
    // Simulates: Multiple state paths updating simultaneously
    let changed_paths = vec![
        "user.name".to_string(),
        "user.email".to_string(),
        "settings.theme".to_string(),
        "settings.language".to_string(),
    ];

    let mut dependencies = DependencyGraph::new();

    // Register some fake dependencies using real NodeIds
    use hypen_engine::{ir::Element, reactive::Binding, reconcile::InstanceTree};
    let mut tree = InstanceTree::new();
    let node1 = tree.create_node(&Element::new("Text"), &serde_json::json!({}));
    let node2 = tree.create_node(&Element::new("Text"), &serde_json::json!({}));

    dependencies.add_dependency(
        node1,
        &Binding::state(vec!["user".to_string(), "name".to_string()]),
    );
    dependencies.add_dependency(
        node2,
        &Binding::state(vec!["settings".to_string(), "theme".to_string()]),
    );

    let mut affected = indexmap::IndexSet::new();
    for path in &changed_paths {
        affected.extend(dependencies.get_affected_nodes(path));
    }

    assert!(
        affected.contains(&node1),
        "Should track user.name dependency"
    );
    assert!(
        affected.contains(&node2),
        "Should track settings.theme dependency"
    );
}
