use crate::core::pipeline::generate_manifest;
use crate::data::repository::{ISnapshotProvider, hydrate_snapshot};
use crate::data::swap::RegistryState;
use bistun_core::{
CapabilityManifest, Direction, LmsError, MorphType, SdkState, SegType, SyncMetrics, TraitKey,
TraitValue,
};
use std::sync::{Arc, RwLock};
use std::time::{SystemTime, UNIX_EPOCH};
#[cfg(feature = "async-worker")]
use std::time::Duration;
#[cfg(feature = "async-worker")]
use tokio::time;
use tracing::{error, info};
#[derive(Debug, Clone)]
pub struct LinguisticManager {
state: RegistryState,
status: Arc<RwLock<SdkState>>,
pub metrics: Arc<RwLock<SyncMetrics>>,
}
impl Default for LinguisticManager {
fn default() -> Self {
Self::new()
}
}
impl LinguisticManager {
pub fn new() -> Self {
Self {
state: RegistryState::new(),
status: Arc::new(RwLock::new(SdkState::Bootstrapping)),
metrics: Arc::new(RwLock::new(SyncMetrics::default())),
}
}
fn now_secs() -> u64 {
SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs()
}
pub async fn initialize(&self, provider: &impl ISnapshotProvider, public_key_b64: &str) {
self.metrics.write().unwrap().last_attempted_sync = Self::now_secs();
match hydrate_snapshot(provider, public_key_b64).await {
Ok(store) => {
self.state.swap_registry(store);
*self.status.write().unwrap() = SdkState::Ready;
self.metrics.write().unwrap().last_successful_sync = Self::now_secs();
info!("LinguisticManager initialized successfully.");
}
Err(e) => {
*self.status.write().unwrap() = SdkState::Degraded;
self.metrics.write().unwrap().sync_error_count += 1;
error!(
"LinguisticManager failed to initialize. Triggering Circuit Breaker. Reason: {}",
e
);
}
}
}
#[cfg(feature = "async-worker")]
pub fn spawn_background_sync<P>(&self, interval_secs: u64, provider: P, public_key_b64: String)
where
P: ISnapshotProvider + 'static,
{
let state = self.state.clone();
let status = self.status.clone();
let metrics = self.metrics.clone();
tokio::spawn(async move {
info!("Background sync worker started (Interval: {}s)", interval_secs);
let mut interval_timer = time::interval(Duration::from_secs(interval_secs));
loop {
interval_timer.tick().await;
match hydrate_snapshot(&provider, &public_key_b64).await {
Ok(store) => {
state.swap_registry(store);
let mut s = status.write().unwrap();
if *s != SdkState::Ready {
*s = SdkState::Ready;
}
info!("Background sync successful. Registry hot-swapped.");
}
Err(e) => {
metrics.write().unwrap().sync_error_count += 1;
error!(
"Background sync failed. Retaining current registry state. Reason: {}",
e
);
}
}
}
});
}
pub fn status(&self) -> SdkState {
*self.status.read().unwrap()
}
pub fn metrics(&self) -> SyncMetrics {
self.metrics.read().unwrap().clone()
}
pub fn resolve_capabilities(&self, tag: &str) -> Result<CapabilityManifest, LmsError> {
if self.status() == SdkState::Degraded {
return Ok(Self::generate_circuit_breaker_manifest());
}
generate_manifest(tag, &self.state)
}
fn generate_circuit_breaker_manifest() -> CapabilityManifest {
let mut manifest = CapabilityManifest::new("en-US".to_string());
manifest.traits.insert(TraitKey::PrimaryDirection, TraitValue::Direction(Direction::LTR));
manifest.traits.insert(TraitKey::HasBidiElements, TraitValue::Boolean(false));
manifest.traits.insert(TraitKey::RequiresShaping, TraitValue::Boolean(false));
manifest.traits.insert(TraitKey::SegmentationStrategy, TraitValue::SegType(SegType::SPACE));
manifest
.traits
.insert(TraitKey::MorphologyType, TraitValue::MorphType(MorphType::FUSIONAL));
manifest.metadata.insert("registry_version".to_string(), "CIRCUIT_BREAKER".to_string());
manifest.metadata.insert("resolution_path".to_string(), "DEGRADED_FALLBACK".to_string());
manifest.metadata.insert("resolution_time_ms".to_string(), "0.0000".to_string());
manifest
}
}
#[cfg(all(test, feature = "simulation"))]
mod tests {
use super::*;
use crate::data::repository::SimulatedSnapshotProvider;
#[test]
fn test_manager_starts_in_bootstrapping_state() {
let manager = LinguisticManager::new();
assert_eq!(manager.status(), SdkState::Bootstrapping);
}
#[tokio::test]
async fn test_manager_initializes_into_ready_state() {
let manager = LinguisticManager::new();
let provider = SimulatedSnapshotProvider::new();
manager.initialize(&provider, &provider.public_key).await;
assert_eq!(manager.status(), SdkState::Ready);
let metrics = manager.metrics();
assert!(metrics.last_successful_sync > 0);
assert_eq!(metrics.sync_error_count, 0);
}
#[tokio::test]
async fn test_manager_delegates_to_dynamic_pipeline() {
let manager = LinguisticManager::new();
let provider = SimulatedSnapshotProvider::new();
manager.initialize(&provider, &provider.public_key).await;
let manifest = manager.resolve_capabilities("th-TH").expect("SDK delegation failed");
assert_eq!(manifest.resolved_locale, "th-TH");
assert!(!manifest.traits.is_empty());
}
#[tokio::test]
async fn test_circuit_breaker_intercepts_requests() {
let manager = LinguisticManager::new();
*manager.status.write().unwrap() = SdkState::Degraded;
let manifest = manager.resolve_capabilities("ar-EG").expect("Circuit breaker failed");
assert_eq!(manifest.resolved_locale, "en-US");
assert_eq!(manifest.metadata.get("registry_version").unwrap(), "CIRCUIT_BREAKER");
}
}