///|
/// CoreStore: contiguous vector storage + id mapping.
/// Provides a compact foundation for both bruteforce and approximate search.
/// Normalizes vectors for cosine metric, maintains id->index mapping,
/// and supports O(1) compaction on remove.
pub struct CoreStore {
  dim : Int
  metric : @types.Metric
  mut ids : Array[@types.VectorId]
  mut data : Array[Double] // flattened: data[i*dim..(i+1)*dim] is vector i
  mut attrs : Array[@types.Attrs]
  pos : Map[@types.VectorId, Int] // id -> index mapping
  mut capacity : Int
  mut count : Int
}

///|
/// Create a new CoreStore
pub fn CoreStore::new(
  dim : Int,
  metric : @types.Metric,
  capacity? : Int = 1024,
) -> CoreStore {
  guard dim > 0 else { abort("dim must be positive") }
  let cap = if capacity < 1 { 1 } else { capacity }
  {
    dim,
    metric,
    ids: Array::make(cap, @types.Int64Id(0L)),
    data: Array::make(cap * dim, 0.0),
    attrs: Array::make(cap, @types.empty_attrs()),
    pos: {},
    capacity: cap,
    count: 0,
  }
}

///|
/// Get the current size (number of vectors)
pub fn CoreStore::size(self : CoreStore) -> Int {
  self.count
}

///|
/// Get the current capacity
pub fn CoreStore::capacity(self : CoreStore) -> Int {
  self.capacity
}

///|
/// Check if an id exists in the store
pub fn CoreStore::has(self : CoreStore, id : @types.VectorId) -> Bool {
  self.pos.contains(id)
}

///|
/// Ensure capacity for additional elements
pub fn CoreStore::ensure_capacity(self : CoreStore, extra : Int) -> Bool {
  if self.count + extra <= self.capacity {
    return false
  }
  // Grow capacity
  let mut new_cap = self.capacity
  while new_cap < self.count + extra {
    new_cap = new_cap * 2
  }
  // Resize ids
  let new_ids = Array::make(new_cap, @types.Int64Id(0L))
  for i in 0.. Unit {
  let base = index * self.dim
  for i in 0.. 0.0 { 1.0 / sum.sqrt() } else { 1.0 }
    for i in 0.. Int? {
  self.pos.get(id)
}

///|
/// Get vector and attrs by index
pub fn CoreStore::get_by_index(
  self : CoreStore,
  index : Int,
) -> @types.VectorRecord {
  let base = index * self.dim
  let vec = Array::make(self.dim, 0.0)
  for i in 0.. @types.VectorId? {
  if index < 0 || index >= self.count {
    None
  } else {
    Some(self.ids[index])
  }
}

///|
/// Get vector and attrs by id
pub fn CoreStore::get(
  self : CoreStore,
  id : @types.VectorId,
) -> @types.VectorRecord? {
  match self.get_index(id) {
    Some(idx) => Some(self.get_by_index(idx))
    None => None
  }
}

///|
/// Add or update a vector. Returns (index, created).
/// If upsert is false and id exists, raises error.
pub fn CoreStore::add_or_update(
  self : CoreStore,
  id : @types.VectorId,
  vector : Array[Double],
  attrs : @types.Attrs,
  upsert? : Bool = false,
) -> (Int, Bool) {
  guard vector.length() == self.dim else {
    abort(
      "dim mismatch: got " +
      vector.length().to_string() +
      ", want " +
      self.dim.to_string(),
    )
  }
  match self.get_index(id) {
    Some(idx) => {
      guard upsert else {
        abort("id already exists: " + id.to_display_string())
      }
      self.write_vector_at(idx, vector)
      self.attrs[idx] = attrs.copy() // Defensive copy to prevent external mutation
      (idx, false)
    }
    None => {
      let _ = self.ensure_capacity(1)
      let idx = self.count
      self.ids[idx] = id
      self.attrs[idx] = attrs.copy() // Defensive copy to prevent external mutation
      self.pos.set(id, idx)
      self.write_vector_at(idx, vector)
      self.count = self.count + 1
      (idx, true)
    }
  }
}

///|
/// Move info returned by remove
pub struct MoveInfo {
  moved_id : @types.VectorId?
  moved_from : Int?
  moved_to : Int?
} derive(Debug)

///|
pub impl Show for MoveInfo with fn output(self, logger) {
  logger.write_string("{ moved_id: ")
  Debug::to_repr(self.moved_id).output(logger)
  logger.write_string(", moved_from: ")
  Debug::to_repr(self.moved_from).output(logger)
  logger.write_string(", moved_to: ")
  Debug::to_repr(self.moved_to).output(logger)
  logger.write_string(" }")
}

///|
/// Remove a vector by id. Returns move info if compaction occurred.
pub fn CoreStore::remove_by_id(
  self : CoreStore,
  id : @types.VectorId,
) -> MoveInfo? {
  match self.get_index(id) {
    None => None
    Some(idx) => {
      let last = self.count - 1
      if idx != last {
        // Swap with last element
        self.ids[idx] = self.ids[last]
        self.attrs[idx] = self.attrs[last]
        let src_base = last * self.dim
        let dst_base = idx * self.dim
        for i in 0.. Bool {
  match self.get_index(id) {
    Some(idx) => {
      self.attrs[idx] = attrs.copy() // Defensive copy to prevent external mutation
      true
    }
    None => false
  }
}

///|
/// Shrink backing arrays to fit current count
pub fn CoreStore::shrink_to_fit(self : CoreStore) -> Unit {
  let n = if self.count < 1 { 1 } else { self.count }
  if n >= self.capacity {
    return
  }
  // Shrink ids
  let new_ids = Array::make(n, @types.Int64Id(0L))
  for i in 0.. Array[Double] {
  if self.metric != @types.Cosine {
    return q
  }
  @vecmath.normalize(q)
}

///|
/// Serialize CoreStore to bytes
pub fn CoreStore::serialize(self : CoreStore) -> Bytes {
  let w = @binary.BinaryWriter::new()
  // Store version: magic 0x53544F52 ("STOR") + version 2
  w.push_u32(0x53544F52U)
  w.push_u32(2U)
  // Write header: dim, metric, count
  w.push_u32(self.dim.reinterpret_as_uint())
  w.push_u32(
    match self.metric {
      @types.Cosine => 0U
      @types.L2 => 1U
      @types.Dot => 2U
    },
  )
  w.push_u32(self.count.reinterpret_as_uint())
  // Write ids: length-prefixed wire bytes via VectorId::to_wire_bytes.
  for i in 0.. w.push_u32(0U)
        Some(@types.Null) => w.push_u32(0U)
        Some(@types.Bool(b)) => {
          w.push_u32(1U)
          w.push_u32(if b { 1U } else { 0U })
        }
        Some(@types.AttrValue::Int(n)) => {
          w.push_u32(2U)
          w.push_i64(n)
        }
        Some(@types.Float(f)) => {
          w.push_u32(3U)
          w.push_f64(f)
        }
        Some(@types.AttrValue::String(s)) => {
          w.push_u32(4U)
          let s_chars = s.to_array()
          w.push_u32(s_chars.length().reinterpret_as_uint())
          for c in s_chars {
            w.push_u32(c.to_uint())
          }
        }
      }
    }
  }
  w.concat()
}

///|
/// Deserialize CoreStore from bytes
pub fn CoreStore::deserialize(data : Bytes) -> CoreStore {
  let r = @binary.BinaryReader::new(data)
  // Detect format: v2+ starts with magic 0x53544F52 ("STOR")
  let first_u32 = r.read_u32()
  let store_version : UInt = if first_u32 == 0x53544F52U {
    r.read_u32()
  } else {
    1U // legacy format — first_u32 is dim
  }
  // Read header
  let dim = if first_u32 == 0x53544F52U {
    r.read_u32().reinterpret_as_int()
  } else {
    first_u32.reinterpret_as_int()
  }
  let metric = match r.read_u32() {
    0U => @types.Cosine
    1U => @types.L2
    _ => @types.Dot
  }
  let count = r.read_u32().reinterpret_as_int()
  let store = CoreStore::new(dim, metric, capacity=count)
  // Read ids
  for i in 0.. v
          None => @types.Int64Id(0L)
        }
      } else {
        @types.Int64Id(
          // Int64Id: 8 bytes
          r.read_u64().reinterpret_as_int64(),
        )
      }
    } else {
      // v2+: length-prefixed wire bytes via VectorId::from_wire_bytes.
      let wire_len = r.read_u32().reinterpret_as_int()
      let wire = r.read_bytes(wire_len)
      match @types.VectorId::from_wire_bytes(wire) {
        Some(v) => v
        None => @types.Int64Id(0L)
      }
    }
    store.ids[i] = id
    store.pos.set(id, i)
  }
  // Read data
  for i in 0..<(count * dim) {
    store.data[i] = r.read_f64()
  }
  // Read attrs
  for i in 0.. @types.Null
        1U => @types.Bool(r.read_u32() != 0U)
        2U => @types.AttrValue::Int(r.read_i64())
        3U => @types.Float(r.read_f64())
        _ => {
          let s_len = r.read_u32().reinterpret_as_int()
          let s_chars : Array[Char] = []
          for _ in 0..