///|
/// vcdb - A high-performance vector database for approximate nearest neighbor search.
///
/// This module provides the main VectorDB facade that unifies access to different
/// ANN (Approximate Nearest Neighbor) indexing strategies:
/// - Bruteforce: Exact linear scan, best for small datasets (<1000 vectors)
/// - HNSW: Hierarchical Navigable Small World graphs, best for general use
/// - IVF: Inverted File Index, best for large datasets with training
///
/// Example:
/// ```moonbit
/// let db = VectorDB::with_hnsw(128, metric=Cosine)
/// db.add(VectorId::from_int(1), embedding, attrs)
/// let results = db.search(query, k=10, filter=None)
/// ```

///|
/// Re-export factory functions for convenience
pub fn empty_attrs() -> @types.Attrs {
  @types.empty_attrs()
}

///|
/// VectorDB - Internal ANN index engine.
///
/// Provides unified access to all ANN strategies: Bruteforce, HNSW, and IVF.
/// The engine automatically handles:
/// - Vector normalization for cosine similarity
/// - Index updates on add/remove operations
/// - Tombstone management for HNSW deletions
///
/// This is an internal implementation detail. The public API is VectorDB[W, S].
pub struct VectorDB {
  store : @store.CoreStore
  attr_index : @attr.BPTreeAttrIndex
  strategy : @types.Strategy
  bruteforce : @ann.BruteforceState?
  hnsw : @ann.HNSWState?
  ivf : @ann.IVFState?
}

///|
/// Create a new VectorDB with the given options.
///
/// Parameters:
/// - options: Configuration including dimension, metric, capacity, and strategy
///
/// Returns: A new VectorDB instance configured according to options
pub fn VectorDB::new(options : @types.DatabaseOptions) -> VectorDB {
  let store = @store.CoreStore::new(
    options.dim,
    options.metric,
    capacity=options.capacity,
  )
  let attr_index = @attr.BPTreeAttrIndex::new()
  match options.strategy {
    Bruteforce =>
      {
        attr_index,
        bruteforce: Some(@ann.BruteforceState::new(options.metric)),
        hnsw: None,
        ivf: None,
        store,
        strategy: options.strategy,
      }
    HNSW(params) =>
      {
        attr_index,
        bruteforce: None,
        hnsw: Some(
          @ann.HNSWState::new(params, options.metric, options.capacity),
        ),
        ivf: None,
        store,
        strategy: options.strategy,
      }
    IVF(params) =>
      {
        attr_index,
        bruteforce: None,
        hnsw: None,
        ivf: Some(@ann.IVFState::new(params, options.metric, options.dim)),
        store,
        strategy: options.strategy,
      }
  }
}

///|
/// Create a VectorDB with default options (Bruteforce strategy).
///
/// Parameters:
/// - dim: Vector dimension
///
/// Returns: A VectorDB with Bruteforce strategy and Cosine metric
pub fn VectorDB::with_dim(dim : Int) -> VectorDB {
  VectorDB::new(@types.DatabaseOptions::default(dim))
}

///|
/// Create a VectorDB with HNSW strategy.
///
/// HNSW (Hierarchical Navigable Small World) is recommended for most use cases.
/// It provides O(log n) search time with high recall.
///
/// Parameters:
/// - dim: Vector dimension
/// - metric: Similarity metric (default: Cosine)
///
/// Returns: A VectorDB configured with HNSW index
pub fn VectorDB::with_hnsw(
  dim : Int,
  metric? : @types.Metric = Cosine,
) -> VectorDB {
  let options : @types.DatabaseOptions = {
    dim,
    metric,
    capacity: 1024,
    strategy: @types.Strategy::default_hnsw(),
  }
  VectorDB::new(options)
}

///|
/// Create a VectorDB with IVF strategy.
///
/// IVF (Inverted File Index) is best for very large datasets.
/// Requires training with train() before optimal search performance.
///
/// Parameters:
/// - dim: Vector dimension
/// - metric: Similarity metric (default: Cosine)
///
/// Returns: A VectorDB configured with IVF index
pub fn VectorDB::with_ivf(
  dim : Int,
  metric? : @types.Metric = Cosine,
) -> VectorDB {
  let options : @types.DatabaseOptions = {
    dim,
    metric,
    capacity: 1024,
    strategy: @types.Strategy::default_ivf(),
  }
  VectorDB::new(options)
}

///|
/// Get the number of accessible vectors in the database.
///
/// For HNSW, this excludes tombstoned (deleted) vectors.
/// Use raw_size() to get the total count including tombstones.
pub fn VectorDB::size(self : VectorDB) -> Int {
  match self.hnsw {
    Some(state) => {
      let (alive, _) = @ann.hnsw_tombstone_stats(state, self.store)
      alive
    }
    None => self.store.size()
  }
}

///|
/// Get the raw size including tombstoned vectors.
/// This is primarily for internal use and serialization.
pub fn VectorDB::raw_size(self : VectorDB) -> Int {
  self.store.size()
}

///|
/// Get the dimension of vectors in this database.
pub fn VectorDB::dim(self : VectorDB) -> Int {
  self.store.dim
}

///|
/// Get the similarity metric used by this database.
pub fn VectorDB::metric(self : VectorDB) -> @types.Metric {
  self.store.metric
}

///|
/// Get the ANN strategy used by this database.
pub fn VectorDB::strategy(self : VectorDB) -> @types.Strategy {
  self.strategy
}

///|
/// Get access to the underlying CoreStore (for distributed storage operations)
pub fn VectorDB::store(self : VectorDB) -> @store.CoreStore {
  self.store
}

///|
/// Create VectorDB from an existing CoreStore (for loading from distributed storage)
pub fn VectorDB::from_store(
  store : @store.CoreStore,
  options : @types.DatabaseOptions,
) -> VectorDB {
  let attr_index = @attr.BPTreeAttrIndex::new()
  for i in 0..
      {
        attr_index,
        bruteforce: Some(@ann.BruteforceState::new(store.metric)),
        hnsw: None,
        ivf: None,
        store,
        strategy: options.strategy,
      }
    HNSW(params) => {
      let hnsw = @ann.HNSWState::new(params, store.metric, options.capacity)
      // Rebuild HNSW index from store
      for i in 0.. {
      let ivf = @ann.IVFState::new(params, store.metric, store.dim)
      // IVF requires training
      {
        attr_index,
        bruteforce: None,
        hnsw: None,
        ivf: Some(ivf),
        store,
        strategy: options.strategy,
      }
    }
  }
}

///|
/// Rebuild the metadata index from persisted store contents.
///
/// When `tombstone` is provided, tombstoned rows are excluded so the metadata
/// index matches the set of visible vectors.
fn rebuild_metadata_index(
  store : @store.CoreStore,
  tombstone : Array[Bool]?,
) -> @attr.BPTreeAttrIndex {
  let index = @attr.BPTreeAttrIndex::new()
  for i in 0.. if i < bits.length() && bits[i] { continue }
      None => ()
    }
    index.set_attrs(store.ids[i], store.attrs[i])
  }
  index
}

///|
/// Check if an id is tombstoned in HNSW (internal helper).
///
/// HNSW uses tombstones for soft-delete to maintain graph connectivity.
/// Tombstoned vectors are excluded from search results but remain in storage
/// until compact() is called.
fn VectorDB::is_tombstoned(self : VectorDB, id : @types.VectorId) -> Bool {
  match self.hnsw {
    None => false
    Some(state) =>
      match self.store.get_index(id) {
        None => false
        Some(idx) => idx < state.tombstone.length() && state.tombstone[idx]
      }
  }
}

///|
/// Check if an id exists and is accessible.
///
/// For HNSW mode, tombstoned IDs are considered non-existent.
///
/// Parameters:
/// - id: The vector ID to check
///
/// Returns: true if the vector exists and is not tombstoned
pub fn VectorDB::has(self : VectorDB, id : @types.VectorId) -> Bool {
  if !self.store.has(id) {
    return false
  }
  !self.is_tombstoned(id)
}

///|
/// Get a vector and its attributes by ID.
///
/// For HNSW mode, tombstoned IDs return None.
///
/// Parameters:
/// - id: The vector ID to retrieve
///
/// Returns: The vector record if found and accessible, None otherwise
pub fn VectorDB::get(
  self : VectorDB,
  id : @types.VectorId,
) -> @types.VectorRecord? {
  if self.is_tombstoned(id) {
    return None
  }
  self.store.get(id)
}

///|
/// Scroll points in ascending ID order, similar to Qdrant scroll.
///
/// Thin wrapper over scroll_filtered with no filter expression.
/// Offset semantics: returns entries with ID strictly greater than offset.
/// Pass the last ID from previous page as offset to get the next page.
pub fn VectorDB::scroll(
  self : VectorDB,
  offset? : @types.VectorId? = None,
  limit? : Int = 10,
) -> Array[(@types.VectorId, @types.VectorRecord)] {
  self.scroll_filtered(expr=None, offset~, limit~)
}

///|
/// Scroll points in ascending ID order with optional filter expression.
///
/// When expr is None, returns all (non-tombstoned) entries.
/// When expr is Some, resolves candidates via the B+ tree attribute
/// index — every expression variant is handled by the index with a
/// single execution path.
///
/// Offset semantics: returns entries with ID strictly greater than
/// offset (cursor = last ID seen by caller).
///
/// Parameters:
/// - expr: Optional filter expression to match against attributes
/// - offset: Cursor — only IDs strictly greater than this value are returned
/// - limit: Maximum number of entries to return
///
/// Returns: Array of (VectorId, VectorRecord) sorted by ID ascending
pub fn VectorDB::scroll_filtered(
  self : VectorDB,
  expr? : @filter.FilterExpr? = None,
  offset? : @types.VectorId? = None,
  limit? : Int = 10,
) -> Array[(@types.VectorId, @types.VectorRecord)] {
  let candidates = match expr {
    Some(e) => @filter.resolve_candidates_bptree(e, self.attr_index)
    None => self.attr_index.all_ids()
  }
  candidates.sort_by(fn(a, b) { a.compare(b) })
  let points : Array[(@types.VectorId, @types.VectorRecord)] = []
  for id in candidates {
    match offset {
      Some(cursor) => if id.compare(cursor) <= 0 { continue }
      None => ()
    }
    if self.is_tombstoned(id) {
      continue
    }
    match self.store.get(id) {
      Some(record) => points.push((id, record))
      None => ()
    }
    if points.length() >= limit {
      break
    }
  }
  points
}

///|
/// Count vectors matching an optional filter expression.
///
/// When expr is None, counts all non-tombstoned vectors.
/// When expr is Some, resolves the expression to a candidate set via
/// the B+ tree index, then subtracts tombstoned entries.
/// No vector data is touched.
///
/// Parameters:
/// - expr: Optional filter expression to count
///
/// Returns: Number of matching, non-tombstoned vectors
pub fn VectorDB::count_filtered(
  self : VectorDB,
  expr? : @filter.FilterExpr? = None,
) -> Int {
  let candidates = match expr {
    Some(e) => @filter.resolve_candidates_bptree(e, self.attr_index)
    None => self.attr_index.all_ids()
  }
  let mut count = 0
  for id in candidates {
    if !self.is_tombstoned(id) {
      count = count + 1
    }
  }
  count
}

///|
/// Add a new vector to the database.
///
/// For HNSW mode, allows re-adding tombstoned IDs (clears tombstone and re-indexes).
/// Fails if the ID already exists and is not tombstoned.
///
/// Parameters:
/// - id: Unique identifier for the vector
/// - vector: The embedding vector (must match database dimension)
/// - attrs: Metadata attributes for filtering
pub fn VectorDB::add(
  self : VectorDB,
  id : @types.VectorId,
  vector : Array[Double],
  attrs : @types.Attrs,
) -> Unit {
  // Check if this is a tombstoned ID in HNSW mode (can be re-added)
  let was_tombstoned = self.is_tombstoned(id)
  if was_tombstoned {
    // Clear tombstone and update vector
    match self.hnsw {
      Some(state) =>
        match self.store.get_index(id) {
          Some(idx) =>
            if idx < state.tombstone.length() {
              state.tombstone[idx] = false
            }
          None => ()
        }
      None => ()
    }
    // Update the existing vector with upsert
    let _ = self.store.add_or_update(id, vector, attrs, upsert=true)
    self.attr_index.set_attrs(id, attrs)
    // Re-add to HNSW graph
    match self.hnsw {
      Some(state) => @ann.hnsw_add(state, self.store, id)
      None => ()
    }
    return
  }
  // Normal add path
  let (_, created) = self.store.add_or_update(id, vector, attrs, upsert=false)
  if created {
    self.attr_index.set_attrs(id, attrs)
    match self.hnsw {
      Some(state) => @ann.hnsw_add(state, self.store, id)
      None => ()
    }
    match self.ivf {
      Some(state) => @ann.ivf_add(state, self.store, id)
      None => ()
    }
  }
}

///|
/// Add or update a vector (upsert operation).
///
/// If the ID exists, updates the vector and reindexes.
/// If the ID doesn't exist, adds a new vector.
///
/// Parameters:
/// - id: Unique identifier for the vector
/// - vector: The embedding vector
/// - attrs: Metadata attributes
///
/// Returns: true if a new vector was created, false if existing was updated
pub fn VectorDB::upsert(
  self : VectorDB,
  id : @types.VectorId,
  vector : Array[Double],
  attrs : @types.Attrs,
) -> Bool {
  let existed = self.store.has(id)
  let (_, created) = self.store.add_or_update(id, vector, attrs, upsert=true)
  self.attr_index.set_attrs(id, attrs)
  if created {
    // New vector: add to index
    match self.hnsw {
      Some(state) => @ann.hnsw_add(state, self.store, id)
      None => ()
    }
    match self.ivf {
      Some(state) => @ann.ivf_add(state, self.store, id)
      None => ()
    }
  } else if existed {
    // Updated existing vector: need to reindex for ANN strategies
    match self.hnsw {
      Some(state) => {
        @ann.hnsw_remove(state, id, self.store)
        @ann.hnsw_add(state, self.store, id)
      }
      None => ()
    }
    match self.ivf {
      Some(state) => {
        @ann.ivf_remove(state, id)
        @ann.ivf_assign(state, self.store, id)
      }
      None => ()
    }
  }
  created
}

///|
/// Remove a vector by ID.
///
/// Behavior differs by strategy:
/// - Bruteforce/IVF: Physically removes the vector
/// - HNSW: Marks as tombstone (soft delete) to maintain graph connectivity
///
/// For HNSW, use compact() to reclaim space from tombstoned vectors.
///
/// Parameters:
/// - id: The vector ID to remove
///
/// Returns: true if the vector was removed, false if not found or already deleted
pub fn VectorDB::remove(self : VectorDB, id : @types.VectorId) -> Bool {
  guard self.store.has(id) else { return false }
  // For HNSW, check if already tombstoned
  match self.hnsw {
    Some(state) => {
      if self.is_tombstoned(id) {
        return false
      }
      @ann.hnsw_remove(state, id, self.store)
      self.attr_index.remove_id(id)
      return true // Don't remove from store, HNSW relies on stable indices
    }
    None => ()
  }
  // For IVF, remove from index
  match self.ivf {
    Some(state) => @ann.ivf_remove(state, id)
    None => ()
  }
  // Remove from store (only if not using HNSW)
  match self.store.remove_by_id(id) {
    Some(_) => {
      self.attr_index.remove_id(id)
      true
    }
    None => false
  }
}

///|
/// Search for k nearest neighbors to a query vector.
///
/// Parameters:
/// - query: The query vector (must match database dimension)
/// - k: Number of results to return
/// - filter: Optional predicate to filter results by ID and attributes
///
/// Returns: Array of SearchHit sorted by score (highest/most similar first)
pub fn VectorDB::search(
  self : VectorDB,
  query : Array[Double],
  k : Int,
  filter : ((@types.VectorId, @types.Attrs) -> Bool)?,
) -> Array[@types.SearchHit] {
  match self.strategy {
    Bruteforce =>
      match self.bruteforce {
        Some(state) => @ann.bf_search(state, self.store, query, k, filter)
        None => []
      }
    HNSW(_) =>
      match self.hnsw {
        Some(state) => @ann.hnsw_search(state, self.store, query, k, filter)
        None => []
      }
    IVF(_) =>
      match self.ivf {
        Some(state) => @ann.ivf_search(state, self.store, query, k, filter)
        None => []
      }
  }
}

///|
/// Find the single best match for a query.
///
/// Convenience method equivalent to search(query, 1, filter).
///
/// Parameters:
/// - query: The query vector
/// - filter: Optional filter predicate
///
/// Returns: The best matching SearchHit, or None if no matches
pub fn VectorDB::find(
  self : VectorDB,
  query : Array[Double],
  filter : ((@types.VectorId, @types.Attrs) -> Bool)?,
) -> @types.SearchHit? {
  let results = self.search(query, 1, filter)
  if results.is_empty() {
    None
  } else {
    Some(results[0])
  }
}

///|
/// Search with filter expressions and metadata index support.
///
/// Supports two filtering strategies:
/// - PreFilter: Use attr index to get candidate IDs, then score only those
/// - PostFilter: Score all vectors, then filter results
/// - Auto: Automatically choose based on estimated selectivity
///
/// PreFilter is more efficient when the filter is selective (<50% of vectors).
/// PostFilter is better for broad filters or when no attr index is available.
///
/// Parameters:
/// - query: The query vector
/// - k: Number of results to return
/// - expr: Optional filter expression (Must/MustNot/Should/Leaf)
/// - attr_index: Optional B+ tree index override for pre-filtering
/// - strategy: Filtering strategy (PreFilter/PostFilter/Auto)
///
/// Returns: Array of SearchHit matching the filter criteria
pub fn VectorDB::search_with_expr(
  self : VectorDB,
  query : Array[Double],
  k : Int,
  expr : @filter.FilterExpr?,
  attr_index : @attr.BPTreeAttrIndex?,
  strategy : @filter.FilterStrategy,
) -> Array[@types.SearchHit] {
  match expr {
    None => self.search(query, k, None)
    Some(filter_expr) => {
      let index = match attr_index {
        Some(override_index) => override_index
        None => self.attr_index
      }
      // Resolve candidates from the attribute index — always succeeds,
      // no fallback path needed.
      let candidates = @filter.resolve_candidates_bptree(filter_expr, index)
      let use_prefilter = match strategy {
        PreFilter => true
        PostFilter => false
        Auto =>
          // Use pre-filter if candidates < 50% of total
          candidates.length() < self.store.size() / 2
      }
      if use_prefilter {
        self.search_candidates(query, k, candidates)
      } else {
        let pred = @filter.compile_filter(filter_expr)
        self.search(query, k, Some(pred))
      }
    }
  }
}

///|
/// Search only within a set of candidate IDs (for pre-filtering).
///
/// Internal method used by search_with_expr for pre-filter strategy.
/// Scores only the specified candidate vectors instead of all vectors.
fn VectorDB::search_candidates(
  self : VectorDB,
  query : Array[Double],
  k : Int,
  candidates : Array[@types.VectorId],
) -> Array[@types.SearchHit] {
  guard query.length() == self.store.dim else { return [] }
  if candidates.is_empty() {
    return []
  }
  let q = self.store.normalize_query(query)
  let score_fn = @vecmath.get_score_fn(self.store.metric)
  let dim = self.store.dim
  let results : Array[@types.SearchHit] = []
  for id in candidates {
    // Skip tombstoned IDs (HNSW deletions)
    if self.is_tombstoned(id) {
      continue
    }
    match self.store.get_index(id) {
      None => continue
      Some(idx) => {
        let base = idx * dim
        let score = score_fn(self.store.data, base, q, dim)
        let attrs = self.store.attrs[idx]
        @collection.push_top_k(
          results,
          @types.SearchHit::{ id, score, attrs },
          k,
          fn(hit) { hit.score },
        )
      }
    }
  }
  results
}

///|
/// Update only the attributes for a vector (without changing the embedding).
///
/// Parameters:
/// - id: The vector ID to update
/// - attrs: New attributes to set
///
/// Returns: true if updated, false if ID not found
pub fn VectorDB::update_attrs(
  self : VectorDB,
  id : @types.VectorId,
  attrs : @types.Attrs,
) -> Bool {
  if self.store.update_attrs(id, attrs) {
    self.attr_index.set_attrs(id, attrs)
    true
  } else {
    false
  }
}

///|
/// Train the IVF index for optimal search performance.
///
/// Only applicable for IVF strategy. Call this after adding a representative
/// sample of vectors to build the centroid clusters.
///
/// Parameters:
/// - iterations: Number of k-means iterations (default: 10)
pub fn VectorDB::train(self : VectorDB, iterations? : Int = 10) -> Unit {
  match self.ivf {
    Some(state) => @ann.ivf_train(state, self.store, iterations~)
    None => ()
  }
}

///|
/// Compact the database by removing tombstoned vectors (HNSW only).
///
/// HNSW uses soft-delete (tombstones) for remove(). Over time, tombstones
/// accumulate and waste memory. compact() rebuilds the HNSW graph
/// without tombstoned entries.
///
/// For Bruteforce and IVF, this is a no-op (they physically remove).
///
/// Returns: A new VectorDB with the compacted state, and the number of
/// removed tombstones.
pub fn VectorDB::compact(self : VectorDB) -> (VectorDB, Int) {
  match self.hnsw {
    Some(state) => {
      let (new_state, removed) = @ann.hnsw_compact_and_rebuild(
        state,
        self.store,
      )
      let new_db : VectorDB = {
        store: self.store,
        attr_index: self.attr_index,
        strategy: self.strategy,
        bruteforce: None,
        hnsw: Some(new_state),
        ivf: None,
      }
      (new_db, removed)
    }
    None => (self, 0)
  }
}

///|
/// Serialize the database to bytes for persistence.
///
/// Includes all vectors, attributes, metadata index state, and ANN index state.
/// Use deserialize() to restore.
///
/// Returns: Binary representation of the database
pub fn VectorDB::serialize(self : VectorDB) -> Bytes {
  let w = @binary.BinaryWriter::new()
  // Snapshot header: magic "VCDB" + version 2
  w.push_u32(0x56434442U) // "VCDB" magic
  w.push_u32(2U) // snapshot format version
  // Write strategy type and params
  match self.strategy {
    Bruteforce => w.push_u32(0U)
    HNSW(params) => {
      w.push_u32(1U)
      w.push_u32(params.m.reinterpret_as_uint())
      w.push_u32(params.ef_construction.reinterpret_as_uint())
      w.push_u32(params.ef_search.reinterpret_as_uint())
      w.push_f64(params.level_mult)
      w.push_u64(params.seed)
      w.push_byte(if params.allow_replace_deleted { b'\x01' } else { b'\x00' })
    }
    IVF(params) => {
      w.push_u32(2U)
      w.push_u32(params.nlist.reinterpret_as_uint())
      w.push_u32(params.nprobe.reinterpret_as_uint())
    }
  }
  // Write store
  let store_bytes = self.store.serialize()
  w.push_u32(store_bytes.length().reinterpret_as_uint())
  w.push_bytes(store_bytes)
  // Write index state
  match self.strategy {
    Bruteforce => {
      let bf_bytes = match self.bruteforce {
        Some(state) => @ann.bf_serialize(state)
        None => Bytes::new(0)
      }
      w.push_u32(bf_bytes.length().reinterpret_as_uint())
      w.push_bytes(bf_bytes)
    }
    HNSW(_) => {
      let hnsw_bytes = match self.hnsw {
        Some(state) => @ann.hnsw_serialize(state)
        None => Bytes::new(0)
      }
      w.push_u32(hnsw_bytes.length().reinterpret_as_uint())
      w.push_bytes(hnsw_bytes)
    }
    IVF(_) => {
      let ivf_bytes = match self.ivf {
        Some(state) => @ann.ivf_serialize(state, self.store.dim)
        None => Bytes::new(0)
      }
      w.push_u32(ivf_bytes.length().reinterpret_as_uint())
      w.push_bytes(ivf_bytes)
    }
  }
  let attr_index_bytes = self.attr_index.serialize()
  w.push_u32(attr_index_bytes.length().reinterpret_as_uint())
  w.push_bytes(attr_index_bytes)
  w.concat()
}

///|
/// Deserialize a database from bytes.
///
/// Restores all vectors, attributes, metadata index state, and ANN index state.
///
/// Parameters:
/// - data: Binary data from serialize()
///
/// Returns: The restored VectorDB instance
pub fn VectorDB::deserialize(data : Bytes) -> VectorDB {
  let r = @binary.BinaryReader::new(data)
  // Detect format: v2+ starts with magic 0x56434442 ("VCDB"),
  // v1 (legacy) starts with strategy_type (0, 1, or 2).
  let first_u32 = r.read_u32()
  let _snapshot_version = if first_u32 == 0x56434442U {
    let ver = r.read_u32()
    ver
  } else {
    1U // legacy format — first_u32 is strategy_type, no version header
  }
  let strategy_type = if first_u32 == 0x56434442U {
    r.read_u32()
  } else {
    first_u32
  }
  let (hnsw_params, ivf_params) : (@types.HNSWParams?, @types.IVFParams?) = match
    strategy_type {
    0U => (None, None)
    1U => {
      let m = r.read_u32().reinterpret_as_int()
      let ef_construction = r.read_u32().reinterpret_as_int()
      let ef_search = r.read_u32().reinterpret_as_int()
      let level_mult = r.read_f64()
      let seed = r.read_u64()
      let allow_replace_deleted = r.read_byte() != b'\x00'
      (
        Some(@types.HNSWParams::{
          m,
          ef_construction,
          ef_search,
          level_mult,
          seed,
          allow_replace_deleted,
        }),
        None,
      )
    }
    _ => {
      let nlist = r.read_u32().reinterpret_as_int()
      let nprobe = r.read_u32().reinterpret_as_int()
      (None, Some(@types.IVFParams::{ nlist, nprobe }))
    }
  }
  let store_len = r.read_u32().reinterpret_as_int()
  let store_bytes = r.read_bytes(store_len)
  let store = @store.CoreStore::deserialize(store_bytes)
  match strategy_type {
    0U => {
      let _ = r.read_u32()
      let attr_index = if r.has_more() {
        let len = r.read_u32().reinterpret_as_int()
        @attr.BPTreeAttrIndex::deserialize(r.read_bytes(len))
      } else {
        rebuild_metadata_index(store, None)
      }
      {
        store,
        attr_index,
        strategy: @types.Bruteforce,
        bruteforce: Some(@ann.BruteforceState::new(store.metric)),
        hnsw: None,
        ivf: None,
      }
    }
    1U => {
      let hnsw_len = r.read_u32().reinterpret_as_int()
      let hnsw_bytes = r.read_bytes(hnsw_len)
      let params = hnsw_params.unwrap()
      let state = @ann.HNSWState::new(params, store.metric, store.capacity())
      @ann.hnsw_deserialize(state, hnsw_bytes)
      let attr_index = if r.has_more() {
        let len = r.read_u32().reinterpret_as_int()
        @attr.BPTreeAttrIndex::deserialize(r.read_bytes(len))
      } else {
        rebuild_metadata_index(store, Some(state.tombstone))
      }
      {
        store,
        attr_index,
        strategy: @types.HNSW(params),
        bruteforce: None,
        hnsw: Some(state),
        ivf: None,
      }
    }
    _ => {
      let ivf_len = r.read_u32().reinterpret_as_int()
      let ivf_bytes = r.read_bytes(ivf_len)
      let params = ivf_params.unwrap()
      let state = @ann.IVFState::new(params, store.metric, store.dim)
      @ann.ivf_deserialize(state, ivf_bytes, store.dim)
      let attr_index = if r.has_more() {
        let len = r.read_u32().reinterpret_as_int()
        @attr.BPTreeAttrIndex::deserialize(r.read_bytes(len))
      } else {
        rebuild_metadata_index(store, None)
      }
      {
        store,
        attr_index,
        strategy: @types.IVF(params),
        bruteforce: None,
        hnsw: None,
        ivf: Some(state),
      }
    }
  }
}