git-alias 1.2.0

A fast git alias tool with dual-style command support
mod alias;
mod config;
mod shell;
mod utils;

use colored::Colorize;
use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;

#[cfg(windows)]
fn enable_ansi_support() {
    unsafe {
        use windows::Win32::System::Console::{
            CONSOLE_MODE, GetConsoleMode, GetStdHandle, STD_OUTPUT_HANDLE,
        };
        use windows::Win32::System::LibraryLoader::{GetModuleHandleW, GetProcAddress};
        use windows::core::{s, w};

        let kernel32 = GetModuleHandleW(w!("kernel32")).unwrap();
        let set_console_mode_ptr = GetProcAddress(kernel32, s!("SetConsoleMode")).unwrap();
        let set_console_mode: extern "system" fn(
            windows::Win32::Foundation::HANDLE,
            CONSOLE_MODE,
        ) -> i32 = std::mem::transmute(set_console_mode_ptr);

        let console_handle = GetStdHandle(STD_OUTPUT_HANDLE).unwrap();
        let mut mode = CONSOLE_MODE(0);
        GetConsoleMode(console_handle, &mut mode).ok();
        mode |= CONSOLE_MODE(0x0004); // ENABLE_VIRTUAL_TERMINAL_PROCESSING
        set_console_mode(console_handle, mode);
    }
}

fn main() {
    #[cfg(windows)]
    enable_ansi_support();

    colored::control::set_override(true);

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

    let aliases = load_aliases();

    // 检查1: 阻止非 g 前缀的直接调用
    if !exe_name.starts_with('g') && aliases.contains_key(&exe_name) {
        eprintln!(
            "{}: '{}' is not a valid command",
            "Error".red().bold(),
            exe_name
        );
        eprintln!();
        eprintln!("Use 'g {}' instead", exe_name.green());
        std::process::exit(1);
    }

    if exe_name == "g" || exe_name == "git-alias" {
        handle_subcommand(&args, &aliases);
    } else {
        execute_alias(&exe_name, &args[1..], &aliases);
    }
}

fn get_exe_name() -> String {
    std::env::args()
        .next()
        .and_then(|p| {
            std::path::Path::new(&p)
                .file_stem()
                .map(|s| s.to_string_lossy().to_string())
        })
        .unwrap_or_else(|| "g".to_string())
}

fn load_aliases() -> HashMap<String, Vec<String>> {
    let mut aliases = alias::get_builtin_aliases();
    let config = config::load_config();
    aliases.extend(config.aliases);
    aliases
}

fn execute_alias(name: &str, args: &[String], aliases: &HashMap<String, Vec<String>>) {
    let config = config::load_config();

    if let Some(git_args) = aliases.get(name) {
        let mut full_args = git_args.clone();
        full_args.extend_from_slice(args);

        if config.verbose {
            eprintln!("{} git {}", "Executing:".cyan(), full_args.join(" "));
        }

        let exit_code = utils::execute_git_command(&full_args);
        std::process::exit(exit_code);
    } else {
        let mut full_args = vec![name.to_string()];
        full_args.extend_from_slice(args);

        if config.verbose {
            eprintln!("{} git {}", "Executing:".cyan(), full_args.join(" "));
        }

        let exit_code = utils::execute_git_command(&full_args);
        std::process::exit(exit_code);
    }
}

fn handle_subcommand(args: &[String], aliases: &HashMap<String, Vec<String>>) {
    if args.len() < 2 {
        print_help();
        return;
    }

    match args[1].as_str() {
        "list" => {
            let filter = args.get(2);
            alias::list_aliases(aliases, filter.map(|s| s.as_str()));
        }
        "completions" => {
            if let Some(shell) = args.get(2) {
                shell::print_completions(shell, aliases);
            } else {
                print_completions_help();
            }
        }
        "init" => {
            if let Err(e) = config::create_default_config() {
                eprintln!("{}: {}", "Error".red().bold(), e);
                std::process::exit(1);
            }
        }
        "install" => {
            if let Err(e) = install_all() {
                eprintln!("{}: {}", "Error".red().bold(), e);
                std::process::exit(1);
            }
        }
        "--help" | "-h" => {
            print_help();
        }
        "--version" | "-V" => {
            println!("git-alias 1.0.0");
        }
        alias_name => {
            // 检查2: 阻止 g g* 调用
            if alias_name.starts_with('g') && aliases.contains_key(alias_name) {
                eprintln!(
                    "{}: 'g {}' is not needed",
                    "Hint".yellow().bold(),
                    alias_name
                );
                eprintln!();
                eprintln!("Use '{}' directly instead", alias_name.green());
                std::process::exit(1);
            }

            if aliases.contains_key(alias_name) {
                let remaining_args = &args[2..];
                let git_args = aliases.get(alias_name).unwrap();
                let mut full_args = git_args.clone();
                full_args.extend_from_slice(remaining_args);

                let exit_code = utils::execute_git_command(&full_args);
                std::process::exit(exit_code);
            } else {
                let mut full_args = vec![alias_name.to_string()];
                full_args.extend_from_slice(&args[2..]);

                let exit_code = utils::execute_git_command(&full_args);
                std::process::exit(exit_code);
            }
        }
    }
}

fn install_all() -> Result<(), String> {
    let install_dir = get_install_dir()?;

    fs::create_dir_all(&install_dir).map_err(|e| {
        format!(
            "Failed to create directory {}: {}",
            install_dir.display(),
            e
        )
    })?;

    let current_exe = std::env::current_exe()
        .map_err(|e| format!("Failed to get current executable path: {}", e))?;

    let aliases = load_aliases();

    let mut installed = vec![];

    for alias_name in aliases.keys() {
        // 只安装 g 前缀的命令
        if !alias_name.starts_with('g') {
            continue;
        }

        let target_path = install_dir.join(alias_name);

        #[cfg(windows)]
        {
            fs::copy(&current_exe, &target_path).map_err(|e| {
                format!("Failed to copy binary to {}: {}", target_path.display(), e)
            })?;
        }

        #[cfg(unix)]
        {
            if target_path.exists() || target_path.symlink_metadata().is_ok() {
                fs::remove_file(&target_path)
                    .map_err(|e| format!("Failed to remove existing file: {}", e))?;
            }

            std::os::unix::fs::symlink(&current_exe, &target_path)
                .map_err(|e| format!("Failed to create symlink: {}", e))?;
        }

        installed.push(alias_name.clone());
    }

    // 安装 g 命令本身
    let g_path = install_dir.join("g");
    fs::copy(&current_exe, &g_path)
        .map_err(|e| format!("Failed to copy binary to {}: {}", g_path.display(), e))?;
    installed.push("g".to_string());

    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        let permissions = fs::Permissions::from_mode(0o755);
        fs::set_permissions(&g_path, permissions)
            .map_err(|e| format!("Failed to set permissions: {}", e))?;
    }

    println!("{}", "Installation successful!".green().bold());
    println!();
    println!("Installed to: {}", install_dir.display());
    println!("Total: {} commands installed", installed.len());
    println!();
    println!("Usage:");
    println!("  g s         # git status");
    println!("  gs          # git status");
    println!("  g ls        # git log --no-merges");
    println!("  gls         # git log --no-merges");
    println!();
    println!("Run: {} list", "g".green());

    Ok(())
}

fn get_install_dir() -> Result<PathBuf, String> {
    let home = dirs::home_dir().ok_or_else(|| "Failed to get home directory".to_string())?;

    let install_dir = home.join(".local").join("bin");

    Ok(install_dir)
}

fn print_help() {
    println!("{}", "Git-Alias - Fast Git Alias Tool".cyan().bold());
    println!();
    println!("{}", "USAGE:".yellow().bold());
    println!("  {} [args...]          Run git command directly", "gs".green());
    println!("  g {} [args...]        Run git command via g prefix", "s".green());
    println!();
    println!("{}", "SUBCOMMANDS:".yellow().bold());
    println!("  g list [filter]       List all aliases");
    println!("  g completions <sh>    Generate shell completions (bash/zsh/fish)");
    println!("  g init                Create config file at ~/.git-alias.toml");
    println!("  g install             Install all alias commands to ~/.local/bin");
    println!();
    println!("{}", "NOTES:".yellow().bold());
    println!("  - Direct call must start with 'g' (e.g., gs, gls)");
    println!("  - g prefix works with any alias (e.g., g s, g ls)");
    println!("  - g gs is not supported, use gs instead");
    println!("  - Run 'g list' to see all available aliases");
}

fn print_completions_help() {
    println!("{}", "Shell Completions".cyan().bold());
    println!();
    println!("{}", "Usage:".yellow().bold());
    println!("  {} <shell>", "g completions".green());
    println!();
    println!("{}", "Supported shells:".yellow().bold());
    println!("  {}    Bash shell", "bash".cyan());
    println!("  {}     Zsh shell", "zsh".cyan());
    println!("  {}    Fish shell", "fish".cyan());
    println!();
    println!("{}", "Examples:".yellow().bold());
    println!("  {} {}", "#".dimmed(), "Bash - 临时启用".white());
    println!("  {}", "source <(g completions bash)".green());
    println!();
    println!("  {} {}", "#".dimmed(), "Bash - 持久化 (添加到 ~/.bashrc)".white());
    println!("  {}", "echo 'source <(g completions bash)' >> ~/.bashrc".green());
    println!();
    println!("  {} {}", "#".dimmed(), "Zsh - 临时启用".white());
    println!("  {}", "source <(g completions zsh)".green());
    println!();
    println!("  {} {}", "#".dimmed(), "Zsh - 持久化 (添加到 ~/.zshrc)".white());
    println!("  {}", "echo 'source <(g completions zsh)' >> ~/.zshrc".green());
    println!();
    println!("  {} {}", "#".dimmed(), "Fish - 持久化".white());
    println!("  {}", "g completions fish > ~/.config/fish/completions/g.fish".green());
}