use std::collections::HashMap;

use crate::gui_builder::nodes::{Node, NodeCategory, NodeLibrary, NodeParams, NodeType, Pin, PinType};
use crate::gui_builder::interpreter::ExecutionStack;
use crate::gui_builder::host_runtime::{HostContext, RuntimeValue};

#[derive(Clone, Copy)]
pub struct PinDef {
    pub name: &'static str,
    pub pin_type: PinType,
    pub is_input: bool,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ParamKind {
    None,
    ComponentSelector,
    StringLiteral,
    StringFormat,
    HttpHeaders,
    JsonPath,
    NumberLiteral,
    BoolLiteral,
    ColorLiteral,
    MathOperation,
    BigIntOperation,
    TypeCast,
    VariableSelector,
}

#[derive(Clone, Copy)]
pub struct NodeSpec {
    pub id: &'static str,
    pub display_name: &'static str,
    pub documentation: &'static str,
    pub category: NodeCategory,
    pub library: NodeLibrary,

    pub param_kind: ParamKind,

    pub default_params: fn() -> NodeParams,
    pub pins: fn(next_pin_id: &mut usize) -> Vec<Pin>,

    pub execute: Option<fn(graph: &crate::gui_builder::nodes::NodeGraph, ctx: &mut dyn HostContext, node: &Node, stack: &mut ExecutionStack) -> Result<Option<usize>, String>>,
    pub evaluate: Option<fn(graph: &crate::gui_builder::nodes::NodeGraph, ctx: &mut dyn HostContext, node: &Node, pin_id: usize) -> Result<RuntimeValue, String>>,
}

inventory::collect!(NodeSpec);

pub struct NodeRegistry {
    by_id: HashMap<&'static str, &'static NodeSpec>,
}

impl NodeRegistry {
    pub fn new() -> Self {
        let mut by_id = HashMap::new();
        for spec in inventory::iter::<NodeSpec> {
            by_id.insert(spec.id, spec);
        }
        Self { by_id }
    }

    pub fn get_by_id(&self, id: &str) -> Option<&'static NodeSpec> {
        self.by_id.get(id).copied()
    }

    pub fn get_by_node_type(&self, node_type: &NodeType) -> Option<&'static NodeSpec> {
        self.get_by_id(node_type.as_str())
    }

    pub fn all(&self) -> impl Iterator<Item = &'static NodeSpec> + '_ {
        self.by_id.values().copied()
    }
}

pub fn registry() -> &'static NodeRegistry {
    static mut REGISTRY: Option<NodeRegistry> = None;
    static ONCE: std::sync::Once = std::sync::Once::new();

    unsafe {
        ONCE.call_once(|| {
            REGISTRY = Some(NodeRegistry::new());
        });
        REGISTRY.as_ref().expect("NodeRegistry not initialized")
    }
}

#[macro_export]
macro_rules! register_node {
    ($spec:expr) => {
        inventory::submit! { $spec }
    };
}
