cc-hook 0.1.0

A cross-platform CLI that installs a git commit-msg hook to enforce Conventional Commits
mod git;
mod hook;
mod output;
mod validation;

use std::process;

use color_eyre::eyre::{Context, Result, eyre};

fn main() -> Result<()> {
    color_eyre::install()?;
    output::enable_ansi_support();

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

    match args.get(1).map(|s| s.as_str()) {
        Some("install") => cmd_install(),
        Some("validate") => {
            let file = args
                .get(2)
                .ok_or_else(|| eyre!("uso: cc-hook validate <arquivo>"))?;
            cmd_validate(file)
        }
        _ => {
            print_usage();
            Ok(())
        }
    }
}

fn print_usage() {
    println!("cc-hook - Conventional Commits Hook");
    println!();
    println!("Uso:");
    println!("  cc-hook install              Instala o hook commit-msg no repositório atual");
    println!("  cc-hook validate <arquivo>   Valida a mensagem de commit no arquivo");
    println!();
    println!("Exemplos:");
    println!("  cc-hook install");
    println!("  cc-hook validate .git/COMMIT_EDITMSG");
}

fn cmd_install() -> Result<()> {
    let current_dir =
        std::env::current_dir().wrap_err("não foi possível obter o diretório atual")?;

    let git_root = match git::find_git_root(&current_dir) {
        Some(root) => root,
        None => {
            output::print_error(
                "Este diretório não é um repositório Git (não existe a pasta .git).",
            );
            println!();
            println!("Para inicializar um repositório Git, execute:");
            println!("  git init");
            process::exit(1);
        }
    };

    let hook_file = hook::hook_path(&git_root);

    if hook_file.exists() {
        output::print_warning("Já existe um hook commit-msg.");

        if !prompt_overwrite()? {
            output::print_info("Operação cancelada.");
            return Ok(());
        }
    }

    let backup = hook::install_hook(&git_root)
        .wrap_err("falha ao instalar o hook")?;

    if let Some(backup_path) = backup {
        output::print_info(&format!(
            "Backup criado: {}",
            backup_path.display()
        ));
    }

    output::print_success("Hook commit-msg configurado com sucesso!");
    println!();
    output::print_info(&format!(
        "O hook foi instalado em: {}",
        hook_file.display()
    ));
    println!();
    println!("🎯 A partir de agora, todos os commits deste repositório serão validados");
    println!("   automaticamente para seguir o padrão Conventional Commits.");
    println!();
    println!("💡 Para testar o hook, faça um commit:");
    println!("   git add .");
    println!("   git commit -m \"feat: adicionar nova funcionalidade\"");
    println!();
    println!("🔧 Para remover o hook, execute:");
    println!("   rm {}", hook_file.display());

    Ok(())
}

fn prompt_overwrite() -> Result<bool> {
    use std::io::{self, BufRead, Write};

    print!("Deseja sobrescrever? (y/N): ");
    io::stdout().flush()?;

    let mut input = String::new();
    io::stdin().lock().read_line(&mut input)?;

    Ok(input.trim().eq_ignore_ascii_case("y"))
}

fn cmd_validate(file: &str) -> Result<()> {
    let content = std::fs::read_to_string(file)
        .wrap_err_with(|| format!("não foi possível ler o arquivo: {file}"))?;

    match validation::validate_commit_message(&content) {
        validation::ValidationResult::Valid | validation::ValidationResult::Skip => {
            process::exit(0);
        }
        validation::ValidationResult::EmptyMessage => {
            output::print_error("Mensagem de commit vazia.");
            process::exit(1);
        }
        validation::ValidationResult::Invalid => {
            let first_line = content.lines().next().unwrap_or("").trim();
            output::print_validation_error(first_line);
            process::exit(1);
        }
    }
}