// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
const UTF8_BOM_0 : Byte = b'\xEF'
///|
const UTF8_BOM_1 : Byte = b'\xBB'
///|
const UTF8_BOM_2 : Byte = b'\xBF'
///|
#borrow(src)
#intrinsic("%utf8.len_from_utf16")
fn utf8_len_from_utf16(src : String, src_offset : Int, src_length : Int) -> Int {
let end = src_offset + src_length
for index = src_offset, len = 0; index < end; {
let code = src.unsafe_get(index)
let code_int = code.to_int()
if code_int < 0x80 {
continue index + 1, len + 1
} else if code_int < 0x800 {
continue index + 1, len + 2
} else if code.is_leading_surrogate() || code.is_trailing_surrogate() {
continue index + 1, len + 2
} else {
continue index + 1, len + 3
}
} nobreak {
len
}
}
///|
#borrow(src, dst)
#intrinsic("%utf8.encode_from_utf16")
fn utf8_encode_from_utf16(
src : String,
src_offset : Int,
src_length : Int,
dst : FixedArray[Byte],
dst_offset : Int,
) -> Int {
let end = src_offset + src_length
for index = src_offset, written = 0; index < end; {
let unit = src.unsafe_get(index)
let code = unit.to_int()
if code < 0x80 {
dst.unsafe_set(dst_offset + written, code.to_byte())
continue index + 1, written + 1
}
if code < 0x800 {
dst.unsafe_set(dst_offset + written, (0xC0 | (code >> 6)).to_byte())
dst.unsafe_set(dst_offset + written + 1, (0x80 | (code & 0x3F)).to_byte())
continue index + 1, written + 2
}
if unit.is_leading_surrogate() {
if index + 1 < end {
let trail = src.unsafe_get(index + 1)
if trail.is_trailing_surrogate() {
let scalar = ((code - 0xD800) << 10) +
(trail.to_int() - 0xDC00) +
0x10000
dst.unsafe_set(
dst_offset + written,
(0xF0 | (scalar >> 18)).to_byte(),
)
dst.unsafe_set(
dst_offset + written + 1,
(0x80 | ((scalar >> 12) & 0x3F)).to_byte(),
)
dst.unsafe_set(
dst_offset + written + 2,
(0x80 | ((scalar >> 6) & 0x3F)).to_byte(),
)
dst.unsafe_set(
dst_offset + written + 3,
(0x80 | (scalar & 0x3F)).to_byte(),
)
continue index + 2, written + 4
}
}
break -(index - src_offset + 1)
}
if unit.is_trailing_surrogate() {
break -(index - src_offset + 1)
}
dst.unsafe_set(dst_offset + written, (0xE0 | (code >> 12)).to_byte())
dst.unsafe_set(
dst_offset + written + 1,
(0x80 | ((code >> 6) & 0x3F)).to_byte(),
)
dst.unsafe_set(dst_offset + written + 2, (0x80 | (code & 0x3F)).to_byte())
continue index + 1, written + 3
} nobreak {
written
}
}
///|
/// Encodes a string into a UTF-8 byte array.
///
/// Panics if the string contains an invalid surrogate pair.
pub fn encode(str : StringView, bom? : Bool = false) -> Bytes {
let src = str.data()
let src_offset = str.start_offset()
let src_length = str.length()
let utf8_length = utf8_len_from_utf16(src, src_offset, src_length)
let bom_length = if bom { 3 } else { 0 }
let arr = FixedArray::make(utf8_length + bom_length, b'\x00')
if bom {
arr[0] = UTF8_BOM_0
arr[1] = UTF8_BOM_1
arr[2] = UTF8_BOM_2
}
match utf8_encode_from_utf16(src, src_offset, src_length, arr, bom_length) {
written if written >= 0 => arr.unsafe_reinterpret_as_bytes()
_ => abort("invalid surrogate pair")
}
}