pub mod cosine;
pub mod dot;
pub mod l2;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Metric {
    Cosine,
    Dot,
    L2,
}

impl Metric {
    pub fn as_u8(self) -> u8 {
        match self {
            Metric::Cosine => 0,
            Metric::Dot    => 1,
            Metric::L2     => 2,
        }
    }

    pub fn from_u8(v: u8) -> crate::error::Result<Self> {
        match v {
            0 => Ok(Metric::Cosine),
            1 => Ok(Metric::Dot),
            2 => Ok(Metric::L2),
            _ => Err(crate::error::MonaVecError::InvalidMetric(v)),
        }
    }

    /// Higher is better for all metrics (L2 is negated).
    pub fn score(self, a: &[f32], b: &[f32]) -> f32 {
        match self {
            Metric::Cosine => cosine::score(a, b),
            Metric::Dot    => dot::score(a, b),
            Metric::L2     => l2::score(a, b),
        }
    }

    /// Adjust raw quantized dot-product score using the stored per-vector quant_norm.
    ///
    /// - Cosine: divide by quant_norm (RaBitQ length-renorm — removes quantization magnitude bias)
    /// - Dot:    no adjustment (magnitude is intentional signal)
    /// - L2:     subtract 0.5 × norm² (converts dot product to approximate -L2² ranking;
    ///           ||q||² is constant per query so it does not affect ranking)
    pub fn adjust_score(self, raw: f32, quant_norm: f32) -> f32 {
        match self {
            Metric::Cosine => if quant_norm > 1e-9 { raw / quant_norm } else { raw },
            Metric::Dot    => raw,
            Metric::L2     => raw - 0.5 * quant_norm * quant_norm,
        }
    }
}
