use std::collections::{HashMap, HashSet};
use std::sync::{Arc, Mutex};
use smos_domain::{Fact, FactId, FactStatus, Heat, MemoryKey, SessionId, Timestamp};
use crate::errors::RepoError;
use crate::ports::FactRepository;
use crate::types::SearchHit;
#[derive(Default, Clone)]
pub struct InMemoryFacts {
store: Arc<Mutex<HashMap<String, Fact>>>,
dedup_hits: Arc<Mutex<Vec<SearchHit>>>,
search_hits: Arc<Mutex<Vec<SearchHit>>>,
}
impl InMemoryFacts {
pub fn seed(&self, fact: Fact) {
self.store
.lock()
.unwrap()
.insert(fact.id().as_str().to_string(), fact);
}
pub fn get_clone(&self, id: &FactId) -> Option<Fact> {
self.store.lock().unwrap().get(id.as_str()).cloned()
}
pub fn script_dedup_hits(&self, hits: Vec<SearchHit>) {
*self.dedup_hits.lock().unwrap() = hits;
}
pub fn script_search_hits(&self, hits: Vec<SearchHit>) {
*self.search_hits.lock().unwrap() = hits;
}
pub fn is_empty(&self) -> bool {
self.store.lock().unwrap().is_empty()
}
pub fn contains(&self, id: &FactId) -> bool {
self.store.lock().unwrap().contains_key(id.as_str())
}
}
impl FactRepository for InMemoryFacts {
async fn save(&self, fact: &Fact) -> Result<(), RepoError> {
self.store
.lock()
.unwrap()
.insert(fact.id().as_str().to_string(), fact.clone());
Ok(())
}
async fn get(&self, id: &FactId, _memory_key: &MemoryKey) -> Result<Option<Fact>, RepoError> {
Ok(self.get_clone(id))
}
async fn list_accepted(&self, _memory_key: &MemoryKey) -> Result<Vec<Fact>, RepoError> {
Ok(self
.store
.lock()
.unwrap()
.values()
.filter(|f| f.status() == FactStatus::Accepted)
.cloned()
.collect())
}
async fn list_pending(&self, _memory_key: &MemoryKey) -> Result<Vec<Fact>, RepoError> {
Ok(self
.store
.lock()
.unwrap()
.values()
.filter(|f| f.status() == FactStatus::Pending)
.cloned()
.collect())
}
async fn list_memory_keys_for_session(
&self,
session_id: &SessionId,
) -> Result<Vec<MemoryKey>, RepoError> {
let mut out: Vec<MemoryKey> = Vec::new();
let mut seen: HashSet<String> = HashSet::new();
for fact in self.store.lock().unwrap().values() {
if !fact.source_sessions().iter().any(|s| s == session_id) {
continue;
}
let mk_str = fact.memory_key().as_str().to_string();
if seen.insert(mk_str) {
out.push(fact.memory_key().clone());
}
}
Ok(out)
}
async fn list_memory_keys(&self) -> Result<Vec<MemoryKey>, RepoError> {
let mut out: Vec<MemoryKey> = Vec::new();
let mut seen: HashSet<String> = HashSet::new();
for fact in self.store.lock().unwrap().values() {
let mk_str = fact.memory_key().as_str().to_string();
if seen.insert(mk_str) {
out.push(fact.memory_key().clone());
}
}
Ok(out)
}
async fn search_similar(
&self,
_embedding: Vec<f32>,
_memory_key: &MemoryKey,
_limit: usize,
) -> Result<Vec<SearchHit>, RepoError> {
Ok(self.search_hits.lock().unwrap().clone())
}
async fn search_for_dedup(
&self,
_embedding: Vec<f32>,
_memory_key: &MemoryKey,
_limit: usize,
) -> Result<Vec<SearchHit>, RepoError> {
Ok(self.dedup_hits.lock().unwrap().clone())
}
async fn update_heat_batch(
&self,
_ids: &[FactId],
_memory_key: &MemoryKey,
_heat_base: Heat,
_last_access: Timestamp,
) -> Result<(), RepoError> {
Ok(())
}
}