//! Shared rendering logic for Engine and WasmEngine
//!
//! This module contains the common rendering logic to avoid code duplication
//! between the native Engine and WASM WasmEngine implementations.

use crate::{
    ir::{NodeId, Value},
    lifecycle::ModuleInstance,
    reactive::{DependencyGraph, Scheduler},
    reconcile::{evaluate_binding, InstanceTree, Patch},
};

/// Render only dirty nodes (optimized for state changes)
/// This is shared logic used by both Engine and WasmEngine
pub fn render_dirty_nodes(
    scheduler: &mut Scheduler,
    tree: &mut InstanceTree,
    module: Option<&ModuleInstance>,
) -> Vec<Patch> {
    render_dirty_nodes_with_deps(scheduler, tree, module, &mut DependencyGraph::new())
}

/// Render only dirty nodes with dependency tracking for List reconciliation
pub fn render_dirty_nodes_with_deps(
    scheduler: &mut Scheduler,
    tree: &mut InstanceTree,
    module: Option<&ModuleInstance>,
    dependencies: &mut DependencyGraph,
) -> Vec<Patch> {
    if !scheduler.has_dirty() {
        return Vec::new();
    }

    let dirty_nodes = scheduler.take_dirty();
    let state = module
        .map(|m| m.get_state())
        .unwrap_or(&serde_json::Value::Null);

    // Store old props before updating, then update and generate patches for changed props only
    let mut patches = Vec::new();
    for node_id in dirty_nodes {
        // Check if this is a List node (has array binding in raw_props AND element_template)
        // The element_template is set for List elements that need to re-render children
        let is_list_node = tree
            .get(node_id)
            .map(|n| {
                n.raw_props
                    .get("0")
                    .map(|v| matches!(v, Value::Binding(_)))
                    .unwrap_or(false)
                    && n.element_template.is_some()
            })
            .unwrap_or(false);

        if is_list_node {
            // For List nodes, we need to re-reconcile the entire list
            render_dirty_list(node_id, tree, state, &mut patches, dependencies);
        } else {
            // Regular node: just update props
            let old_props = tree.get(node_id).map(|n| n.props.clone());

            // Update props with new state
            if let Some(node) = tree.get_mut(node_id) {
                node.update_props(state);
            }

            // Compare and generate patches only for changed props
            if let (Some(old), Some(node)) = (old_props, tree.get(node_id)) {
                for (key, new_value) in &node.props {
                    if old.get(key) != Some(new_value) {
                        patches.push(Patch::set_prop(node_id, key.clone(), new_value.clone()));
                    }
                }
                // Remove props that no longer exist
                for key in old.keys() {
                    if !node.props.contains_key(key) {
                        patches.push(Patch::remove_prop(node_id, key.clone()));
                    }
                }
            }
        }
    }

    patches
}

/// Render a dirty List node by re-reconciling its children
/// Uses keyed reconciliation for efficient updates (minimizes DOM operations)
fn render_dirty_list(
    node_id: NodeId,
    tree: &mut InstanceTree,
    state: &serde_json::Value,
    patches: &mut Vec<Patch>,
    dependencies: &mut DependencyGraph,
) {
    use crate::ir::Element;
    use crate::reconcile::diff::{reconcile_keyed_children, replace_item_bindings};

    // Get the array binding, element template, and old children
    let (array_binding, element_template, old_children) = {
        let node = match tree.get(node_id) {
            Some(n) => n,
            None => return,
        };

        let binding = match node.raw_props.get("0") {
            Some(Value::Binding(b)) => b.clone(),
            _ => return,
        };

        let template = match &node.element_template {
            Some(t) => t.clone(),
            None => return, // No template stored, can't re-render
        };

        (binding, template, node.children.clone())
    };

    // Evaluate the array binding
    let array = evaluate_binding(&array_binding, state).unwrap_or(serde_json::Value::Array(vec![]));

    let items = match &array {
        serde_json::Value::Array(items) => items,
        _ => return,
    };

    // Build new child elements with keys from items
    // Each item generates one or more children from the template
    let mut new_children: Vec<Element> = Vec::new();

    for (index, item) in items.iter().enumerate() {
        for child_template in &element_template.children {
            // Replace ${item.x} bindings with actual item data
            // The key is set by replace_item_bindings:
            // - Uses item.id or item.key for stable identity (enables efficient reordering)
            // - Falls back to index-based key if no id field
            let child_with_item = replace_item_bindings(child_template, item, index);
            new_children.push(child_with_item);
        }
    }

    // Use keyed reconciliation for efficient updates
    // Convert im::Vector to Vec for the slice interface
    let old_children_vec: Vec<_> = old_children.iter().copied().collect();
    let keyed_patches = reconcile_keyed_children(
        tree,
        node_id,
        &old_children_vec,
        &new_children,
        state,
        dependencies,
    );

    patches.extend(keyed_patches);
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{ir::Element, lifecycle::Module, reactive::Binding};
    use serde_json::json;

    #[test]
    fn test_render_dirty_nodes_no_dirty() {
        let mut scheduler = Scheduler::new();
        let mut tree = InstanceTree::new();

        let patches = render_dirty_nodes(&mut scheduler, &mut tree, None);
        assert_eq!(patches.len(), 0);
    }

    #[test]
    fn test_render_dirty_nodes_with_changes() {
        let mut scheduler = Scheduler::new();
        let mut tree = InstanceTree::new();

        // Create a module with state
        let module = Module::new("TestModule");
        let initial_state = json!({"count": 0});
        let instance = ModuleInstance::new(module, initial_state);

        // Create a node with a binding
        let element = Element::new("Text");
        let node_id = tree.create_node(&element, instance.get_state());

        // Mark the node as dirty
        scheduler.mark_dirty(node_id);

        // Render dirty nodes
        let _patches = render_dirty_nodes(&mut scheduler, &mut tree, Some(&instance));

        // Should have processed the dirty node (though patches may be empty if props didn't change)
        assert!(
            !scheduler.has_dirty(),
            "Scheduler should have no more dirty nodes"
        );
    }

    #[test]
    fn test_render_dirty_nodes_state_change() {
        use crate::ir::Value;

        let mut scheduler = Scheduler::new();
        let mut tree = InstanceTree::new();

        // Create module with initial state
        let module = Module::new("TestModule");
        let initial_state = json!({"text": "Hello"});
        let mut instance = ModuleInstance::new(module, initial_state);

        // Create a node with a binding to state
        let mut element = Element::new("Text");
        element.props.insert(
            "0".to_string(),
            Value::Binding(Binding::state(vec!["text".to_string()])),
        );
        let node_id = tree.create_node(&element, instance.get_state());

        // Update the node's props to reflect initial state
        if let Some(node) = tree.get_mut(node_id) {
            node.update_props(instance.get_state());
        }

        // Update state
        instance.update_state(json!({"text": "World"}));

        // Mark node as dirty
        scheduler.mark_dirty(node_id);

        // Render dirty nodes
        let patches = render_dirty_nodes(&mut scheduler, &mut tree, Some(&instance));

        // Should generate a SetProp patch for the changed text
        let set_prop_count = patches
            .iter()
            .filter(|p| matches!(p, Patch::SetProp { .. }))
            .count();
        assert!(
            set_prop_count > 0,
            "Should have SetProp patches for changed state"
        );
    }

    #[test]
    fn test_render_dirty_nodes_multiple_nodes() {
        let mut scheduler = Scheduler::new();
        let mut tree = InstanceTree::new();

        let module = Module::new("TestModule");
        let initial_state = json!({});
        let instance = ModuleInstance::new(module, initial_state);

        // Create multiple nodes
        let element1 = Element::new("Text");
        let element2 = Element::new("Text");
        let element3 = Element::new("Text");

        let node_id_1 = tree.create_node(&element1, instance.get_state());
        let _node_id_2 = tree.create_node(&element2, instance.get_state());
        let node_id_3 = tree.create_node(&element3, instance.get_state());

        // Mark only some nodes as dirty
        scheduler.mark_dirty(node_id_1);
        scheduler.mark_dirty(node_id_3);

        // Should have 2 dirty nodes
        assert!(scheduler.has_dirty());

        render_dirty_nodes(&mut scheduler, &mut tree, Some(&instance));

        // All dirty nodes should be processed
        assert!(!scheduler.has_dirty());
    }
}
