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

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

///|
fn SetBudget::check(
  self : SetBudget,
  count : UInt64,
  offset : Int32,
) -> Unit raise ParseError {
  check_resource_limit("set-members", offset, count, self.limit)
  check_resource_limit(
    "set-members",
    offset,
    self.used.to_uint64() + count,
    self.limit,
  )
}

///|
fn SetBudget::charge(
  self : SetBudget,
  count : UInt64,
  offset : Int32,
) -> Unit raise ParseError {
  self.check(count, offset)
  self.used += count.to_int()
}

///|
fn decode_set_listpack(
  input : Bytes,
  base : Int32,
  max_string_bytes : Int32,
  budget : DecodeBudget,
  sets : SetBudget,
) -> Array[Bytes] raise ParseError {
  let (reader, declared) = open_listpack(input, base)
  if declared != 65535 {
    sets.check(declared.to_uint64(), base)
  }
  let members = []
  let seen : Map[Bytes, Unit] = Map([])
  while reader.pos < input.length() - 1 {
    sets.charge(1UL, base)
    let element = reader.read_listpack_entry(base, max_string_bytes, budget)
    if seen.contains(element) {
      raise InvalidSet(offset=base, reason="duplicate member")
    }
    seen[element] = ()
    members.push(element)
  }
  if declared != 65535 && declared != members.length() {
    raise InvalidListpack(
      offset=base,
      inner_offset=4,
      reason="element count mismatch",
    )
  }
  if members.is_empty() {
    raise InvalidSet(offset=base, reason="empty set")
  }
  members
}

///|
fn Reader::read_set_record(
  self : Reader,
  prefix : KeyPrefix,
  database : UInt64,
  max_string_bytes : Int32,
  max_packed_bytes : Int32,
  budget : DecodeBudget,
  sets : SetBudget,
) -> SetRecord raise ParseError {
  let key = self.read_string(
    max_string_bytes,
    budget,
    allow_lzf=true,
    resource="string-bytes",
  )
  let offset = self.pos
  let (members, storage, packed_storage) : (
    Array[Bytes],
    SetStorage,
    StringStorage?,
  ) = match prefix.object_type.to_int() {
    2 => {
      let count = self.read_count()
      sets.charge(count, offset)
      if count == 0UL {
        raise InvalidSet(offset~, reason="empty set")
      }
      let members = []
      let seen : Map[Bytes, Unit] = Map([])
      for _ in 0.. {
      let scratch : DecodeBudget = { limit: max_packed_bytes, used: 0, }
      let packed = self.read_string(
        max_packed_bytes,
        scratch,
        allow_lzf=true,
        resource="packed-bytes",
      )
      if value == 11 {
        let (members, width) = decode_intset(
          packed.value,
          offset,
          max_string_bytes,
          budget,
          sets,
        )
        (members, Intset(width), Some(packed.storage))
      } else {
        (
          decode_set_listpack(
            packed.value,
            offset,
            max_string_bytes,
            budget,
            sets,
          ),
          Listpack,
          Some(packed.storage),
        )
      }
    }
  }
  {
    database,
    key: key.value,
    key_storage: key.storage,
    members,
    storage,
    packed_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(),
  }
}