// r8r: rust port of n8n.io
// Initial author: Michael Cochran <mcochran@cochranblock.org>
// Built for: Tanner Woodrum, intended cofounder
// License: Unlicense (public domain dedication, see UNLICENSE)

use async_trait::async_trait;
use crate::core::credentials::CredentialStore;
use crate::core::error::NodeError;
use crate::core::expr::{eval_str, eval_value, ExprCtx};
use crate::core::types::*;
use serde_json::Value;
use std::collections::HashMap;
use std::sync::Arc;

#[async_trait]
pub trait Node: Send + Sync + 'static {
    fn metadata(&self) -> &'static NodeMetadata;
    async fn execute(&self, ctx: &mut ExecuteCtx<'_>) -> Result<NodeOutput, NodeError>;
}

pub struct NodeFactory {
    pub name: &'static str,
    pub make: fn() -> Arc<dyn Node>,
}

impl NodeFactory {
    pub const fn new(name: &'static str, make: fn() -> Arc<dyn Node>) -> Self {
        Self { name, make }
    }
}

inventory::collect!(NodeFactory);

pub struct ExecuteCtx<'a> {
    pub node_def: &'a NodeDef,
    pub input: Vec<NodeItem>,
    pub run_data: &'a HashMap<String, Vec<NodeItem>>,
    pub credentials: &'a CredentialStore,
    pub workflow_name: &'a str,
    pub execution_id: &'a str,
}

impl<'a> ExecuteCtx<'a> {
    pub fn param_str(&self, name: &str) -> Result<String, NodeError> {
        let raw = self.node_def.parameters.get(name)
            .ok_or_else(|| NodeError::MissingParameter(name.to_string()))?;
        let s = match raw {
            Value::String(s) => s.clone(),
            other => other.to_string(),
        };
        let item = self.input.first().cloned().unwrap_or_default();
        let ctx = ExprCtx {
            current_item: &item,
            item_index: 0,
            node_data: self.run_data,
            workflow_name: self.workflow_name,
            execution_id: self.execution_id,
            parameters: &self.node_def.parameters,
            now: chrono::Utc::now(),
        };
        eval_str(&s, &ctx)
    }

    pub fn param_value(&self, name: &str) -> Result<Value, NodeError> {
        let raw = self.node_def.parameters.get(name)
            .ok_or_else(|| NodeError::MissingParameter(name.to_string()))?;
        let item = self.input.first().cloned().unwrap_or_default();
        let ctx = ExprCtx {
            current_item: &item,
            item_index: 0,
            node_data: self.run_data,
            workflow_name: self.workflow_name,
            execution_id: self.execution_id,
            parameters: &self.node_def.parameters,
            now: chrono::Utc::now(),
        };
        eval_value(raw, &ctx)
    }

    pub fn param_opt_str(&self, name: &str) -> Option<String> {
        self.param_str(name).ok()
    }

    pub fn credential_field(&self, field: &str) -> Result<&Value, NodeError> {
        let cred_name = self.node_def.credentials.as_deref()
            .ok_or_else(|| NodeError::MissingCredential("(none configured on node)".into()))?;
        self.credentials.field(cred_name, field)
    }
}
