//! Semantic Projector — 将高维 Embedding 投影到 3 维语义轴空间
//!
//! 灵感来源：Nature 2016 语义图谱论文（Huth et al.）+ BriLLM SiFu 架构
//!
//! 提供三种投影模式：
//! - **RandomProjector**：基于 Johnson-Lindenstrauss 引理的随机投影（无需训练）
//! - **PcaProjector**：PCA 主成分分析（从数据学习）
//! - **AnchorProjector**：锚点对齐（用户标注语义轴含义）

use crate::types::SemanticAxes;
use serde::{Deserialize, Serialize};
use std::io;
use std::path::Path;

/// 语义轴投影输出维度（固定为 3）
pub const SEMANTIC_AXES_DIM: usize = 3;

// ============================================================================
// Trait
// ============================================================================

/// 语义投影器：将高维 Embedding 投影到低维语义轴空间
pub trait SemanticProjection: Send + Sync {
    /// 投影单个向量
    fn project(&self, embedding: &[f32]) -> SemanticAxes;

    /// 批量投影
    fn project_batch(&self, embeddings: &[&[f32]]) -> Vec<SemanticAxes> {
        embeddings.iter().map(|e| self.project(e)).collect()
    }

    /// 投影器名称
    fn name(&self) -> &str;

    /// 投影矩阵维度 (input_dim, output_dim=3)
    fn dims(&self) -> (usize, usize);

    /// 序列化投影器参数到字节
    fn to_bytes(&self) -> Vec<u8>;
}

// ============================================================================
// Random Projector
// ============================================================================

/// 基于 Johnson-Lindenstrauss 引理的随机投影器
///
/// 使用确定性种子生成随机投影矩阵，保证相同种子生成相同矩阵。
/// 优点：无需训练数据，即开即用
/// 缺点：语义轴无特定含义（但距离关系保持）
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RandomProjector {
    /// 投影矩阵 [SEMANTIC_AXES_DIM × input_dim]（行优先）
    matrix: Vec<f32>,
    /// 输入向量维度
    input_dim: usize,
    /// 随机种子
    seed: u64,
}

impl RandomProjector {
    /// 创建随机投影器
    ///
    /// 使用简单的 xorshift64 伪随机数生成器生成高斯分布矩阵
    pub fn new(input_dim: usize, seed: u64) -> Self {
        let total = SEMANTIC_AXES_DIM * input_dim;
        let scale = 1.0 / (SEMANTIC_AXES_DIM as f32).sqrt();
        let mut matrix = Vec::with_capacity(total);
        let mut rng_state = seed;

        for _ in 0..total {
            // xorshift64 PRNG
            rng_state ^= rng_state << 13;
            rng_state ^= rng_state >> 7;
            rng_state ^= rng_state << 17;
            // Box-Muller transform for approximate Gaussian
            let u1 = (rng_state & 0xFFFFFFFF) as f32 / u32::MAX as f32;
            rng_state ^= rng_state << 13;
            rng_state ^= rng_state >> 7;
            rng_state ^= rng_state << 17;
            let u2 = (rng_state & 0xFFFFFFFF) as f32 / u32::MAX as f32;
            // Prevent log(0)
            let u1 = u1.max(1e-10);
            let gauss = (-2.0 * u1.ln()).sqrt() * (2.0 * std::f32::consts::PI * u2).cos();
            matrix.push(gauss * scale);
        }

        Self { matrix, input_dim, seed }
    }
}

impl SemanticProjection for RandomProjector {
    fn project(&self, embedding: &[f32]) -> SemanticAxes {
        debug_assert!(
            embedding.len() >= self.input_dim,
            "Embedding dim {} < expected {}",
            embedding.len(),
            self.input_dim
        );
        let mut result = [0.0f32; SEMANTIC_AXES_DIM];
        for axis in 0..SEMANTIC_AXES_DIM {
            let row_offset = axis * self.input_dim;
            let mut sum = 0.0f32;
            for j in 0..self.input_dim.min(embedding.len()) {
                sum += self.matrix[row_offset + j] * embedding[j];
            }
            result[axis] = sum;
        }
        SemanticAxes::from_slice(&result)
    }

    fn name(&self) -> &str {
        "random"
    }

    fn dims(&self) -> (usize, usize) {
        (self.input_dim, SEMANTIC_AXES_DIM)
    }

    fn to_bytes(&self) -> Vec<u8> {
        bincode_serialize(self)
    }
}

// ============================================================================
// PCA Projector
// ============================================================================

/// PCA 主成分分析投影器
///
/// 从数据集中学习前 3 个主成分方向作为投影轴。
/// 语义轴对应数据方差最大的 3 个方向。
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PcaProjector {
    /// 投影矩阵 [SEMANTIC_AXES_DIM × input_dim]（前 3 主成分，行优先）
    components: Vec<f32>,
    /// 数据均值 [input_dim]（用于中心化）
    mean: Vec<f32>,
    /// 各主成分解释方差比例
    explained_variance_ratio: [f32; SEMANTIC_AXES_DIM],
    /// 输入维度
    input_dim: usize,
}

impl PcaProjector {
    /// 从 embedding 数据集训练 PCA 投影器
    ///
    /// 使用幂迭代法（Power Iteration）提取前 3 个特征向量。
    /// 适合高维数据（无需完整 SVD 分解）。
    ///
    /// # Arguments
    /// * `embeddings` - 训练数据（每个元素是一个 embedding 切片）
    /// * `input_dim` - 向量维度
    /// * `max_iterations` - 幂迭代最大次数（默认 100）
    pub fn fit(embeddings: &[&[f32]], input_dim: usize, max_iterations: usize) -> Self {
        let n = embeddings.len();
        if n == 0 {
            return Self {
                components: vec![0.0; SEMANTIC_AXES_DIM * input_dim],
                mean: vec![0.0; input_dim],
                explained_variance_ratio: [0.0; SEMANTIC_AXES_DIM],
                input_dim,
            };
        }

        // 1. 计算均值
        let mut mean = vec![0.0f64; input_dim];
        for emb in embeddings {
            for (j, &v) in emb.iter().enumerate().take(input_dim) {
                mean[j] += v as f64;
            }
        }
        let n_f = n as f64;
        for m in mean.iter_mut() {
            *m /= n_f;
        }

        // 2. 中心化数据矩阵（存储为 n × input_dim）
        let mut centered: Vec<Vec<f64>> = embeddings
            .iter()
            .map(|emb| {
                emb.iter()
                    .take(input_dim)
                    .enumerate()
                    .map(|(j, &v)| v as f64 - mean[j])
                    .collect()
            })
            .collect();

        // 3. 幂迭代提取前 3 个主成分
        let mut components = Vec::with_capacity(SEMANTIC_AXES_DIM * input_dim);
        let mut eigenvalues = [0.0f64; SEMANTIC_AXES_DIM];

        for pc in 0..SEMANTIC_AXES_DIM {
            // 初始随机向量
            let mut v = vec![0.0f64; input_dim];
            let mut rng_state = (pc as u64).wrapping_add(42).wrapping_mul(6364136223846793005u64).wrapping_add(1);
            for vi in v.iter_mut() {
                rng_state ^= rng_state << 13;
                rng_state ^= rng_state >> 7;
                rng_state ^= rng_state << 17;
                *vi = (rng_state as f64) / (u64::MAX as f64) - 0.5;
            }
            normalize_vec(&mut v);

            // 幂迭代：v_{k+1} = X^T X v_k / ||X^T X v_k||
            for _ in 0..max_iterations {
                // Xv = centered @ v
                let xv: Vec<f64> = centered.iter().map(|row| {
                    row.iter().zip(v.iter()).map(|(a, b)| a * b).sum::<f64>()
                }).collect();

                // X^T (Xv)
                let mut xtxv = vec![0.0f64; input_dim];
                for (i, &xvi) in xv.iter().enumerate() {
                    for (j, val) in centered[i].iter().enumerate() {
                        xtxv[j] += val * xvi;
                    }
                }

                let norm = vec_norm(&xtxv);
                if norm < 1e-12 {
                    break;
                }
                for (vi, &xi) in v.iter_mut().zip(xtxv.iter()) {
                    *vi = xi / norm;
                }
            }

            // 计算特征值（方差）
            let xv: Vec<f64> = centered.iter().map(|row| {
                row.iter().zip(v.iter()).map(|(a, b)| a * b).sum::<f64>()
            }).collect();
            eigenvalues[pc] = xv.iter().map(|x| x * x).sum::<f64>() / n_f;

            // 存储主成分
            for &vi in &v {
                components.push(vi as f32);
            }

            // 正交化残差：从中心化数据中减去该主成分的贡献（deflation）
            for row in centered.iter_mut() {
                let proj: f64 = row.iter().zip(v.iter()).map(|(a, b)| a * b).sum();
                for (j, rj) in row.iter_mut().enumerate() {
                    *rj -= proj * v[j];
                }
            }
        }

        // 计算解释方差比例
        let total_var: f64 = eigenvalues.iter().sum();
        let explained_variance_ratio = if total_var > 0.0 {
            [
                (eigenvalues[0] / total_var) as f32,
                (eigenvalues[1] / total_var) as f32,
                (eigenvalues[2] / total_var) as f32,
            ]
        } else {
            [0.0; SEMANTIC_AXES_DIM]
        };

        let mean_f32: Vec<f32> = mean.iter().map(|&v| v as f32).collect();

        Self {
            components,
            mean: mean_f32,
            explained_variance_ratio,
            input_dim,
        }
    }

    /// 获取解释方差比例
    pub fn explained_variance_ratio(&self) -> &[f32; SEMANTIC_AXES_DIM] {
        &self.explained_variance_ratio
    }
}

impl SemanticProjection for PcaProjector {
    fn project(&self, embedding: &[f32]) -> SemanticAxes {
        let mut result = [0.0f32; SEMANTIC_AXES_DIM];
        for axis in 0..SEMANTIC_AXES_DIM {
            let row_offset = axis * self.input_dim;
            let mut sum = 0.0f32;
            for j in 0..self.input_dim.min(embedding.len()) {
                sum += self.components[row_offset + j] * (embedding[j] - self.mean[j]);
            }
            result[axis] = sum;
        }
        SemanticAxes::from_slice(&result)
    }

    fn name(&self) -> &str {
        "pca"
    }

    fn dims(&self) -> (usize, usize) {
        (self.input_dim, SEMANTIC_AXES_DIM)
    }

    fn to_bytes(&self) -> Vec<u8> {
        bincode_serialize(self)
    }
}

// ============================================================================
// Anchor Projector
// ============================================================================

/// 基于锚点对齐的投影器
///
/// 用户提供 (embedding, 语义轴标注) 对作为锚点，
/// 通过最小二乘法拟合投影矩阵，使投影结果尽可能接近标注值。
///
/// 适用场景：用户有明确的语义维度定义（如 Social/Sensory/Locality）
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnchorProjector {
    /// 投影矩阵 [SEMANTIC_AXES_DIM × input_dim]（行优先）
    matrix: Vec<f32>,
    /// 偏置 [SEMANTIC_AXES_DIM]
    bias: [f32; SEMANTIC_AXES_DIM],
    /// 输入维度
    input_dim: usize,
    /// 训练时的拟合误差（MSE）
    fit_mse: f32,
}

impl AnchorProjector {
    /// 从锚点数据训练投影器
    ///
    /// 使用梯度下降法求解：min Σ ||W*x_i + b - y_i||^2
    ///
    /// # Arguments
    /// * `anchors` - (embedding, target_axes) 训练对
    /// * `input_dim` - 向量维度
    /// * `learning_rate` - 学习率（默认 0.001）
    /// * `epochs` - 训练轮数（默认 500）
    pub fn fit(
        anchors: &[(Vec<f32>, SemanticAxes)],
        input_dim: usize,
        learning_rate: f32,
        epochs: usize,
    ) -> Self {
        let n = anchors.len();
        if n == 0 {
            return Self {
                matrix: vec![0.0; SEMANTIC_AXES_DIM * input_dim],
                bias: [0.0; SEMANTIC_AXES_DIM],
                input_dim,
                fit_mse: 0.0,
            };
        }

        // 初始化权重（小随机值）
        let mut matrix = vec![0.0f32; SEMANTIC_AXES_DIM * input_dim];
        let mut bias = [0.0f32; SEMANTIC_AXES_DIM];
        let mut rng_state: u64 = 12345;
        for w in matrix.iter_mut() {
            rng_state ^= rng_state << 13;
            rng_state ^= rng_state >> 7;
            rng_state ^= rng_state << 17;
            *w = ((rng_state as f32) / (u64::MAX as f32) - 0.5) * 0.01;
        }

        let n_f = n as f32;

        // 梯度下降
        for _epoch in 0..epochs {
            // 累积梯度
            let mut grad_w = vec![0.0f32; SEMANTIC_AXES_DIM * input_dim];
            let mut grad_b = [0.0f32; SEMANTIC_AXES_DIM];

            for (emb, target) in anchors {
                let target_arr = target.to_array();
                for axis in 0..SEMANTIC_AXES_DIM {
                    let row_offset = axis * input_dim;
                    let mut pred = bias[axis];
                    for j in 0..input_dim.min(emb.len()) {
                        pred += matrix[row_offset + j] * emb[j];
                    }
                    let err = pred - target_arr[axis];
                    // dL/dW = 2 * err * x / N
                    for j in 0..input_dim.min(emb.len()) {
                        grad_w[row_offset + j] += 2.0 * err * emb[j] / n_f;
                    }
                    grad_b[axis] += 2.0 * err / n_f;
                }
            }

            // 更新权重
            for (w, g) in matrix.iter_mut().zip(grad_w.iter()) {
                *w -= learning_rate * g;
            }
            for (b, g) in bias.iter_mut().zip(grad_b.iter()) {
                *b -= learning_rate * g;
            }
        }

        // 计算最终 MSE
        let mut mse = 0.0f32;
        for (emb, target) in anchors {
            let target_arr = target.to_array();
            for axis in 0..SEMANTIC_AXES_DIM {
                let row_offset = axis * input_dim;
                let mut pred = bias[axis];
                for j in 0..input_dim.min(emb.len()) {
                    pred += matrix[row_offset + j] * emb[j];
                }
                mse += (pred - target_arr[axis]).powi(2);
            }
        }
        mse /= (n * SEMANTIC_AXES_DIM) as f32;

        Self { matrix, bias, input_dim, fit_mse: mse }
    }

    /// 获取训练时的 MSE
    pub fn fit_mse(&self) -> f32 {
        self.fit_mse
    }
}

impl SemanticProjection for AnchorProjector {
    fn project(&self, embedding: &[f32]) -> SemanticAxes {
        let mut result = [0.0f32; SEMANTIC_AXES_DIM];
        for axis in 0..SEMANTIC_AXES_DIM {
            let row_offset = axis * self.input_dim;
            let mut sum = self.bias[axis];
            for j in 0..self.input_dim.min(embedding.len()) {
                sum += self.matrix[row_offset + j] * embedding[j];
            }
            result[axis] = sum;
        }
        SemanticAxes::from_slice(&result)
    }

    fn name(&self) -> &str {
        "anchor"
    }

    fn dims(&self) -> (usize, usize) {
        (self.input_dim, SEMANTIC_AXES_DIM)
    }

    fn to_bytes(&self) -> Vec<u8> {
        bincode_serialize(self)
    }
}

// ============================================================================
// Persistence
// ============================================================================

/// 投影器类型标签（用于序列化/反序列化）
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
enum ProjectorType {
    Random,
    Pca,
    Anchor,
}

/// 保存投影器到文件
pub fn save_projector(projector: &dyn SemanticProjection, path: &Path) -> io::Result<()> {
    let bytes = projector.to_bytes();
    std::fs::write(path, bytes)
}

/// 从文件加载投影器
pub fn load_projector(path: &Path) -> io::Result<Box<dyn SemanticProjection>> {
    let bytes = std::fs::read(path)?;
    if bytes.len() < 2 {
        return Err(io::Error::new(io::ErrorKind::InvalidData, "File too small"));
    }

    // 尝试各种反序列化
    if let Ok(p) = bincode_deserialize::<(ProjectorType, RandomProjector)>(&bytes) {
        return Ok(Box::new(p.1));
    }
    if let Ok(p) = bincode_deserialize::<(ProjectorType, PcaProjector)>(&bytes) {
        return Ok(Box::new(p.1));
    }
    if let Ok(p) = bincode_deserialize::<(ProjectorType, AnchorProjector)>(&bytes) {
        return Ok(Box::new(p.1));
    }

    // 尝试不带 type tag 的反序列化（向后兼容）
    if let Ok(p) = bincode_deserialize::<RandomProjector>(&bytes) {
        return Ok(Box::new(p));
    }
    if let Ok(p) = bincode_deserialize::<PcaProjector>(&bytes) {
        return Ok(Box::new(p));
    }
    if let Ok(p) = bincode_deserialize::<AnchorProjector>(&bytes) {
        return Ok(Box::new(p));
    }

    Err(io::Error::new(
        io::ErrorKind::InvalidData,
        "Unknown projector format",
    ))
}

// ============================================================================
// Helpers
// ============================================================================

fn normalize_vec(v: &mut [f64]) {
    let norm = vec_norm(v);
    if norm > 1e-12 {
        for vi in v.iter_mut() {
            *vi /= norm;
        }
    }
}

fn vec_norm(v: &[f64]) -> f64 {
    v.iter().map(|x| x * x).sum::<f64>().sqrt()
}

fn bincode_serialize<T: Serialize>(v: &T) -> Vec<u8> {
    // 简单的 JSON 序列化（避免引入 bincode 依赖）
    serde_json::to_vec(v).unwrap_or_default()
}

fn bincode_deserialize<T: for<'de> Deserialize<'de>>(bytes: &[u8]) -> Result<T, ()> {
    serde_json::from_slice(bytes).map_err(|_| ())
}

// ============================================================================
// Morton Code (for Semantic Layout Storage)
// ============================================================================

/// 将 3 个 f32 语义轴坐标编码为 Morton Code (Z-order curve)
///
/// Morton Code 将 3D 坐标映射为 1D 排序键，保持空间局部性：
/// 语义相似的点（轴坐标接近）的 Morton Code 也接近。
///
/// 每个轴量化为 21 位整数（0-2097151），交错排列为 63 位 u64。
pub fn morton_code_3d(axis_0: f32, axis_1: f32, axis_2: f32) -> u64 {
    // 归一化到 [0, 1] 范围（假设轴值在 [-3, 3] 区间内）
    let a0 = ((axis_0 + 3.0) / 6.0).clamp(0.0, 1.0);
    let a1 = ((axis_1 + 3.0) / 6.0).clamp(0.0, 1.0);
    let a2 = ((axis_2 + 3.0) / 6.0).clamp(0.0, 1.0);

    // 量化为 21 位整数
    let i0 = (a0 * 2097151.0) as u64;
    let i1 = (a1 * 2097151.0) as u64;
    let i2 = (a2 * 2097151.0) as u64;

    // 交错位
    expand_bits(i0) | (expand_bits(i1) << 1) | (expand_bits(i2) << 2)
}

/// 将 21 位整数的位扩展为每 3 位中的 1 位（用于 Morton 交错）
fn expand_bits(mut v: u64) -> u64 {
    v &= 0x1fffff; // 只保留低 21 位
    v = (v | (v << 32)) & 0x001f00000000ffff;
    v = (v | (v << 16)) & 0x001f0000ff0000ff;
    v = (v | (v << 8)) & 0x100f00f00f00f00f;
    v = (v | (v << 4)) & 0x10c30c30c30c30c3;
    v = (v | (v << 2)) & 0x1249249249249249;
    v
}

// ============================================================================
// Tests
// ============================================================================

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

    #[test]
    fn test_random_projector_deterministic() {
        let p1 = RandomProjector::new(128, 42);
        let p2 = RandomProjector::new(128, 42);

        let emb = vec![0.1f32; 128];
        let axes1 = p1.project(&emb);
        let axes2 = p2.project(&emb);

        assert_eq!(axes1, axes2, "Same seed should produce same projection");
    }

    #[test]
    fn test_random_projector_different_seeds() {
        let p1 = RandomProjector::new(128, 42);
        let p2 = RandomProjector::new(128, 99);

        let emb = vec![0.1f32; 128];
        let axes1 = p1.project(&emb);
        let axes2 = p2.project(&emb);

        assert_ne!(axes1, axes2, "Different seeds should produce different projections");
    }

    #[test]
    fn test_random_projector_dims() {
        let p = RandomProjector::new(768, 42);
        assert_eq!(p.dims(), (768, 3));
        assert_eq!(p.name(), "random");
    }

    #[test]
    fn test_random_projector_preserves_distance_order() {
        let p = RandomProjector::new(8, 42);

        // 两个相近的向量
        let a = vec![1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
        let b = vec![0.9, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
        // 一个远离的向量
        let c = vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0];

        let pa = p.project(&a);
        let pb = p.project(&b);
        let pc = p.project(&c);

        let dist_ab = pa.distance(&pb);
        let dist_ac = pa.distance(&pc);

        assert!(
            dist_ab < dist_ac,
            "Near vectors should be closer after projection: ab={} ac={}",
            dist_ab,
            dist_ac
        );
    }

    #[test]
    fn test_pca_projector_basic() {
        // 创建有明显方差差异的数据（方差 100:9:1:0）
        let embeddings_data: Vec<Vec<f32>> = (0..200)
            .map(|i| {
                let t = i as f32 / 200.0;
                vec![
                    t * 100.0,      // 最大方差方向（方差 ~100²）
                    t * 30.0,       // 第二方差方向（方差 ~30²）
                    t * 5.0,        // 第三方差方向（方差 ~5²）
                    0.1,            // 几乎无方差
                ]
            })
            .collect();

        let emb_refs: Vec<&[f32]> = embeddings_data.iter().map(|e| e.as_slice()).collect();
        let p = PcaProjector::fit(&emb_refs, 4, 200);

        assert_eq!(p.dims(), (4, 3));
        assert_eq!(p.name(), "pca");

        // 第一主成分应解释最多方差
        let evr = p.explained_variance_ratio();
        assert!(evr[0] > 0.5, "PC1 should explain majority of variance, got {}", evr[0]);

        // 投影不应全为零
        let axes = p.project(&[50.0, 15.0, 2.5, 0.1]);
        let sum_abs = axes.axis_0.abs() + axes.axis_1.abs() + axes.axis_2.abs();
        assert!(sum_abs > 0.01, "Projection should not be all zeros");
    }

    #[test]
    fn test_pca_projector_empty() {
        let p = PcaProjector::fit(&[], 4, 100);
        assert_eq!(p.dims(), (4, 3));
        let axes = p.project(&[0.0, 0.0, 0.0, 0.0]);
        assert_eq!(axes, SemanticAxes::default());
    }

    #[test]
    fn test_anchor_projector_basic() {
        // 简单的线性可分锚点
        let anchors = vec![
            (vec![1.0, 0.0, 0.0, 0.0], SemanticAxes::new(1.0, 0.0, 0.0)),
            (vec![0.0, 1.0, 0.0, 0.0], SemanticAxes::new(0.0, 1.0, 0.0)),
            (vec![0.0, 0.0, 1.0, 0.0], SemanticAxes::new(0.0, 0.0, 1.0)),
            (vec![0.0, 0.0, 0.0, 1.0], SemanticAxes::new(0.0, 0.0, 0.0)),
        ];

        let p = AnchorProjector::fit(&anchors, 4, 0.01, 1000);

        assert_eq!(p.dims(), (4, 3));
        assert_eq!(p.name(), "anchor");

        // 投影应接近锚点标注
        let axes = p.project(&[1.0, 0.0, 0.0, 0.0]);
        assert!(
            (axes.axis_0 - 1.0).abs() < 0.3,
            "axis_0 should be close to 1.0, got {}",
            axes.axis_0
        );

        // MSE 应该较低
        assert!(p.fit_mse() < 0.5, "MSE should be low, got {}", p.fit_mse());
    }

    #[test]
    fn test_batch_projection() {
        let p = RandomProjector::new(4, 42);
        let embeddings = vec![
            vec![1.0, 0.0, 0.0, 0.0],
            vec![0.0, 1.0, 0.0, 0.0],
            vec![0.0, 0.0, 1.0, 0.0],
        ];
        let refs: Vec<&[f32]> = embeddings.iter().map(|e| e.as_slice()).collect();

        let results = p.project_batch(&refs);
        assert_eq!(results.len(), 3);

        // 批量结果应与单个投影一致
        for (emb, axes) in embeddings.iter().zip(results.iter()) {
            assert_eq!(&p.project(emb), axes);
        }
    }

    #[test]
    fn test_projector_serialization() {
        let p = RandomProjector::new(8, 42);
        let bytes = p.to_bytes();
        assert!(!bytes.is_empty());

        // 反序列化
        let p2: RandomProjector = serde_json::from_slice(&bytes).unwrap();
        let emb = vec![0.1f32; 8];
        assert_eq!(p.project(&emb), p2.project(&emb));
    }

    #[test]
    fn test_morton_code_locality() {
        // 相近的语义轴坐标应产生相近的 Morton Code
        let m1 = morton_code_3d(0.5, 0.5, 0.5);
        let m2 = morton_code_3d(0.51, 0.51, 0.51);
        let m3 = morton_code_3d(-2.0, -2.0, -2.0);

        let diff_near = (m1 as i64 - m2 as i64).unsigned_abs();
        let diff_far = (m1 as i64 - m3 as i64).unsigned_abs();

        assert!(
            diff_near < diff_far,
            "Near points should have closer Morton codes: near_diff={} far_diff={}",
            diff_near,
            diff_far
        );
    }

    #[test]
    fn test_morton_code_range() {
        // 极端值不应溢出
        let _ = morton_code_3d(-100.0, -100.0, -100.0);
        let _ = morton_code_3d(100.0, 100.0, 100.0);
        let _ = morton_code_3d(0.0, 0.0, 0.0);
    }

    #[test]
    fn test_semantic_axes_in_range() {
        let axes = SemanticAxes::new(0.5, -0.3, 0.8);

        assert!(axes.in_range(Some((0.0, 1.0)), None, None));
        assert!(!axes.in_range(Some((0.6, 1.0)), None, None));
        assert!(axes.in_range(
            Some((0.0, 1.0)),
            Some((-1.0, 0.0)),
            Some((0.5, 1.0)),
        ));
        assert!(!axes.in_range(
            Some((0.0, 1.0)),
            Some((0.0, 1.0)), // axis_1=-0.3 out of range
            None,
        ));
    }
}
