g-cli 0.1.0

Git that talks back. A human-friendly CLI wrapper for Git.
use colored::Colorize;
use dialoguer::Select;

use crate::git;

pub fn run_save(message: &str) {
    let files = git::parse_status();
    if files.is_empty() {
        println!();
        println!("  {} Nothing to stash — working directory is clean.", "".green());
        println!();
        return;
    }

    println!();

    let result = git::run(&["stash", "push", "-m", message]);
    if result.success {
        println!("  {} Stashed: \"{}\"", "".green().bold(), message.bold());
        println!();
        println!("  {} file(s) tucked away.", files.len());
        println!("  Restore later with {}", "g stash pop".cyan());
    } else {
        println!("  {} Failed: {}", "".red(), result.stderr);
    }
    println!();
}

pub fn run_pop() {
    let stashes = list_stashes();

    if stashes.is_empty() {
        println!();
        println!("  {} No stashes found.", "".yellow());
        println!();
        return;
    }

    println!();
    println!("  {}:", "Your stashes".bold());
    println!();

    let display: Vec<String> = stashes
        .iter()
        .map(|(idx, msg)| {
            format!("{}  {}", format!("stash@{{{}}}", idx).dimmed(), msg)
        })
        .collect();

    let refs: Vec<&str> = display.iter().map(|s| s.as_str()).collect();

    let selection = Select::new()
        .with_prompt("  Which stash to restore?")
        .items(&refs)
        .default(0)
        .interact();

    let selection = match selection {
        Ok(s) => s,
        Err(_) => {
            println!("  Cancelled.");
            println!();
            return;
        }
    };

    let (stash_idx, stash_msg) = &stashes[selection];
    let stash_ref = format!("stash@{{{}}}", stash_idx);

    println!();

    let result = git::run(&["stash", "pop", &stash_ref]);
    if result.success {
        println!(
            "  {} Restored: \"{}\"",
            "".green().bold(),
            stash_msg.bold()
        );
        println!("  Changes are back in your working directory.");
    } else {
        // Could be a conflict
        if result.stderr.contains("CONFLICT") || result.stdout.contains("CONFLICT") {
            println!(
                "  {} Restored with conflicts!",
                "".yellow().bold()
            );
            println!("  Some files have merge conflicts. Resolve them, then:");
            println!("    {} {}", "".dimmed(), "g save \"resolved stash conflicts\"".cyan());
        } else {
            println!("  {} Failed: {}", "".red(), result.stderr);
        }
    }
    println!();
}

pub fn run_list() {
    let stashes = list_stashes();

    if stashes.is_empty() {
        println!();
        println!("  {} No stashes found.", "".yellow());
        println!();
        return;
    }

    println!();
    println!("  {}:", "Your stashes".bold());
    println!();

    for (idx, msg) in &stashes {
        println!(
            "    {} {}",
            format!("{})", idx).dimmed(),
            msg
        );
    }

    println!();
    println!("  Restore with {}", "g stash pop".cyan());
    println!();
}

/// Returns Vec of (stash_index, message)
fn list_stashes() -> Vec<(usize, String)> {
    let result = git::run(&["stash", "list", "--pretty=format:%gd|%s"]);
    if !result.success || result.stdout.is_empty() {
        return vec![];
    }

    result
        .stdout
        .lines()
        .enumerate()
        .filter_map(|(i, line)| {
            let parts: Vec<&str> = line.splitn(2, '|').collect();
            if parts.len() < 2 {
                return None;
            }
            // Extract the message after "On branch: " or "WIP on branch: "
            let raw_msg = parts[1].trim();
            // stash messages look like "On main: my message" or "WIP on main: my message"
            let msg = if let Some(pos) = raw_msg.find(": ") {
                raw_msg[pos + 2..].to_string()
            } else {
                raw_msg.to_string()
            };
            Some((i, msg))
        })
        .collect()
}