smarana 0.9.9

An extensible note taking system for typst.
use std::path::PathBuf;

use crate::config;

/// Initialize a notebook at the given path.
/// - Creates `<path>/.smarana/config.toml` with default aliases
/// - Creates `XDG_CONFIG_HOME/smarana/config.toml` with the notebook path
/// - Deploys Typst framework files (library, styles, templates)
pub fn initialize(path: &str) {
    let notebook_dir = if path == "." {
        std::env::current_dir().unwrap_or_else(|e| {
            eprintln!("Failed to get current directory: {e}");
            std::process::exit(1);
        })
    } else {
        let p = PathBuf::from(path);
        if p.is_absolute() {
            p
        } else {
            std::env::current_dir()
                .unwrap_or_else(|_| PathBuf::from("."))
                .join(p)
        }
    };

    // Canonicalize to resolve symlinks and ../ etc.
    let notebook_dir = notebook_dir.canonicalize().unwrap_or(notebook_dir);

    // Check if a notebook is already registered in the global config
    let global = config::GlobalConfig::load();
    if let Some(existing) = global.notebook_path() {
        crate::vprintln!("A notebook is already initialized at: {}", existing.display());
        return;
    }

    crate::vprintln!("Initializing notebook at: {}", notebook_dir.display());

    // 1. Create <notebook_dir>/.smarana/
    let smarana_dir = notebook_dir.join(".smarana");
    if let Err(e) = std::fs::create_dir_all(&smarana_dir) {
        eprintln!("Failed to create .smarana directory: {e}");
        std::process::exit(1);
    }

    // 2. Create <notebook_dir>/.smarana/config.toml with default aliases
    let notebook_config = smarana_dir.join("config.toml");
    if notebook_config.exists() {
        crate::vprintln!("Notebook config already exists: {}", notebook_config.display());
    } else {
        let template = include_str!("../res/default_config.toml");
        if let Err(e) = std::fs::write(&notebook_config, template) {
            eprintln!("Failed to create notebook config: {e}");
            std::process::exit(1);
        }
        crate::vprintln!("Created: {}", notebook_config.display());
    }

    // Initialize templates directory and all templates
    let templates_dir = smarana_dir.join("templates");
    if let Err(e) = std::fs::create_dir_all(&templates_dir) {
        eprintln!("Failed to create templates dir: {e}");
        std::process::exit(1);
    }
    
    // Initialize lib directory for typst framework
    let lib_dir = smarana_dir.join("lib");
    if let Err(e) = std::fs::create_dir_all(&lib_dir) {
        eprintln!("Failed to create lib dir: {e}");
        std::process::exit(1);
    }

    // Deploy default (atomic) template
    deploy_file(
        &templates_dir.join("atomic.typ"),
        include_str!("../res/templates/atomic.typ"),
        "atomic template",
    );
    // Deploy fleeting template
    deploy_file(
        &templates_dir.join("fleeting.typ"),
        include_str!("../res/templates/fleeting.typ"),
        "fleeting template",
    );
    // Deploy capture template
    deploy_file(
        &templates_dir.join("capture.typ"),
        include_str!("../res/templates/capture.typ"),
        "capture template",
    );

    // Deploy library.typ (core framework)
    deploy_file(
        &lib_dir.join("library.typ"),
        include_str!("../res/library.typ"),
        "library.typ",
    );

    // Deploy compartment styling files
    deploy_file(
        &lib_dir.join("fleeting.typ"),
        include_str!("../res/fleeting.typ"),
        "fleeting.typ",
    );
    deploy_file(
        &lib_dir.join("capture.typ"),
        include_str!("../res/capture.typ"),
        "capture.typ",
    );
    deploy_file(
        &lib_dir.join("atomic.typ"),
        include_str!("../res/atomic.typ"),
        "atomic.typ",
    );

    // Theme File
    deploy_file(
        &lib_dir.join("theme.typ"),
        include_str!("../res/theme.typ"),
        "theme.typ",
    );

    // Section Dividers
    deploy_file(
        &lib_dir.join("dividers.typ"),
        include_str!("../res/dividers.typ"),
        "dividers.typ",
    );

    // Appendix (Tag Index Pages)
    deploy_file(
        &lib_dir.join("appendix.typ"),
        include_str!("../res/appendix.typ"),
        "appendix.typ",
    );

    // Create user.typ configuration wrapper (only if it doesn't exist)
    let user_typ_path = smarana_dir.join("user.typ");
    if !user_typ_path.exists() {
        deploy_file(
            &user_typ_path,
            include_str!("../res/user.typ"),
            "user.typ",
        );
    } else {
        crate::vprintln!("User configuration wrapper already exists: {}", user_typ_path.display());
    }

    // 3. Create XDG_CONFIG_HOME/smarana/config.toml with notebook path
    let global_config_path = config::global_config_path();
    if let Some(parent) = global_config_path.parent() {
        if let Err(e) = std::fs::create_dir_all(parent) {
            eprintln!("Failed to create global config directory: {e}");
            std::process::exit(1);
        }
    }

    let portable_path = path_with_home_var(&notebook_dir);
    let global_content = format!(
        "# Smarana Global Configuration\n# Path to the notebook directory\nnotebook = \"{portable_path}\"\n"
    );

    if let Err(e) = std::fs::write(&global_config_path, global_content) {
        eprintln!("Failed to create global config: {e}");
        std::process::exit(1);
    }
    crate::vprintln!("Created: {}", global_config_path.display());

    // 4. Initialize Database
    if let Err(e) = crate::db::init_db(&notebook_dir) {
        eprintln!("Failed to initialize database: {e}");
        std::process::exit(1);
    }
    
    // Generate the initial smarana.typ index
    crate::db::export_smarana_typ(&notebook_dir);

    crate::vprintln!("Initialized database at {}", notebook_dir.join(".smarana").join("index.db").display());

    crate::vprintln!("Notebook initialized successfully!");
}

/// Updates the `.smarana/lib` framework files for an existing notebook.
pub fn update_library(path: &str) {
    let notebook_dir = PathBuf::from(path);
    let smarana_dir = notebook_dir.join(".smarana");
    let lib_dir = smarana_dir.join("lib");

    if !smarana_dir.exists() {
        eprintln!("Notebook not initialized at {}; cannot update library.", path);
        std::process::exit(1);
    }

    if let Err(e) = std::fs::create_dir_all(&lib_dir) {
        eprintln!("Failed to ensure lib dir exists: {e}");
        std::process::exit(1);
    }

    deploy_file(
        &lib_dir.join("library.typ"),
        include_str!("../res/library.typ"),
        "library.typ",
    );
    deploy_file(
        &lib_dir.join("fleeting.typ"),
        include_str!("../res/fleeting.typ"),
        "fleeting.typ",
    );
    deploy_file(
        &lib_dir.join("capture.typ"),
        include_str!("../res/capture.typ"),
        "capture.typ",
    );
    deploy_file(
        &lib_dir.join("atomic.typ"),
        include_str!("../res/atomic.typ"),
        "atomic.typ",
    );
    deploy_file(
        &lib_dir.join("theme.typ"),
        include_str!("../res/theme.typ"),
        "theme.typ",
    );
    deploy_file(
        &lib_dir.join("dividers.typ"),
        include_str!("../res/dividers.typ"),
        "dividers.typ",
    );
    deploy_file(
        &lib_dir.join("appendix.typ"),
        include_str!("../res/appendix.typ"),
        "appendix.typ",
    );

    crate::vprintln!("Library updated successfully!");
}

/// Deploy a file, printing status. Overwrites if it already exists.
fn deploy_file(path: &std::path::Path, content: &str, label: &str) {
    if let Err(e) = std::fs::write(path, content) {
        eprintln!("Failed to create {}: {e}", label);
        std::process::exit(1);
    }
    crate::vprintln!("Created: {}", path.display());
}

/// Convert an absolute path to use `$HOME/...` for portability.
/// Falls back to the absolute path if $HOME is not set or the path is not under $HOME.
fn path_with_home_var(path: &std::path::Path) -> String {
    if let Ok(home) = std::env::var("HOME") {
        let home_path = PathBuf::from(&home);
        if let Ok(relative) = path.strip_prefix(&home_path) {
            return format!("$HOME/{}", relative.display());
        }
    }
    path.display().to_string()
}