use alloc::{
format,
string::{String, ToString},
vec::Vec,
};
use io_replica::placement::{ReplicaLinkId, ReplicaMeta, ReplicaSortKey};
use serde::{Deserialize, Serialize};
use crate::conventions::{PimdirDerivation, fnv1a64, unfold};
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct PimdirCardMeta {
pub v: u8,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub uid: Option<String>,
#[serde(default, rename = "fn")]
pub fn_: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub emails: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub size: Option<u64>,
}
pub fn derive(body: &[u8]) -> PimdirDerivation {
let lines = unfold(body, false);
let uid = property(&lines, "UID");
let display_name = property(&lines, "FN").unwrap_or_default();
let link_id = match &uid {
Some(uid) => uid.clone(),
None => format!("hash:{}", fnv1a64(body)),
};
let meta = PimdirCardMeta {
v: 1,
uid,
fn_: display_name.clone(),
emails: properties(&lines, "EMAIL"),
size: Some(body.len() as u64),
};
PimdirDerivation {
link_id: ReplicaLinkId(link_id),
meta: ReplicaMeta(serde_json::to_string(&meta).unwrap_or_default()),
sort_key: ReplicaSortKey(sort_key(&display_name)),
}
}
fn sort_key(display_name: &str) -> String {
display_name.to_lowercase().trim().to_string()
}
fn property(lines: &[String], name: &str) -> Option<String> {
lines.iter().find_map(|line| value_of(line, name))
}
fn properties(lines: &[String], name: &str) -> Vec<String> {
lines
.iter()
.filter_map(|line| value_of(line, name))
.collect()
}
fn value_of(line: &str, name: &str) -> Option<String> {
let (head, value) = line.split_once(':')?;
let head = head.split(';').next().unwrap_or_default();
let property = head.rsplit('.').next().unwrap_or_default();
property
.trim()
.eq_ignore_ascii_case(name)
.then(|| value.to_string())
}