// Copyright 2026 Noverberrain.
// SPDX-License-Identifier: Apache-2.0

///|
/// Bounded table-id cache associated with one format and caller-managed scope.
/// decode_rows clears at statement ends; manual consumers must call clear.
/// Reset for a new file or format. Failed apply/decode_rows clears cached maps.
pub struct TableMapContext {
  priv tables : Map[UInt64, TableMap]
  priv mut format : FormatDescription
  priv max_tables : Int32
  priv max_bytes : UInt64
  priv mut byte_size : UInt64
}

///|
/// `max_bytes` bounds retained wire-event sizes, not exact heap usage.
pub fn TableMapContext::new(
  format : FormatDescription,
  max_tables~ : Int32 = 1024,
  max_bytes~ : UInt64 = 33_554_432,
) -> TableMapContext raise DecodeError {
  guard max_tables > 0 && max_bytes > 0 else {
    raise InvalidTableContextLimit(max_tables~, max_bytes~)
  }
  { tables: {}, format, max_tables, max_bytes, byte_size: 0, }
}

///|
pub fn TableMapContext::length(self : TableMapContext) -> Int32 {
  self.tables.length()
}

///|
pub fn TableMapContext::get(
  self : TableMapContext,
  table_id : UInt64,
) -> TableMap? {
  self.tables.get(table_id)
}

///|
pub fn TableMapContext::clear(self : TableMapContext) -> Unit {
  self.tables.clear()
  self.byte_size = 0
}

///|
pub fn TableMapContext::reset(
  self : TableMapContext,
  format : FormatDescription,
) -> Unit {
  self.clear()
  self.format = format
}

///|
/// Validate and install a map. Reusing an id replaces its earlier definition.
/// Invalid input and quota failures invalidate the entire current map scope.
pub fn TableMapContext::apply(
  self : TableMapContext,
  data : BytesView,
  file_offset~ : UInt64,
  max_columns~ : Int32 = 1024,
  max_event_size~ : UInt32 = 16_777_216,
) -> TableMap raise DecodeError {
  errdefer self.clear()
  let table = TableMap::parse(
    self.format,
    data,
    file_offset~,
    max_columns~,
    max_event_size~,
  )
  let previous_size = match self.tables.get(table.table_id) {
    Some(previous) => previous.header.event_size.to_uint64()
    None => 0UL
  }
  guard previous_size > 0 || self.tables.length() < self.max_tables else {
    raise TableContextLimitExceeded(file_offset~, reason="too many table ids")
  }
  let byte_size = self.byte_size -
    previous_size +
    table.header.event_size.to_uint64()
  guard byte_size <= self.max_bytes else {
    raise TableContextLimitExceeded(
      file_offset~,
      reason="retained event-byte budget exceeded",
    )
  }
  self.tables[table.table_id] = table
  self.byte_size = byte_size
  table
}