// 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)
//
// `execute_command` — run a binary with an explicit args array. Unlike
// `code`, which goes through `sh -c "..."` as a string, this gives you
// a no-shell direct exec — friendlier for federal allow-listing.

use async_trait::async_trait;
use crate::core::error::NodeError;
use crate::core::types::*;
use crate::engine::{ExecuteCtx, Node, NodeFactory};
use serde_json::json;
use std::sync::Arc;
use tokio::process::Command;

pub struct ExecuteCommandNode;

static META: NodeMetadata = NodeMetadata {
    name: "execute_command",
    display_name: "Execute Command",
    version: &[1],
    group: &["transform"],
    description: "Run a program with an args array (no shell). Captures stdout/stderr/exit code.",
    inputs: 1,
    outputs: 1,
    properties: &[],
    credentials: &[],
};

#[async_trait]
impl Node for ExecuteCommandNode {
    fn metadata(&self) -> &'static NodeMetadata { &META }

    async fn execute(&self, ctx: &mut ExecuteCtx<'_>) -> Result<NodeOutput, NodeError> {
        let program = ctx.param_str("program")?;
        let args: Vec<String> = ctx.node_def.parameters.get("args")
            .and_then(|v| v.as_array())
            .map(|arr| arr.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect())
            .unwrap_or_default();
        let cwd = ctx.node_def.parameters.get("cwd")
            .and_then(|v| v.as_str())
            .map(|s| s.to_string());

        let mut cmd = Command::new(&program);
        cmd.args(&args);
        if let Some(d) = &cwd { cmd.current_dir(d); }
        let output = cmd.output().await
            .map_err(|e| NodeError::Other(format!("execute_command: spawn `{}`: {}", program, e)))?;
        Ok(vec![vec![NodeItem {
            json: json!({
                "exit_code": output.status.code(),
                "stdout": String::from_utf8_lossy(&output.stdout).into_owned(),
                "stderr": String::from_utf8_lossy(&output.stderr).into_owned(),
                "success": output.status.success(),
            }),
            ..Default::default()
        }]])
    }
}

inventory::submit! { NodeFactory::new("execute_command", || Arc::new(ExecuteCommandNode)) }
