hypen-engine 0.4.82

A Rust implementation of the Hypen engine
Documentation
use super::resolve::{resolve_props, resolve_props_full};
use crate::ir::{Element, IRNode, NodeId, Props};
use indexmap::IndexMap;
use slotmap::SlotMap;
use std::sync::Arc;

/// Data sources type alias for readability
type DataSources = indexmap::IndexMap<String, serde_json::Value>;

/// The kind of control flow node for re-reconciliation
#[derive(Debug, Clone)]
pub enum ControlFlowKind {
    /// ForEach iteration container
    ForEach {
        item_name: String,
        key_path: Option<String>,
    },
    /// Conditional (When/If) container
    Conditional,
    /// Router container — selects which Route's children to render based on
    /// the current location.
    Router,
}

/// Instance node - a concrete instance of an element in the tree
///
/// Uses im::Vector for children to enable O(1) structural sharing during clones.
/// This is critical for reconciliation performance where nodes are frequently cloned.
#[derive(Debug, Clone)]
pub struct InstanceNode {
    /// Unique node ID
    pub id: NodeId,

    /// Element type (e.g., "Column", "Text", "__ForEach", "__Conditional")
    pub element_type: String,

    /// Resolved props (bindings evaluated to actual values)
    pub props: IndexMap<String, serde_json::Value>,

    /// Raw props (including bindings) for change detection - Arc-wrapped for O(1) clone
    pub raw_props: Props,

    /// Original element template (for List re-rendering) - legacy
    /// Only populated for List elements that need to re-render children
    /// Arc-wrapped for O(1) clone during reconciliation
    pub element_template: Option<Arc<Element>>,

    /// Original IRNode template (for ForEach/Conditional re-rendering)
    /// Used for control flow nodes that need to re-render on state change
    /// Arc-wrapped for O(1) clone during reconciliation
    pub ir_node_template: Option<Arc<IRNode>>,

    /// Control flow metadata for ForEach/Conditional nodes
    pub control_flow: Option<ControlFlowKind>,

    // Event handling removed - now done at renderer level
    /// Optional key for reconciliation
    pub key: Option<String>,

    /// Parent node ID
    pub parent: Option<NodeId>,

    /// Child node IDs (ordered) - uses im::Vector for O(1) clone
    pub children: im::Vector<NodeId>,

    /// Module scope this node belongs to (if any).
    /// Used during dirty re-rendering to resolve `@{state.xxx}` against
    /// the correct named module's state.
    pub module_scope: Option<String>,
}

impl InstanceNode {
    pub fn new(id: NodeId, element: &Element, state: &serde_json::Value) -> Self {
        Self::new_full(id, element, state, None)
    }

    pub fn new_full(
        id: NodeId,
        element: &Element,
        state: &serde_json::Value,
        data_sources: Option<&DataSources>,
    ) -> Self {
        let props = resolve_props_full(&element.props, state, None, data_sources);

        Self {
            id,
            element_type: element.element_type.clone(),
            props,
            raw_props: element.props.clone(),
            element_template: None,
            ir_node_template: None,
            control_flow: None,
            key: element.key.clone(),
            parent: None,
            children: im::Vector::new(),
            module_scope: element.module_scope.clone(),
        }
    }

    /// Create a control flow container node (ForEach or Conditional)
    pub fn new_control_flow(
        id: NodeId,
        element_type: &str,
        props: IndexMap<String, serde_json::Value>,
        raw_props: Props,
        control_flow: ControlFlowKind,
        ir_node_template: IRNode,
    ) -> Self {
        Self {
            id,
            element_type: element_type.to_string(),
            props,
            raw_props,
            element_template: None,
            ir_node_template: Some(Arc::new(ir_node_template)),
            control_flow: Some(control_flow),
            key: None,
            parent: None,
            children: im::Vector::new(),
            module_scope: None,
        }
    }

    /// Update props by re-evaluating bindings against new state
    pub fn update_props(&mut self, state: &serde_json::Value) {
        self.props = resolve_props(&self.raw_props, state);
    }

    /// Update props by re-evaluating bindings against state and data sources
    pub fn update_props_with_data_sources(
        &mut self,
        state: &serde_json::Value,
        data_sources: Option<&indexmap::IndexMap<String, serde_json::Value>>,
    ) {
        self.props = resolve_props_full(&self.raw_props, state, None, data_sources);
    }

    /// Check if this is a ForEach control flow node
    pub fn is_foreach(&self) -> bool {
        matches!(self.control_flow, Some(ControlFlowKind::ForEach { .. }))
    }

    /// Check if this is a Conditional control flow node
    pub fn is_conditional(&self) -> bool {
        matches!(self.control_flow, Some(ControlFlowKind::Conditional))
    }

    /// Check if this is a Router control flow node
    pub fn is_router(&self) -> bool {
        matches!(self.control_flow, Some(ControlFlowKind::Router))
    }
}

/// The instance tree - maintains the current UI tree state
pub struct InstanceTree {
    /// All nodes in the tree
    nodes: SlotMap<NodeId, InstanceNode>,

    /// Root node ID
    root: Option<NodeId>,
}

impl InstanceTree {
    pub fn new() -> Self {
        Self {
            nodes: SlotMap::with_key(),
            root: None,
        }
    }

    /// Clear all nodes and reset the tree
    pub fn clear(&mut self) {
        self.nodes.clear();
        self.root = None;
    }

    /// Create a new node and return its ID
    pub fn create_node(&mut self, element: &Element, state: &serde_json::Value) -> NodeId {
        self.nodes
            .insert_with_key(|id| InstanceNode::new(id, element, state))
    }

    /// Create a new node with data sources and return its ID
    pub fn create_node_full(
        &mut self,
        element: &Element,
        state: &serde_json::Value,
        data_sources: Option<&DataSources>,
    ) -> NodeId {
        self.nodes
            .insert_with_key(|id| InstanceNode::new_full(id, element, state, data_sources))
    }

    /// Create a control flow node (ForEach or Conditional) and return its ID
    pub fn create_control_flow_node(
        &mut self,
        element_type: &str,
        props: IndexMap<String, serde_json::Value>,
        raw_props: Props,
        control_flow: ControlFlowKind,
        ir_node_template: IRNode,
    ) -> NodeId {
        self.nodes.insert_with_key(|id| {
            InstanceNode::new_control_flow(
                id,
                element_type,
                props,
                raw_props,
                control_flow,
                ir_node_template,
            )
        })
    }

    /// Get a node by ID
    pub fn get(&self, id: NodeId) -> Option<&InstanceNode> {
        self.nodes.get(id)
    }

    /// Get a mutable node by ID
    pub fn get_mut(&mut self, id: NodeId) -> Option<&mut InstanceNode> {
        self.nodes.get_mut(id)
    }

    /// Remove a node and all its descendants
    pub fn remove(&mut self, id: NodeId) -> Option<InstanceNode> {
        if let Some(node) = self.nodes.get(id) {
            let children = node.children.clone();
            // Remove all children recursively
            for child_id in children {
                self.remove(child_id);
            }
        }
        self.nodes.remove(id)
    }

    /// Set the root node
    pub fn set_root(&mut self, id: NodeId) {
        self.root = Some(id);
    }

    /// Get the root node ID
    pub fn root(&self) -> Option<NodeId> {
        self.root
    }

    /// Add a child to a parent node
    pub fn add_child(&mut self, parent_id: NodeId, child_id: NodeId, before: Option<NodeId>) {
        if let Some(parent) = self.nodes.get_mut(parent_id) {
            if let Some(before_id) = before {
                if let Some(pos) = parent.children.iter().position(|&id| id == before_id) {
                    parent.children.insert(pos, child_id);
                } else {
                    parent.children.push_back(child_id);
                }
            } else {
                parent.children.push_back(child_id);
            }
        }

        if let Some(child) = self.nodes.get_mut(child_id) {
            child.parent = Some(parent_id);
        }
    }

    /// Remove a child from its parent
    pub fn remove_child(&mut self, parent_id: NodeId, child_id: NodeId) {
        if let Some(parent) = self.nodes.get_mut(parent_id) {
            parent.children = parent
                .children
                .iter()
                .filter(|&&id| id != child_id)
                .copied()
                .collect();
        }

        if let Some(child) = self.nodes.get_mut(child_id) {
            child.parent = None;
        }
    }

    /// Update all nodes that depend on changed state
    pub fn update_nodes(
        &mut self,
        node_ids: &indexmap::IndexSet<NodeId>,
        state: &serde_json::Value,
    ) {
        for &node_id in node_ids {
            if let Some(node) = self.nodes.get_mut(node_id) {
                node.update_props(state);
            }
        }
    }

    /// Return the total number of nodes in the tree.
    pub fn len(&self) -> usize {
        self.nodes.len()
    }

    /// Return whether the tree is empty.
    pub fn is_empty(&self) -> bool {
        self.nodes.is_empty()
    }

    /// Iterate over all nodes
    pub fn iter(&self) -> impl Iterator<Item = (NodeId, &InstanceNode)> {
        self.nodes.iter()
    }
}

impl Default for InstanceTree {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {

    use crate::reconcile::resolve::evaluate_binding;
    use serde_json::json;

    #[test]
    fn test_evaluate_binding() {
        use crate::reactive::Binding;

        let state = json!({
            "user": {
                "name": "Alice",
                "age": 30
            }
        });

        let name_binding = Binding::state(vec!["user".to_string(), "name".to_string()]);
        let age_binding = Binding::state(vec!["user".to_string(), "age".to_string()]);
        let email_binding = Binding::state(vec!["user".to_string(), "email".to_string()]);

        assert_eq!(
            evaluate_binding(&name_binding, &state),
            Some(json!("Alice"))
        );
        assert_eq!(evaluate_binding(&age_binding, &state), Some(json!(30)));
        assert_eq!(evaluate_binding(&email_binding, &state), None);
    }
}