use serde_json::Value;
use std::collections::HashMap;
use std::fs;
use std::path::Path;
use crate::avatars::emojis::pick_emoji;
use crate::avatars::{
display_name_of, get_random_emoji_for_name, kind_of, load_avatars, role_of, save_avatars,
set_meta, MAIN_EMOJI,
};
use crate::commands::utils::input::{ask_string, ask_yes_no, default_yes};
use crate::renderer::table::render_table;
use colored::*;
use serde_json::json;
pub fn count_messages_per_avatar() -> HashMap<String, usize> {
let mut counts = HashMap::new();
let messages_dir = Path::new(".fur/messages");
if !messages_dir.exists() {
return counts;
}
if let Ok(entries) = fs::read_dir(messages_dir) {
for entry in entries.flatten() {
if let Ok(content) = fs::read_to_string(entry.path()) {
if let Ok(json) = serde_json::from_str::<Value>(&content) {
if let Some(avatar) = json["avatar"].as_str() {
*counts.entry(avatar.to_string()).or_insert(0) += 1;
}
}
}
}
}
counts
}
pub fn run_avatar_view() {
let avatars = load_avatars();
let Some(map) = avatars.as_object() else {
return;
};
if map.is_empty() {
println!("(no avatars yet)");
return;
}
let msg_counts = count_messages_per_avatar();
let any_role = map
.keys()
.any(|name| !crate::avatars::is_reserved_key(name) && role_of(&avatars, name).is_some());
let mut rows = Vec::new();
let mut active_idx = None;
for (name, value) in map.iter() {
if name == "main" {
let Some(target) = value.as_str() else {
continue;
};
let count = msg_counts.get(target).copied().unwrap_or(0);
let mut row = vec![
"⭐ main".to_string(),
target.to_string(),
count.to_string(),
kind_of(&avatars, target),
];
if any_role {
row.push(role_of(&avatars, target).unwrap_or_default());
}
active_idx = Some(rows.len());
rows.push(row);
continue;
}
if crate::avatars::is_reserved_key(name) {
continue;
}
let emoji = value.as_str().unwrap_or("🐾").to_string();
let count = msg_counts.get(name).copied().unwrap_or(0);
let mut row = vec![
name.to_string(),
emoji,
count.to_string(),
kind_of(&avatars, name),
];
if any_role {
row.push(role_of(&avatars, name).unwrap_or_default());
}
rows.push(row);
}
if any_role {
render_table(
"Avatars",
&["Role", "Emoji", "Messages", "Kind", "Function"],
rows,
active_idx,
true,
);
} else {
render_table(
"Avatars",
&["Role", "Emoji", "Messages", "Kind"],
rows,
active_idx,
true,
);
}
}
pub fn run_avatar_onboarding() {
let mut avatars = load_avatars();
println!("\n{}", "== Create Avatar ==".bright_magenta().bold());
println!(
"{}",
"A secondary avatar is anyone who isn’t you (the main user).\n\
Examples: an AI, your boss, your therapist, or your cat. \n\
If you choose [n], you’re creating or replacing the *main* avatar."
.bright_cyan()
);
let is_secondary = ask_yes_no("Secondary avatar? [Y/n]: ", default_yes);
if is_secondary {
create_secondary_avatar(&mut avatars);
} else {
create_main_avatar(&mut avatars);
}
save_avatars(&avatars);
println!("✅ Avatar creation complete. Use `fur avatar --view` to list all avatars.");
}
fn create_main_avatar(avatars: &mut serde_json::Value) {
let name = ask_string("Main avatar name [me]: ", Some("me"));
avatars["main"] = json!(name);
avatars[name.clone()] = json!(MAIN_EMOJI);
println!("[OK] Main avatar set: {}", name);
}
fn create_secondary_avatar(avatars: &mut serde_json::Value) {
let name = ask_string("Choose name [ai]: ", Some("ai"));
let suggested = get_random_emoji_for_name(&name);
let skip = ask_yes_no(
&format!("Use suggested emoji {}? [Y/n]: ", suggested),
default_yes,
);
let emoji = if skip {
suggested
} else {
pick_emoji("Your choice: ", None)
};
avatars[name.clone()] = json!(emoji);
println!(
"[OK] Other avatar '{}' created with emoji '{}'",
name, emoji
);
}
pub fn run_avatar_meta(
handle: &str,
display_name: Option<&str>,
role: Option<&str>,
kind: Option<&str>,
clear_role: bool,
) {
let mut avatars = load_avatars();
if avatars.get(handle).is_none() {
let emoji = get_random_emoji_for_name(handle);
avatars[handle] = json!(emoji);
println!("[OK] Avatar '{}' created {}", handle, emoji);
}
if let Some(value) = display_name {
set_meta(&mut avatars, handle, "name", Some(value));
}
if clear_role {
set_meta(&mut avatars, handle, "role", None);
} else if let Some(value) = role {
set_meta(&mut avatars, handle, "role", Some(value));
}
if let Some(value) = kind {
set_meta(&mut avatars, handle, "kind", Some(value));
}
save_avatars(&avatars);
println!(
"🏷️ @{} · {} · {} · {}",
handle.bright_yellow(),
display_name_of(&avatars, handle).unwrap_or_else(|| "—".to_string()),
kind_of(&avatars, handle),
role_of(&avatars, handle).unwrap_or_else(|| "—".to_string())
);
}