use crate::config::ReferenceEntry;
use crate::config::RepoConfigManager;
use crate::config::RepoMappingManager;
use crate::config::validation::canonical_reference_key;
use crate::config::validation::is_git_url;
use crate::config::validation::validate_reference_url;
use crate::git::utils::find_repo_root;
use crate::git::utils::get_control_repo_root;
use crate::git::utils::get_remote_url;
use crate::git::utils::is_git_repo;
use crate::utils::paths::expand_path;
use anyhow::Context;
use anyhow::Result;
use anyhow::bail;
use colored::Colorize;
use std::path::PathBuf;
#[expect(clippy::unused_async, reason = "async for command API consistency")]
pub async fn execute(input: String) -> Result<()> {
let repo_root = get_control_repo_root(&std::env::current_dir()?)?;
let mgr = RepoConfigManager::new(repo_root);
let mut cfg = mgr.ensure_v2_default()?;
let mut existing_keys = std::collections::HashSet::new();
for e in &cfg.references {
let url = match e {
ReferenceEntry::Simple(s) => s.as_str(),
ReferenceEntry::WithMetadata(rm) => rm.remote.as_str(),
};
if let Ok(key) = canonical_reference_key(url) {
existing_keys.insert(key);
}
}
let (final_url, local_path_for_mapping) = if is_git_url(&input) {
validate_reference_url(&input)?;
let key = canonical_reference_key(&input)?;
if existing_keys.contains(&key) {
println!(
"{}\nReference already exists (detected by normalized host/org/repo):\n {}",
"Note:".yellow(),
input
);
return Ok(());
}
(input, None)
} else {
let path = PathBuf::from(&input);
let expanded = expand_path(&path)?;
if !expanded.exists() {
bail!(
"Path does not exist: {}\n\nTo add a remote repository by URL:\n thoughts references add <git-url>",
expanded.display()
);
}
if !expanded.is_dir() {
bail!(
"Path is not a directory: {}\nPlease provide a directory path (the repository root).",
expanded.display()
);
}
if is_git_repo(&expanded) {
let url = get_remote_url(&expanded).context(
"Git repository has no 'origin' remote.\nAdd a remote first:\n git remote add origin <git-url>",
)?;
validate_reference_url(&url)?;
let key = canonical_reference_key(&url)?;
if existing_keys.contains(&key) {
println!(
"{}\nReference already exists for repository:\n {}\nLocal path was resolved to the same origin URL.",
"Note:".yellow(),
url
);
return Ok(());
}
let mut repo_mapping = RepoMappingManager::new()?;
repo_mapping.add_mapping(&url, expanded.clone(), false)?;
(url, Some(expanded))
} else if let Ok(repo_root) = find_repo_root(&expanded) {
bail!(
"Cannot add subdirectory as a reference:\n {}\n\nReferences are repo-level only.\nDetected repository root:\n {}\n\nTry one of:\n 1) Add the repository root as a reference:\n thoughts references add {}\n 2) If you need a subdirectory mount, use:\n thoughts mount add {}",
expanded.display(),
repo_root.display(),
repo_root.display(),
expanded.display(),
);
} else {
bail!(
"Path is not a git repository: {}\n\nInitialize and add a remote first:\n git init\n git remote add origin <git-url>\n thoughts references add <repo-root>",
expanded.display()
);
}
};
cfg.references
.push(ReferenceEntry::Simple(final_url.clone()));
let warnings = mgr.save_v2_validated(&cfg)?;
for w in warnings {
eprintln!("Warning: {w}");
}
println!("{} Added reference: {}", "✓".green(), final_url);
if let Some(lp) = local_path_for_mapping {
println!(
"Local repository mapped for sync:\n {} -> {}",
final_url,
lp.display()
);
}
println!("Run 'thoughts references sync' to clone/update and mount it.");
Ok(())
}