// 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)
//
// `convert_to_file` — turn JSON content into a binary attachment so it
// can be written by `write_binary_file`, attached to email_send, etc.

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

pub struct ConvertToFileNode;

static META: NodeMetadata = NodeMetadata {
    name: "convert_to_file",
    display_name: "Convert to File",
    version: &[1],
    group: &["transform"],
    description: "Convert JSON content into binary item data",
    inputs: 1,
    outputs: 1,
    properties: &[],
    credentials: &[],
};

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

    async fn execute(&self, ctx: &mut ExecuteCtx<'_>) -> Result<NodeOutput, NodeError> {
        let format = ctx.node_def.parameters.get("format")
            .and_then(|v| v.as_str()).unwrap_or("text").to_string();
        let key = ctx.node_def.parameters.get("binary_key")
            .and_then(|v| v.as_str()).unwrap_or("data").to_string();
        let file_name = ctx.param_opt_str("file_name").unwrap_or_else(|| "out.bin".to_string());

        let mut out = Vec::with_capacity(ctx.input.len());
        for item in &ctx.input {
            let bytes = match format.as_str() {
                "text" => {
                    let s = ctx.param_opt_str("content").unwrap_or_default();
                    s.into_bytes()
                }
                "json" => {
                    let v = ctx.param_value("content").unwrap_or(item.json.clone());
                    serde_json::to_vec_pretty(&v).map_err(NodeError::Json)?
                }
                "csv" => {
                    let arr = ctx.param_value("content").unwrap_or(item.json.clone());
                    let arr = arr.as_array().cloned().unwrap_or_default();
                    let mut csv = String::new();
                    if let Some(first) = arr.first().and_then(|v| v.as_object()) {
                        let cols: Vec<&str> = first.keys().map(|k| k.as_str()).collect();
                        csv.push_str(&cols.join(","));
                        csv.push('\n');
                        for row in &arr {
                            if let Some(obj) = row.as_object() {
                                let vals: Vec<String> = cols.iter().map(|c| {
                                    obj.get(*c).map(|v| match v {
                                        serde_json::Value::String(s) => s.clone(),
                                        other => other.to_string(),
                                    }).unwrap_or_default()
                                }).collect();
                                csv.push_str(&vals.join(","));
                                csv.push('\n');
                            }
                        }
                    }
                    csv.into_bytes()
                }
                other => return Err(NodeError::Other(format!("convert_to_file: unknown format `{}`", other))),
            };
            let mime = match format.as_str() {
                "json" => "application/json",
                "csv" => "text/csv",
                _ => "text/plain",
            }.to_string();
            let mut binary = item.binary.clone().unwrap_or_default();
            binary.insert(key.clone(), BinaryData {
                mime_type: mime,
                file_name: Some(file_name.clone()),
                data: bytes,
            });
            out.push(NodeItem { binary: Some(binary), ..item.clone() });
        }
        Ok(vec![out])
    }
}

inventory::submit! { NodeFactory::new("convert_to_file", || Arc::new(ConvertToFileNode)) }
