//! Vector search: HNSW graph construction, search, and kNN queries.
//!
//! See [[hierarchical-navigable-small-world]] and [[architecture-overview|milestone-4]].

pub mod hnsw;
pub mod quantize;
pub mod query;

/// Distance metric for vector similarity.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DistanceMetric {
    /// Cosine distance: 1 - cosine_similarity. Lower = more similar.
    Cosine,
    /// Negative dot product. Lower = more similar (for pre-normalized vectors).
    DotProduct,
    /// Euclidean (L2) distance. Lower = more similar.
    L2,
}

/// Compute distance between two vectors.
pub fn distance(a: &[f32], b: &[f32], metric: DistanceMetric) -> f32 {
    debug_assert_eq!(a.len(), b.len());
    match metric {
        DistanceMetric::Cosine => cosine_distance(a, b),
        DistanceMetric::DotProduct => -dot_product(a, b),
        DistanceMetric::L2 => l2_distance(a, b),
    }
}

fn dot_product(a: &[f32], b: &[f32]) -> f32 {
    a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
}

fn cosine_distance(a: &[f32], b: &[f32]) -> f32 {
    let dot = dot_product(a, b);
    let norm_a = a.iter().map(|x| x * x).sum::<f32>().sqrt();
    let norm_b = b.iter().map(|x| x * x).sum::<f32>().sqrt();
    let denom = norm_a * norm_b;
    if denom == 0.0 {
        1.0
    } else {
        1.0 - dot / denom
    }
}

fn l2_distance(a: &[f32], b: &[f32]) -> f32 {
    a.iter()
        .zip(b.iter())
        .map(|(x, y)| (x - y) * (x - y))
        .sum::<f32>()
        .sqrt()
}

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

    #[test]
    fn cosine_identical() {
        let v = vec![1.0, 2.0, 3.0];
        let d = distance(&v, &v, DistanceMetric::Cosine);
        assert!(d.abs() < 1e-5, "identical vectors should have cosine distance ~0, got {d}");
    }

    #[test]
    fn cosine_orthogonal() {
        let a = vec![1.0, 0.0];
        let b = vec![0.0, 1.0];
        let d = distance(&a, &b, DistanceMetric::Cosine);
        assert!((d - 1.0).abs() < 1e-5, "orthogonal vectors should have cosine distance ~1, got {d}");
    }

    #[test]
    fn cosine_opposite() {
        let a = vec![1.0, 0.0];
        let b = vec![-1.0, 0.0];
        let d = distance(&a, &b, DistanceMetric::Cosine);
        assert!((d - 2.0).abs() < 1e-5, "opposite vectors should have cosine distance ~2, got {d}");
    }

    #[test]
    fn dot_product_metric() {
        let a = vec![1.0, 2.0];
        let b = vec![3.0, 4.0];
        let d = distance(&a, &b, DistanceMetric::DotProduct);
        // dot = 1*3 + 2*4 = 11, negated = -11
        assert_eq!(d, -11.0);
    }

    #[test]
    fn l2_distance_metric() {
        let a = vec![0.0, 0.0];
        let b = vec![3.0, 4.0];
        let d = distance(&a, &b, DistanceMetric::L2);
        assert!((d - 5.0).abs() < 1e-5, "L2 distance should be 5.0, got {d}");
    }

    #[test]
    fn l2_identical() {
        let v = vec![1.0, 2.0, 3.0];
        let d = distance(&v, &v, DistanceMetric::L2);
        assert!(d.abs() < 1e-5);
    }

    #[test]
    fn zero_vector_cosine() {
        let a = vec![0.0, 0.0];
        let b = vec![1.0, 1.0];
        let d = distance(&a, &b, DistanceMetric::Cosine);
        assert_eq!(d, 1.0); // zero vector returns distance 1.0
    }

    #[test]
    fn unit_vectors() {
        let a = vec![1.0, 0.0, 0.0];
        let b = vec![0.0, 1.0, 0.0];
        let d_cos = distance(&a, &b, DistanceMetric::Cosine);
        let d_l2 = distance(&a, &b, DistanceMetric::L2);
        assert!((d_cos - 1.0).abs() < 1e-5);
        assert!((d_l2 - std::f32::consts::SQRT_2).abs() < 1e-5);
    }
}
