use std::collections::HashMap;
use std::time::{Duration, SystemTime};
use tokio::sync::Mutex;
use uuid::Uuid;
pub(crate) const EDIT_WINDOW: Duration = Duration::from_secs(15 * 60);
#[derive(Debug, Clone)]
pub(crate) struct OutboxEntry {
pub message_id: String,
pub body: String,
pub sent_at: SystemTime,
}
impl OutboxEntry {
pub fn new(message_id: impl Into<String>, body: impl Into<String>) -> Self {
Self {
message_id: message_id.into(),
body: body.into(),
sent_at: SystemTime::now(),
}
}
pub fn editable(&self) -> bool {
match self.sent_at.elapsed() {
Ok(age) => age < EDIT_WINDOW,
Err(_) => true,
}
}
}
#[derive(Default)]
pub(crate) struct Outbox {
entries: Mutex<HashMap<Uuid, OutboxEntry>>,
}
impl Outbox {
pub async fn record(&self, session: Uuid, entry: OutboxEntry) {
self.entries.lock().await.insert(session, entry);
}
pub async fn get(&self, session: Uuid) -> Option<OutboxEntry> {
self.entries.lock().await.get(&session).cloned()
}
pub async fn set_body(&self, session: Uuid, body: impl Into<String>) -> bool {
match self.entries.lock().await.get_mut(&session) {
Some(entry) => {
entry.body = body.into();
true
}
None => false,
}
}
pub async fn clear(&self, session: Uuid) -> Option<OutboxEntry> {
self.entries.lock().await.remove(&session)
}
#[cfg(test)]
pub async fn len(&self) -> usize {
self.entries.lock().await.len()
}
}
impl super::WhatsAppState {
pub(crate) async fn record_outbound(&self, session_id: Uuid, entry: OutboxEntry) {
self.outbox.record(session_id, entry).await;
}
pub(crate) async fn editable_outbound(&self, session_id: Uuid) -> Option<OutboxEntry> {
let entry = self.outbox.get(session_id).await?;
if entry.editable() {
return Some(entry);
}
tracing::debug!(
target: "whatsapp",
session = %session_id,
message_id = %entry.message_id,
"outbox entry past the 15-minute edit window; falling back to append"
);
self.outbox.clear(session_id).await;
None
}
pub(crate) async fn update_outbound_body(&self, session_id: Uuid, body: impl Into<String>) {
if !self.outbox.set_body(session_id, body).await {
tracing::debug!(
target: "whatsapp",
session = %session_id,
"outbox entry vanished before the body update; next chunk will send fresh"
);
}
}
pub(crate) async fn clear_outbound(&self, session_id: Uuid) {
self.outbox.clear(session_id).await;
}
}