//! Hamming distance computation and visual similarity trait.

use crate::error::Result;
use stemedb_core::types::{Hash, PHash};

/// Compute hamming distance between two 8-byte perceptual hashes.
///
/// Returns the number of differing bits (0-64). Lower distance means
/// more visually similar images.
///
/// # Example
///
/// ```rust
/// use stemedb_storage::hamming_distance;
///
/// let a = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
/// let b = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
/// assert_eq!(hamming_distance(&a, &b), 64); // All bits differ
///
/// let c = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01];
/// assert_eq!(hamming_distance(&a, &c), 1); // One bit differs
/// ```
#[inline]
pub fn hamming_distance(a: &PHash, b: &PHash) -> u32 {
    a.iter().zip(b.iter()).map(|(x, y)| (x ^ y).count_ones()).sum()
}

/// Trait for visual similarity indexes.
///
/// Implementations provide O(log N) search over perceptual hashes
/// using hamming distance as the metric.
pub trait VisualIndex: Send + Sync {
    /// Insert a visual hash associated with an assertion hash.
    ///
    /// # Arguments
    /// * `hash` - The content-addressed hash of the assertion
    /// * `phash` - The 8-byte perceptual hash
    fn insert(&self, hash: &Hash, phash: &PHash) -> Result<()>;

    /// Search for visual hashes within threshold hamming distance.
    ///
    /// # Arguments
    /// * `query` - The query perceptual hash
    /// * `threshold` - Maximum hamming distance (0-64)
    ///
    /// # Returns
    /// Vector of (assertion_hash, hamming_distance) pairs within threshold,
    /// sorted by distance ascending.
    fn search(&self, query: &PHash, threshold: u32) -> Result<Vec<(Hash, u32)>>;

    /// Get the number of indexed hashes.
    fn len(&self) -> Result<usize>;

    /// Check if the index is empty.
    fn is_empty(&self) -> Result<bool> {
        Ok(self.len()? == 0)
    }

    /// Checkpoint the index to disk.
    ///
    /// For in-memory indexes, this may be a no-op or return an error.
    fn checkpoint(&self) -> Result<()>;
}
