smarana 0.3.2

An extensible note taking system for typst.
use std::io::Write;
use std::process::{Command, Stdio};

use crate::config::{AppConfig, GlobalConfig};


/// Creates a new note, handles collisions by silently opening the existing file,
/// and automatically spawns the editor on creation.
pub fn create_note(name: Option<String>, template_override: Option<String>, body_content: Option<String>) {
    let global = GlobalConfig::load();
    let notebook_path = global.notebook_path().unwrap_or_else(|| {
        eprintln!("Notebook not initialized. Run `sma -I` first.");
        std::process::exit(1);
    });

    let config = AppConfig::load();
    
    let title = name.unwrap_or_else(|| config.smarana.default_title.clone());

    // Generate filename slug
    let filename_slug = generate_filename(&title, &config.smarana.filename_gen, &config.smarana.shell);
    let filename = format!("{}.typ", filename_slug.trim());

    let file_path = notebook_path.join(&filename);

    if !file_path.exists() {

        // Read Template
        let template_name = template_override.unwrap_or_else(|| config.frontmatter.template.clone());
        let template_path = notebook_path.join(".smarana").join("templates").join(&template_name);
        
        let mut matter = match std::fs::read_to_string(&template_path) {
            Ok(content) => content,
            Err(e) => {
                eprintln!("Failed to read template `{}`: {}", template_path.display(), e);
                std::process::exit(1);
            }
        };

        // Built-in {title} replacement
        matter = matter.replace("{title}", &title);

        // Dynamic shell replacements
        for (k, v) in &config.frontmatter.variables {
            let output = eval_shell_script(v, &config.smarana.shell);
            matter = matter.replace(&format!("{{{}}}", k), output.trim());
        }

        // Add stdin content if in interactive mode
        if let Some(content) = body_content {
            matter.push_str("\n");
            matter.push_str(&content);
            matter.push_str("\n");
        }

        // Write file
        if let Err(e) = std::fs::write(&file_path, matter) {
            eprintln!("Failed to create note file: {e}");
            std::process::exit(1);
        }

        println!("Created note: {}", file_path.display());
    }

    // Launch Editor natively
    let status = Command::new(&config.smarana.editor)
        .arg(&file_path)
        .status();

    match status {
        Ok(s) if !s.success() => {
            eprintln!("Editor exited with non-zero status");
        }
        Err(e) => {
            eprintln!("Failed to open editor '{}': {}", config.smarana.editor, e);
        }
        _ => {}
    }

    // Capture modifications inherently saved by the Editor back to the DB cleanly.
    crate::db::sync_all(&notebook_path);
}

fn generate_filename(name: &str, script: &str, shell: &str) -> String {
    let mut child = Command::new(shell)
        .arg("-c")
        .arg(script)
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()
        .unwrap_or_else(|e| {
            eprintln!("Failed to spawn filename generation script: {e}");
            std::process::exit(1);
        });

    if let Some(mut stdin) = child.stdin.take() {
        if let Err(e) = stdin.write_all(name.as_bytes()) {
            eprintln!("Failed to pipe name to filename_gen script: {e}");
        }
    }

    let output = child.wait_with_output().unwrap_or_else(|e| {
        eprintln!("Failed to wait on filename_gen script: {e}");
        std::process::exit(1);
    });

    if !output.status.success() {
        eprintln!("filename_gen script failed with status: {}", output.status);
        std::process::exit(1);
    }

    String::from_utf8_lossy(&output.stdout).to_string()
}

fn eval_shell_script(script: &str, shell: &str) -> String {
    let output = Command::new(shell)
        .arg("-c")
        .arg(script)
        .output()
        .unwrap_or_else(|e| {
            eprintln!("Failed to execute frontmatter script `{}`: {e}", script);
            std::process::exit(1);
        });

    if !output.status.success() {
        eprintln!("frontmatter script `{}` failed with status: {}", script, output.status);
    }

    String::from_utf8_lossy(&output.stdout).to_string()
}