// 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)
//
// `write_binary_file` — write a binary attachment from incoming items to
// disk. The bytes come from item.binary[<key>], the path from a parameter
// (which can reference $json fields).

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;

pub struct WriteBinaryFileNode;

static META: NodeMetadata = NodeMetadata {
    name: "write_binary_file",
    display_name: "Write Binary File",
    version: &[1],
    group: &["transform"],
    description: "Write each item's binary data to a file on disk",
    inputs: 1,
    outputs: 1,
    properties: &[],
    credentials: &[],
};

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

    async fn execute(&self, ctx: &mut ExecuteCtx<'_>) -> Result<NodeOutput, NodeError> {
        let key = ctx.node_def.parameters.get("binary_key")
            .and_then(|v| v.as_str()).unwrap_or("data").to_string();
        let mut out = Vec::with_capacity(ctx.input.len());
        for item in &ctx.input {
            let path = ctx.param_str("path")?;
            let bytes = item.binary.as_ref()
                .and_then(|m| m.get(&key))
                .map(|b| b.data.clone())
                .ok_or_else(|| NodeError::Other(format!("write_binary_file: no binary[{}] on item", key)))?;
            tokio::fs::write(&path, &bytes).await
                .map_err(|e| NodeError::Other(format!("write_binary_file: {}: {}", path, e)))?;
            let mut new_json = item.json.clone();
            if !new_json.is_object() { new_json = json!({}); }
            new_json.as_object_mut().unwrap().insert("path".into(), json!(path));
            new_json.as_object_mut().unwrap().insert("bytes_written".into(), json!(bytes.len()));
            out.push(NodeItem { json: new_json, ..item.clone() });
        }
        Ok(vec![out])
    }
}

inventory::submit! { NodeFactory::new("write_binary_file", || Arc::new(WriteBinaryFileNode)) }
