//! HNSW Index persistence methods
//!
//! Contains save/load functionality for persisting the index to disk.

use std::fs::File;
use std::io::{BufReader, BufWriter, Write};
use std::path::Path;
use std::sync::atomic::{AtomicUsize, AtomicU64, AtomicBool, Ordering as AtomicOrdering};

use parking_lot::RwLock;

use crate::types::{HNSWConfig, DistanceMetric};
use crate::vector::distance::DistanceCalculator;

use super::index::HNSWIndex;
use super::types::{HNSWPersisted, HNSWError};

impl HNSWIndex {
    // ============================================================================
    // Persistence Methods
    // ============================================================================
    
    /// Save the index to a file
    pub fn save(&self, path: impl AsRef<Path>) -> Result<(), HNSWError> {
        let path = path.as_ref();
        
        // Create parent directory if needed
        if let Some(parent) = path.parent() {
            std::fs::create_dir_all(parent)?;
        }
        
        let persisted = HNSWPersisted {
            config: self.config.clone(),
            nodes: self.nodes.read().clone(),
            entry_point: *self.entry_point.read(),
            max_level: self.max_level.load(AtomicOrdering::SeqCst),
            id_map: self.id_map.read().clone(),
            reverse_id_map: self.reverse_id_map.read().clone(),
            level_mult: self.level_mult,
            seed: self.seed,
            deleted: self.deleted.read().clone(),
            // P1-1: PQ data
            quantizer: self.quantizer.read().clone(),
            pq_codes: self.pq_codes.read().clone(),
            store_original: self.store_original,
        };
        
        let file = File::create(path)?;
        let mut writer = BufWriter::new(file);
        
        bincode::serialize_into(&mut writer, &persisted)
            .map_err(|e| HNSWError::Serialization(e.to_string()))?;
        
        writer.flush()?;
        
        Ok(())
    }
    
    /// Load an index from a file
    pub fn load(path: impl AsRef<Path>) -> Result<Self, HNSWError> {
        let path = path.as_ref();
        let file = File::open(path)?;
        let reader = BufReader::new(file);
        
        let persisted: HNSWPersisted = bincode::deserialize_from(reader)
            .map_err(|e| HNSWError::Serialization(e.to_string()))?;
        
        // Create per-node locks for loaded nodes
        let node_count = persisted.nodes.len();
        let neighbor_locks: Vec<RwLock<()>> = (0..node_count).map(|_| RwLock::new(())).collect();
        
        Ok(Self {
            config: persisted.config.clone(),
            distance: DistanceCalculator::new(DistanceMetric::Cosine),
            nodes: RwLock::new(persisted.nodes),
            neighbor_locks: RwLock::new(neighbor_locks),
            entry_point: RwLock::new(persisted.entry_point),
            max_level: AtomicUsize::new(persisted.max_level),
            id_map: RwLock::new(persisted.id_map),
            reverse_id_map: RwLock::new(persisted.reverse_id_map),
            level_mult: persisted.level_mult,
            seed: persisted.seed,
            deleted: RwLock::new(persisted.deleted),
            // P0-3: Transaction support
            active_transaction: RwLock::new(None),
            transaction_counter: AtomicU64::new(0),
            savepoints: RwLock::new(Vec::new()),
            // P1-1: PQ support
            quantizer: RwLock::new(persisted.quantizer),
            pq_codes: RwLock::new(persisted.pq_codes),
            store_original: persisted.store_original,
            // Optimization flags
            use_rng_heuristic: AtomicBool::new(true),
        })
    }
    
    /// Load an index from a file, or create a new one if the file doesn't exist
    pub fn load_or_create(path: impl AsRef<Path>, config: HNSWConfig) -> Result<Self, HNSWError> {
        let path = path.as_ref();
        
        if path.exists() {
            Self::load(path)
        } else {
            Ok(Self::new(config))
        }
    }
}
