// 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 {
  mut data : FixedArray[UInt16]
  mut len : Int
}

///|
/// Creates a new string builder with an optional initial capacity hint.
///
/// Parameters:
///
/// * `size_hint` : An optional initial capacity hint for the internal buffer. If
/// less than 1, a minimum capacity of 1 is used. Defaults to 0. It is the size of bytes, 
/// not the size of characters. `size_hint` may be ignored on some platforms, JS for example.
///
/// Returns a new `StringBuilder` instance with the specified initial capacity.
///
#alias(new)
pub fn StringBuilder::StringBuilder(size_hint? : Int = 0) -> StringBuilder {
  let initial = if size_hint < 1 { 1 } else { (size_hint + 1) / 2 }
  let data : FixedArray[UInt16] = FixedArray::make(initial, 0)
  { 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 : Int,
  len : Int,
  required : Int,
) -> Int {
  if required < len {
    abort("StringBuilder capacity overflow")
  }
  let enough_space = for space = current; space < required; {
    let next = space * 2
    if next <= space {
      break required
    }
    continue next
  } nobreak {
    space
  }
  enough_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.
fn StringBuilder::grow(self : StringBuilder, required : Int) -> Unit {
  let new_capacity = stringbuilder_growth_capacity(
    self.data.length(),
    self.len,
    required,
  )
  let new_data = FixedArray::make_and_blit(
    self.data,
    allocate_len=new_capacity,
    init=(Default::default() : UInt16),
    len=self.len,
  )
  self.data = new_data
}

///|
fn FixedArray::unsafe_blit_from_string(
  self : FixedArray[UInt16],
  dst_offset : Int,
  str : String,
  str_offset : Int,
  len : Int,
) -> 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))
  }
}

///|
/// 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(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 part of the given string to the StringBuilder.
/// 
/// Parameters:
///
/// * `self` : The StringBuilder to write to.
/// * `str` : The given string.
/// * `start` : The start index of the substring to write.
/// * `len` : The length of the substring 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(
    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 if self.len == self.data.length() {
    unsafe_fixedarray_uint16_to_string(self.data)
  } else {
    let data = FixedArray::make_and_blit(
      self.data,
      allocate_len=self.len,
      init=(Default::default() : UInt16),
      len=self.len,
    )
    unsafe_fixedarray_uint16_to_string(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.
pub fn StringBuilder::reset(self : StringBuilder) -> Unit {
  self.data = FixedArray::make(
    self.data.length(),
    (Default::default() : UInt16),
  )
  self.len = 0
}