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

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

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

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

///|
// Only decimal ASCII and explicit infinities; reject NaN, whitespace, NUL and
// implementation-specific syntax. Scratch score text is bounded independently.
fn parse_sorted_set_score(
  raw : Bytes,
  offset : Int32,
) -> Double raise ParseError {
  let invalid = InvalidSortedSet(offset~, reason="invalid score")
  if raw == b"inf" || raw == b"+inf" {
    return 0x7ff0000000000000UL.reinterpret_as_double()
  }
  if raw == b"-inf" {
    return 0xfff0000000000000UL.reinterpret_as_double()
  }
  if raw.is_empty() {
    raise invalid
  }
  let chars : Array[Char] = []
  for byte in raw {
    let n = byte.to_int()
    if !((n >= 48 && n <= 57) ||
      n == 43 ||
      n == 45 ||
      n == 46 ||
      n == 101 ||
      n == 69) {
      raise invalid
    }
    guard n.to_char() is Some(c) else { raise invalid }
    chars.push(c)
  }
  let text = String::from_array(chars)
  let score = @string.parse_double(text[:]) catch { _ => raise invalid }
  if score.is_nan() || score.is_inf() {
    raise invalid
  }
  score
}

///|
fn Reader::read_sorted_set_record(
  self : Reader,
  prefix : KeyPrefix,
  database : UInt64,
  max_string_bytes : Int32,
  max_packed_bytes : Int32,
  budget : DecodeBudget,
  sorted_sets : SortedSetBudget,
) -> SortedSetRecord raise ParseError {
  let key = self.read_string(
    max_string_bytes,
    budget,
    allow_lzf=true,
    resource="string-bytes",
  )
  let base = self.pos
  let entries : Array[SortedSetEntry] = []
  let seen : Map[Bytes, Unit] = Map([])
  let (storage, packed_storage) : (SortedSetStorage, StringStorage?) = if prefix.object_type ==
    b'\x05' {
    let count = self.read_count()
    sorted_sets.charge(count, base)
    for _ in 0..