//! Shared prop resolution and binding evaluation.
//!
//! These functions are used by both the initial tree builder (`tree.rs`),
//! the reconciler/differ (`diff.rs`), and the dirty-node renderer (`render.rs`).
//! Keeping a single copy avoids the subtle divergence bugs that come from
//! copy-pasting the same path-walking logic in three places.

use crate::ir::Value;
use crate::reactive::Binding;
use indexmap::IndexMap;

/// Navigate a binding path against a root JSON value.
/// If the path is empty, returns the root value itself (handles bare `@item`).
pub fn evaluate_binding_path(
    binding: &Binding,
    root: &serde_json::Value,
) -> Option<serde_json::Value> {
    if binding.path.is_empty() {
        return Some(root.clone());
    }

    let mut current = root;
    for segment in &binding.path {
        current = current.get(segment)?;
    }
    Some(current.clone())
}

/// Evaluate a state binding (delegates to [`evaluate_binding_path`]).
pub fn evaluate_binding(binding: &Binding, state: &serde_json::Value) -> Option<serde_json::Value> {
    evaluate_binding_path(binding, state)
}

/// Evaluate an item binding against the item object (delegates to [`evaluate_binding_path`]).
pub fn evaluate_item_binding(
    binding: &Binding,
    item: &serde_json::Value,
) -> Option<serde_json::Value> {
    evaluate_binding_path(binding, item)
}

/// Resolve props by evaluating bindings against state (no item context).
pub fn resolve_props(
    props: &IndexMap<String, Value>,
    state: &serde_json::Value,
) -> IndexMap<String, serde_json::Value> {
    resolve_props_with_item(props, state, None)
}

/// Resolve props with optional item context (for list iteration).
pub fn resolve_props_with_item(
    props: &IndexMap<String, Value>,
    state: &serde_json::Value,
    item: Option<&serde_json::Value>,
) -> IndexMap<String, serde_json::Value> {
    let mut resolved = IndexMap::new();

    for (key, value) in props {
        let resolved_value = match value {
            Value::Static(v) => v.clone(),
            Value::Binding(binding) => {
                if binding.is_item() {
                    // Evaluate item binding
                    if let Some(item_value) = item {
                        evaluate_item_binding(binding, item_value)
                            .unwrap_or(serde_json::Value::Null)
                    } else {
                        serde_json::Value::Null
                    }
                } else {
                    // Evaluate state binding
                    evaluate_binding(binding, state).unwrap_or(serde_json::Value::Null)
                }
            }
            Value::TemplateString { template, .. } => {
                // Use expression evaluation for template strings - handles both
                // simple bindings and complex expressions like ternaries
                match crate::reactive::evaluate_template_string(template, state, item) {
                    Ok(result) => serde_json::Value::String(result),
                    Err(_) => serde_json::Value::String(template.clone()),
                }
            }
            Value::Action(action) => {
                // Actions are serialized with @ prefix for renderer to detect
                serde_json::Value::String(format!("@{}", action))
            }
        };
        resolved.insert(key.clone(), resolved_value);
    }

    resolved
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn test_evaluate_binding_simple() {
        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 missing_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(&missing_binding, &state), None);
    }

    #[test]
    fn test_evaluate_binding_path_empty() {
        let root = json!({"hello": "world"});
        let binding = Binding::state(vec![]);
        assert_eq!(evaluate_binding_path(&binding, &root), Some(root.clone()));
    }

    #[test]
    fn test_evaluate_item_binding_bare() {
        let item = json!("just a string");
        let binding = Binding::item(vec![]);
        assert_eq!(evaluate_item_binding(&binding, &item), Some(item.clone()));
    }

    #[test]
    fn test_evaluate_item_binding_nested() {
        let item = json!({"name": "Bob", "address": {"city": "NYC"}});
        let binding = Binding::item(vec!["address".to_string(), "city".to_string()]);
        assert_eq!(evaluate_item_binding(&binding, &item), Some(json!("NYC")));
    }

    #[test]
    fn test_resolve_props_static() {
        let mut props = IndexMap::new();
        props.insert("text".to_string(), Value::Static(json!("Hello")));
        let state = json!({});
        let resolved = resolve_props(&props, &state);
        assert_eq!(resolved.get("text"), Some(&json!("Hello")));
    }

    #[test]
    fn test_resolve_props_binding() {
        let mut props = IndexMap::new();
        props.insert(
            "text".to_string(),
            Value::Binding(Binding::state(vec!["name".to_string()])),
        );
        let state = json!({"name": "Alice"});
        let resolved = resolve_props(&props, &state);
        assert_eq!(resolved.get("text"), Some(&json!("Alice")));
    }

    #[test]
    fn test_resolve_props_action() {
        let mut props = IndexMap::new();
        props.insert("onClick".to_string(), Value::Action("submit".to_string()));
        let state = json!({});
        let resolved = resolve_props(&props, &state);
        assert_eq!(resolved.get("onClick"), Some(&json!("@submit")));
    }
}
