// 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)
//
// `if` — route items to true/false outputs based on a comparison.

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

pub struct IfNode;

static META: NodeMetadata = NodeMetadata {
    name: "if",
    display_name: "If",
    version: &[1, 2],
    group: &["transform"],
    description: "Routes items to true or false output based on a condition",
    inputs: 1,
    outputs: 2,
    properties: &[],
    credentials: &[],
};

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

    async fn execute(&self, ctx: &mut ExecuteCtx<'_>) -> Result<NodeOutput, NodeError> {
        let left_template = ctx.node_def.parameters.get("left")
            .ok_or_else(|| NodeError::MissingParameter("left".into()))?
            .clone();
        let right_template = ctx.node_def.parameters.get("right")
            .ok_or_else(|| NodeError::MissingParameter("right".into()))?
            .clone();
        let op = ctx.node_def.parameters.get("op")
            .and_then(|v| v.as_str()).unwrap_or("eq").to_string();

        let mut t_out = Vec::new();
        let mut f_out = Vec::new();
        for (idx, item) in ctx.input.iter().enumerate() {
            let expr_ctx = ExprCtx {
                current_item: item,
                item_index: idx as u32,
                node_data: ctx.run_data,
                workflow_name: ctx.workflow_name,
                execution_id: ctx.execution_id,
                parameters: &ctx.node_def.parameters,
                now: chrono::Utc::now(),
            };
            let left = eval_value(&left_template, &expr_ctx)?;
            let right = eval_value(&right_template, &expr_ctx)?;
            let pass = compare(&left, &right, &op)?;
            if pass { t_out.push(item.clone()) } else { f_out.push(item.clone()) }
        }
        Ok(vec![t_out, f_out])
    }
}

fn compare(l: &Value, r: &Value, op: &str) -> Result<bool, NodeError> {
    Ok(match op {
        "eq" | "==" => l == r,
        "neq" | "!=" => l != r,
        "lt" | "<" => num(l)? < num(r)?,
        "lte" | "<=" => num(l)? <= num(r)?,
        "gt" | ">" => num(l)? > num(r)?,
        "gte" | ">=" => num(l)? >= num(r)?,
        "contains" => l.as_str().zip(r.as_str())
            .map(|(a, b)| a.contains(b)).unwrap_or(false),
        "starts_with" => l.as_str().zip(r.as_str())
            .map(|(a, b)| a.starts_with(b)).unwrap_or(false),
        "ends_with" => l.as_str().zip(r.as_str())
            .map(|(a, b)| a.ends_with(b)).unwrap_or(false),
        "is_empty" => match l {
            Value::Null => true,
            Value::String(s) => s.is_empty(),
            Value::Array(a) => a.is_empty(),
            Value::Object(o) => o.is_empty(),
            _ => false,
        },
        other => return Err(NodeError::Other(format!("if: unknown op `{}`", other))),
    })
}

fn num(v: &Value) -> Result<f64, NodeError> {
    v.as_f64().ok_or_else(|| NodeError::Other(format!("if: expected number, got {:?}", v)))
}

inventory::submit! { NodeFactory::new("if", || Arc::new(IfNode)) }
