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

///|
/// Exact bytes of a non-NULL BLOB/TEXT, without its length prefix.
pub struct DecodedBlob {
  value : Bytes
  bytes_read : Int32
} derive(Debug, Eq)

///|
pub extend DecodedBlob with Eq::{equal, not_equal}

///|
pub extend DecodedBlob with @debug.Debug::{to_repr}

///|
/// Decode canonical MySQL BLOB (252), including TINY/MEDIUM/LONG BLOB/TEXT.
/// Its one metadata byte specifies a 1..4-byte little-endian length prefix.
/// `file_offset` is the field's first byte; `column_index` is zero-based.
/// The limit excludes the prefix; 0 allows only empty values. Values above
/// 2_147_483_643 are invalid limits so total consumed bytes fit Int32.
/// Check limits and available input before allocating or converting indices.
/// The caller handles row NULL/presence bitmaps and TEXT charset conversion.
pub fn TableColumn::decode_blob(
  self : TableColumn,
  data : BytesView,
  file_offset~ : UInt64,
  column_index~ : Int32,
  max_value_bytes~ : Int32 = 16_777_216,
) -> DecodedBlob raise DecodeError {
  guard max_value_bytes >= 0 && max_value_bytes <= 2_147_483_643 else {
    raise InvalidBlobLimit(max_value_bytes~)
  }
  guard self.wire_type == 252 else {
    raise UnsupportedBlobColumn(
      file_offset~,
      column_index~,
      wire_type=self.wire_type,
    )
  }
  guard self.metadata.length() == 1 else {
    raise InvalidBlobMetadata(
      file_offset~,
      column_index~,
      reason="expected one metadata byte",
    )
  }
  let width = self.metadata[0].to_int()
  guard width >= 1 && width <= 4 else {
    raise InvalidBlobMetadata(
      file_offset~,
      column_index~,
      reason="length prefix width must be 1..4",
    )
  }
  guard data.length() >= width else {
    raise TruncatedColumnValue(
      file_offset~,
      column_index~,
      expected=width,
      actual=data.length(),
    )
  }
  let mut length = 0UL
  for i in 0..= bytes_read else {
    raise TruncatedColumnValue(
      file_offset~,
      column_index~,
      expected=bytes_read,
      actual=data.length(),
    )
  }
  { value: data[width:bytes_read].to_owned(), bytes_read, }
}