//! `lunaris-embed-native` — pure-Rust ModernBERT FP16 embedder built on
//! `candle-transformers::models::modernbert`, purpose-built for
//! [`ibm-granite/granite-embedding-311m-multilingual-r2`].
//!
//! ## Why a new crate (not a feature on `lunaris-embed`)
//!
//! `lunaris-embed` is the ONNX/fastembed/candle-gemma trio that ships
//! EmbeddingGemma 300M. This crate hosts the v0.4 milestone's *replacement*
//! plan: a single candle-native backend serving granite-311m-r2 + the
//! companion reranker. Keeping the two crates separate lets us:
//!
//! - swap embedders behind the `Embedder` trait without touching the
//!   existing crate's feature matrix,
//! - run a numerical-equivalence gate (≤0.5% mean cosine drift vs HF
//!   `sentence-transformers`) without coupling to fastembed's ORT version,
//! - quantize / port the reranker / delete fastembed in follow-up phases
//!   without dragging the rest of the workspace.
//!
//! ## Architecture (FP16 path)
//!
//! ```text
//! NativeEmbedder
//!   └─ Inner = Arc<{ tokenizer, model: ModernBert, device, scratch_mutex }>
//!       ├─ tokenizer    : `tokenizers::Tokenizer` (loaded from `tokenizer.json`)
//!       └─ model        : candle_transformers::models::modernbert::ModernBert
//!                         (the FP32-computed forward pass — fp16 weights are
//!                          cast to fp32 on load to hit the 0.5% drift gate)
//! ```
//!
//! `NativeEmbedder::embed_batch` runs inside `tokio::task::spawn_blocking`;
//! the internal `parking_lot::Mutex<Tensor>` scratch (if used) is acquired
//! INSIDE the blocking closure — never across `.await` (CLAUDE.md lock
//! discipline).
//!
//! ## Numerical-equivalence gate
//!
//! `tests/numerical_equivalence.rs` (feature `embedder-it`) runs
//! `NativeEmbedder` against a 100-prompt panel (35 EN + 35 VI + 30 code) and
//! asserts mean cosine drift ≤ 0.005 and max ≤ 0.02 against
//! `sentence-transformers/SentenceTransformer("ibm-granite/granite-embedding-311m-multilingual-r2")`.
//! The reference fixture is generated by
//! `scripts/spike-generate-reference-embeddings.py` and committed under
//! `tests/fixtures/reference_embeddings.json`.
//!
//! ## Out of scope (follow-up spawns)
//!
//! - Q4 / quantized path — partial: GGUF conversion gate cleared, scaffold
//!   landed behind the `embedder-gguf` feature flag; forward-pass body is
//!   the next-session deliverable. See
//!   `.planning/phases/N-01-step-2-quantized-gguf/conversion-evidence.md`.
//! - Reranker port
//! - Backend deletions (`lunaris-embed`, candle-gemma, ollama)
//!
//! [`ibm-granite/granite-embedding-311m-multilingual-r2`]:
//! https://huggingface.co/ibm-granite/granite-embedding-311m-multilingual-r2

#![deny(rust_2018_idioms, unreachable_pub)]
#![forbid(unsafe_code)]

pub mod config;
// O-01-D — CUDA-specific docs + diagnostic stub. Compiled only under
// `--features cuda`; on Apple Silicon (this host) the module compiles only
// when the CUDA toolchain is present.
#[cfg(feature = "cuda")]
pub mod cuda_notes;
// O-01-C / O-01-D — Device-upgrade + warm-up helpers used by both the FP16
// and quantized embedder construction paths.
pub mod device_select;
pub mod embedder;
pub mod modernbert;
// O-01-B — one-shot rayon thread-pool init at physical-core count.
// Public because the umbrella crate may want to pre-init before the first
// `NativeEmbedder::open()` (e.g., during process startup).
pub mod rayon_pool;
pub mod tokenizer;

#[cfg(feature = "embedder-gguf")]
pub mod quantized_embedder;
#[cfg(feature = "embedder-gguf")]
pub mod quantized_modernbert;

pub use config::ModernBertConfig;
pub use embedder::{NativeEmbedder, NativeEmbedderError, NativeEmbedderOpts};

#[cfg(feature = "embedder-gguf")]
pub use quantized_embedder::{
    NativeQuantizedEmbedder, NativeQuantizedEmbedderError, NativeQuantizedEmbedderOpts,
};
#[cfg(feature = "embedder-gguf")]
pub use quantized_modernbert::{QuantizedError, QuantizedModernBert};

/// Output dimensionality of `granite-embedding-311m-multilingual-r2`.
///
/// 768-d unit vectors, matching the Lunaris default storage schema
/// (Moon `FT.SEARCH` 768-d HNSW index + Postgres `vector(768)`).
pub const GRANITE_R2_DIM: usize = 768;

/// SHA-256 of the canonical Q4_K_M GGUF for
/// `ibm-granite/granite-embedding-311m-multilingual-r2`, produced by
/// `scripts/spike-convert-granite-r2-to-gguf.sh` (llama.cpp HEAD at PR
/// [#22716], converter `convert_hf_to_gguf.py` from the same PR, quantizer
/// `llama-quantize Q4_K_M`).
///
/// Producers should call `scripts/spike-convert-granite-r2-to-gguf.sh` to
/// (re)produce this artifact; consumers should verify the SHA-256 of the
/// `.gguf` file at load time and refuse to proceed on mismatch.
///
/// File size at this pin: **240.7 MiB** (≤ 250 MiB phase budget).
///
/// [#22716]: https://github.com/ggml-org/llama.cpp/pull/22716
pub const GRANITE_R2_GGUF_Q4_SHA256: &str =
    "0768a38b0bc9900e89bb15ae0b6ea2ca7db130759e0eca226119610aedf5e276";
