// 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.

///|
struct StringBuilder {
  // Only 0.. StringBuilder {
  guard size_hint >= 0 else { abort("size_hint must be non-negative") }
  let data : UninitializedArray[UInt16] = UninitializedArray::make(size_hint)
  { data, len: 0, }
}

///|
/// Return whether the given buffer is empty.
pub fn StringBuilder::is_empty(self : StringBuilder) -> Bool {
  self.len == 0
}

///|
/// Compute the next capacity without allocating. Since appends never shrink the
/// builder, `required < len` means the required-size calculation overflowed.
#inline
fn stringbuilder_growth_capacity(
  current : Int32,
  len : Int32,
  required : Int32,
) -> Int32 {
  if required < len {
    abort("StringBuilder capacity overflow")
  }
  // Start doubling from at least 1 so a zero-length initial buffer can grow.
  for space = current.max(1); space < required; {
    let next = space * 2
    if next <= space {
      break required
    }
    continue next
  } nobreak {
    space
  }
}

///|
/// Grow the builder to at least `required`. Callers keep the capacity check on
/// their fast path and enter here only when growth or overflow handling is
/// needed. The builder invariant `0 <= len <= data.length()` lets fixed-size
/// appends compare against the remaining capacity without overflowing.
#inline(never)
fn StringBuilder::grow(self : StringBuilder, required : Int32) -> Unit {
  let new_capacity = stringbuilder_growth_capacity(
    self.data.length(),
    self.len,
    required,
  )
  let new_data = UninitializedArray::make_and_blit(
    self.data,
    allocate_len=new_capacity,
    len=self.len,
  )
  self.data = new_data
}

///|
fn UninitializedArray::unsafe_blit_from_string_uninit(
  self : UninitializedArray[UInt16],
  dst_offset : Int32,
  str : String,
  str_offset : Int32,
  len : Int32,
) -> Unit {
  unsafe_uint16_buffer_as_fixedarray(self).unsafe_blit_from_string(
    dst_offset, str, str_offset, len,
  )
}

///|
#cfg(not(target="wasm"))
fn FixedArray::unsafe_blit_from_string(
  self : FixedArray[UInt16],
  dst_offset : Int32,
  str : String,
  str_offset : Int32,
  len : Int32,
) -> Unit {
  let end_str_offset = str_offset + len
  for i = str_offset, j = dst_offset; i < end_str_offset; i = i + 1, j = j + 1 {
    self.unsafe_set(j, str.unsafe_get(i))
  }
}

///|
#cfg(target="wasm")
fn FixedArray::unsafe_blit_from_string(
  self : FixedArray[UInt16],
  dst_offset : Int32,
  str : String,
  str_offset : Int32,
  len : Int32,
) -> Unit {
  if len >= 8 {
    return self.unsafe_blit_from_string_simd(dst_offset, str, str_offset, len)
  }
  let end_str_offset = str_offset + len
  for i = str_offset, j = dst_offset; i < end_str_offset; i = i + 1, j = j + 1 {
    self.unsafe_set(j, str.unsafe_get(i))
  }
}

///|
#cfg(target="wasm")
fn FixedArray::unsafe_blit_from_string_simd(
  self : FixedArray[UInt16],
  dst_offset : Int32,
  str : String,
  str_offset : Int32,
  len : Int32,
) -> Unit {
  let vector_len = len - (len & 7)
  // Source strings have separate storage from the builder's mutable buffer.
  // SAFETY: both ranges are in bounds. Each vector stays inside the requested
  // range; the remainder is copied one unit at a time.
  for i = 0; i < vector_len; i = i + 8 {
    v128_store_i16x8(self, dst_offset + i, v128_load_i16x8(str, str_offset + i))
  }
  for i in vector_len.. FixedArray[UInt16] = "%identity"

///|
/// Writes a string to the StringBuilder.
pub impl Logger for StringBuilder with fn write_string(self, str) {
  let str_len = str.length()
  if str_len == 0 {
    return
  }
  let required = self.len + str_len
  if required > self.data.length() || required < self.len {
    self.grow(required)
  }
  self.data.unsafe_blit_from_string_uninit(self.len, str, 0, str_len)
  self.len += str_len
}

///|
/// Writes a character to the StringBuilder.
pub impl Logger for StringBuilder with fn write_char(self, ch) {
  let code = ch.to_uint()
  if code <= 0xFFFFU {
    if self.len >= self.data.length() {
      self.grow(self.len + 1)
    }
    self.data[self.len] = code.to_uint16()
    self.len += 1
  } else if code <= 0x10FFFFU {
    if self.data.length() - self.len < 2 {
      self.grow(self.len + 2)
    }
    let code = code - 0x10000U
    self.data[self.len] = (0xD800U + (code >> 10)).to_uint16()
    self.data[self.len + 1] = (0xDC00U + code.land(0x3FFU)).to_uint16()
    self.len += 2
  } else {
    abort("invalid code point")
  }
}

///|
/// Writes a string view to the StringBuilder.
///
/// Parameters:
///
/// * `self` : The StringBuilder to write to.
/// * `str` : The view of the string to write.
///
/// Example:
///
/// ```mbt check
/// test {
///   let sb = StringBuilder()
///   sb.write_view("Hello, world!"[:5])
///   @test.assert_eq(sb.to_string(), "Hello")
/// }
/// ```
pub impl Logger for StringBuilder with fn write_view(
  self : StringBuilder,
  str : StringView,
) -> Unit {
  let str_len = str.length()
  if str_len == 0 {
    return
  }
  let required = self.len + str_len
  if required > self.data.length() || required < self.len {
    self.grow(required)
  }
  self.data.unsafe_blit_from_string_uninit(
    self.len,
    str.data(),
    str.start_offset(),
    str_len,
  )
  self.len += str_len
}

///|
/// Returns the current content of the StringBuilder as a string.
pub fn StringBuilder::to_string(self : StringBuilder) -> String {
  if self.len == 0 {
    ""
  } else {
    let data = UninitializedArray::make_and_blit(
      self.data,
      allocate_len=self.len,
      len=self.len,
    )
    unsafe_fixedarray_uint16_to_string(unsafe_uint16_buffer_as_fixedarray(data))
  }
}

///|
#doc(hidden)
pub fn StringBuilder::write_end(self : StringBuilder) -> String {
  self.to_string()
}

///|
pub impl Show for StringBuilder with fn to_string(self) {
  StringBuilder::to_string(self)
}

///|
/// Resets the string builder to an empty state.
///
/// Keeps the allocated capacity by default for reuse. Set `keep_capacity=false`
/// to discard the backing buffer. Previously returned strings are unaffected.
/// This option has no effect on JS, which does not retain a mutable buffer.
///
/// ```mbt check
/// test {
///   let builder = StringBuilder()
///   builder.write_string("hello")
///   builder.reset(keep_capacity=false)
///   inspect(builder.to_string(), content="")
/// }
/// ```
pub fn StringBuilder::reset(
  self : StringBuilder,
  keep_capacity~ : Bool = true,
) -> Unit {
  self.len = 0
  if !keep_capacity {
    self.data = UninitializedArray::make(0)
  }
}