use super::super::framework::{EvalImpl, NodeImpl, eval_input, get_default_exec_out};
use crate::gui_builder::node_registry::{NodeSpec, ParamKind};
use crate::gui_builder::nodes::{Node, NodeCategory, NodeGraph, NodeLibrary, NodeParams, Pin, PinType};
use crate::gui_builder::interpreter::ExecutionStack;
use crate::gui_builder::runtime::{RuntimeContext, RuntimeValue};
use std::collections::HashSet;

pub struct JsonCompareArraysNode;

/// Helper function to perform the comparison
fn compare_arrays(array_a: &RuntimeValue, array_b: &RuntimeValue) -> (Vec<RuntimeValue>, Vec<RuntimeValue>, Vec<RuntimeValue>) {
    // Convert RuntimeValue arrays to HashSet<String> for comparison
    let set_a: HashSet<String> = match array_a {
        RuntimeValue::Array(arr) => arr.iter().map(|v| v.as_string()).collect(),
        _ => HashSet::new(),
    };
    
    let set_b: HashSet<String> = match array_b {
        RuntimeValue::Array(arr) => arr.iter().map(|v| v.as_string()).collect(),
        _ => HashSet::new(),
    };
    
    // Calculate intersections and differences
    let matching: Vec<RuntimeValue> = set_a.intersection(&set_b)
        .map(|s| RuntimeValue::String(s.clone()))
        .collect();
    let only_a: Vec<RuntimeValue> = set_a.difference(&set_b)
        .map(|s| RuntimeValue::String(s.clone()))
        .collect();
    let only_b: Vec<RuntimeValue> = set_b.difference(&set_a)
        .map(|s| RuntimeValue::String(s.clone()))
        .collect();
    
    (matching, only_a, only_b)
}

impl NodeImpl for JsonCompareArraysNode {
    fn execute(&self, _graph: &NodeGraph, _ctx: &mut RuntimeContext, node: &Node) -> Result<Option<usize>, String> {
        // Just pass through to default exec out - actual comparison happens in evaluate
        get_default_exec_out(node)
    }
}

impl super::super::framework::NodeDescription for JsonCompareArraysNode {
    fn description(&self) -> &str {
        "Compares two arrays and outputs: matching items, items only in A, items only in B."
    }
}

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

fn pins(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::Array, true, "array_a"));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Array, true, "array_b"));
    *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::Array, false, "matching"));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Array, false, "only_a"));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Array, false, "only_b"));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Float, false, "match_count"));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Float, false, "only_a_count"));
    *next_pin_id += 1;
    pins.push(Pin::new(*next_pin_id, PinType::Float, false, "only_b_count"));
    *next_pin_id += 1;
    pins
}

fn exec(graph: &NodeGraph, ctx: &mut RuntimeContext, node: &Node, _stack: &mut ExecutionStack) -> Result<Option<usize>, String> {
    JsonCompareArraysNode.execute(graph, ctx, node)
}

fn eval(graph: &NodeGraph, ctx: &mut RuntimeContext, node: &Node, pin_id: usize) -> Result<RuntimeValue, String> {
    JsonCompareArraysNode.evaluate(graph, ctx, node, pin_id)
}

crate::register_node!(NodeSpec {
    id: "JsonCompareArrays",
    display_name: "Compare Arrays",
    documentation: "Compares two arrays and outputs: matching items, items only in A, items only in B.",
    category: NodeCategory::Data,
    library: NodeLibrary::Json,
    param_kind: ParamKind::None,
    default_params,
    pins,
    execute: Some(exec),
    evaluate: Some(eval),
});

impl EvalImpl for JsonCompareArraysNode {
    fn evaluate(&self, graph: &NodeGraph, ctx: &mut RuntimeContext, node: &Node, pin_id: usize) -> Result<RuntimeValue, String> {
        // Get input arrays
        let array_a_val = eval_input(graph, ctx, node.id, "array_a")?;
        let array_b_val = eval_input(graph, ctx, node.id, "array_b")?;
        
        // Perform comparison
        let (matching, only_a, only_b) = compare_arrays(&array_a_val, &array_b_val);
        
        // Find output pin name to determine which value to return
        if let Some(pin) = node.pins.iter().find(|p| p.id == pin_id) {
            match pin.name.as_str() {
                "matching" => Ok(RuntimeValue::Array(matching)),
                "only_a" => Ok(RuntimeValue::Array(only_a)),
                "only_b" => Ok(RuntimeValue::Array(only_b)),
                "match_count" => Ok(RuntimeValue::Float(matching.len() as f32)),
                "only_a_count" => Ok(RuntimeValue::Float(only_a.len() as f32)),
                "only_b_count" => Ok(RuntimeValue::Float(only_b.len() as f32)),
                _ => Ok(RuntimeValue::None),
            }
        } else {
            Ok(RuntimeValue::None)
        }
    }
}
