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

///|
priv struct DecodeBudget {
  limit : Int32
  mut used : Int32
}

///|
priv struct DecodedString {
  value : Bytes
  storage : StringStorage
}

///|
fn DecodeBudget::charge(
  self : DecodeBudget,
  count : UInt64,
  offset : Int32,
) -> Unit raise ParseError {
  check_resource_limit(
    "decoded-bytes",
    offset,
    self.used.to_uint64() + count,
    self.limit,
  )
  self.used += count.to_int()
}

///|
fn Reader::read_string(
  self : Reader,
  max_string_bytes : Int32,
  budget : DecodeBudget,
  allow_lzf~ : Bool,
  resource~ : String,
) -> DecodedString raise ParseError {
  let offset = self.pos
  let initial_used = budget.used
  errdefer {
    self.pos = offset
    budget.used = initial_used
  }
  let (value, storage) = match self.read_length() {
    Plain(count) => {
      check_resource_limit(resource, offset, count, max_string_bytes)
      self.require(count)
      budget.charge(count, offset)
      (self.read_bytes(count).to_owned(), Raw)
    }
    Encoded(Int8) => (b"\{self.read_i8()}", Integer8)
    Encoded(Int16) => (b"\{self.read_i16_le()}", Integer16)
    Encoded(Int32) => (b"\{self.read_i32_le()}", Integer32)
    Encoded(Lzf) => {
      if !allow_lzf {
        raise UnsupportedStringEncoding(offset~, encoding=3)
      }
      let compressed_size = self.read_count()
      let output_size = self.read_count()
      check_resource_limit(resource, offset, output_size, max_string_bytes)
      self.require(compressed_size)
      budget.charge(output_size, offset)
      let base_offset = self.pos
      let payload = self.read_bytes(compressed_size)
      (decompress_lzf(payload, output_size.to_int(), base_offset), Lzf)
    }
  }
  if storage is (Integer8 | Integer16 | Integer32) {
    check_resource_limit(
      resource,
      offset,
      value.length().to_uint64(),
      max_string_bytes,
    )
    budget.charge(value.length().to_uint64(), offset)
  }
  { value, storage, }
}

///|
fn Reader::read_string_record(
  self : Reader,
  prefix : KeyPrefix,
  database : UInt64,
  max_string_bytes : Int32,
  budget : DecodeBudget,
) -> StringRecord raise ParseError {
  let key = self.read_string(
    max_string_bytes,
    budget,
    allow_lzf=true,
    resource="string-bytes",
  )
  let value = self.read_string(
    max_string_bytes,
    budget,
    allow_lzf=true,
    resource="string-bytes",
  )
  {
    database,
    key: key.value,
    value: value.value,
    key_storage: key.storage,
    value_storage: value.storage,
    expires_at_ms: prefix.expires_at_ms,
    idle_seconds: prefix.idle_seconds,
    frequency: prefix.frequency,
    record_start: prefix.start,
    record_end: self.pos,
    serialized_bytes: (self.pos - prefix.start).to_uint64(),
  }
}