// 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)
//
// `code` — execute a shell command, emit each stdout line as an item.
// n8n's Code node runs JS in a sandbox; we run shell commands instead.
// JS-in-shell is out of scope for v0.1 (no boa engine yet).

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 CodeNode;

static META: NodeMetadata = NodeMetadata {
    name: "code",
    display_name: "Code",
    version: &[1, 2],
    group: &["transform"],
    description: "Execute a shell command; each line of stdout becomes an output item",
    inputs: 1,
    outputs: 1,
    properties: &[],
    credentials: &[],
};

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

    async fn execute(&self, ctx: &mut ExecuteCtx<'_>) -> Result<NodeOutput, NodeError> {
        let command = ctx.param_str("command")?;
        let shell = ctx.node_def.parameters.get("shell")
            .and_then(|v| v.as_str())
            .unwrap_or("sh -c")
            .to_string();
        let mut parts = shell.split_whitespace();
        let program = parts.next()
            .ok_or_else(|| NodeError::Other("code: empty shell".into()))?
            .to_string();
        let pre_args: Vec<String> = parts.map(|s| s.to_string()).collect();

        let output = Command::new(&program)
            .args(&pre_args)
            .arg(&command)
            .output()
            .await
            .map_err(|e| NodeError::Other(format!("code: spawn failed: {}", e)))?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(NodeError::Other(format!(
                "code: exit {}: {}",
                output.status.code().unwrap_or(-1),
                stderr.trim()
            )));
        }

        let stdout = String::from_utf8_lossy(&output.stdout);
        let items: Vec<NodeItem> = stdout.lines()
            .map(|line| NodeItem {
                json: json!({ "line": line }),
                ..Default::default()
            })
            .collect();
        Ok(vec![items])
    }
}

inventory::submit! { NodeFactory::new("code", || Arc::new(CodeNode)) }
