use std::fmt::Write as _;
use anyhow::Result;
use config::Config;
use pbap_core::{normalize_number, Contact};
use crate::cli::{path_of, PathArg};
use transport::iroh::Endpoint;
use crate::commands::conn;
fn paginate<T>(items: &[T], limit: Option<u16>, page: Option<u16>) -> &[T] {
let n = match limit {
None => return items,
Some(0) => return &[],
Some(n) => usize::from(n),
};
let p = usize::from(page.unwrap_or(1).max(1));
let start = p.saturating_sub(1).saturating_mul(n).min(items.len());
let end = start.saturating_add(n).min(items.len());
items.get(start..end).unwrap_or_default()
}
fn render_contact(c: &Contact, raw: bool) -> String {
let name = c.display_name.as_deref().unwrap_or("(unknown)");
let mut out =
String::with_capacity(name.len().saturating_add(c.phones().len().saturating_mul(20)));
out.push_str(name);
for tel in c.phones() {
let number = if raw { tel.to_owned() } else { normalize_number(tel) };
let _ = write!(out, "\n {number}");
}
out
}
fn render_contacts(contacts: &[Contact], raw: bool) -> String {
contacts.iter().map(|c| render_contact(c, raw)).collect::<Vec<_>>().join("\n\n")
}
fn render_entry(handle: &str, name: Option<&str>) -> String {
name.map_or_else(|| handle.to_owned(), |n| format!("{handle} {n}"))
}
pub(crate) struct ContactsOpts {
pub list: bool,
pub get: Option<String>,
pub lookup: Option<String>,
pub path: PathArg,
pub raw: bool,
pub limit: Option<u16>,
pub page: Option<u16>,
}
pub(crate) async fn run(
cfg: &Config,
endpoint: Option<&Endpoint>,
device: Option<&str>,
opts: ContactsOpts,
) -> Result<String> {
let ContactsOpts { list, get, lookup, path, raw, limit, page } = opts;
let pb = path_of(path);
let mut client = conn::connect_pbap(cfg, endpoint, device).await?;
let out = if let Some(number) = lookup {
client
.find_by_number(pb, &number)
.await?
.map_or_else(|| format!("no contact found for {number}"), |c| render_contact(&c, raw))
} else if let Some(handle) = get {
render_contact(&client.pull(pb, &handle).await?, raw)
} else if list {
let entries = client.list(pb).await?;
paginate(&entries, limit, page)
.iter()
.map(|e| render_entry(e.handle(), e.name()))
.collect::<Vec<_>>()
.join("\n")
} else {
let contacts = client.pull_all(pb).await?;
let page_contacts = paginate(&contacts, limit, page);
if page_contacts.is_empty() {
"(no contacts)".to_owned()
} else {
render_contacts(page_contacts, raw)
}
};
if let Err(e) = client.disconnect().await {
tracing::warn!("PBAP disconnect failed: {e}");
}
Ok(out)
}