use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
static ATOMIC_ANN_ID: AtomicU64 = AtomicU64::new(1);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AnnotationType {
Highlight,
Underline,
Bookmark,
Note,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Annotation {
pub id: String,
pub cfi_range: String,
pub type_: AnnotationType,
pub color: String,
pub note: Option<String>,
pub selected_text: Option<String>,
pub created_at: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AnnotationManager {
annotations: HashMap<String, Annotation>,
}
impl AnnotationManager {
pub fn new() -> Self {
Self {
annotations: HashMap::new(),
}
}
pub fn add(&mut self, annotation: Annotation) {
self.annotations.insert(annotation.id.clone(), annotation);
}
pub fn create_highlight(
&mut self,
cfi_range: &str,
color: &str,
selected_text: Option<&str>,
note: Option<&str>,
) -> Annotation {
let ann = Annotation {
id: generate_unique_id("hl"),
cfi_range: cfi_range.to_string(),
type_: AnnotationType::Highlight,
color: color.to_string(),
note: note.map(|s| s.to_string()),
selected_text: selected_text.map(|s| s.to_string()),
created_at: current_timestamp_str(),
};
self.add(ann.clone());
ann
}
pub fn create_bookmark(&mut self, cfi: &str, note: Option<&str>) -> Annotation {
let ann = Annotation {
id: generate_unique_id("bm"),
cfi_range: cfi.to_string(),
type_: AnnotationType::Bookmark,
color: "#f59e0b".to_string(),
note: note.map(|s| s.to_string()),
selected_text: None,
created_at: current_timestamp_str(),
};
self.add(ann.clone());
ann
}
pub fn get(&self, id: &str) -> Option<&Annotation> {
self.annotations.get(id)
}
pub fn remove(&mut self, id: &str) -> bool {
self.annotations.remove(id).is_some()
}
pub fn list(&self) -> Vec<Annotation> {
self.annotations.values().cloned().collect()
}
}
fn generate_unique_id(prefix: &str) -> String {
let seq = ATOMIC_ANN_ID.fetch_add(1, Ordering::Relaxed);
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
format!("{}-{:x}-{:x}", prefix, nanos, seq)
}
fn current_timestamp_str() -> String {
let secs = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
secs.to_string()
}