/// Node Graph Interpreter
///
/// Executes node graphs directly without compilation.
///
/// Uses a stack-based execution model to support loops, function calls, and control flow.

use super::nodes::{Node, NodeCategory, NodeGraph, NodeLibrary, NodeParams, NodeType, Pin, PinType, MathOp};
use super::node_registry;
use super::host_runtime::{HostContext, RuntimeValue, ComponentContext};
use std::collections::{HashMap, HashSet};
use std::io::Write; // For file operations

use super::node_registry::{NodeSpec, ParamKind};

// ==================== Stack-Based Execution Architecture ====================

/// Represents different types of execution frames on the stack
#[derive(Debug, Clone)]
enum ExecutionFrame {
    /// Linear execution - follow connections normally
    Linear {
        current_pin: usize,
        from_node: usize,
    },

    /// Loop frame - iterate N times
    Loop {
        node_id: usize,
        iteration: usize,
        max_iterations: usize,
        loop_body_start_pin: usize,
        completed_pin: usize,
        index_output_pin: Option<usize>,
    },

    /// While loop frame - check condition each iteration
    WhileLoop {
        node_id: usize,
        condition_node_input_pin: usize,
        loop_body_start_pin: usize,
        completed_pin: usize,
    },

    /// ForEach array loop frame
    ForEachArray {
        node_id: usize,
        array: Vec<RuntimeValue>,
        iteration: usize,
        loop_body_start_pin: usize,
        completed_pin: usize,
        element_output_pin: usize,
        index_output_pin: usize,
    },

    /// Function call frame
    FunctionCall {
        function_id: usize,
        return_address_pin: Option<usize>,
        entry_node_id: usize,
    },

    /// Try-catch frame (for Phase 7)
    TryCatch {
        try_body_pin: usize,
        catch_body_pin: usize,
        finally_pin: Option<usize>,
    },
}

fn default_params_none() -> NodeParams {
    NodeParams::None
}

fn default_params_string_value() -> NodeParams {
    NodeParams::StringValue(String::new())
}

fn default_params_component_name() -> NodeParams {
    NodeParams::ComponentName(String::new())
}

fn pins_for_loop(next_pin_id: &mut usize) -> Vec<Pin> {
    let mut pins = Vec::new();
    pins.push(Pin::new(*next_pin_id, PinType::Exec, true, ""));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Exec, false, "body"));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Exec, false, "completed"));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Float, true, "count"));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Float, false, "index"));
    *next_pin_id += 1;
    pins
}

fn exec_for_loop(graph: &NodeGraph, ctx: &mut dyn HostContext, node: &Node, stack: &mut ExecutionStack) -> Result<Option<usize>, String> {
    let count_value = eval_input(graph, ctx, node.id, "count")?;
    let max_iterations = count_value.as_float() as usize;

    let loop_body_pin = node
        .pins
        .iter()
        .find(|p| !p.is_input && p.pin_type == PinType::Exec && p.name == "body")
        .ok_or("ForLoop missing 'body' exec output pin")?;
    let completed_pin = node
        .pins
        .iter()
        .find(|p| !p.is_input && p.pin_type == PinType::Exec && p.name == "completed")
        .ok_or("ForLoop missing 'completed' exec output pin")?;
    let index_pin = node
        .pins
        .iter()
        .find(|p| !p.is_input && p.pin_type == PinType::Float && p.name == "index");

    stack.push(ExecutionFrame::Loop {
        node_id: node.id,
        iteration: 0,
        max_iterations,
        loop_body_start_pin: loop_body_pin.id,
        completed_pin: completed_pin.id,
        index_output_pin: index_pin.map(|p| p.id),
    });

    Ok(None)
}

fn pins_for_each_array(next_pin_id: &mut usize) -> Vec<Pin> {
    let mut pins = Vec::new();
    pins.push(Pin::new(*next_pin_id, PinType::Exec, true, ""));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Exec, false, "body"));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Exec, false, "completed"));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Array, true, "array"));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Any, false, "element"));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Float, false, "index"));
    *next_pin_id += 1;
    pins
}

fn exec_for_each_array(graph: &NodeGraph, ctx: &mut dyn HostContext, node: &Node, stack: &mut ExecutionStack) -> Result<Option<usize>, String> {
    let array_value = eval_input(graph, ctx, node.id, "array")?;
    let array = array_value.as_array();

    let loop_body_pin = node
        .pins
        .iter()
        .find(|p| !p.is_input && p.pin_type == PinType::Exec && p.name == "body")
        .ok_or("ForEachArray missing 'body' exec output pin")?;
    let completed_pin = node
        .pins
        .iter()
        .find(|p| !p.is_input && p.pin_type == PinType::Exec && p.name == "completed")
        .ok_or("ForEachArray missing 'completed' exec output pin")?;
    let element_pin = node
        .pins
        .iter()
        .find(|p| !p.is_input && p.pin_type == PinType::Any && p.name == "element")
        .ok_or("ForEachArray missing 'element' output pin")?;
    let index_pin = node
        .pins
        .iter()
        .find(|p| !p.is_input && p.pin_type == PinType::Float && p.name == "index")
        .ok_or("ForEachArray missing 'index' output pin")?;

    stack.push(ExecutionFrame::ForEachArray {
        node_id: node.id,
        array,
        iteration: 0,
        loop_body_start_pin: loop_body_pin.id,
        completed_pin: completed_pin.id,
        element_output_pin: element_pin.id,
        index_output_pin: index_pin.id,
    });

    Ok(None)
}

fn pins_for_each_row(next_pin_id: &mut usize) -> Vec<Pin> {
    let mut pins = Vec::new();
    pins.push(Pin::new(*next_pin_id, PinType::Exec, true, ""));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Exec, false, "body"));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Exec, false, "completed"));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Rows, true, "rows"));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Row, false, "row"));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Float, false, "index"));
    *next_pin_id += 1;
    pins
}

fn exec_for_each_row(graph: &NodeGraph, ctx: &mut dyn HostContext, node: &Node, stack: &mut ExecutionStack) -> Result<Option<usize>, String> {
    let rows_value = eval_input(graph, ctx, node.id, "rows")?;
    let array = rows_value.as_array();

    let loop_body_pin = node
        .pins
        .iter()
        .find(|p| !p.is_input && p.pin_type == PinType::Exec && p.name == "body")
        .ok_or("ForEachRow missing 'body' exec output pin")?;
    let completed_pin = node
        .pins
        .iter()
        .find(|p| !p.is_input && p.pin_type == PinType::Exec && p.name == "completed")
        .ok_or("ForEachRow missing 'completed' exec output pin")?;
    let row_pin = node
        .pins
        .iter()
        .find(|p| !p.is_input && p.pin_type == PinType::Row && p.name == "row")
        .ok_or("ForEachRow missing 'row' output pin")?;
    let index_pin = node
        .pins
        .iter()
        .find(|p| !p.is_input && p.pin_type == PinType::Float && p.name == "index")
        .ok_or("ForEachRow missing 'index' output pin")?;

    stack.push(ExecutionFrame::ForEachArray {
        node_id: node.id,
        array,
        iteration: 0,
        loop_body_start_pin: loop_body_pin.id,
        completed_pin: completed_pin.id,
        element_output_pin: row_pin.id,
        index_output_pin: index_pin.id,
    });

    Ok(None)
}

fn pins_while_loop(next_pin_id: &mut usize) -> Vec<Pin> {
    let mut pins = Vec::new();
    pins.push(Pin::new(*next_pin_id, PinType::Exec, true, ""));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Exec, false, "body"));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Exec, false, "completed"));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Bool, true, "condition"));
    *next_pin_id += 1;
    pins
}

fn exec_while_loop(_graph: &NodeGraph, _ctx: &mut dyn HostContext, node: &Node, stack: &mut ExecutionStack) -> Result<Option<usize>, String> {
    let loop_body_pin = node
        .pins
        .iter()
        .find(|p| !p.is_input && p.pin_type == PinType::Exec && p.name == "body")
        .ok_or("WhileLoop missing 'body' exec output pin")?;
    let completed_pin = node
        .pins
        .iter()
        .find(|p| !p.is_input && p.pin_type == PinType::Exec && p.name == "completed")
        .ok_or("WhileLoop missing 'completed' exec output pin")?;
    let condition_pin = node
        .pins
        .iter()
        .find(|p| p.is_input && p.pin_type == PinType::Bool && p.name == "condition")
        .ok_or("WhileLoop missing 'condition' input pin")?;

    stack.push(ExecutionFrame::WhileLoop {
        node_id: node.id,
        condition_node_input_pin: condition_pin.id,
        loop_body_start_pin: loop_body_pin.id,
        completed_pin: completed_pin.id,
    });

    Ok(None)
}

fn pins_break(next_pin_id: &mut usize) -> Vec<Pin> {
    let mut pins = Vec::new();
    pins.push(Pin::new(*next_pin_id, PinType::Exec, true, ""));
    *next_pin_id += 1;
    pins
}

fn exec_break(_graph: &NodeGraph, _ctx: &mut dyn HostContext, _node: &Node, stack: &mut ExecutionStack) -> Result<Option<usize>, String> {
    let loop_frame_index = stack.find_loop_frame_index().ok_or("Break called outside of a loop")?;

    let (loop_node_id, completed_pin) = match &stack.frames[loop_frame_index] {
        ExecutionFrame::Loop { node_id, completed_pin, .. } => (*node_id, *completed_pin),
        ExecutionFrame::WhileLoop { node_id, completed_pin, .. } => (*node_id, *completed_pin),
        ExecutionFrame::ForEachArray { node_id, completed_pin, .. } => (*node_id, *completed_pin),
        _ => return Err("Break found invalid frame type".to_string()),
    };

    stack.truncate_frames(loop_frame_index);
    stack.push(ExecutionFrame::Linear {
        current_pin: completed_pin,
        from_node: loop_node_id,
    });

    Ok(None)
}

fn pins_continue(next_pin_id: &mut usize) -> Vec<Pin> {
    let mut pins = Vec::new();
    pins.push(Pin::new(*next_pin_id, PinType::Exec, true, ""));
    *next_pin_id += 1;
    pins
}

fn exec_continue(_graph: &NodeGraph, _ctx: &mut dyn HostContext, _node: &Node, stack: &mut ExecutionStack) -> Result<Option<usize>, String> {
    let loop_frame_index = stack
        .find_loop_frame_index()
        .ok_or("Continue called outside of a loop")?;

    stack.truncate_frames(loop_frame_index + 1);
    Ok(None)
}

fn pins_return(next_pin_id: &mut usize) -> Vec<Pin> {
    let mut pins = Vec::new();
    pins.push(Pin::new(*next_pin_id, PinType::Exec, true, ""));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Any, true, "value"));
    *next_pin_id += 1;
    pins
}

fn exec_return(graph: &NodeGraph, ctx: &mut dyn HostContext, node: &Node, stack: &mut ExecutionStack) -> Result<Option<usize>, String> {
    let return_value = if is_input_connected(graph, node.id, "value") {
        Some(eval_input(graph, ctx, node.id, "value")?)
    } else {
        None
    };

    if let Some(func_frame_index) = stack.find_function_frame_index() {
        if return_value.is_some() {
            stack.truncate_frames(func_frame_index);
        }
    } else {
        stack.clear_frames();
    }

    Ok(None)
}

fn pins_switch(next_pin_id: &mut usize) -> Vec<Pin> {
    let mut pins = Vec::new();
    pins.push(Pin::new(*next_pin_id, PinType::Exec, true, ""));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Exec, false, "case 1"));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Exec, false, "case 2"));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Exec, false, "case 3"));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Exec, false, "default"));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Any, true, "value"));
    *next_pin_id += 1;
    pins
}

fn exec_switch(graph: &NodeGraph, ctx: &mut dyn HostContext, node: &Node, _stack: &mut ExecutionStack) -> Result<Option<usize>, String> {
    let switch_value = eval_input(graph, ctx, node.id, "value")?;
    let case_num = match switch_value {
        RuntimeValue::Float(f) => f as i32,
        RuntimeValue::String(ref s) => s.parse::<i32>().unwrap_or(0),
        _ => 0,
    };

    let case_pin_name = match case_num {
        1 => "case 1",
        2 => "case 2",
        3 => "case 3",
        _ => "default",
    };

    Ok(node
        .pins
        .iter()
        .find(|p| !p.is_input && p.pin_type == PinType::Exec && p.name == case_pin_name)
        .map(|p| p.id))
}

fn pins_open_modal(next_pin_id: &mut usize) -> Vec<Pin> {
    let mut pins = Vec::new();
    pins.push(Pin::new(*next_pin_id, PinType::Exec, true, ""));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Exec, false, ""));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::String, true, "form_name"));
    *next_pin_id += 1;
    pins
}

fn exec_open_modal(graph: &NodeGraph, ctx: &mut dyn HostContext, node: &Node, _stack: &mut ExecutionStack) -> Result<Option<usize>, String> {
    let form_name = match eval_input_string(graph, ctx, node.id, "form_name") {
        Ok(s) if !s.is_empty() => s,
        _ => match &node.params {
            NodeParams::StringValue(s) => s.clone(),
            _ => String::new(),
        },
    };

    if !form_name.is_empty() {
        ctx.open_modal(&form_name);
    }

    get_default_exec_out(node)
}

fn pins_close_modal(next_pin_id: &mut usize) -> Vec<Pin> {
    pins_open_modal(next_pin_id)
}

fn exec_close_modal(graph: &NodeGraph, ctx: &mut dyn HostContext, node: &Node, _stack: &mut ExecutionStack) -> Result<Option<usize>, String> {
    let form_name = match eval_input_string(graph, ctx, node.id, "form_name") {
        Ok(s) if !s.is_empty() => s,
        _ => match &node.params {
            NodeParams::StringValue(s) => s.clone(),
            _ => String::new(),
        },
    };

    if !form_name.is_empty() {
        ctx.close_modal(&form_name);
    }

    get_default_exec_out(node)
}

fn pins_set_table_data(next_pin_id: &mut usize) -> Vec<Pin> {
    let mut pins = Vec::new();
    pins.push(Pin::new(*next_pin_id, PinType::Exec, true, ""));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Exec, false, ""));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::String, true, "component"));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Any, true, "data"));
    *next_pin_id += 1;
    pins
}

fn exec_set_table_data(graph: &NodeGraph, ctx: &mut dyn HostContext, node: &Node, _stack: &mut ExecutionStack) -> Result<Option<usize>, String> {
    let component = match &node.params {
        NodeParams::ComponentName(s) if !s.is_empty() => s.clone(),
        _ => eval_input_string(graph, ctx, node.id, "component").unwrap_or_default(),
    };

    let data_value = eval_input(graph, ctx, node.id, "data")?;

    match data_value {
        RuntimeValue::Array(_) => {
            ctx.set_table_data_from_array(&component, &data_value);
        }
        RuntimeValue::String(s) if !s.is_empty() => {
            if let Err(e) = ctx.set_table_data_from_json(&component, &s) {
                ctx.log(&format!("SetTableData warning: {}", e));
            }
        }
        RuntimeValue::String(_) => {
            ctx.log("SetTableData: No data connected to 'data' input");
        }
        _ => {
            ctx.log(&format!(
                "SetTableData: Unsupported data type, expected Array or JSON string"
            ));
        }
    }

    get_default_exec_out(node)
}

fn pins_clear_table_data(next_pin_id: &mut usize) -> Vec<Pin> {
    let mut pins = Vec::new();
    pins.push(Pin::new(*next_pin_id, PinType::Exec, true, ""));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Exec, false, ""));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::String, true, "component"));
    *next_pin_id += 1;
    pins
}

fn exec_clear_table_data(graph: &NodeGraph, ctx: &mut dyn HostContext, node: &Node, _stack: &mut ExecutionStack) -> Result<Option<usize>, String> {
    let component = match &node.params {
        NodeParams::ComponentName(s) if !s.is_empty() => s.clone(),
        _ => eval_input_string(graph, ctx, node.id, "component").unwrap_or_default(),
    };
    if !component.is_empty() {
        ctx.clear_table_data(&component);
    }
    get_default_exec_out(node)
}

crate::register_node!(NodeSpec {
    id: "ForLoop",
    display_name: "For Loop",
    documentation: "Loops a specific number of times.",
    category: NodeCategory::ControlFlow,
    library: NodeLibrary::Core,
    param_kind: ParamKind::None,
    default_params: default_params_none,
    pins: pins_for_loop,
    execute: Some(exec_for_loop),
    evaluate: None,
});

crate::register_node!(NodeSpec {
    id: "ForEachArray",
    display_name: "For Each Array",
    documentation: "Iterates through each element of an array.",
    category: NodeCategory::ControlFlow,
    library: NodeLibrary::Core,
    param_kind: ParamKind::None,
    default_params: default_params_none,
    pins: pins_for_each_array,
    execute: Some(exec_for_each_array),
    evaluate: None,
});

crate::register_node!(NodeSpec {
    id: "ForEachRow",
    display_name: "For Each Row",
    documentation: "Iterates through each row of a query result.",
    category: NodeCategory::ControlFlow,
    library: NodeLibrary::Core,
    param_kind: ParamKind::None,
    default_params: default_params_none,
    pins: pins_for_each_row,
    execute: Some(exec_for_each_row),
    evaluate: None,
});

crate::register_node!(NodeSpec {
    id: "WhileLoop",
    display_name: "While Loop",
    documentation: "Loops while a condition is true.",
    category: NodeCategory::ControlFlow,
    library: NodeLibrary::Core,
    param_kind: ParamKind::None,
    default_params: default_params_none,
    pins: pins_while_loop,
    execute: Some(exec_while_loop),
    evaluate: None,
});

crate::register_node!(NodeSpec {
    id: "Break",
    display_name: "Break",
    documentation: "Exits the innermost loop.",
    category: NodeCategory::ControlFlow,
    library: NodeLibrary::Core,
    param_kind: ParamKind::None,
    default_params: default_params_none,
    pins: pins_break,
    execute: Some(exec_break),
    evaluate: None,
});

crate::register_node!(NodeSpec {
    id: "Continue",
    display_name: "Continue",
    documentation: "Skips to the next iteration of the innermost loop.",
    category: NodeCategory::ControlFlow,
    library: NodeLibrary::Core,
    param_kind: ParamKind::None,
    default_params: default_params_none,
    pins: pins_continue,
    execute: Some(exec_continue),
    evaluate: None,
});

crate::register_node!(NodeSpec {
    id: "Return",
    display_name: "Return",
    documentation: "Returns from the current handler.",
    category: NodeCategory::ControlFlow,
    library: NodeLibrary::Core,
    param_kind: ParamKind::None,
    default_params: default_params_none,
    pins: pins_return,
    execute: Some(exec_return),
    evaluate: None,
});

crate::register_node!(NodeSpec {
    id: "Switch",
    display_name: "Switch",
    documentation: "Executes a case branch based on a numeric/string value.",
    category: NodeCategory::ControlFlow,
    library: NodeLibrary::Core,
    param_kind: ParamKind::None,
    default_params: default_params_none,
    pins: pins_switch,
    execute: Some(exec_switch),
    evaluate: None,
});

crate::register_node!(NodeSpec {
    id: "OpenModal",
    display_name: "Open Modal",
    documentation: "Opens a modal form by name.",
    category: NodeCategory::Action,
    library: NodeLibrary::Core,
    param_kind: ParamKind::StringLiteral,
    default_params: default_params_string_value,
    pins: pins_open_modal,
    execute: Some(exec_open_modal),
    evaluate: None,
});

crate::register_node!(NodeSpec {
    id: "CloseModal",
    display_name: "Close Modal",
    documentation: "Closes a modal form by name.",
    category: NodeCategory::Action,
    library: NodeLibrary::Core,
    param_kind: ParamKind::StringLiteral,
    default_params: default_params_string_value,
    pins: pins_close_modal,
    execute: Some(exec_close_modal),
    evaluate: None,
});

crate::register_node!(NodeSpec {
    id: "SetTableData",
    display_name: "Set Table Data",
    documentation: "Sets table data for a component from an array or JSON string.",
    category: NodeCategory::Action,
    library: NodeLibrary::Core,
    param_kind: ParamKind::ComponentSelector,
    default_params: default_params_component_name,
    pins: pins_set_table_data,
    execute: Some(exec_set_table_data),
    evaluate: None,
});

crate::register_node!(NodeSpec {
    id: "ClearTableData",
    display_name: "Clear Table Data",
    documentation: "Clears table data for a component.",
    category: NodeCategory::Action,
    library: NodeLibrary::Core,
    param_kind: ParamKind::ComponentSelector,
    default_params: default_params_component_name,
    pins: pins_clear_table_data,
    execute: Some(exec_clear_table_data),
    evaluate: None,
});

/// Execution stack managing control flow
pub struct ExecutionStack {
    frames: Vec<ExecutionFrame>,
    visited: HashSet<(usize, usize)>, // (node_id, pin_id) to detect infinite loops
    step_count: usize,
    max_steps: usize,
}

impl ExecutionStack {
    pub fn new() -> Self {
        Self {
            frames: Vec::new(),
            visited: HashSet::new(),
            step_count: 0,
            max_steps: 100000, // Prevent infinite loops
        }
    }

    fn push(&mut self, frame: ExecutionFrame) {
        self.frames.push(frame);
    }

    fn truncate_frames(&mut self, len: usize) {
        self.frames.truncate(len);
    }

    fn clear_frames(&mut self) {
        self.frames.clear();
    }

    fn pop(&mut self) -> Option<ExecutionFrame> {
        self.frames.pop()
    }

    fn current(&self) -> Option<&ExecutionFrame> {
        self.frames.last()
    }

    fn current_mut(&mut self) -> Option<&mut ExecutionFrame> {
        self.frames.last_mut()
    }

    fn is_empty(&self) -> bool {
        self.frames.is_empty()
    }

    fn increment_step(&mut self) -> Result<(), String> {
        self.step_count += 1;
        if self.step_count > self.max_steps {
            Err(format!("Execution exceeded maximum step count ({}). Possible infinite loop.", self.max_steps))
        } else {
            Ok(())
        }
    }

    fn mark_visited(&mut self, node_id: usize, pin_id: usize) {
        self.visited.insert((node_id, pin_id));
    }

    /// Find the innermost loop frame (for break/continue)
    fn find_loop_frame_index(&self) -> Option<usize> {
        self.frames.iter().rposition(|frame| {
            matches!(frame, ExecutionFrame::Loop { .. }
                   | ExecutionFrame::WhileLoop { .. }
                   | ExecutionFrame::ForEachArray { .. })
        })
    }

    /// Find the innermost function frame (for return)
    fn find_function_frame_index(&self) -> Option<usize> {
        self.frames.iter().rposition(|frame| {
            matches!(frame, ExecutionFrame::FunctionCall { .. })
        })
    }
}

// ==================== Stack-Based Execution Engine ====================

pub fn execute_graph(graph: &NodeGraph, ctx: &mut dyn HostContext) -> Result<(), String> {
    if graph.nodes.is_empty() {
        return Ok(());
    }

    // Clear temp values from previous run
    ctx.clear_temp_values();

    // Find the EventStart node
    let start_node = graph.nodes.iter()
        .find(|n| n.node_type.as_str() == "EventStart")
        .ok_or_else(|| "No EventStart node found".to_string())?;

    // Find the exec output pin
    if let Some(exec_pin) = start_node.pins.iter().find(|p| !p.is_input && p.pin_type == PinType::Exec) {
        // Start execution with stack-based model
        execute_with_stack(graph, ctx, start_node.id, exec_pin.id)
    } else {
        Ok(())
    }
}

/// New stack-based execution engine
fn execute_with_stack(
    graph: &NodeGraph,
    ctx: &mut dyn HostContext,
    start_node: usize,
    start_pin: usize,
) -> Result<(), String> {
    let mut stack = ExecutionStack::new();

    // Push initial linear execution frame
    stack.push(ExecutionFrame::Linear {
        current_pin: start_pin,
        from_node: start_node,
    });

    // Main execution loop - iterate while we have frames to process
    while !stack.is_empty() {
        stack.increment_step()?;

        let current_frame = match stack.current() {
            Some(frame) => frame.clone(), // Clone to avoid borrow checker issues
            None => break,
        };

        match current_frame {
            ExecutionFrame::Linear { current_pin, from_node } => {
                // Pop this frame since we're processing it
                stack.pop();

                // Find all connections from this exec pin
                let connections: Vec<_> = graph.connections.iter()
                    .filter(|c| c.from_node == from_node && c.from_pin == current_pin)
                    .collect();

                // Execute each connected node
                for conn in connections {
                    let next_node = graph.get_node(conn.to_node)
                        .ok_or_else(|| "Invalid node reference in connection".to_string())?;

                    // Execute the node and get next exec pin (if any)
                    let result = execute_node(graph, ctx, next_node.id, &mut stack)?;

                    // If node returned a next exec pin, push it as a new linear frame
                    if let Some(next_pin_id) = result {
                        stack.push(ExecutionFrame::Linear {
                            current_pin: next_pin_id,
                            from_node: next_node.id,
                        });
                    }
                }
            }

            ExecutionFrame::Loop {
                node_id,
                mut iteration,
                max_iterations,
                loop_body_start_pin,
                completed_pin,
                index_output_pin,
            } => {
                // Check if loop should continue
                if iteration < max_iterations {
                    // Store current iteration index in temp values (for index output pin)
                    // Use pin name "index" as key (eval_node_output looks up by pin name)
                    if index_output_pin.is_some() {
                        ctx.store_temp_value(node_id, "index", RuntimeValue::Float(iteration as f32));
                    }

                    // Update frame with incremented iteration and push back
                    stack.pop();
                    stack.push(ExecutionFrame::Loop {
                        node_id,
                        iteration: iteration + 1,
                        max_iterations,
                        loop_body_start_pin,
                        completed_pin,
                        index_output_pin,
                    });

                    // Push linear frame to execute loop body
                    stack.push(ExecutionFrame::Linear {
                        current_pin: loop_body_start_pin,
                        from_node: node_id,
                    });
                } else {
                    // Loop completed - pop loop frame and continue with completed pin
                    stack.pop();
                    stack.push(ExecutionFrame::Linear {
                        current_pin: completed_pin,
                        from_node: node_id,
                    });
                }
            }

            ExecutionFrame::WhileLoop {
                node_id,
                condition_node_input_pin,
                loop_body_start_pin,
                completed_pin,
            } => {
                // Evaluate condition
                let condition_value = eval_input(graph, ctx, node_id, "condition")?;
                let should_continue = condition_value.as_bool();

                if should_continue {
                    // Continue loop - execute body again
                    stack.push(ExecutionFrame::Linear {
                        current_pin: loop_body_start_pin,
                        from_node: node_id,
                    });
                } else {
                    // Loop completed
                    stack.pop();
                    stack.push(ExecutionFrame::Linear {
                        current_pin: completed_pin,
                        from_node: node_id,
                    });
                }
            }

            ExecutionFrame::ForEachArray {
                node_id,
                array,
                iteration,
                loop_body_start_pin,
                completed_pin,
                element_output_pin,
                index_output_pin,
            } => {
                // Check if more elements to iterate
                if iteration < array.len() {
                    // Store current element and index using pin names
                    // Need to determine the pin name from element_output_pin
                    // This is a bit tricky - let's use a generic approach
                    // For ForEachRow it's "row", for ForEachArray it's "element"
                    let element = array[iteration].clone();

                    // Get the node to check which type it is
                    if let Some(loop_node) = graph.get_node(node_id) {
                        let element_pin_name = loop_node.pins.iter()
                            .find(|p| p.id == element_output_pin)
                            .map(|p| p.name.as_str())
                            .unwrap_or("element");

                        ctx.store_temp_value(node_id, element_pin_name, element);
                        ctx.store_temp_value(node_id, "index", RuntimeValue::Float(iteration as f32));
                    }

                    // Update and push frame back
                    stack.pop();
                    stack.push(ExecutionFrame::ForEachArray {
                        node_id,
                        array,
                        iteration: iteration + 1,
                        loop_body_start_pin,
                        completed_pin,
                        element_output_pin,
                        index_output_pin,
                    });

                    // Execute loop body
                    stack.push(ExecutionFrame::Linear {
                        current_pin: loop_body_start_pin,
                        from_node: node_id,
                    });
                } else {
                    // Loop completed
                    stack.pop();
                    stack.push(ExecutionFrame::Linear {
                        current_pin: completed_pin,
                        from_node: node_id,
                    });
                }
            }

            ExecutionFrame::FunctionCall {
                function_id,
                return_address_pin,
                entry_node_id,
            } => {
                // TODO: Implement function call execution in Phase 5
                // For now, just pop and continue
                stack.pop();
                if let Some(return_pin) = return_address_pin {
                    stack.push(ExecutionFrame::Linear {
                        current_pin: return_pin,
                        from_node: entry_node_id,
                    });
                }
            }

            ExecutionFrame::TryCatch {
                try_body_pin,
                catch_body_pin,
                finally_pin,
            } => {
                // TODO: Implement try/catch in Phase 7
                stack.pop();
            }
        }
    }

    Ok(())
}

// Old recursive execution (REMOVED - replaced by stack-based execution)

/// Executes a single node and returns the ID of the output Exec pin to follow, if any.
/// Now takes ExecutionStack to allow nodes to push frames (for loops, function calls, etc.)
fn execute_node(
    graph: &NodeGraph,
    ctx: &mut dyn HostContext,
    node_id: usize,
    stack: &mut ExecutionStack,
) -> Result<Option<usize>, String> {
    let node = graph.get_node(node_id).ok_or("Node not found")?;

    // Dynamic registry dispatch (allows new nodes without touching interpreter.rs)
    if let Some(spec) = node_registry::registry().get_by_node_type(&node.node_type) {
        if let Some(exec_fn) = spec.execute {
            return exec_fn(graph, ctx, node, stack);
        }

        return Err(format!(
            "Node type '{}' has no execute handler in registry",
            node.node_type.as_str()
        ));
    }

    Err(format!(
        "Node type '{}' is not registered in NodeRegistry",
        node.node_type.as_str()
    ))
}

fn get_default_exec_out(node: &super::nodes::Node) -> Result<Option<usize>, String> {
    Ok(node.pins.iter()
        .find(|p| !p.is_input && p.pin_type == PinType::Exec && p.name.is_empty())
        .map(|p| p.id))
}


// Evaluation functions for data inputs (recursive)

fn eval_input_string(graph: &NodeGraph, ctx: &mut dyn HostContext, node_id: usize, pin_name: &str) -> Result<String, String> {
    let val = eval_input(graph, ctx, node_id, pin_name)?;
    Ok(val.as_string())
}

fn eval_input_float(graph: &NodeGraph, ctx: &mut dyn HostContext, node_id: usize, pin_name: &str) -> Result<f32, String> {
    let val = eval_input(graph, ctx, node_id, pin_name)?;
    Ok(val.as_float())
}

fn eval_input_bool(graph: &NodeGraph, ctx: &mut dyn HostContext, node_id: usize, pin_name: &str) -> Result<bool, String> {
    let val = eval_input(graph, ctx, node_id, pin_name)?;
    Ok(val.as_bool())
}

fn eval_input_color(graph: &NodeGraph, ctx: &mut dyn HostContext, node_id: usize, pin_name: &str) -> Result<[f32; 4], String> {
    let val = eval_input(graph, ctx, node_id, pin_name)?;
    Ok(val.as_color())
}

fn eval_input(graph: &NodeGraph, ctx: &mut dyn HostContext, node_id: usize, pin_name: &str) -> Result<RuntimeValue, String> {
    let node = graph.get_node(node_id).ok_or("Node not found")?;
    
    // Find input pin
    let input_pin = node.pins.iter()
        .find(|p| p.is_input && p.name == pin_name)
        .ok_or_else(|| format!("Pin {} not found on node {}", pin_name, node_id))?;
        
    // Find connection
    let connection = graph.connections.iter()
        .find(|c| c.to_node == node_id && c.to_pin == input_pin.id);
        
    if let Some(conn) = connection {
        eval_node_output(graph, ctx, conn.from_node, conn.from_pin)
    } else {
        // If unconnected, return default for type
        match input_pin.pin_type {
            PinType::String => Ok(RuntimeValue::String(String::new())),
            PinType::Float => Ok(RuntimeValue::Float(0.0)),
            PinType::Bool => Ok(RuntimeValue::Bool(false)),
            PinType::Color => Ok(RuntimeValue::Color([1.0, 1.0, 1.0, 1.0])),
            PinType::Array => Ok(RuntimeValue::Array(Vec::new())),
            PinType::Map => Ok(RuntimeValue::Map(std::collections::HashMap::new())),
            PinType::Tuple => Ok(RuntimeValue::Tuple(Vec::new())),
            _ => Ok(RuntimeValue::None),
        }
    }
}

fn is_input_connected(graph: &NodeGraph, node_id: usize, pin_name: &str) -> bool {
    let node = if let Some(n) = graph.get_node(node_id) { n } else { return false; };
    
    // Find input pin
    if let Some(pin) = node.pins.iter().find(|p| p.is_input && p.name == pin_name) {
        // Check for connections
        graph.connections.iter().any(|c| c.to_node == node_id && c.to_pin == pin.id)
    } else {
        false
    }
}

fn get_target_component_name(node: &super::nodes::Node) -> Option<String> {
    node.get_component_name().map(|name| {
        if let Some(pos) = name.find("::") {
            name[pos + 2..].to_string()
        } else {
            name.to_string()
        }
    })
}

pub fn eval_node_output(
    graph: &NodeGraph,
    ctx: &mut dyn HostContext,
    node_id: usize,
    pin_id: usize,
) -> Result<RuntimeValue, String> {
    let node = graph.get_node(node_id).ok_or("Source node not found")?;
    
    // Check if we have a stored temp value for this node (from Flow execution)
    // We need to identify *which* output pin it is to know the key.
    let pin = node.pins.iter().find(|p| p.id == pin_id).ok_or("Pin not found")?;
    
    if let Some(val) = ctx.get_temp_value(node_id, &pin.name) {
        return Ok(val.clone());
    }

    // Dynamic registry dispatch (allows new value nodes without touching interpreter.rs)
    if let Some(spec) = node_registry::registry().get_by_node_type(&node.node_type) {
        if let Some(eval_fn) = spec.evaluate {
            return eval_fn(graph, ctx, node, pin_id);
        }
    }

    Err(format!(
        "Node type '{}' has no evaluate handler in registry",
        node.node_type.as_str()
    ))
}
