use std::collections::HashMap;
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;

const ASPKG_VERSION: &str = env!("CARGO_PKG_VERSION");
const REGISTRY_URL: &str =
    "https://raw.githubusercontent.com/ayaan511/AyaanScript--Packages/main/packages/";
const REGISTRY_INDEX: &str =
    "https://raw.githubusercontent.com/ayaan511/AyaanScript--Packages/main/index.txt";

fn package_dir() -> PathBuf {
    // Windows uses USERPROFILE/USERNAME; Unix uses HOME/USER.
    let home = env::var("HOME")
        .or_else(|_| env::var("USERPROFILE"))
        .unwrap_or_else(|_| ".".to_string());
    let user = env::var("USER")
        .or_else(|_| env::var("USERNAME"))
        .unwrap_or_else(|_| "user".to_string());

    Path::new(&home)
        .join(".config")
        .join(user)
        .join("aspkg")
        .join("packages")
}

fn ensure_dirs() {
    let dir = package_dir();
    fs::create_dir_all(&dir).expect("Failed to create aspkg directory");
}

fn package_path(name: &str) -> PathBuf {
    package_dir().join(format!("{}.aspkg", name))
}

fn package_dir_path(name: &str) -> PathBuf {
    package_dir().join(name)
}

fn package_config_path(name: &str) -> PathBuf {
    package_dir_path(name).join("package.aspkg")
}

fn extract_imports(content: &str) -> Vec<String> {
    let mut out = Vec::new();
    for line in content.lines() {
        let trimmed = line.trim();
        if let Some(rest) = trimmed.strip_prefix("importayaan ") {
            let raw = rest.trim().trim_matches('"').to_string();
            if !raw.is_empty() && !raw.contains('{') {
                out.push(raw);
            }
        }
    }
    out
}

fn fetch_recursive(name: &str, entry: &str, pkg_dir: &Path) -> usize {
    use std::collections::HashSet;
    let mut visited: HashSet<String> = HashSet::new();
    let mut queue: Vec<String> = vec![entry.to_string()];
    let mut count = 0;

    while let Some(file) = queue.pop() {
        if !visited.insert(file.clone()) {
            continue;
        }

        if !safe_pkg_filename(&file) {
            println!("ASPkg WARN: skipping unsafe filename '{}'", file);
            continue;
        }

        let url = format!("{}{}/{}", REGISTRY_URL, name, file);
        let bytes = match fetch_file(&url) {
            Some(b) => b,
            None => {
                println!("ASPkg WARN: failed to fetch '{}'", file);
                continue;
            }
        };

        let dest = pkg_dir.join(&file);
        if let Some(parent) = dest.parent() {
            let _ = fs::create_dir_all(parent);
        }
        if let Err(e) = fs::write(&dest, &bytes) {
            println!("ASPkg WARN: failed to write '{}': {}", file, e);
            continue;
        }
        count += 1;

        let content = String::from_utf8_lossy(&bytes);
        for imp in extract_imports(&content) {
            if !visited.contains(&imp) {
                queue.push(imp);
            }
        }
    }

    count
}

fn normalize_name(name: &str) -> String {
    name.trim().trim_end_matches(".aspkg").to_string()
}

fn parse_package_meta(content: &str) -> HashMap<String, String> {
    let mut meta = HashMap::new();

    for line in content.lines() {
        let trimmed = line.trim();

        if trimmed.eq_ignore_ascii_case("---code---") {
            break;
        }
        if trimmed.starts_with("---file:") {
            break;
        }

        if let Some(rest) = trimmed.strip_prefix('@') {
            if let Some((key, value)) = rest.split_once(' ') {
                meta.insert(key.trim().to_lowercase(), value.trim().to_string());
            }
        }
    }

    meta
}

fn safe_pkg_filename(name: &str) -> bool {
    !name.is_empty()
        && !name.contains("..")
        && !name.starts_with('/')
        && !name.contains('\\')
        && !name.contains(':')
}

fn fetch_file(url: &str) -> Option<Vec<u8>> {
    let resp = reqwest::blocking::get(url).ok()?;
    if !resp.status().is_success() {
        return None;
    }
    resp.bytes().ok().map(|b| b.to_vec())
}

fn install(name: &str) {
    let name = normalize_name(name);
    let single_path = package_path(&name);
    let dir_path = package_dir_path(&name);

    if single_path.exists() || dir_path.exists() {
        println!("ASPkg: '{}' already installed.", name);
        return;
    }

    println!("ASPkg: downloading '{}'", name);

    let cfg_url = format!("{}{}/package.aspkg", REGISTRY_URL, name);
    if let Some(cfg_bytes) = fetch_file(&cfg_url) {
        let cfg_text = String::from_utf8_lossy(&cfg_bytes).to_string();
        let meta = parse_package_meta(&cfg_text);

        let main_file = meta
            .get("main")
            .cloned()
            .unwrap_or_else(|| "main.ayaan".to_string());

        if !safe_pkg_filename(&main_file) {
            println!("ASPkg ERROR: unsafe @main filename '{}'", main_file);
            return;
        }

        if let Err(e) = fs::create_dir_all(&dir_path) {
            println!("ASPkg ERROR: failed to create package dir: {}", e);
            return;
        }

        if let Err(e) = fs::write(package_config_path(&name), &cfg_bytes) {
            println!("ASPkg ERROR: failed to write package.aspkg: {}", e);
            return;
        }

        let count = fetch_recursive(&name, &main_file, &dir_path);
        let total = count + 1;
        println!(
            "ASPkg: installed '{}' ({} file{})",
            name,
            total,
            if total == 1 { "" } else { "s" }
        );
        return;
    }

    let url = format!("{}{}.aspkg", REGISTRY_URL, name);
    let bytes = match fetch_file(&url) {
        Some(b) => b,
        None => {
            println!("ASPkg ERROR: package not found");
            return;
        }
    };

    if let Err(e) = fs::write(&single_path, bytes) {
        println!("ASPkg ERROR: failed to write package: {}", e);
        return;
    }

    println!("ASPkg: installed '{}'", name);
}

fn remove(name: &str) {
    let name = normalize_name(name);
    let single_path = package_path(&name);
    let dir_path = package_dir_path(&name);

    if dir_path.exists() && dir_path.is_dir() {
        match fs::remove_dir_all(&dir_path) {
            Ok(_) => println!("ASPkg: removed '{}'", name),
            Err(e) => println!("ASPkg ERROR: failed to remove '{}': {}", name, e),
        }
        return;
    }

    if single_path.exists() {
        match fs::remove_file(&single_path) {
            Ok(_) => println!("ASPkg: removed '{}'", name),
            Err(e) => println!("ASPkg ERROR: failed to remove '{}': {}", name, e),
        }
        return;
    }

    println!("ASPkg: '{}' not installed.", name);
}

fn compile(input: &str, output: Option<&str>) {
    use std::collections::HashSet;

    let dir = PathBuf::from(input);
    if !dir.exists() || !dir.is_dir() {
        println!("ASPkg ERROR: '{}' is not a directory", input);
        return;
    }

    let cfg_path = dir.join("package.aspkg");
    if !cfg_path.exists() {
        println!("ASPkg ERROR: missing package.aspkg in '{}'", input);
        return;
    }

    let cfg = match fs::read_to_string(&cfg_path) {
        Ok(c) => c,
        Err(e) => {
            println!("ASPkg ERROR: failed to read package.aspkg: {}", e);
            return;
        }
    };
    let meta = parse_package_meta(&cfg);

    let pkg_name = meta
        .get("name")
        .cloned()
        .unwrap_or_else(|| "package".to_string());
    let main_file = meta
        .get("main")
        .cloned()
        .unwrap_or_else(|| "main.ayaan".to_string());

    if !safe_pkg_filename(&main_file) {
        println!("ASPkg ERROR: unsafe @main filename '{}'", main_file);
        return;
    }

    if !dir.join(&main_file).exists() {
        println!("ASPkg ERROR: main file '{}' not found", main_file);
        return;
    }

    let mut visited: HashSet<String> = HashSet::new();
    let mut order: Vec<String> = Vec::new();
    let mut queue: Vec<String> = vec![main_file.clone()];

    while let Some(file) = queue.pop() {
        if !visited.insert(file.clone()) {
            continue;
        }
        if !safe_pkg_filename(&file) {
            println!("ASPkg WARN: skipping unsafe '{}'", file);
            continue;
        }
        let file_path = dir.join(&file);
        if !file_path.exists() {
            println!("ASPkg WARN: '{}' not found, skipping", file);
            continue;
        }
        order.push(file.clone());
        let content = fs::read_to_string(&file_path).unwrap_or_default();
        for imp in extract_imports(&content) {
            if !visited.contains(&imp) {
                queue.push(imp);
            }
        }
    }

    if let Some(pos) = order.iter().position(|f| *f == main_file) {
        let m = order.remove(pos);
        order.insert(0, m);
    }

    let mut out = String::new();
    if let Some(v) = meta.get("name") {
        out.push_str(&format!("@name {}\n", v));
    }
    if let Some(v) = meta.get("version") {
        out.push_str(&format!("@version {}\n", v));
    }
    if let Some(v) = meta.get("description") {
        out.push_str(&format!("@description {}\n", v));
    }
    out.push_str(&format!("@main {}\n", main_file));

    for file in &order {
        let file_path = dir.join(file);
        let content = fs::read_to_string(&file_path).unwrap_or_default();
        out.push_str(&format!("---file: {}---\n", file));
        out.push_str(&content);
        if !content.ends_with('\n') {
            out.push('\n');
        }
    }

    let output_path = match output {
        Some(o) => PathBuf::from(o),
        None => PathBuf::from(format!("{}.aspkg", pkg_name)),
    };

    match fs::write(&output_path, &out) {
        Ok(_) => println!(
            "ASPkg: compiled '{}' -> {} ({} file{})",
            pkg_name,
            output_path.display(),
            order.len(),
            if order.len() == 1 { "" } else { "s" }
        ),
        Err(e) => println!("ASPkg ERROR: failed to write output: {}", e),
    }
}

fn reinstall(name: &str) {
    let name = normalize_name(name);
    let single_path = package_path(&name);
    let dir_path = package_dir_path(&name);

    if dir_path.exists() {
        let _ = fs::remove_dir_all(&dir_path);
    }
    if single_path.exists() {
        let _ = fs::remove_file(&single_path);
    }

    install(&name);
}

fn update(name: &str) {
    reinstall(name);
}

fn list() {
    let dir = package_dir();

    let entries = match fs::read_dir(&dir) {
        Ok(e) => e,
        Err(e) => {
            println!("ASPkg ERROR: failed to read package directory: {}", e);
            return;
        }
    };

    let mut rows: Vec<String> = Vec::new();

    for entry in entries.flatten() {
        let path = entry.path();

        if path.is_dir() {
            let cfg_path = path.join("package.aspkg");
            if !cfg_path.exists() {
                continue;
            }
            let pkg_name = path
                .file_name()
                .map(|s| s.to_string_lossy().to_string())
                .unwrap_or_else(|| "unknown".into());

            let content = fs::read_to_string(&cfg_path).unwrap_or_default();
            let meta = parse_package_meta(&content);
            let version = meta
                .get("version")
                .cloned()
                .unwrap_or_else(|| "unknown".into());

            rows.push(format!(" - {} (v{}) [multi-file]", pkg_name, version));
            continue;
        }

        if path.extension().and_then(|e| e.to_str()) == Some("aspkg") {
            let pkg_name = path
                .file_stem()
                .map(|s| s.to_string_lossy().to_string())
                .unwrap_or_else(|| "unknown".into());

            let content = fs::read_to_string(&path).unwrap_or_default();
            let meta = parse_package_meta(&content);
            let version = meta.get("version").cloned().unwrap_or_else(|| "unknown".into());

            rows.push(format!(" - {} (v{})", pkg_name, version));
        }
    }

    rows.sort();

    println!("Installed packages:");
    if rows.is_empty() {
        println!(" (none)");
    } else {
        for row in rows {
            println!("{}", row);
        }
    }
}

fn info(name: &str) {
    let name = normalize_name(name);
    let single_path = package_path(&name);
    let dir_path = package_dir_path(&name);
    let cfg_path = package_config_path(&name);

    let (read_from, display_path, is_multi) = if cfg_path.exists() {
        (cfg_path.clone(), dir_path.clone(), true)
    } else if single_path.exists() {
        (single_path.clone(), single_path.clone(), false)
    } else {
        println!("ASPkg: '{}' not installed.", name);
        return;
    };

    let content = match fs::read_to_string(&read_from) {
        Ok(c) => c,
        Err(e) => {
            println!("ASPkg ERROR: failed to read '{}': {}", name, e);
            return;
        }
    };

    let meta = parse_package_meta(&content);

    println!("Package info: {}", name);
    println!("Path: {}", display_path.display());
    println!("Layout: {}", if is_multi { "multi-file" } else { "single-file" });

    if meta.is_empty() {
        println!("Metadata: none (legacy package format)");
    } else {
        println!(
            "Name: {}",
            meta.get("name").cloned().unwrap_or_else(|| name.clone())
        );
        println!(
            "Version: {}",
            meta.get("version")
                .cloned()
                .unwrap_or_else(|| "unknown".into())
        );
        println!(
            "Author: {}",
            meta.get("author")
                .cloned()
                .unwrap_or_else(|| "unknown".into())
        );
        println!(
            "Description: {}",
            meta.get("description")
                .cloned()
                .unwrap_or_else(|| "none".into())
        );
        if let Some(main) = meta.get("main") {
            println!("Main: {}", main);
        }
    }
}

fn print_help() {
    println!("ASPkg v{} - AyaanScriptPlus Package Manager", ASPKG_VERSION);
    println!();
    println!("Package operations:");
    println!("  aspkg install <name>           Install a package from the registry");
    println!("  aspkg add <name>               Alias for install");
    println!("  aspkg remove <name>            Uninstall a package");
    println!("  aspkg reinstall <name>         Force re-download and reinstall");
    println!("  aspkg update <name>            Refresh a package (same as reinstall)");
    println!("  aspkg upgrade-all              Reinstall every installed package");
    println!("  aspkg outdated                 Show installed packages older than registry");
    println!();
    println!("Discovery & inspection:");
    println!("  aspkg search <query>           Search the registry by name");
    println!("  aspkg list                     List installed packages");
    println!("  aspkg info <name>              Show local package metadata");
    println!("  aspkg deps <name>              Show import dependency tree");
    println!();
    println!("Authoring & running:");
    println!("  aspkg init <name>              Scaffold a new package in ./<name>/");
    println!("  aspkg compile <dir> [out]      Bundle a package dir into one .aspkg file");
    println!("  aspkg run <name> [args...]     Execute an installed package via ayaan");
    println!();
    println!("Utilities:");
    println!("  aspkg path                     Print package storage directory");
    println!("  aspkg version                  Print aspkg version");
    println!("  aspkg help                     Show this help");
}

// ===== NEW COMMAND: init =====
fn init_pkg(name: &str) {
    let dir = PathBuf::from(name);
    if dir.exists() {
        println!("ASPkg ERROR: directory '{}' already exists", name);
        return;
    }
    if let Err(e) = fs::create_dir_all(&dir) {
        println!("ASPkg ERROR: failed to create dir: {}", e);
        return;
    }
    let cfg = format!(
        "@name {}\n\
         @version 0.1.0\n\
         @author you\n\
         @description Brand new AyaanScript+ package.\n\
         @main main.ayaan\n",
        name
    );
    let main_src = format!(
        ": main.ayaan — entry point for the '{}' package.\n\
         : Edit me to do something useful.\n\
         \n\
         psyska -/ \"Hello from {}!\"\n",
        name, name
    );
    let _ = fs::write(dir.join("package.aspkg"), cfg);
    let _ = fs::write(dir.join("main.ayaan"), main_src);
    let _ = fs::write(dir.join(".gitignore"), "*.aspkg\n");
    println!("ASPkg: scaffolded package '{}' in ./{}", name, name);
    println!("       edit ./{}/main.ayaan, then 'aspkg compile {}'.", name, name);
}

// ===== NEW COMMAND: search =====
fn search(query: &str) {
    let q = query.to_lowercase();
    let bytes = match fetch_file(REGISTRY_INDEX) {
        Some(b) => b,
        None => {
            println!("ASPkg: couldn't fetch registry index. Try later.");
            return;
        }
    };
    let text = String::from_utf8_lossy(&bytes);
    let mut matches: Vec<&str> = Vec::new();
    for line in text.lines() {
        let trimmed = line.trim();
        if trimmed.is_empty() || trimmed.starts_with('#') { continue; }
        if trimmed.to_lowercase().contains(&q) {
            matches.push(trimmed);
        }
    }
    if matches.is_empty() {
        println!("ASPkg: no packages match '{}'", query);
        return;
    }
    println!("Matches for '{}':", query);
    for m in matches {
        println!(" - {}", m);
    }
}

// ===== NEW COMMAND: outdated =====
fn outdated() {
    let dir = package_dir();
    let entries = match fs::read_dir(&dir) {
        Ok(e) => e,
        Err(e) => { println!("ASPkg ERROR: {}", e); return; }
    };
    let mut any = false;
    for entry in entries.flatten() {
        let path = entry.path();
        let (pkg_name, local_ver) = if path.is_dir() {
            let cfg = path.join("package.aspkg");
            if !cfg.exists() { continue; }
            let n = path.file_name().map(|s| s.to_string_lossy().to_string()).unwrap_or_default();
            let c = fs::read_to_string(&cfg).unwrap_or_default();
            let m = parse_package_meta(&c);
            (n, m.get("version").cloned().unwrap_or_else(|| "unknown".into()))
        } else if path.extension().and_then(|e| e.to_str()) == Some("aspkg") {
            let n = path.file_stem().map(|s| s.to_string_lossy().to_string()).unwrap_or_default();
            let c = fs::read_to_string(&path).unwrap_or_default();
            let m = parse_package_meta(&c);
            (n, m.get("version").cloned().unwrap_or_else(|| "unknown".into()))
        } else {
            continue;
        };
        // fetch remote version
        let url = format!("{}{}/package.aspkg", REGISTRY_URL, pkg_name);
        let remote_ver = fetch_file(&url)
            .map(|b| String::from_utf8_lossy(&b).to_string())
            .map(|s| parse_package_meta(&s).get("version").cloned().unwrap_or_else(|| "?".into()))
            .unwrap_or_else(|| "(not in registry)".into());
        if remote_ver != local_ver && remote_ver != "(not in registry)" {
            println!("  {} : local v{} -> remote v{}", pkg_name, local_ver, remote_ver);
            any = true;
        }
    }
    if !any {
        println!("ASPkg: all packages up to date.");
    }
}

// ===== NEW COMMAND: upgrade-all =====
fn upgrade_all() {
    let dir = package_dir();
    let entries = match fs::read_dir(&dir) {
        Ok(e) => e,
        Err(e) => { println!("ASPkg ERROR: {}", e); return; }
    };
    let mut names: Vec<String> = Vec::new();
    for entry in entries.flatten() {
        let path = entry.path();
        if path.is_dir() {
            if path.join("package.aspkg").exists() {
                if let Some(n) = path.file_name() { names.push(n.to_string_lossy().to_string()); }
            }
        } else if path.extension().and_then(|e| e.to_str()) == Some("aspkg") {
            if let Some(n) = path.file_stem() { names.push(n.to_string_lossy().to_string()); }
        }
    }
    if names.is_empty() {
        println!("ASPkg: no packages installed.");
        return;
    }
    println!("ASPkg: upgrading {} package(s)...", names.len());
    for n in &names {
        reinstall(n);
    }
}

// ===== NEW COMMAND: deps =====
fn deps(name: &str) {
    let name = normalize_name(name);
    let dir_path = package_dir_path(&name);
    let single_path = package_path(&name);

    if dir_path.exists() {
        let cfg_path = package_config_path(&name);
        let cfg_content = fs::read_to_string(&cfg_path).unwrap_or_default();
        let meta = parse_package_meta(&cfg_content);
        let main = meta.get("main").cloned().unwrap_or_else(|| "main.ayaan".into());
        println!("Dependencies for '{}' (entry: {}):", name, main);
        let mut visited: std::collections::HashSet<String> = std::collections::HashSet::new();
        walk_deps(&dir_path, &main, &mut visited, 0);
    } else if single_path.exists() {
        println!("Package '{}' is single-file (no separate dependencies).", name);
    } else {
        println!("ASPkg: '{}' not installed.", name);
    }
}

fn walk_deps(dir: &Path, file: &str, visited: &mut std::collections::HashSet<String>, depth: usize) {
    let indent = "  ".repeat(depth);
    if !visited.insert(file.to_string()) {
        println!("{}- {} (already shown)", indent, file);
        return;
    }
    println!("{}- {}", indent, file);
    let content = fs::read_to_string(dir.join(file)).unwrap_or_default();
    for imp in extract_imports(&content) {
        walk_deps(dir, &imp, visited, depth + 1);
    }
}

// ===== NEW COMMAND: run =====
fn run_pkg(name: &str, extra_args: &[String]) {
    let name = normalize_name(name);
    let dir_path = package_dir_path(&name);
    let single_path = package_path(&name);

    let target = if dir_path.exists() {
        let cfg = package_config_path(&name);
        let content = fs::read_to_string(&cfg).unwrap_or_default();
        let meta = parse_package_meta(&content);
        let main = meta.get("main").cloned().unwrap_or_else(|| "main.ayaan".into());
        dir_path.join(main)
    } else if single_path.exists() {
        single_path.clone()
    } else {
        println!("ASPkg: '{}' not installed.", name);
        return;
    };

    if !target.exists() {
        println!("ASPkg ERROR: entry point '{}' missing", target.display());
        return;
    }

    // Try to find ayaan binary next to ourselves first; fall back to PATH lookup
    let ayaan = locate_ayaan();
    let mut cmd = Command::new(ayaan);
    cmd.arg(&target);
    for a in extra_args { cmd.arg(a); }
    match cmd.status() {
        Ok(s) if s.success() => {}
        Ok(s) => println!("ASPkg: exited with status {:?}", s.code()),
        Err(e) => println!("ASPkg ERROR: failed to launch ayaan: {}", e),
    }
}

fn locate_ayaan() -> PathBuf {
    // 1. neighbor of current exe
    if let Ok(this_exe) = env::current_exe() {
        if let Some(parent) = this_exe.parent() {
            let candidate = parent.join(if cfg!(windows) { "ayaan.exe" } else { "ayaan" });
            if candidate.exists() { return candidate; }
        }
    }
    // 2. fall back to PATH lookup
    PathBuf::from(if cfg!(windows) { "ayaan.exe" } else { "ayaan" })
}

fn main() {
    ensure_dirs();

    let args: Vec<String> = env::args().collect();

    if args.len() < 2 {
        print_help();
        return;
    }

    match args[1].as_str() {
        "install" | "add" | "i" => {
            if args.len() < 3 {
                println!("ASPkg ERROR: missing package name");
                return;
            }
            install(&args[2]);
        }
        "remove" | "uninstall" | "rm" | "delete" => {
            if args.len() < 3 {
                println!("ASPkg ERROR: missing package name");
                return;
            }
            remove(&args[2]);
        }
        "reinstall" => {
            if args.len() < 3 {
                println!("ASPkg ERROR: missing package name");
                return;
            }
            reinstall(&args[2]);
        }
        "update" | "upgrade" => {
            if args.len() < 3 {
                println!("ASPkg ERROR: missing package name");
                return;
            }
            update(&args[2]);
        }
        "list" | "ls" => list(),
        "info" => {
            if args.len() < 3 {
                println!("ASPkg ERROR: missing package name");
                return;
            }
            info(&args[2]);
        }
        "compile" | "build" | "bundle" => {
            if args.len() < 3 {
                println!("ASPkg ERROR: missing directory");
                return;
            }
            let output = args.get(3).map(|s| s.as_str());
            compile(&args[2], output);
        }
        "path" => {
            println!("{}", package_dir().display());
        }
        "init" | "new" => {
            if args.len() < 3 {
                println!("ASPkg ERROR: missing package name");
                println!("Usage: aspkg init <name>");
                return;
            }
            init_pkg(&args[2]);
        }
        "search" | "find" => {
            if args.len() < 3 {
                println!("ASPkg ERROR: missing search query");
                return;
            }
            search(&args[2]);
        }
        "outdated" => outdated(),
        "upgrade-all" | "upgrade_all" => upgrade_all(),
        "deps" | "dependencies" => {
            if args.len() < 3 {
                println!("ASPkg ERROR: missing package name");
                return;
            }
            deps(&args[2]);
        }
        "run" | "exec" => {
            if args.len() < 3 {
                println!("ASPkg ERROR: missing package name");
                return;
            }
            let extra: Vec<String> = args.iter().skip(3).cloned().collect();
            run_pkg(&args[2], &extra);
        }
        "version" | "--version" | "-V" | "-v" => {
            println!("aspkg {}", ASPKG_VERSION);
        }
        "help" | "--help" | "-h" => print_help(),
        _ => {
            println!("ASPkg ERROR: unknown command '{}'", args[1]);
            println!("Run: aspkg help");
        }
    }
}