// 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.
///
/// Throws an error if the indices are out of bounds or if `start` is greater
/// than `end`.
#alias("_[_:_]")
pub fn[T] UninitializedArray::sub(
  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 start index out of bounds")
  }
  ArrayView::make(self, start, end - start)
}

///|
/// 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[-1:])
}

///|
test "panic as_view with invalid_end" {
  let arr : UninitializedArray[Int] = UninitializedArray::make(5)
  ignore(arr[2: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
  }
}