// 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)

use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use r8r::core::credentials::CredentialStore;
use r8r::core::types::NodeItem;
use r8r::core::workflow::Workflow;
use r8r::engine::{execute, Registry};
use std::path::PathBuf;

// Force-reference nodes so inventory::submit! survives dead-code elimination.
#[allow(unused_imports)]
use r8r::nodes as _;

mod serve;

#[derive(Parser)]
#[command(name = "r8r", version, about = "Workflow runner. Rust port of n8n.io.")]
struct Cli {
    #[command(subcommand)]
    cmd: Cmd,
}

#[derive(Subcommand)]
enum Cmd {
    /// Run a workflow.toml to completion
    Run {
        workflow: PathBuf,
        #[arg(long, default_value = "~/.flow/credentials.toml")]
        credentials: String,
    },
    /// List every registered node type
    ListNodes,
    /// Validate a workflow file without running it
    Check { workflow: PathBuf },
    /// Print version + node count summary
    Info,
    /// Serve the canvas + JSON API on a port
    Serve {
        #[arg(long, default_value_t = 8080)]
        port: u16,
        #[arg(long, default_value = "~/.flow/credentials.toml")]
        credentials: String,
    },
}

#[tokio::main]
async fn main() -> Result<()> {
    tracing_subscriber::fmt()
        .with_env_filter(tracing_subscriber::EnvFilter::try_from_default_env()
            .unwrap_or_else(|_| "info,r8r::engine=info".into()))
        .init();

    let cli = Cli::parse();
    match cli.cmd {
        Cmd::Run { workflow, credentials } => run(workflow, credentials).await,
        Cmd::ListNodes => list_nodes(),
        Cmd::Check { workflow } => check(workflow),
        Cmd::Info => info(),
        Cmd::Serve { port, credentials } => serve::run(port, credentials).await,
    }
}

async fn run(workflow_path: PathBuf, credentials_path: String) -> Result<()> {
    let workflow = Workflow::from_toml_file(&workflow_path)
        .with_context(|| format!("loading workflow {}", workflow_path.display()))?;
    let creds_path = expand_tilde(&credentials_path);
    let credentials = if creds_path.exists() {
        CredentialStore::from_toml_file(&creds_path)
            .with_context(|| format!("loading credentials {}", creds_path.display()))?
    } else {
        CredentialStore::default()
    };
    let initial = vec![NodeItem::default()];
    let state = execute(&workflow, &credentials, initial).await?;
    if let Some(err) = state.error {
        anyhow::bail!("workflow failed: {err}");
    }
    let last = state.last_node_executed.unwrap_or_else(|| "(none)".into());
    println!("ok - {} nodes executed, last = {}", state.run_data.len(), last);
    Ok(())
}

fn list_nodes() -> Result<()> {
    let r = Registry::global();
    for name in r.names() { println!("{}", name); }
    eprintln!("\n{} nodes registered", r.count());
    Ok(())
}

fn check(workflow_path: PathBuf) -> Result<()> {
    let wf = Workflow::from_toml_file(&workflow_path)?;
    println!("ok - {} nodes, {} edges", wf.nodes.len(), wf.edges.len());
    Ok(())
}

fn info() -> Result<()> {
    let r = Registry::global();
    println!("r8r {}", env!("CARGO_PKG_VERSION"));
    println!("  registered nodes: {}", r.count());
    Ok(())
}

fn expand_tilde(s: &str) -> PathBuf {
    if let Some(rest) = s.strip_prefix("~/") {
        if let Ok(home) = std::env::var("HOME") {
            return PathBuf::from(home).join(rest);
        }
    }
    PathBuf::from(s)
}
