selfware 0.6.1

Your personal AI workshop — software you own, software that lasts
//! Minimal local-first optimization module
//!
//! Provides basic offline support and response caching.

use std::collections::HashMap;

/// Local-first coordinator for offline support
#[derive(Debug)]
pub struct LocalFirstCoordinator {
    /// Cached responses
    response_cache: LocalCache<String>,
}

impl LocalFirstCoordinator {
    /// Create a new coordinator
    pub fn new() -> Self {
        Self {
            response_cache: LocalCache::new(),
        }
    }

    /// Cache a response
    pub fn cache_response(&mut self, key: &str, response: String, size_bytes: usize) {
        let entry = LocalCacheEntry {
            key: key.to_string(),
            value: response,
            size_bytes,
        };
        self.response_cache.put(entry);
    }

    /// Get statistics
    pub fn stats(&self) -> LocalFirstStats {
        let cache_stats = self.response_cache.stats();
        LocalFirstStats {
            offline_status: OfflineStatus::Online,
            bandwidth_saved_bytes: cache_stats.total_size_bytes,
            cache_stats,
        }
    }
}

impl Default for LocalFirstCoordinator {
    fn default() -> Self {
        Self::new()
    }
}

/// Simple local cache for generic values
#[derive(Debug)]
pub struct LocalCache<T> {
    entries: HashMap<String, LocalCacheEntry<T>>,
    max_entries: usize,
}

#[derive(Debug, Clone)]
pub struct LocalCacheEntry<T> {
    key: String,
    #[allow(dead_code)]
    value: T,
    size_bytes: usize,
}

impl LocalCache<String> {
    /// Create a new cache with default settings
    pub fn new() -> Self {
        Self {
            entries: HashMap::new(),
            max_entries: 1000,
        }
    }

    /// Insert an entry
    pub fn put(&mut self, entry: LocalCacheEntry<String>) {
        if self.entries.len() >= self.max_entries {
            // Simple eviction: remove first entry
            if let Some(first_key) = self.entries.keys().next().cloned() {
                self.entries.remove(&first_key);
            }
        }
        self.entries.insert(entry.key.clone(), entry);
    }

    /// Get cache statistics
    pub fn stats(&self) -> CacheStats {
        let total_size_bytes: usize = self.entries.values().map(|e| e.size_bytes).sum();
        CacheStats {
            entry_count: self.entries.len(),
            total_size_bytes,
            hit_rate: 0.0, // Simplified - no tracking in minimal version
        }
    }
}

impl Default for LocalCache<String> {
    fn default() -> Self {
        Self::new()
    }
}

/// Cache statistics
#[derive(Debug, Clone)]
pub struct CacheStats {
    pub entry_count: usize,
    pub total_size_bytes: usize,
    pub hit_rate: f64,
}

/// Offline status
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OfflineStatus {
    Online,
    #[allow(dead_code)]
    Offline,
}

impl std::fmt::Display for OfflineStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            OfflineStatus::Online => write!(f, "Online"),
            OfflineStatus::Offline => write!(f, "Offline"),
        }
    }
}

/// Local-first statistics
#[derive(Debug, Clone)]
pub struct LocalFirstStats {
    pub offline_status: OfflineStatus,
    pub bandwidth_saved_bytes: usize,
    pub cache_stats: CacheStats,
}

#[cfg(test)]
#[path = "../../tests/unit/session/local_first/local_first_test.rs"]
mod tests;