// 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 UninitializedArray[T](FixedArray[UnsafeMaybeUninit[T]])

///|
/// Creates an uninitialized array of the specified size.
///
/// Parameters:
///
/// - `size` : The number of elements the array should hold.
///
/// Returns an uninitialized array of type `T` with the specified size.
pub fn[T] UninitializedArray::make(size : Int) -> UninitializedArray[T] = "%fixedarray.make_uninit"

///|
/// Retrieves the element at the specified index from an uninitialized array.
///
/// Parameters:
///
/// - `array` : The uninitialized array from which to retrieve the element.
/// - `index` : The index of the element to retrieve.
///
/// Returns the element at the specified index.
#alias("_[_]")
pub fn[T] UninitializedArray::at(
  self : UninitializedArray[T],
  index : Int,
) -> T = "%fixedarray.get"

///|
#internal(unsafe, "For internal use only.")
fn[T] UninitializedArray::unsafe_get(
  self : UninitializedArray[T],
  index : Int,
) -> T = "%fixedarray.unsafe_get"

///|
/// Sets the value at the specified index in an uninitialized array.
///
/// Parameters:
///
/// - `array` : The uninitialized array where the value will be set.
/// - `index` : The position in the array where the value will be set.
/// - `value` : The value to be set at the specified index.
#alias("_[_]=_")
pub fn[T] UninitializedArray::set(
  self : UninitializedArray[T],
  index : Int,
  value : T,
) = "%fixedarray.set"

///|
#internal(unsafe, "For internal use only.")
fn[T] UninitializedArray::unsafe_set(
  self : UninitializedArray[T],
  index : Int,
  value : T,
) -> Unit = "%fixedarray.unsafe_set"

///|
/// Creates a view into a portion of the uninitialized array.
///
/// Parameters:
///
/// * `array` : The uninitialized array to create a view from.
/// * `start` : The starting index of the view (inclusive). Defaults to 0.
/// * `end` : The ending index of the view (exclusive). If not provided, defaults
/// to the length of the array.
///
/// Returns an `ArrayView` that provides a window into the specified portion of
/// the array.
///
/// Panics if the indices are out of bounds or if `start` is greater
/// than `end`.
#alias(sub, deprecated="Use `exact_view` instead")
pub fn[T] UninitializedArray::exact_view(
  self : UninitializedArray[T],
  start? : Int = 0,
  end? : Int,
) -> ArrayView[T] {
  let len = self.length()
  let end = match end {
    Some(end) | (None with end = len) => end
  }
  guard start >= 0 && start <= end && end <= len else {
    abort("View index out of bounds")
  }
  ArrayView::make(self, start, end - start)
}

///|
/// Creates a view of a portion of the uninitialized array without copying.
/// Each bound is clamped to `[0, length()]`; an inverted range yields an empty
/// view at the clamped `start`. Defaults to the full array when both bounds
/// are omitted. Elements must be initialized before they are read through the
/// returned view.
///
/// Also available as `arr[start:end]`. Use `UninitializedArray::exact_view` to validate bounds.
///
/// ```mbt check
/// test {
///   let arr = @builtin.UninitializedArray::make(3)
///   for i in 0..<3 {
///     arr[i] = i + 1
///   }
///   json_inspect(arr.clamped_view(start=1, end=10).to_owned(), content=[2, 3])
/// }
/// ```
#alias("_[_:_]")
pub fn[T] UninitializedArray::clamped_view(
  self : UninitializedArray[T],
  start? : Int = 0,
  end? : Int,
) -> ArrayView[T] {
  let len = self.length()
  let lo = clamp_offset(start, len)
  let hi = match end {
    None => len
    Some(end) => clamp_offset(end, len)
  }
  let count = if hi > lo { hi - lo } else { 0 }
  ArrayView::make(self, lo, count)
}

///|
/// Returns the length of an uninitialized array.
///
/// Parameters:
///
/// - `array` : The uninitialized array whose length is to be determined.
///
/// Returns the length of the uninitialized array as an integer.
pub fn[A] UninitializedArray::length(self : UninitializedArray[A]) -> Int {
  self.0.length()
}

///|
/// Unsafe variant of `blit`.
#internal(unsafe, "For internal use only.")
#doc(hidden)
pub fn[T] UninitializedArray::unsafe_blit(
  dst : UninitializedArray[T],
  dst_offset : Int,
  src : UninitializedArray[T],
  src_offset : Int,
  len : Int,
) -> Unit {
  FixedArray::unsafe_blit(dst.0, dst_offset, src.0, src_offset, len)
}

///|
/// Unsafe variant of `make` followed by `unsafe_blit`.
#intrinsic("%fixedarray.make_with_blit")
#owned(src)
fn[T] UninitializedArray::unsafe_make_and_blit(
  src : UninitializedArray[T],
  allocate_len : Int,
  src_offset : Int,
  dst_offset : Int,
  blit_len : Int,
) -> UninitializedArray[T] {
  let dst = UninitializedArray::make(allocate_len)
  UninitializedArray::unsafe_blit(dst, dst_offset, src, src_offset, blit_len)
  dst
}

///|
/// Unsafe variant of `make` with `init` followed by `unsafe_blit`.
#cfg(not(target="js"))
#intrinsic("%fixedarray.make_with_init_blit")
#owned(src, init)
fn[T] UninitializedArray::unsafe_make_and_blit_with_init(
  src : UninitializedArray[T],
  allocate_len : Int,
  init : T,
  src_offset : Int,
  dst_offset : Int,
  blit_len : Int,
) -> UninitializedArray[T] {
  let dst = UninitializedArray::make(allocate_len)
  dst.unchecked_fill(0, init, allocate_len)
  UninitializedArray::unsafe_blit(dst, dst_offset, src, src_offset, blit_len)
  dst
}

///|
/// Checked variant of `unsafe_make_and_blit`.
#owned(src)
pub fn[T] UninitializedArray::make_and_blit(
  src : UninitializedArray[T],
  allocate_len~ : Int,
  len~ : Int,
  src_offset? : Int = 0,
  dst_offset? : Int = 0,
) -> UninitializedArray[T] {
  guard allocate_len >= 0 &&
    len >= 0 &&
    src_offset >= 0 &&
    dst_offset >= 0 &&
    src_offset + len <= src.length() &&
    dst_offset + len <= allocate_len else {
    abort(
      "bounds check failed: allocate_len = \{allocate_len}, src_offset = \{src_offset}, dst_offset = \{dst_offset}, len = \{len}, src.length = \{src.length()}",
    )
  }
  UninitializedArray::unsafe_make_and_blit(
    src, allocate_len, src_offset, dst_offset, len,
  )
}

///|
/// Unsafe variant of `make` followed by `unsafe_blit_fixed`.
#cfg(not(target="js"))
#intrinsic("%fixedarray.make_with_blit")
#owned(src)
fn[T] UninitializedArray::unsafe_make_and_blit_from_fixed(
  src : FixedArray[T],
  allocate_len : Int,
  src_offset : Int,
  dst_offset : Int,
  blit_len : Int,
) -> UninitializedArray[T] {
  let dst = UninitializedArray::make(allocate_len)
  UninitializedArray::unsafe_blit_fixed(
    dst, dst_offset, src, src_offset, blit_len,
  )
  dst
}

///|
test "as_view with valid_range" {
  let arr : UninitializedArray[Int] = UninitializedArray::make(5)
  let view = arr[1:4]
  inspect(view.start(), content="1")
  inspect(view.len(), content="3")
}

///|
test "panic as_view with invalid_start" {
  let arr : UninitializedArray[Int] = UninitializedArray::make(5)
  ignore(arr.exact_view(start=-1))
}

///|
test "panic as_view with invalid_end" {
  let arr : UninitializedArray[Int] = UninitializedArray::make(5)
  ignore(arr.exact_view(start=2, end=10))
}

///|
#coverage.skip
#intrinsic("%fixedarray.fill")
#cfg(not(target="js"))
#owned(value)
fn[T] UninitializedArray::unchecked_fill(
  self : UninitializedArray[T],
  start : Int,
  value : T,
  len : Int,
) -> Unit {
  for i in start..<(start + len) {
    self[i] = value
  }
}