use std::collections::BTreeMap;
use std::path::PathBuf;
use std::process::Command;
use std::sync::OnceLock;
use serde::{Deserialize, Serialize};
use wipe_core::model::IdentityKind;
use wipe_core::{ops, GlobalConfig, Store};
static OVERRIDE: OnceLock<Option<String>> = OnceLock::new();
pub fn set_override(agentid: Option<String>) {
let _ = OVERRIDE.set(agentid.filter(|s| !s.trim().is_empty()));
}
fn override_id() -> Option<String> {
OVERRIDE.get().cloned().flatten()
}
pub fn resolve(explicit: Option<String>) -> String {
if let Some(a) = explicit.filter(|s| !s.trim().is_empty()) {
return a;
}
if let Some(a) = override_id() {
return a;
}
if let Some(a) = active() {
return a;
}
if let Ok(a) = std::env::var("WIPE_AUTHOR") {
if !a.trim().is_empty() {
return a;
}
}
let store = Store::discover(".").ok();
ops::resolve_identity(store.as_ref(), None)
}
pub fn source(explicit: Option<&str>) -> &'static str {
let prefer_default = {
let g = GlobalConfig::load();
g.prefer_default_identity.unwrap_or(false)
&& g.default_identity
.as_deref()
.map(|s| !s.trim().is_empty())
.unwrap_or(false)
};
if explicit.map(|s| !s.trim().is_empty()).unwrap_or(false) {
"explicit --author"
} else if override_id().is_some() {
"--agentid override"
} else if active().is_some() {
"session (wipe identity use)"
} else if std::env::var("WIPE_AUTHOR")
.map(|v| !v.trim().is_empty())
.unwrap_or(false)
{
"$WIPE_AUTHOR"
} else if prefer_default {
"global default (preferred)"
} else if Store::discover(".")
.ok()
.map(|s| wipe_core::vcs::identity(s.root()).is_some())
.unwrap_or_else(|| wipe_core::vcs::identity(std::path::Path::new(".")).is_some())
{
"project VCS"
} else {
"default identity"
}
}
#[derive(Debug, Default, Serialize, Deserialize)]
struct Sessions {
#[serde(default)]
active: BTreeMap<String, String>,
}
fn sessions_path() -> Option<PathBuf> {
if let Ok(dir) = std::env::var("WIPE_CONFIG_DIR") {
if !dir.trim().is_empty() {
return Some(PathBuf::from(dir).join("sessions.json"));
}
}
directories::ProjectDirs::from("dev", "wipe", "wipe")
.map(|d| d.config_dir().join("sessions.json"))
}
fn session_key() -> String {
std::env::var("WIPE_SESSION")
.ok()
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.unwrap_or_else(|| "default".to_string())
}
fn load_sessions() -> Sessions {
sessions_path()
.and_then(|p| std::fs::read(p).ok())
.and_then(|b| serde_json::from_slice(&b).ok())
.unwrap_or_default()
}
fn save_sessions(s: &Sessions) -> anyhow::Result<()> {
if let Some(path) = sessions_path() {
if let Some(dir) = path.parent() {
std::fs::create_dir_all(dir)?;
}
let mut json = serde_json::to_string_pretty(s).unwrap_or_default();
json.push('\n');
let tmp = path.with_extension("json.tmp");
std::fs::write(&tmp, json)?;
std::fs::rename(&tmp, &path)?;
}
Ok(())
}
pub fn active() -> Option<String> {
load_sessions()
.active
.get(&session_key())
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
}
pub fn set_active(author: &str) -> anyhow::Result<()> {
let mut s = load_sessions();
s.active.insert(session_key(), author.to_string());
save_sessions(&s)
}
pub fn clear_active() -> anyhow::Result<bool> {
let mut s = load_sessions();
let removed = s.active.remove(&session_key()).is_some();
if removed {
save_sessions(&s)?;
}
Ok(removed)
}
pub fn export_hint(author: &str) -> String {
if cfg!(windows) {
format!("$env:WIPE_AUTHOR = \"{author}\"")
} else {
format!("export WIPE_AUTHOR=\"{author}\"")
}
}
pub fn ensure_registered(id: &str, name: Option<&str>, agent: bool) {
let id = id.trim();
if id.is_empty() {
return;
}
if let Ok(store) = Store::discover(".") {
if store
.load_identities()
.map(|list| list.iter().any(|i| i.id == id))
.unwrap_or(false)
{
return; }
let kind = if agent {
IdentityKind::Agent
} else {
IdentityKind::Human
};
let _ = ops::upsert_identity(&store, id, name.unwrap_or(id), Some(kind));
}
}
pub fn git_available() -> bool {
Command::new("git")
.arg("--version")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
pub fn effective_default() -> String {
GlobalConfig::load()
.default_identity
.filter(|s| !s.trim().is_empty())
.unwrap_or_else(|| "human".to_string())
}