use std::collections::HashMap;
use std::path::PathBuf;
use anyhow::{Context, Result};
use serde_json::{json, Value};
use trusty_common::memory_core::room_identity::room_type_tag;
use trusty_common::memory_core::store::rooms::list_room_summaries;
use uuid::Uuid;
use super::helpers::open_palace_handle;
use crate::AppState;
pub(crate) const PALACE_INDEX_DETAIL_LIMIT: usize = 24;
pub(crate) enum PalaceScope {
Palace(String),
Index(Value),
}
pub(crate) async fn resolve_palace_or_index(
state: &AppState,
args: &Value,
tool: &str,
) -> Result<PalaceScope> {
if let Some(p) = args.get("palace").and_then(|v| v.as_str()) {
return Ok(PalaceScope::Palace(p.to_string()));
}
if let Some(p) = state.default_palace.clone() {
return Ok(PalaceScope::Palace(p));
}
Ok(PalaceScope::Index(palace_index(state, tool).await?))
}
pub(crate) async fn palace_index(state: &AppState, tool: &str) -> Result<Value> {
let root = state.data_root.clone();
let palaces = tokio::task::spawn_blocking(move || {
trusty_common::memory_core::PalaceRegistry::list_palaces(&root)
})
.await
.context("join list_palaces")??;
let total = palaces.len();
let dirs: Vec<(String, PathBuf)> = palaces
.iter()
.map(|p| (p.id.as_str().to_string(), p.data_dir.clone()))
.collect();
let state_for_rows = state.clone();
let rows = tokio::task::spawn_blocking(move || index_rows(&state_for_rows, dirs))
.await
.context("join palace index rows")?;
Ok(json!({
"status": "no_palace_specified",
"tool": tool,
"hint": format!(
"No 'palace' argument and no server default, so this is an index of \
the palaces on this host rather than an error. Retry {tool} with \
palace set to one of the ids below."
),
"palace_count": total,
"detail_limit": PALACE_INDEX_DETAIL_LIMIT,
"palaces": rows,
}))
}
fn index_rows(state: &AppState, dirs: Vec<(String, PathBuf)>) -> Vec<Value> {
let mut ordered: Vec<(Option<u64>, String)> = dirs
.into_iter()
.map(|(id, dir)| (crate::palace_last_used::read(&dir), id))
.collect();
ordered.sort_by(|a, b| b.0.cmp(&a.0).then_with(|| a.1.cmp(&b.1)));
ordered
.into_iter()
.enumerate()
.map(|(i, (last_used, id))| {
if i < PALACE_INDEX_DETAIL_LIMIT {
palace_row(state, &id, last_used)
} else {
json!({ "palace": id, "last_used_unix": last_used, "detail": "omitted" })
}
})
.collect()
}
fn palace_row(state: &AppState, id: &str, last_used_unix: Option<u64>) -> Value {
let handle = match open_palace_handle(state, id) {
Ok(h) => h,
Err(e) => {
return json!({
"palace": id,
"last_used_unix": last_used_unix,
"unreadable": format!("{e:#}"),
})
}
};
let drawer_count = handle.list_drawers(None, None, usize::MAX).len();
let per_room: HashMap<Uuid, usize> =
handle
.drawers
.read()
.iter()
.fold(HashMap::new(), |mut acc, d| {
*acc.entry(d.room_id).or_insert(0) += 1;
acc
});
let store = handle.kg.store();
let wing_count = store.list_wings().map(|w| w.len()).unwrap_or(0);
let rooms = list_room_summaries(&store).unwrap_or_default();
let room_rows: Vec<Value> = rooms
.iter()
.map(|r| {
json!({
"label": r.label,
"room_type": room_type_tag(&r.room_type),
"drawer_count": per_room.get(&r.id).copied().unwrap_or(0),
})
})
.collect();
json!({
"palace": id,
"drawer_count": drawer_count,
"room_count": rooms.len(),
"wing_count": wing_count,
"last_used_unix": last_used_unix,
"rooms": room_rows,
})
}