quick-commit 0.2.7

Commit changes quickly
mod ai;
mod config;
mod git;
mod ui;

use colored::*;
use git2::Repository;
use std::env;
use std::thread;

fn main() {
    let verbose = parse_args();
    let repo = Repository::discover(".").unwrap_or_else(|_| {
        eprintln!("{}", "Error opening git repo •◠•".red());
        std::process::exit(1);
    });

    let repo_name = repo
        .path()
        .parent()
        .and_then(|p| p.file_name())
        .and_then(|n| n.to_str())
        .unwrap_or("no name");
    ui::print_repo_name(repo_name);

    git::stage_all();
    let (insertions, deletions, files) = git::get_stats(&repo).unwrap_or_else(|_| {
        eprintln!("{}", "Error reading git info •◠•".red());
        std::process::exit(1);
    });
    if files.is_empty() {
        println!("{}", "No changes to commit •◡•".yellow());
        std::process::exit(0);
    }
    ui::print_changes(&files, insertions, deletions);

    // Resolve config before spawning (may prompt user on first run)
    let api_key = config::get_api_key().unwrap_or_else(|e| {
        eprintln!("{}", format!("Error: {}", e).red());
        std::process::exit(1);
    });
    let model = config::get_model().unwrap_or_else(|e| {
        eprintln!("{}", format!("Error: {}", e).red());
        std::process::exit(1);
    });

    let semantic_types = config::get_semantic_types().unwrap_or_else(|e| {
        eprintln!("{}", format!("Error: {}", e).red());
        std::process::exit(1);
    });

    let diff = git::build_diff_context(&files, insertions, deletions);

    // Start AI generation in background — always request branch name so we
    // don't block on the user's branch choice before firing the request.
    let ai_handle = thread::spawn(move || {
        ai::generate_commit_info(&diff, true, &api_key, &model, &semantic_types)
    });

    // While AI is working, ask the user about creating a new branch
    let create_new_branch =
        ui::prompt_input("\nCreate new branch? (y/N): ").eq_ignore_ascii_case("y");

    // Wait for the AI result (spinner shown only while still pending)
    let spinner = ui::Spinner::start("Generating commit message...");
    let result = ai_handle
        .join()
        .unwrap_or_else(|_| Err("AI thread panicked".to_string()));
    spinner.stop();

    let generated = result.unwrap_or_else(|e| {
        eprintln!("{}", format!("AI generation failed: {}", e).red());
        eprintln!("{}", "Falling back to manual input.".yellow());
        ai::GeneratedCommitInfo {
            commit_message: String::new(),
            branch_name: Some(String::new()),
            stats: ai::QueryStats {
                total_time_ms: None,
                input_tokens: None,
                output_tokens: None,
            },
        }
    });

    if verbose {
        ui::print_ai_query_stats(&generated.stats);
    }

    // Present AI-generated text as editable — just press Enter to accept, or edit inline
    let final_message = ui::editable_prompt("Commit: ", &generated.commit_message.trim())
        .trim()
        .to_string();
    let final_branch = if create_new_branch {
        Some(
            ui::editable_prompt(
                "Branch: ",
                &generated.branch_name.unwrap_or_default().trim(),
            )
            .trim()
            .to_string(),
        )
    } else {
        None
    };

    if let Some(ref branch) = final_branch {
        git::create_branch(&repo, branch);
    }

    git::stage_and_commit(&final_message);

    let push_input = ui::prompt_input("Push to remote? (Y/n): ");
    if !push_input.eq_ignore_ascii_case("n") {
        git::push(&repo, final_branch.is_some());
    }
}

fn parse_args() -> bool {
    let mut verbose = false;

    for arg in env::args().skip(1) {
        match arg.as_str() {
            "-v" | "--verbose" => verbose = true,
            "-h" | "--help" => {
                println!("Usage: qc [-v|--verbose]");
                println!("  -v, --verbose    Show AI total time and tokens");
                std::process::exit(0);
            }
            _ => {
                eprintln!("{}", format!("Unknown argument: {}", arg).red());
                eprintln!("Usage: qc [-v|--verbose]");
                std::process::exit(2);
            }
        }
    }

    verbose
}