///|
/// Decodes unit_count UTF-16LE code units starting at byte offset.
///
/// Decoding stops at the first NUL code unit, matching how REGF stores the
/// fixed-width base block file name. Unpaired surrogates become U+FFFD so the
/// result is always valid UTF-8.
pub fn decode_utf16le(
  bytes : Bytes,
  offset : Int32,
  unit_count : Int32,
) -> String {
  let out : Array[Byte] = []
  let mut index = 0
  while index < unit_count {
    let lo = bytes[offset + index * 2].to_int()
    let hi = bytes[offset + index * 2 + 1].to_int()
    let unit = lo | (hi << 8)
    if unit == 0 {
      break
    }
    let mut code = unit
    if unit >= 0xD800 && unit <= 0xDBFF && index + 1 < unit_count {
      let lo2 = bytes[offset + (index + 1) * 2].to_int()
      let hi2 = bytes[offset + (index + 1) * 2 + 1].to_int()
      let low = lo2 | (hi2 << 8)
      if low >= 0xDC00 && low <= 0xDFFF {
        code = 0x10000 + ((unit - 0xD800) << 10) + (low - 0xDC00)
        index = index + 1
      }
    }
    if code >= 0xD800 && code <= 0xDFFF {
      code = 0xFFFD
    }
    if code < 0x80 {
      out.push((code & 0xFF).to_byte())
    } else if code < 0x800 {
      out.push((0xC0 | (code >> 6)).to_byte())
      out.push((0x80 | (code & 0x3F)).to_byte())
    } else if code < 0x10000 {
      out.push((0xE0 | (code >> 12)).to_byte())
      out.push((0x80 | ((code >> 6) & 0x3F)).to_byte())
      out.push((0x80 | (code & 0x3F)).to_byte())
    } else {
      out.push((0xF0 | (code >> 18)).to_byte())
      out.push((0x80 | ((code >> 12) & 0x3F)).to_byte())
      out.push((0x80 | ((code >> 6) & 0x3F)).to_byte())
      out.push((0x80 | (code & 0x3F)).to_byte())
    }
    index = index + 1
  }
  let encoded = Bytes::from_array(out)
  @utf8.decode_lossy(encoded.clamped_view(start=0, end=encoded.length()))
}

///|
/// Decodes count single-byte characters starting at byte offset.
///
/// REGF stores compressed key names as one byte per character. Bytes are
/// decoded as UTF-8 with replacement so ASCII names round-trip exactly and
/// stray high bytes stay readable.
pub fn decode_single_byte(
  bytes : Bytes,
  offset : Int32,
  count : Int32,
) -> String {
  @utf8.decode_lossy(bytes.clamped_view(start=offset, end=offset + count))
}