thoughts-bin 0.1.12

CLI for flexible thought management using filesystem mounts
use crate::config::RepoConfigManager;
use crate::config::RepoMappingManager;
use crate::config::validation::validate_reference_url;
use crate::git::clone::CloneOptions;
use crate::git::clone::clone_repository;
use crate::git::pull::pull_ff_only;
use crate::git::utils::HeadState;
use crate::git::utils::get_control_repo_root;
use crate::git::utils::get_head_state;
use anyhow::Result;
use colored::Colorize;
use thoughts_tool::repo_identity::RepoIdentity;

#[expect(clippy::unused_async, reason = "async for command API consistency")]
pub async fn execute(verbose: bool) -> Result<()> {
    let repo_root = get_control_repo_root(&std::env::current_dir()?)?;
    let mgr = RepoConfigManager::new(repo_root);
    let mut mapping_mgr = RepoMappingManager::new()?;

    let ds = mgr.load_desired_state()?.ok_or_else(|| {
        anyhow::anyhow!("No repository configuration found. Run 'thoughts init'.")
    })?;

    if ds.references.is_empty() {
        println!("No references configured.");
        return Ok(());
    }

    let mut cloned_count = 0;
    let mut updated_count = 0;
    let mut skipped_count = 0;
    let mut invalid_count = 0;

    for rm in &ds.references {
        let url = &rm.remote;
        let ref_name = rm.ref_name.as_deref();

        if let Err(e) = validate_reference_url(url) {
            println!(
                "{} Skipping invalid reference: {}\n{}",
                "".yellow(),
                url,
                e
            );
            invalid_count += 1;
            continue;
        }

        // Print canonical identity in verbose mode
        if verbose && let Ok(id) = RepoIdentity::parse(url) {
            let key = id.canonical_key();
            println!("Reference: {url}");
            println!("  canonical: {}/{}/{}", key.host, key.org_path, key.repo);
            if let Some(ref_name) = ref_name {
                println!("  ref: {ref_name}");
            }
        }

        if let Some(local_path) = mapping_mgr.resolve_reference_url(url, ref_name)? {
            if verbose {
                println!("  mapping: {}", local_path.display());
            }

            // Try fast-forward-only pull; skip detached/unborn head
            match get_head_state(&local_path) {
                Ok(HeadState::Attached(branch)) => {
                    match pull_ff_only(&local_path, "origin", Some(&branch)) {
                        Ok(()) => {
                            if verbose {
                                println!("  action: updated (ff-only)");
                            } else {
                                println!("{} Updated {} (ff-only)", "".green(), url);
                            }
                            updated_count += 1;
                            if let Err(e) = mapping_mgr.update_reference_sync_time(url, ref_name) {
                                tracing::warn!(
                                    url = %url,
                                    ref_name = ?ref_name,
                                    error = ?e,
                                    "Failed to update repos.json last_sync (continuing)"
                                );
                            }
                        }
                        Err(e) => {
                            if verbose {
                                println!("  action: FAILED - {e}");
                            } else {
                                println!(
                                    "{} Failed to update {} (continuing): {}",
                                    "".red(),
                                    url,
                                    e
                                );
                            }
                            skipped_count += 1;
                        }
                    }
                }
                Ok(HeadState::Detached | HeadState::Unborn(_)) | Err(_) => {
                    if verbose {
                        println!("  action: skipped (non-standard HEAD state)");
                    } else {
                        println!(
                            "{} Skipping update (non-standard HEAD state): {}",
                            "".yellow(),
                            url
                        );
                    }
                    skipped_count += 1;
                }
            }
            continue;
        }

        // Not mapped locally - clone to default path
        let default_path = RepoMappingManager::get_default_reference_clone_path(url, ref_name)?;

        if verbose {
            println!("  mapping: (none)");
            println!("  action: cloning to {}", default_path.display());
        }

        match clone_repository(&CloneOptions {
            url: url.clone(),
            target_path: default_path.clone(),
            branch: ref_name.map(std::string::ToString::to_string),
        }) {
            Ok(()) => {
                // Add mapping
                mapping_mgr.add_reference_mapping(url, ref_name, default_path, true)?;
                if !verbose {
                    println!("{} Cloned {}", "".green(), url);
                }
                cloned_count += 1;
                if let Err(e) = mapping_mgr.update_reference_sync_time(url, ref_name) {
                    tracing::warn!(
                        url = %url,
                        ref_name = ?ref_name,
                        error = ?e,
                        "Failed to update repos.json last_sync (continuing)"
                    );
                }
            }
            Err(e) => {
                if verbose {
                    println!("  action: FAILED - {e}");
                } else {
                    println!("{} Failed to clone {}: {}", "".red(), url, e);
                }
                skipped_count += 1;
            }
        }
    }

    println!();
    println!(
        "Cloned: {cloned_count}, Updated: {updated_count}, Skipped: {skipped_count}, Invalid: {invalid_count}"
    );

    if cloned_count + updated_count > 0 {
        println!("Run 'thoughts mount update' to mount the references.");
    }

    Ok(())
}