//! View Dependency Analysis
//!
//! This module analyzes view functions to track which DOM nodes depend on which
//! model fields. This enables Svelte-style compile-time optimizations where
//! only the DOM nodes that depend on changed fields are updated.
//!
//! # How it works
//!
//! 1. Parse the view function body
//! 2. For each Html element/text node, track which model fields it accesses
//! 3. Build a dependency graph: Node -> Set<ModelField>
//! 4. Generate efficient update code that only updates changed nodes

use crate::ast::{Expr, Ident, Statement};
use std::collections::{HashMap, HashSet};

/// Represents a path to a model field (e.g., "model.user.name" -> ["user", "name"])
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FieldPath(pub Vec<String>);

impl FieldPath {
    pub fn new(fields: Vec<String>) -> Self {
        FieldPath(fields)
    }

    pub fn single(field: String) -> Self {
        FieldPath(vec![field])
    }

    pub fn to_js_path(&self) -> String {
        self.0.join(".")
    }
}

/// A unique identifier for a DOM node in the view tree
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct NodeId(pub usize);

/// Information about a DOM node and its dependencies
#[derive(Debug, Clone)]
pub struct NodeInfo {
    /// Unique ID for this node
    pub id: NodeId,
    /// The HTML tag (e.g., "div", "span") or "text" for text nodes
    pub tag: String,
    /// Which model fields this node's content depends on
    pub content_deps: HashSet<FieldPath>,
    /// Which model fields this node's attributes depend on
    pub attr_deps: HashMap<String, HashSet<FieldPath>>,
    /// Child node IDs
    pub children: Vec<NodeId>,
    /// Parent node ID (None for root)
    pub parent: Option<NodeId>,
}

/// The result of analyzing a view function
#[derive(Debug)]
pub struct ViewAnalysis {
    /// All nodes in the view tree
    pub nodes: HashMap<NodeId, NodeInfo>,
    /// The root node ID
    pub root: Option<NodeId>,
    /// All model fields that are accessed anywhere in the view
    pub all_deps: HashSet<FieldPath>,
    /// Counter for generating unique node IDs
    next_id: usize,
}

impl ViewAnalysis {
    pub fn new() -> Self {
        ViewAnalysis {
            nodes: HashMap::new(),
            root: None,
            all_deps: HashSet::new(),
            next_id: 0,
        }
    }

    fn next_node_id(&mut self) -> NodeId {
        let id = NodeId(self.next_id);
        self.next_id += 1;
        id
    }

    /// Analyze a view function body
    pub fn analyze(&mut self, expr: &Expr, model_param: &str) {
        if let Some(node_id) = self.analyze_expr(expr, model_param, None) {
            self.root = Some(node_id);
        }
    }

    /// Analyze an expression, returning a NodeId if it creates a DOM node
    fn analyze_expr(&mut self, expr: &Expr, model_param: &str, parent: Option<NodeId>) -> Option<NodeId> {
        match expr {
            // Function call - check if it's an Html builder
            Expr::Call { func, args, .. } => {
                self.analyze_call(func, args, model_param, parent)
            }

            // Let binding - analyze the body
            Expr::Let { value, body, .. } => {
                // Analyze value for side effects
                self.analyze_expr(value, model_param, parent.clone());
                // The result is from the body
                self.analyze_expr(body, model_param, parent)
            }

            // Block - analyze statements and return result
            Expr::Block { statements, result, .. } => {
                for stmt in statements {
                    if let Statement::Let { value, .. } = stmt {
                        self.analyze_expr(value, model_param, parent.clone());
                    }
                }
                self.analyze_expr(result, model_param, parent)
            }

            // If expression - analyze both branches
            Expr::If { condition, then_branch, else_branch, .. } => {
                let cond_deps = self.collect_field_deps(condition, model_param);
                self.all_deps.extend(cond_deps.clone());

                // For now, treat conditional nodes as depending on the condition
                // A more sophisticated analysis would track both branches
                let then_node = self.analyze_expr(then_branch, model_param, parent.clone());
                self.analyze_expr(else_branch, model_param, parent);
                then_node
            }

            // Match expression - analyze all arms
            Expr::Match { scrutinee, arms, .. } => {
                let scrutinee_deps = self.collect_field_deps(scrutinee, model_param);
                self.all_deps.extend(scrutinee_deps);

                for arm in arms {
                    self.analyze_expr(&arm.body, model_param, parent.clone());
                }
                None
            }

            // Variable reference - no DOM node created
            Expr::Var(_) => None,

            // Field access - collect dependencies but no DOM node
            Expr::Field { .. } => {
                let deps = self.collect_field_deps(expr, model_param);
                self.all_deps.extend(deps);
                None
            }

            // Literal - no DOM node
            Expr::Literal(_) => None,

            _ => None,
        }
    }

    /// Analyze a function call, checking if it's an Html builder
    fn analyze_call(
        &mut self,
        func: &Expr,
        args: &[Expr],
        model_param: &str,
        parent: Option<NodeId>,
    ) -> Option<NodeId> {
        // Check if this is an Html element constructor
        let func_name = self.get_func_name(func);

        match func_name.as_deref() {
            // Element constructors: div, span, button, etc.
            Some(name) if is_html_element(name) => {
                self.analyze_element(name, args, model_param, parent)
            }

            // Text node constructor
            Some("text") => {
                self.analyze_text_node(args, model_param, parent)
            }

            // Element constructor (generic)
            Some("Element") => {
                if args.len() >= 3 {
                    if let Expr::Literal(lit) = &args[0] {
                        if let crate::ast::LiteralKind::String(tag) = &lit.kind {
                            return self.analyze_element(tag, &args[1..], model_param, parent);
                        }
                    }
                }
                None
            }

            // Other function calls - check for dependencies in args
            _ => {
                for arg in args {
                    let deps = self.collect_field_deps(arg, model_param);
                    self.all_deps.extend(deps);
                }
                None
            }
        }
    }

    /// Analyze an HTML element (div, span, etc.)
    fn analyze_element(
        &mut self,
        tag: &str,
        args: &[Expr],
        model_param: &str,
        parent: Option<NodeId>,
    ) -> Option<NodeId> {
        let node_id = self.next_node_id();

        let mut attr_deps: HashMap<String, HashSet<FieldPath>> = HashMap::new();
        let mut children = Vec::new();

        // First arg is typically attributes list
        if let Some(attrs_expr) = args.get(0) {
            if let Expr::List { elements, .. } = attrs_expr {
                for attr in elements {
                    self.analyze_attribute(attr, model_param, &mut attr_deps);
                }
            }
        }

        // Second arg is typically children list
        if let Some(children_expr) = args.get(1) {
            if let Expr::List { elements, .. } = children_expr {
                for child in elements {
                    if let Some(child_id) = self.analyze_expr(child, model_param, Some(node_id.clone())) {
                        children.push(child_id);
                    }
                }
            }
        }

        let node = NodeInfo {
            id: node_id.clone(),
            tag: tag.to_string(),
            content_deps: HashSet::new(),
            attr_deps,
            children,
            parent,
        };

        self.nodes.insert(node_id.clone(), node);
        Some(node_id)
    }

    /// Analyze a text node
    fn analyze_text_node(
        &mut self,
        args: &[Expr],
        model_param: &str,
        parent: Option<NodeId>,
    ) -> Option<NodeId> {
        let node_id = self.next_node_id();

        let mut content_deps = HashSet::new();
        if let Some(content_expr) = args.get(0) {
            content_deps = self.collect_field_deps(content_expr, model_param);
            self.all_deps.extend(content_deps.clone());
        }

        let node = NodeInfo {
            id: node_id.clone(),
            tag: "text".to_string(),
            content_deps,
            attr_deps: HashMap::new(),
            children: Vec::new(),
            parent,
        };

        self.nodes.insert(node_id.clone(), node);
        Some(node_id)
    }

    /// Analyze an attribute expression
    fn analyze_attribute(
        &mut self,
        attr: &Expr,
        model_param: &str,
        attr_deps: &mut HashMap<String, HashSet<FieldPath>>,
    ) {
        // Attributes are typically constructor calls like Class("foo") or OnClick(msg)
        if let Expr::Call { func, args, .. } = attr {
            if let Some(attr_name) = self.get_func_name(func) {
                let deps = args.iter()
                    .flat_map(|arg| self.collect_field_deps(arg, model_param))
                    .collect::<HashSet<_>>();

                if !deps.is_empty() {
                    attr_deps.insert(attr_name, deps.clone());
                    self.all_deps.extend(deps);
                }
            }
        }
    }

    /// Collect all model field accesses in an expression
    fn collect_field_deps(&self, expr: &Expr, model_param: &str) -> HashSet<FieldPath> {
        let mut deps = HashSet::new();
        self.collect_field_deps_recursive(expr, model_param, &mut deps, &mut Vec::new());
        deps
    }

    fn collect_field_deps_recursive(
        &self,
        expr: &Expr,
        model_param: &str,
        deps: &mut HashSet<FieldPath>,
        current_path: &mut Vec<String>,
    ) {
        match expr {
            Expr::Field { object, field, .. } => {
                current_path.push(field.name.clone());
                self.collect_field_deps_recursive(object, model_param, deps, current_path);
                current_path.pop();
            }

            Expr::Var(ident) if ident.name == model_param => {
                // Found model.field1.field2...
                if !current_path.is_empty() {
                    let mut path = current_path.clone();
                    path.reverse();
                    deps.insert(FieldPath::new(path));
                }
            }

            Expr::Call { func, args, .. } => {
                self.collect_field_deps_recursive(func, model_param, deps, &mut Vec::new());
                for arg in args {
                    self.collect_field_deps_recursive(arg, model_param, deps, &mut Vec::new());
                }
            }

            Expr::BinaryOp { left, right, .. } => {
                self.collect_field_deps_recursive(left, model_param, deps, &mut Vec::new());
                self.collect_field_deps_recursive(right, model_param, deps, &mut Vec::new());
            }

            Expr::UnaryOp { operand, .. } => {
                self.collect_field_deps_recursive(operand, model_param, deps, &mut Vec::new());
            }

            Expr::If { condition, then_branch, else_branch, .. } => {
                self.collect_field_deps_recursive(condition, model_param, deps, &mut Vec::new());
                self.collect_field_deps_recursive(then_branch, model_param, deps, &mut Vec::new());
                self.collect_field_deps_recursive(else_branch, model_param, deps, &mut Vec::new());
            }

            Expr::Let { value, body, .. } => {
                self.collect_field_deps_recursive(value, model_param, deps, &mut Vec::new());
                self.collect_field_deps_recursive(body, model_param, deps, &mut Vec::new());
            }

            Expr::Block { statements, result, .. } => {
                for stmt in statements {
                    if let Statement::Let { value, .. } = stmt {
                        self.collect_field_deps_recursive(value, model_param, deps, &mut Vec::new());
                    }
                }
                self.collect_field_deps_recursive(result, model_param, deps, &mut Vec::new());
            }

            Expr::Match { scrutinee, arms, .. } => {
                self.collect_field_deps_recursive(scrutinee, model_param, deps, &mut Vec::new());
                for arm in arms {
                    self.collect_field_deps_recursive(&arm.body, model_param, deps, &mut Vec::new());
                }
            }

            Expr::List { elements, .. } => {
                for elem in elements {
                    self.collect_field_deps_recursive(elem, model_param, deps, &mut Vec::new());
                }
            }

            Expr::Lambda { body, .. } => {
                self.collect_field_deps_recursive(body, model_param, deps, &mut Vec::new());
            }

            _ => {}
        }
    }

    /// Get the name of a function being called
    fn get_func_name(&self, expr: &Expr) -> Option<String> {
        match expr {
            Expr::Var(ident) => Some(ident.name.clone()),
            Expr::Field { object, field, .. } => {
                // For qualified calls like Html.div
                if let Expr::Var(module) = object.as_ref() {
                    Some(format!("{}.{}", module.name, field.name))
                } else {
                    Some(field.name.clone())
                }
            }
            _ => None,
        }
    }
}

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

/// Check if a function name is an HTML element constructor
fn is_html_element(name: &str) -> bool {
    // Strip module prefix if present
    let name = name.strip_prefix("Html.").unwrap_or(name);

    matches!(
        name,
        "div" | "span" | "p" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6"
            | "button" | "input" | "form" | "label" | "textarea" | "select" | "option"
            | "a" | "img" | "ul" | "ol" | "li" | "table" | "tr" | "td" | "th"
            | "thead" | "tbody" | "header" | "footer" | "nav" | "main" | "aside"
            | "section" | "article" | "pre" | "code" | "blockquote"
            | "strong" | "em" | "small" | "br" | "hr"
    )
}

/// Generate JavaScript code for efficient view updates
pub struct ViewCodeGen {
    analysis: ViewAnalysis,
}

impl ViewCodeGen {
    pub fn new(analysis: ViewAnalysis) -> Self {
        ViewCodeGen { analysis }
    }

    /// Generate the view_create function
    pub fn generate_create(&self) -> String {
        let mut code = String::new();
        code.push_str("function view_create(model) {\n");

        if let Some(root_id) = &self.analysis.root {
            self.generate_create_node(&mut code, root_id, "  ");
            code.push_str("  return { root: node_0");

            // Include references to all nodes that have dependencies
            for (id, node) in &self.analysis.nodes {
                if !node.content_deps.is_empty() || !node.attr_deps.is_empty() {
                    code.push_str(&format!(", node_{}: node_{}", id.0, id.0));
                }
            }

            code.push_str(" };\n");
        }

        code.push_str("}\n");
        code
    }

    fn generate_create_node(&self, code: &mut String, node_id: &NodeId, indent: &str) {
        if let Some(node) = self.analysis.nodes.get(node_id) {
            let var_name = format!("node_{}", node_id.0);

            if node.tag == "text" {
                // Text node
                code.push_str(&format!(
                    "{}const {} = document.createTextNode(\"\");\n",
                    indent, var_name
                ));
            } else {
                // Element node
                code.push_str(&format!(
                    "{}const {} = document.createElement(\"{}\");\n",
                    indent, var_name, node.tag
                ));
            }

            // Create children
            for child_id in &node.children {
                self.generate_create_node(code, child_id, indent);
                code.push_str(&format!(
                    "{}{}.appendChild(node_{});\n",
                    indent, var_name, child_id.0
                ));
            }
        }
    }

    /// Generate the view_update function
    pub fn generate_update(&self) -> String {
        let mut code = String::new();
        code.push_str("function view_update(nodes, oldModel, newModel) {\n");

        // Generate update code for each node that has dependencies
        for (id, node) in &self.analysis.nodes {
            // Check content dependencies
            if !node.content_deps.is_empty() {
                let conditions: Vec<String> = node.content_deps.iter()
                    .map(|dep| format!("oldModel.{} !== newModel.{}", dep.to_js_path(), dep.to_js_path()))
                    .collect();

                code.push_str(&format!(
                    "  if ({}) {{\n",
                    conditions.join(" || ")
                ));

                if node.tag == "text" {
                    code.push_str(&format!(
                        "    nodes.node_{}.textContent = /* render content */;\n",
                        id.0
                    ));
                }

                code.push_str("  }\n");
            }

            // Check attribute dependencies
            for (attr, deps) in &node.attr_deps {
                let conditions: Vec<String> = deps.iter()
                    .map(|dep| format!("oldModel.{} !== newModel.{}", dep.to_js_path(), dep.to_js_path()))
                    .collect();

                code.push_str(&format!(
                    "  if ({}) {{\n",
                    conditions.join(" || ")
                ));
                code.push_str(&format!(
                    "    // Update {} attribute on node_{}\n",
                    attr, id.0
                ));
                code.push_str("  }\n");
            }
        }

        code.push_str("}\n");
        code
    }
}

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

    #[test]
    fn test_field_path() {
        let path = FieldPath::new(vec!["user".to_string(), "name".to_string()]);
        assert_eq!(path.to_js_path(), "user.name");
    }

    #[test]
    fn test_is_html_element() {
        assert!(is_html_element("div"));
        assert!(is_html_element("Html.div"));
        assert!(is_html_element("button"));
        assert!(!is_html_element("foo"));
        assert!(!is_html_element("toString"));
    }
}
