//! Configuration types for AiFastDb

use serde::{Deserialize, Serialize};

/// HNSW Index Configuration
/// 
/// Controls the behavior and performance characteristics of the HNSW index.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct HNSWConfig {
    /// Maximum number of connections per node per layer (default: 16)
    /// Higher values improve recall but increase memory and build time
    /// Recommended: 16-64
    pub m: usize,
    
    /// Maximum number of connections for layer 0 (default: 2 * m)
    /// Layer 0 is the bottom layer with the most connections
    pub m_max0: usize,
    
    /// Size of the dynamic candidate list during construction (default: 200)
    /// Higher values improve index quality but slow down construction
    pub ef_construction: usize,
    
    /// Size of the dynamic candidate list during search (default: 50)
    /// Higher values improve recall but slow down search
    pub ef_search: usize,
    
    /// Vector dimension
    pub dimension: usize,
    
    /// Maximum number of elements (for pre-allocation)
    pub max_elements: usize,
    
    /// Random seed for reproducible index construction
    pub seed: Option<u64>,
}

impl Default for HNSWConfig {
    fn default() -> Self {
        Self {
            m: 16,
            m_max0: 32,
            ef_construction: 200,
            ef_search: 50,
            dimension: 1536, // OpenAI ada-002 default
            max_elements: 1_000_000,
            seed: None,
        }
    }
}

impl HNSWConfig {
    /// Create a new config with specified dimension
    pub fn new(dimension: usize) -> Self {
        Self {
            dimension,
            ..Default::default()
        }
    }
    
    /// Builder pattern: set M parameter
    pub fn with_m(mut self, m: usize) -> Self {
        self.m = m;
        self.m_max0 = m * 2;
        self
    }
    
    /// Builder pattern: set ef_construction
    pub fn with_ef_construction(mut self, ef: usize) -> Self {
        self.ef_construction = ef;
        self
    }
    
    /// Builder pattern: set ef_search
    pub fn with_ef_search(mut self, ef: usize) -> Self {
        self.ef_search = ef;
        self
    }
    
    /// Builder pattern: set max_elements
    pub fn with_max_elements(mut self, max: usize) -> Self {
        self.max_elements = max;
        self
    }
    
    /// Calculate the maximum level for a given number of elements
    /// level_mult = 1 / ln(M)
    pub fn max_level(&self, num_elements: usize) -> usize {
        if num_elements == 0 {
            return 0;
        }
        let level_mult = 1.0 / (self.m as f64).ln();
        (level_mult * (num_elements as f64).ln()).floor() as usize
    }
}

/// Distance metric for vector similarity
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DistanceMetric {
    /// Cosine similarity (1 - cosine_distance)
    Cosine,
    /// Euclidean (L2) distance
    Euclidean,
    /// Dot product (inner product)
    DotProduct,
}

impl Default for DistanceMetric {
    fn default() -> Self {
        Self::Cosine
    }
}

/// Storage backend type selector
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum StorageType {
    /// Legacy JSONL-based document storage (always available, fallback when lance-storage feature is disabled)
    Legacy,
    /// Lance columnar format — default backend (requires `lance-storage` feature)
    Lance,
}

impl Default for StorageType {
    fn default() -> Self {
        Self::Lance
    }
}

/// Storage configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct StorageConfig {
    /// Base path for database files
    pub path: String,
    
    /// Whether to use memory-mapped files
    pub use_mmap: bool,
    
    /// Maximum cache size in bytes
    pub cache_size: usize,
    
    /// Whether to sync writes to disk immediately
    pub sync_on_write: bool,
    
    /// Storage backend type (legacy JSONL or Lance columnar)
    pub backend: StorageType,
    
    /// Embedding dimension for Lance storage (auto-detected if None)
    pub embedding_dim: Option<i32>,
    
    /// Lance write batch size (number of records to buffer before flushing)
    pub lance_write_batch_size: usize,
}

impl Default for StorageConfig {
    fn default() -> Self {
        Self {
            path: "./aifastdb_data".to_string(),
            use_mmap: true,
            cache_size: 1024 * 1024 * 1024, // 1GB
            sync_on_write: false,
            backend: StorageType::Lance,
            embedding_dim: None,
            lance_write_batch_size: 256,
        }
    }
}

/// Main AiFastDb configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct AiFastDbConfig {
    /// HNSW index configuration
    pub hnsw: HNSWConfig,
    
    /// Storage configuration
    pub storage: StorageConfig,
    
    /// Distance metric for vector similarity
    pub distance_metric: DistanceMetric,
    
    /// Default decay rate for memory importance
    pub default_decay_rate: f64,
}

impl Default for AiFastDbConfig {
    fn default() -> Self {
        Self {
            hnsw: HNSWConfig::default(),
            storage: StorageConfig::default(),
            distance_metric: DistanceMetric::default(),
            default_decay_rate: 0.0001,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_hnsw_config_default() {
        let config = HNSWConfig::default();
        assert_eq!(config.m, 16);
        assert_eq!(config.m_max0, 32);
        assert_eq!(config.ef_construction, 200);
        assert_eq!(config.dimension, 1536);
    }

    #[test]
    fn test_hnsw_config_builder() {
        let config = HNSWConfig::new(768)
            .with_m(32)
            .with_ef_construction(400)
            .with_max_elements(10_000_000);
        
        assert_eq!(config.dimension, 768);
        assert_eq!(config.m, 32);
        assert_eq!(config.m_max0, 64);
        assert_eq!(config.ef_construction, 400);
        assert_eq!(config.max_elements, 10_000_000);
    }

    #[test]
    fn test_max_level_calculation() {
        let config = HNSWConfig::new(1536).with_m(16);
        
        // For M=16, level_mult = 1/ln(16) ≈ 0.3607
        // max_level = floor(level_mult * ln(n))
        assert_eq!(config.max_level(1), 0);
        assert_eq!(config.max_level(1000), 2);  // floor(0.3607 * 6.91) = 2
        assert_eq!(config.max_level(1_000_000), 4);  // floor(0.3607 * 13.82) = 4
    }
}
