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

///|
/// Creates a new dynamic array from a fixed-size array.
///
/// Parameters:
///
/// * `arr` : The fixed-size array to convert. The elements of this array will be
/// copied to the new array.
///
/// Returns a new dynamic array containing all elements from the input fixed-size
/// array.
///
/// Example:
///
/// ```mbt check
/// test {
///   let fixed = FixedArray::make(3, 42)
///   let dynamic = Array::from_fixed_array(fixed)
///   debug_inspect(dynamic, content="[42, 42, 42]")
/// }
/// ```
#owned(arr)
pub fn[T] Array::from_fixed_array(arr : FixedArray[T]) -> Array[T] {
  let len = arr.length()
  Array::unsafe_make_and_blit_from_fixed(arr, allocate_len=len, len~)
}

///|
/// Creates a new array with a specified length and initializes all elements with
/// the given value.
///
/// Parameters:
///
/// * `length` : The length of the array to create. Must be a non-negative
/// integer.
/// * `initial_value` : The value used to initialize all elements in the array.
///
/// Returns a new array of type `Array[T]` with `length` elements, where each
/// element is initialized to `initial_value`.
///
/// Throws an error if `length` is negative.
///
/// Example:
///
/// ```mbt check
/// test {
///   let arr = Array::make(3, 42)
///   debug_inspect(arr, content="[42, 42, 42]")
/// }
/// ```
///
/// WARNING: A common pitfall is creating with the same initial value, for example:
/// ```mbt check
/// test {
///   let two_dimension_array = Array::make(10, Array::make(10, 0))
///   two_dimension_array[0][5] = 10
///   @test.assert_eq(two_dimension_array[5][5], 10)
/// }
/// ```
/// This is because all the cells reference to the same object (the Array[Int] in this case).
/// One should use makei() instead which creates an object for each index.
#owned(elem)
pub fn[T] Array::make(len : Int, elem : T) -> Array[T] {
  let arr = Array::make_uninit(len)
  for i in 0.. i * 2)
///   debug_inspect(arr, content="[0, 2, 4]")
/// }
/// ```
#locals(f)
pub fn[T] Array::makei(length : Int, f : (Int) -> T raise?) -> Array[T] raise? {
  if length <= 0 {
    []
  } else {
    let array = Array::make_uninit(length)
    for i in 0.. Int {
  self.buffer().0.length()
}

///|
/// Retrieves the element at the specified index from an array without bounds
/// checking.
///
/// Parameters:
///
/// * `array` : The array from which to retrieve the element.
/// * `index` : The position in the array from which to retrieve the element.
///
/// Returns the element at the specified index.
///
/// Example:
///
/// ```mbt check
/// test {
///   let arr : Array[Int] = [1, 2, 3]
///   inspect(arr.unsafe_get(1), content="2")
/// }
/// ```
///
#intrinsic("%array.unsafe_get")
pub fn[T] Array::unsafe_get(self : Array[T], idx : Int) -> T {
  self.buffer().unsafe_get(idx)
}

///|
/// Retrieves an element from the array at the specified index.
///
/// Parameters:
///
/// * `array` : The array to get the element from.
/// * `index` : The position in the array from which to retrieve the element.
///
/// Returns the element at the specified index.
///
/// Throws a panic if the index is negative or greater than or equal to the
/// length of the array.
///
/// Example:
///
/// ```mbt check
/// test {
///   let arr : ReadOnlyArray[Int] = [1, 2, 3]
///   inspect(arr[1], content="2")
/// }
/// ```
///
#intrinsic("%array.get")
#alias("_[_]")
pub fn[T] Array::at(self : Array[T], index : Int) -> T {
  let len = self.length()
  guard! index >= 0 && index < len
  self.buffer().unsafe_get(index)
}

///|
/// Retrieves the element at the specified index from the array.
///
/// Parameters:
///
/// * `self` : The array to get the element from.
/// * `index` : The position in the array from which to retrieve the element.
///
/// Returns `Some(element)` if the index is within bounds, or `None` if the index
/// is out of bounds.
///
/// Example:
///
/// ```mbt check
/// test {
///   let arr = [1, 2, 3]
///   debug_inspect(arr.get(-1), content="None")
///   debug_inspect(arr.get(0), content="Some(1)")
///   debug_inspect(arr.get(3), content="None")
/// }
/// ```
pub fn[T] Array::get(self : Array[T], index : Int) -> T? {
  let len = self.length()
  guard index >= 0 && index < len else { None }
  Some(self.unsafe_get(index))
}

///|
/// Write `val` to `idx` without bounds checking.
///
/// This is unsafe: caller must ensure `0 <= idx < self.length()`.
///
/// Example:
///
/// ```mbt check
/// test {
///   let arr = [1, 2, 3]
///   arr.unsafe_set(1, 99)
///   debug_inspect(arr, content="[1, 99, 3]")
/// }
/// ```
#intrinsic("%array.unsafe_set")
#owned(val)
pub fn[T] Array::unsafe_set(self : Array[T], idx : Int, val : T) -> Unit {
  self.buffer().unsafe_set(idx, val)
}

///|
/// Sets the element at the specified index in the array to a new value. The
/// original value at that index is overwritten.
///
/// Parameters:
///
/// * `array` : The array to modify.
/// * `index` : The position in the array where the value will be set.
/// * `value` : The new value to assign at the specified index.
///
/// Throws an error if `index` is negative or greater than or equal to the length
/// of the array.
///
/// Example:
///
/// ```mbt check
/// test {
///   let arr = [1, 2, 3]
///   arr[1] = 42
///   debug_inspect(arr, content="[1, 42, 3]")
/// }
/// ```
///
#intrinsic("%array.set")
#alias("_[_]=_")
#owned(value)
pub fn[T] Array::set(self : Array[T], index : Int, value : T) -> Unit {
  let len = self.length()
  guard! index >= 0 && index < len
  self.buffer().unsafe_set(index, value)
}

///|
/// Compares two arrays for equality. Returns true if both arrays have the same
/// length and contain equal elements in the same order.
///
/// Parameters:
///
/// * `self` : The first array to compare.
/// * `other` : The second array to compare.
///
/// Returns true if the arrays are equal, false otherwise.
///
/// Example:
///
/// ```mbt check
/// test {
///   let arr1 = [1, 2, 3]
///   let arr2 = [1, 2, 3]
///   let arr3 = [1, 2, 4]
///   inspect(arr1 == arr2, content="true")
///   inspect(arr1 == arr3, content="false")
/// }
/// ```
pub impl[T : Eq] Eq for Array[T] with fn equal(self, other) {
  let self_len = self.length()
  let other_len = other.length()
  guard self_len == other_len else { return false }
  for i in 0.. arr1
///   inspect(arr1.compare(arr3), content="1") // arr1 > arr3 (longer)
///   inspect(arr1.compare(arr1), content="0") // arr1 = arr1
/// }
/// ```
pub impl[T : Compare] Compare for Array[T] with fn compare(self, other) {
  let len_self = self.length()
  let len_other = other.length()
  let cmp = len_self.compare(len_other)
  guard cmp is 0 else { return cmp }
  for i in 0.. sum += x)
///   inspect(sum, content="6")
/// }
/// ```
/// This method uses the array iterator. Structural mutations during traversal
/// are unsupported: appended elements are not visited, and shrinking the array
/// with operations such as `remove`, `truncate`, `clear`, or `drain` may cause
/// later iterator steps to fail.
#locals(f)
pub fn[T] Array::each(self : Array[T], f : (T) -> Unit raise?) -> Unit raise? {
  for v in self {
    f(v)
  }
}

///|
/// Iterates over the elements of the array in reverse order, applying the given
/// function to each element.
///
/// Parameters:
///
/// * `array` : The array to iterate over.
/// * `f` : A function that takes an element of type `T` and returns `Unit`. This
/// function is applied to each element of the array in reverse order.
///
/// Example:
///
/// ```mbt check
/// test {
///   let v = [3, 4, 5]
///   let mut sum = 0
///   v.rev_each(x => sum = sum - x)
///   @json.json_inspect(sum, content=-12)
/// }
/// ```
#locals(f)
pub fn[T] Array::rev_each(
  self : Array[T],
  f : (T) -> Unit raise?,
) -> Unit raise? {
  let len = self.length()
  for i in 0.. sum += x + i)
///   @test.assert_eq(sum, 15)
/// }
/// ```
#locals(f)
pub fn[T] Array::rev_eachi(
  self : Array[T],
  f : (Int, T) -> Unit raise?,
) -> Unit raise? {
  let len = self.length()
  for i in 0.. sum += x + i)
///   inspect(sum, content="15")
/// }
/// ```
#locals(f)
pub fn[T] Array::eachi(
  self : Array[T],
  f : (Int, T) -> Unit raise?,
) -> Unit raise? {
  for i, v in self {
    f(i, v)
  }
}

///|
/// Checks whether all elements satisfy the predicate.
///
/// # Example
/// ```mbt check
/// test {
///   let arr = [1, 2, 3, 4, 5]
///   assert_true(arr.all(x => x < 6))
///   assert_false(arr.all(x => x < 5))
/// }
/// ```
#locals(f)
#alias(every)
pub fn[T] Array::all(self : Array[T], f : (T) -> Bool raise?) -> Bool raise? {
  // Inlined for #locals(f); see Array::any.
  for v in self {
    if !f(v) {
      return false
    }
  }
  true
}

///|
/// Checks whether any element satisfies the predicate.
///
/// # Example
/// ```mbt check
/// test {
///   let arr = [1, 2, 3, 4, 5]
///   assert_true(arr.any(x => x < 6))
///   assert_false(arr.any(x => x < 1))
/// }
/// ```
#locals(f)
#alias(exists)
pub fn[T] Array::any(self : Array[T], f : (T) -> Bool raise?) -> Bool raise? {
  // Inlined rather than delegating to `self[:].any(f)`: passing `f` onward
  // counts as an escape, which would reject `#locals(f)` (see issue about
  // propagating localness through #locals-annotated callees).
  for v in self {
    if f(v) {
      return true
    }
  }
  false
}

///|
/// Clears the array, removing all values.
///
/// This method has no effect on the allocated capacity of the array, only setting the length to 0.
///
/// # Example
/// ```mbt check
/// test {
///   let v = [3, 4, 5]
///   v.clear()
///   @test.assert_eq(v.length(), 0)
/// }
/// ```
pub fn[T] Array::clear(self : Array[T]) -> Unit {
  self.unsafe_truncate_to_length(0)
}

///|
/// Maps a function over the elements of the array.
///
/// # Example
/// ```mbt check
/// test {
///   let v = [3, 4, 5]
///   let v2 = v.map(x => x + 1)
///   @test.assert_eq(v2, [4, 5, 6])
/// }
/// ```
#locals(f)
pub fn[T, U] Array::map(
  self : Array[T],
  f : (T) -> U raise?,
) -> Array[U] raise? {
  let arr = Array::make_uninit(self.length())
  for i, v in self {
    arr.unsafe_set(i, f(v))
  }
  arr
}

///|
/// Maps a function over the elements of the array in place.
///
/// # Example
/// ```mbt check
/// test {
///   let v = [3, 4, 5]
///   v.map_in_place(x => x + 1)
///   @test.assert_eq(v, [4, 5, 6])
/// }
/// ```
#locals(f)
#alias(map_inplace, deprecated)
pub fn[T] Array::map_in_place(
  self : Array[T],
  f : (T) -> T raise?,
) -> Unit raise? {
  for i, v in self {
    self[i] = f(v)
  }
}

///|
/// Maps a function over the elements of the array with index.
///
/// # Example
/// ```mbt check
/// test {
///   let v = [3, 4, 5]
///   let v2 = v.mapi((i, x) => x + i)
///   @test.assert_eq(v2, [3, 5, 7])
/// }
/// ```
#locals(f)
pub fn[T, U] Array::mapi(
  self : Array[T],
  f : (Int, T) -> U raise?,
) -> Array[U] raise? {
  if self.is_empty() {
    return []
  }
  let arr = Array::make_uninit(self.length())
  for i, v in self {
    arr.unsafe_set(i, f(i, v))
  }
  arr
}

///|
/// Maps a function over the elements of the array with index in place.
///
/// # Example
/// ```mbt check
/// test {
///   let v = [3, 4, 5]
///   v.mapi_in_place((i, x) => x + i)
///   @test.assert_eq(v, [3, 5, 7])
/// }
/// ```
#locals(f)
#alias(mapi_inplace, deprecated)
pub fn[T] Array::mapi_in_place(
  self : Array[T],
  f : (Int, T) -> T raise?,
) -> Unit raise? {
  for i, v in self {
    self[i] = f(i, v)
  }
}

///|
/// Creates a new array containing all elements from the input array that satisfy
/// the given predicate function.
///
/// Parameters:
///
/// * `array` : The array to filter.
/// * `predicate` : A function that takes an element and returns a boolean
/// indicating whether the element should be included in the result.
///
/// Returns a new array containing only the elements for which the predicate
/// function returns `true`. The relative order of the elements is preserved.
///
/// Example:
///
/// ```mbt check
/// test {
///   let arr = [1, 2, 3, 4, 5]
///   let evens = arr.filter(x => x % 2 == 0)
///   debug_inspect(evens, content="[2, 4]")
/// }
/// ```
#locals(f)
pub fn[T] Array::filter(
  self : Array[T],
  f : (T) -> Bool raise?,
) -> Array[T] raise? {
  let arr = []
  for v in self {
    if f(v) {
      arr.push(v)
    }
  }
  arr
}

///|
/// Tests whether the array contains no elements.
///
/// Parameters:
///
/// * `array` : The array to check.
///
/// Returns `true` if the array has no elements, `false` otherwise.
///
/// Example:
///
/// ```mbt check
/// test {
///   let empty : Array[Int] = []
///   inspect(empty.is_empty(), content="true")
///   let non_empty = [1, 2, 3]
///   inspect(non_empty.is_empty(), content="false")
/// }
/// ```
pub fn[T] Array::is_empty(self : Array[T]) -> Bool {
  self.length() == 0
}

///|
/// Reverses the order of elements in an array in place, modifying the original
/// array.
///
/// Parameters:
///
/// * `self` : The array to be reversed.
///
/// Example:
///
/// ```mbt check
/// test {
///   let arr = [1, 2, 3, 4, 5]
///   arr.rev_in_place()
///   debug_inspect(arr, content="[5, 4, 3, 2, 1]")
///   let arr : Array[Int] = []
///   arr.rev_in_place()
///   debug_inspect(arr, content="[]")
/// }
/// ```
#alias(rev_inplace, deprecated)
pub fn[T] Array::rev_in_place(self : Array[T]) -> Unit {
  let len = self.length()
  for i in 0..<(len / 2) {
    let temp = self.unsafe_get(i)
    self.unsafe_set(i, self.unsafe_get(len - i - 1))
    self.unsafe_set(len - i - 1, temp)
  }
}

///|
/// Creates a new array with elements in reversed order.
///
/// Parameters:
///
/// * `self` : The array to be reversed.
///
/// Returns a new array containing the same elements as the input array but in
/// reverse order. The original array remains unchanged.
///
/// Example:
///
/// ```mbt check
/// test {
///   let arr = [1, 2, 3, 4, 5]
///   debug_inspect(arr.rev(), content="[5, 4, 3, 2, 1]")
///   debug_inspect(arr, content="[1, 2, 3, 4, 5]") // original array unchanged
/// }
/// ```
pub fn[T] Array::rev(self : Array[T]) -> Array[T] {
  let len = self.length()
  let arr = Array::make_uninit(len)
  for i in 0.. (Array[T], Array[T]) {
  if index < 0 || index > self.length() {
    let len = self.length()
    abort(
      "index out of bounds: the len is from 0 to \{len} but the index is \{index}",
    )
  }
  let v1 = Array::unsafe_make_and_blit(
    self.buffer(),
    allocate_len=index,
    len=index,
  )
  let v2 = if index != self.length() {
    let len2 = self.length() - index
    Array::unsafe_make_and_blit(
      self.buffer(),
      allocate_len=len2,
      src_offset=index,
      len=len2,
    )
  } else {
    Array::make_uninit(0)
  }
  (v1, v2)
}

///|
/// Checks whether the array contains an element equal to the given value.
///
/// Parameters:
///
/// * `array` : The array to search in.
/// * `value` : The value to search for.
///
/// Returns `true` if the array contains an element equal to the given value,
/// `false` otherwise.
///
/// Example:
///
/// ```mbt check
/// test {
///   let arr = [1, 2, 3, 4, 5]
///   inspect(arr.contains(3), content="true")
///   inspect(arr.contains(6), content="false")
///   let arr : Array[Int] = []
///   inspect(arr.contains(1), content="false")
/// }
/// ```
pub fn[T : Eq] Array::contains(self : Array[T], value : T) -> Bool {
  for v in self {
    if v == value {
      break true
    }
  } nobreak {
    false
  }
}

///|
/// Counts how many elements in the array are equal to `value`.
///
/// # Example
/// ```mbt check
/// test {
///   let arr = [1, 2, 1, 3, 1]
///   inspect(arr.count(1), content="3")
///   inspect(arr.count(4), content="0")
/// }
/// ```
pub fn[T : Eq] Array::count(self : Array[T], value : T) -> Int {
  self[:].count(value)
}

///|
/// Counts how many elements in the array satisfy the predicate.
///
/// # Example
/// ```mbt check
/// test {
///   let arr = [1, 2, 3, 4, 5]
///   inspect(arr.count_if(x => x % 2 == 0), content="2")
/// }
/// ```
#locals(f)
pub fn[T] Array::count_if(
  self : Array[T],
  f : (T) -> Bool raise?,
) -> Int raise? {
  for v in self; count = 0 {
    if f(v) {
      continue count + 1
    }
    continue count
  } nobreak {
    count
  }
}

///|
/// Checks if the array begins with all elements of the provided prefix array in
/// order.
///
/// Parameters:
///
/// * `self` : The array to check against.
/// * `prefix` : The array containing the sequence of elements to look for at the
/// beginning.
///
/// Returns `true` if the array starts with all elements in `prefix` in the same
/// order, `false` otherwise. An empty prefix array always returns `true`, and a
/// prefix longer than the array always returns `false`.
///
/// Example:
///
/// ```mbt check
/// test {
///   let arr = [1, 2, 3, 4, 5]
///   inspect(arr.starts_with([1, 2]), content="true")
///   inspect(arr.starts_with([2, 3]), content="false")
///   inspect(arr.starts_with([]), content="true")
///   inspect(arr.starts_with([1, 2, 3, 4, 5, 6]), content="false")
/// }
/// ```
pub fn[T : Eq] Array::starts_with(
  self : Array[T],
  prefix : ArrayView[T],
) -> Bool {
  self[:].starts_with(prefix)
}

///|
/// Tests if an array ends with the given suffix.
///
/// Parameters:
///
/// * `self` : The array to check.
/// * `suffix` : The array to test against.
///
/// Returns `true` if the array ends with the given suffix, `false` otherwise.
///
/// Example:
///
/// ```mbt check
/// test {
///   let arr = [1, 2, 3, 4, 5]
///   inspect(arr.ends_with([4, 5]), content="true")
///   inspect(arr.ends_with([3, 4]), content="false")
///   inspect(arr.ends_with([]), content="true")
///   let arr : Array[Int] = []
///   inspect(arr.ends_with([]), content="true")
///   inspect(arr.ends_with([1]), content="false")
/// }
/// ```
pub fn[T : Eq] Array::ends_with(self : Array[T], suffix : ArrayView[T]) -> Bool {
  self[:].ends_with(suffix)
}

///|
/// Strip a prefix from the array.
///
/// If the array starts with the prefix, return a view of the array after the
/// prefix, otherwise return None. The returned view shares the original
/// backing array — no allocation.
///
/// # Example
/// ```mbt check
/// test {
///   let v = [1, 2, 3, 4, 5]
///   let v2 = v.strip_prefix([1, 2])
///   debug_inspect(
///     v2,
///     content=(
///       #|Some()
///     ),
///   )
/// }
/// ```
pub fn[T : Eq] Array::strip_prefix(
  self : Array[T],
  prefix : ArrayView[T],
) -> ArrayView[T]? {
  self[:].strip_prefix(prefix)
}

///|
/// Strip a suffix from the array.
///
/// If the array ends with the suffix, return a view of the array before the
/// suffix, otherwise return None. The returned view shares the original
/// backing array — no allocation.
///
/// # Example
/// ```mbt check
/// test {
///   let v = [3, 4, 5]
///   let v2 = v.strip_suffix([5])
///   debug_inspect(
///     v2,
///     content=(
///       #|Some()
///     ),
///   )
/// }
/// ```
pub fn[T : Eq] Array::strip_suffix(
  self : Array[T],
  suffix : ArrayView[T],
) -> ArrayView[T]? {
  self[:].strip_suffix(suffix)
}

///|
/// Searches for the first occurrence of a value in the array and returns its
/// index.
///
/// Parameters:
///
/// * `self` : The array to search in.
/// * `value` : The value to search for.
///
/// Returns an `Option` containing the index of the first occurrence of `value`
/// if found, or `None` if the value is not present in the array.
///
/// Example:
///
/// ```mbt check
/// test {
///   let arr = [1, 2, 3, 2, 4]
///   debug_inspect(arr.search(2), content="Some(1)") // first occurrence
///   debug_inspect(arr.search(5), content="None") // not found
/// }
/// ```
pub fn[T : Eq] Array::search(self : Array[T], value : T) -> Int? {
  self[:].search(value)
}

///|
/// Search the index of the first element that satisfies the predicate.
///
/// # Example
///
/// ```mbt check
/// test {
///   let v = [1, 2, 3, 4, 5]
///   match v.search_by(x => x == 3) {
///     Some(index) => @test.assert_eq(index, 2) // 2
///     None => println("Not found")
///   }
/// }
/// ```
#locals(f)
#alias(find_index, deprecated)
pub fn[T] Array::search_by(
  self : Array[T],
  f : (T) -> Bool raise?,
) -> Int? raise? {
  for i, v in self {
    if f(v) {
      break Some(i)
    }
  } nobreak {
    None
  }
}

///|
/// Performs a binary search on a sorted array to find the index of a given element.
///
/// # Example
/// ```mbt check
/// test {
///   let v = [3, 4, 5]
///   let result = v.binary_search(3)
///   @test.assert_eq(result, Ok(0)) // The element 3 is found at index 0
/// }
/// ```
///
/// # Arguments
/// - `self`: The array in which to perform the search.
/// - `value`: The element to search for in the array.
///
/// # Returns
/// - `Result[Int, Int]`:
/// If the element is found, an `Ok` variant is returned, containing the index of the matching element in the array.
/// If there are multiple matches, the leftmost match will be returned.
/// If the element is not found, an `Err` variant is returned, containing the index where the element could be inserted to maintain the sorted order.
///
/// # Notes
/// - Ensure that the array is sorted in increasing order before calling this function.
/// - If the array is not sorted, the returned result is undefined and should not be relied on.
pub fn[T : Compare] Array::binary_search(
  self : Array[T],
  value : T,
) -> Result[Int, Int] {
  self[:].binary_search(value)
}

///|
/// Performs a binary search on a sorted array using a custom comparison
/// function. Returns the position of the matching element if found, or the
/// position where the element could be inserted while maintaining the sorted
/// order.
///
/// Parameters:
///
/// * `array` : The sorted array to search in.
/// * `comparator` : A function that compares each element with the target value,
/// returning:
///  * A negative integer if the element is less than the target
///  * Zero if the element equals the target
///  * A positive integer if the element is greater than the target
///
/// Returns a `Result` containing either:
///
/// * `Ok(index)` if a matching element is found at position `index`
/// * `Err(index)` if no match is found, where `index` is the position where the
/// element could be inserted
///
/// Example:
///
/// ```mbt check
/// test {
///   let arr = [1, 3, 5, 7, 9]
///   let find_3 = arr.binary_search_by(x => x.compare(3))
///   debug_inspect(find_3, content="Ok(1)")
///   let find_4 = arr.binary_search_by(x => x.compare(4))
///   debug_inspect(find_4, content="Err(2)")
/// }
/// ```
///
/// Notes:
///
/// * Assumes the array is sorted according to the ordering implied by the
/// comparison function
/// * For multiple matches, returns the leftmost matching position
/// * Returns an insertion point that maintains the sort order when no match is
/// found
pub fn[T] Array::binary_search_by(
  self : Array[T],
  cmp : (T) -> Int raise?,
) -> Result[Int, Int] raise? {
  self[:].binary_search_by(cmp)
}

///|
/// Swaps the values at two positions in the array.
///
/// Parameters:
///
/// * `array` : The array in which to swap elements.
/// * `index1` : The index of the first element to be swapped.
/// * `index2` : The index of the second element to be swapped.
///
/// This function will panic if either index is negative or greater than or equal to
/// the length of the array.
///
/// Example:
///
/// ```mbt check
/// test {
///   let arr = [1, 2, 3]
///   arr.swap(0, 2)
///   debug_inspect(arr, content="[3, 2, 1]")
/// }
/// ```
pub fn[T] Array::swap(self : Array[T], i : Int, j : Int) -> Unit {
  if i >= self.length() || j >= self.length() || i < 0 || j < 0 {
    index_out_of_bounds2(self.length(), i, j)
  }
  let temp = self.unsafe_get(i)
  self.unsafe_set(i, self.unsafe_get(j))
  self.unsafe_set(j, temp)
}

///|
/// Removes all elements from the array that do not satisfy the predicate
/// function, modifying the array in place. The order of remaining elements is
/// preserved.
///
/// Parameters:
///
/// * `array` : The array to be filtered.
/// * `predicate` : A function that takes an element and returns `true` if the
/// element should be kept, `false` if it should be removed.
///
/// Example:
///
/// ```mbt check
/// test {
///   let arr = [1, 2, 3, 4, 5]
///   arr.retain(x => x % 2 == 0)
///   debug_inspect(arr, content="[2, 4]")
///   let arr = [1, 2, 3]
///   arr.retain(x => x > 10)
///   debug_inspect(arr, content="[]")
///   let arr = [1, 2, 3]
///   arr.retain(_ => true)
///   debug_inspect(arr, content="[1, 2, 3]")
/// }
/// ```
#locals(f)
pub fn[T] Array::retain(self : Array[T], f : (T) -> Bool raise?) -> Unit raise? {
  let len = self.length()
  let write = for read, v in self; write = 0 {
    if f(v) {
      if read != write {
        self.unsafe_set(write, v)
      }
      continue write + 1
    }
    continue write
  } nobreak {
    write
  }
  if write != len {
    self.unsafe_truncate_to_length(write)
  }
}

///|
/// Resizes an array to a specified length, either by truncating if the new
/// length is smaller, or by appending copies of a default value if the new
/// length is larger.
///
/// Parameters:
///
/// * `array` : The array to be resized.
/// * `new_length` : The desired length of the array after resizing.
/// * `default_value` : The value to append when extending the array.
///
/// Throws a panic if `new_length` is negative.
///
/// Examples:
///
/// ```mbt check
/// test {
///   let arr = [1, 2, 3, 4, 5]
///   arr.resize(3, 0)
///   debug_inspect(arr, content="[1, 2, 3]")
///   let arr = [1, 2, 3]
///   arr.resize(5, 0)
///   debug_inspect(arr, content="[1, 2, 3, 0, 0]")
/// }
/// ```
///
pub fn[T] Array::resize(self : Array[T], new_len : Int, f : T) -> Unit {
  if new_len < 0 {
    abort("negative new length")
  }
  let len = self.length()
  if new_len < len {
    self.unsafe_truncate_to_length(new_len)
  } else if new_len > len {
    self.unsafe_resize_with_default(new_len, f)
  }
}

///|
/// Flattens an array of arrays into an array.
///
/// Example:
///
/// ```mbt check
/// test {
///   let v = [[3, 4], [5, 6]].flatten()
///   @test.assert_eq(v, [3, 4, 5, 6])
/// }
/// ```
pub fn[T] Array::flatten(self : Array[Array[T]]) -> Array[T] {
  let len = for x in self; len = 0 {
    continue len + x.length()
  } nobreak {
    len
  }
  let res = Array::make_uninit(len)
  for xs in self; i = 0 {
    res.unsafe_blit(i, xs, 0, xs.length())
    continue i + xs.length()
  }
  res
}

///|
/// Create an array by repeating `self` `times` times.
///
/// Aborts if `times` is negative. When `times` is `0`, returns an empty array.
///
/// Example:
///
/// ```mbt check
/// test {
///   let v = [3, 4].repeat(2)
///   @test.assert_eq(v, [3, 4, 3, 4])
/// }
/// ```
pub fn[T] Array::repeat(self : Array[T], times : Int) -> Array[T] {
  if times < 0 {
    abort("negative repeat count")
  }
  let len = self.length()
  if times == 0 || len == 0 {
    return []
  }
  let total = len * times
  guard total / times == len else { abort("repeat result too large") }
  let v = Array::new(capacity=total)
  for _ in 0.. sum + elem)
///   @test.assert_eq(sum, 15)
/// }
/// ```
#locals(f)
#alias(fold_left, deprecated)
pub fn[A, B] Array::fold(
  self : Array[A],
  init~ : B,
  f : (B, A) -> B raise?,
) -> B raise? {
  for item in self; acc = init {
    continue f(acc, item)
  } nobreak {
    acc
  }
}

///|
/// Fold out values from an array according to certain rules in reversed turn.
///
/// Example:
///
/// ```mbt check
/// test {
///   let sum = [1, 2, 3, 4, 5].rev_fold(init=0, (sum, elem) => sum + elem)
///   @test.assert_eq(sum, 15)
/// }
/// ```
#locals(f)
#alias(fold_right, deprecated)
pub fn[A, B] Array::rev_fold(
  self : Array[A],
  init~ : B,
  f : (B, A) -> B raise?,
) -> B raise? {
  for i = self.length() - 1, acc = init; i >= 0; {
    continue i - 1, f(acc, self[i])
  } nobreak {
    acc
  }
}

///|
/// Fold out values from an array according to certain rules with index.
///
/// Example:
///
/// ```mbt check
/// test {
///   let sum = [1, 2, 3, 4, 5].foldi(init=0, (index, sum, _elem) => sum + index)
///   @test.assert_eq(sum, 10)
/// }
/// ```
#locals(f)
#alias(fold_lefti, deprecated)
pub fn[A, B] Array::foldi(
  self : Array[A],
  init~ : B,
  f : (Int, B, A) -> B raise?,
) -> B raise? {
  for i, x in self; acc = init {
    continue f(i, acc, x)
  } nobreak {
    acc
  }
}

///|
/// Fold out values from an array according to certain rules in reversed turn with index.
///
/// Example:
///
/// ```mbt check
/// test {
///   let sum = [1, 2, 3, 4, 5].rev_foldi(init=0, (index, sum, _elem) => sum + index)
///   @test.assert_eq(sum, 10)
/// }
/// ```
#locals(f)
#alias(fold_righti, deprecated)
pub fn[A, B] Array::rev_foldi(
  self : Array[A],
  init~ : B,
  f : (Int, B, A) -> B raise?,
) -> B raise? {
  let len = self.length()
  for i in len>..0; acc = init {
    continue f(len - i - 1, acc, self[i])
  } nobreak {
    acc
  }
}

///|
/// Removes consecutive duplicate elements from an array in-place, using equality
/// comparison. The first occurrence of each element is retained while subsequent
/// equal elements are removed.
///
/// Parameters:
///
/// * `array` : The array to remove duplicates from. Must contain elements that
/// implement the `Eq` trait for equality comparison.
///
/// Example:
///
/// ```mbt check
/// test {
///   let arr = [1, 2, 2, 3, 3, 3, 2]
///   arr.dedup()
///   debug_inspect(arr, content="[1, 2, 3, 2]")
///   let arr = [1, 2, 2, 2, 3, 3]
///   arr.dedup()
///   debug_inspect(arr, content="[1, 2, 3]")
///   let arr : Array[Int] = []
///   arr.dedup()
///   debug_inspect(arr, content="[]")
/// }
/// ```
///
/// Note: For best results when removing all duplicates regardless of position,
/// sort the array before calling this function. When used on an unsorted array,
/// this function only removes consecutive duplicates.
pub fn[T : Eq] Array::dedup(self : Array[T]) -> Unit {
  if self.is_empty() {
    return
  }
  let w = for i in 1.. x % 2 == 0)
///   debug_inspect(extracted, content="[2, 4]")
///   debug_inspect(arr, content="[1, 3, 5]")
/// }
/// ```
#locals(f)
pub fn[T] Array::extract_if(
  self : Array[T],
  f : (T) -> Bool raise?,
) -> Array[T] raise? {
  let removed = []
  for read in 0.., , ]
///     ),
///   )
///   let arr : Array[Int] = []
///   debug_inspect(arr.chunks(3), content="[]")
/// }
/// ```
pub fn[T] Array::chunks(self : Array[T], size : Int) -> Array[ArrayView[T]] {
  guard! size > 0
  let len = self.length()
  if len == 0 {
    return []
  }
  let num_chunks = (len + size - 1) / size
  Array::makei(num_chunks, i => {
    let start = i * size
    let end = Int::min(start + size, len)
    self[start:end]
  })
}

///|
/// Groups consecutive elements of the array into chunks where adjacent elements
/// satisfy the given predicate function.
///
/// Parameters:
///
/// * `array` : The array to be chunked.
/// * `predicate` : A function that takes two adjacent elements and returns
/// `true` if they should be in the same chunk, `false` otherwise.
///
/// Returns an array of arrays, where each inner array is a chunk of consecutive
/// elements that satisfy the predicate with their adjacent elements.
///
/// Example:
///
/// ```mbt check
/// test {
///   let v = [1, 1, 2, 3, 2, 3, 2, 3, 4]
///   let chunks = v.chunk_by((x, y) => x <= y)
///   debug_inspect(
///     chunks,
///     content=(
///       #|[
///       #|  ,
///       #|  ,
///       #|  ,
///       #|]
///     ),
///   )
///   let v : Array[Int] = []
///   debug_inspect(v.chunk_by((x, y) => x <= y), content="[]")
/// }
/// ```
#locals(pred)
pub fn[T] Array::chunk_by(
  self : Array[T],
  pred : (T, T) -> Bool raise?,
) -> Array[ArrayView[T]] raise? {
  let chunks = []
  if self.is_empty() {
    return chunks
  }
  let start = for i in 1..,
///       #|  ,
///       #|  ,
///       #|  ,
///       #|]
///     ),
///   )
///   let arr = [1, 2]
///   debug_inspect(arr.windows(3), content="[]")
/// }
/// ```
pub fn[T] Array::windows(self : Array[T], size : Int) -> Array[ArrayView[T]] {
  guard! size > 0
  let len = self.length() - size + 1
  if len < 1 {
    return []
  }
  Array::makei(len, i => self[i:i + size])
}

///|
/// Return an iterator over all suffix views of this array.
///
/// Suffixes are yielded from longest to shortest. Set `include_empty=true` to
/// include the final empty suffix.
///
/// Example:
///
/// ```mbt check
/// test {
///   let xs = [1, 2]
///   debug_inspect(
///     xs.suffixes().collect(),
///     content=(
///       #|[, ]
///     ),
///   )
///   debug_inspect(
///     xs.suffixes(include_empty=true).collect(),
///     content=(
///       #|[, , ]
///     ),
///   )
/// }
/// ```
pub fn[T] Array::suffixes(
  self : Array[T],
  include_empty? : Bool = false,
) -> Iter[ArrayView[T]] {
  self[:].suffixes(include_empty~)
}

///|
/// Splits an array into chunks using a predicate function. Creates chunks by
/// grouping consecutive elements that do not satisfy the predicate function.
/// Elements that satisfy the predicate function are excluded from the resulting
/// chunks and act as delimiters.
///
/// Parameters:
///
/// * `array` : The array to be split into chunks.
/// * `predicate` : A function that takes an element and returns `true` if the
/// element should be used as a delimiter.
///
/// Returns an array of arrays, where each inner array is a chunk of consecutive
/// elements that do not satisfy the predicate.
///
/// Example:
///
/// ```mbt check
/// test {
///   let arr = [1, 0, 2, 0, 3, 0, 4]
///   debug_inspect(arr.split(x => x == 0), content="[[1], [2], [3], [4]]")
///   let arr = [0, 1, 0, 0, 2, 0]
///   debug_inspect(arr.split(x => x == 0), content="[[], [1], [], [2]]")
/// }
/// ```
#locals(pred)
pub fn[T] Array::split(
  self : Array[T],
  pred : (T) -> Bool raise?,
) -> Array[Array[T]] raise? {
  let chunks = []
  for i = 0; i < self.length(); {
    let chunk = []
    let i = for i = i; i < self.length() && !pred(self[i]); {
      chunk.push(self[i])
      continue i + 1
    } nobreak {
      i
    }
    chunks.push(chunk)
    continue i + 1
  }
  chunks
}

///|
/// Creates an iterator over the elements of the array.
///
/// Parameters:
///
/// * `array` : The array to create an iterator from.
///
/// Returns an iterator that yields each element of the array in order.
/// This iterator is created from `self[:]`, so the traversal bounds are fixed
/// when iteration starts.
///
/// Structural mutations after the iterator is created are unsupported.
/// Appended elements are not visited, and shrinking the array with operations
/// such as `remove`, `truncate`, `clear`, or `drain` may cause later iterator
/// steps to fail. The same caveat applies to `rev_iter()`, `iter2()`, and
/// helpers built on top of them such as `each()`, `eachi()`, and `fold()`.
///
/// Example:
///
/// ```mbt check
/// test {
///   let arr = [1, 2, 3]
///   let mut sum = 0
///   arr.iter().each(x => sum += x)
///   inspect(sum, content="6")
/// }
/// ```
#alias(iterator, deprecated)
pub fn[T] Array::iter(self : Array[T]) -> Iter[T] {
  self[:].iter()
}

///|
/// Returns an iterator that yields elements from the array in reverse order,
/// from the last element to the first.
///
/// Parameters:
///
/// * `array` : The array to iterate over in reverse order.
///
/// Returns an iterator that yields each element of the array, starting from the
/// last element and moving towards the first.
///
/// Example:
///
/// ```mbt check
/// test {
///   let arr = [1, 2, 3]
///   let result = []
///   arr.rev_iter().each(x => result.push(x))
///   debug_inspect(result, content="[3, 2, 1]")
/// }
/// ```
#alias(rev_iterator, deprecated)
pub fn[T] Array::rev_iter(self : Array[T]) -> Iter[T] {
  self[:].rev_iter()
}

///|
/// Returns an iterator that provides both indices and values of the array in
/// order.
///
/// Parameters:
///
/// * `self` : The array to iterate over.
///
/// Returns an iterator that yields tuples of index and value pairs, where
/// indices start from 0.
///
/// Example:
///
/// ```mbt check
/// test {
///   let arr = [10, 20, 30]
///   let mut sum = 0
///   arr.iter2().each((i, x) => sum += i + x)
///   inspect(sum, content="63") // (0 + 10) + (1 + 20) + (2 + 30) = 63
/// }
/// ```
#alias(iterator2, deprecated)
pub fn[A] Array::iter2(self : Array[A]) -> Iter2[Int, A] {
  self[:].iter2()
}

///|
/// Creates a new empty array.
///
/// Returns an empty array of type `Array[T]`.
///
/// Example:
///
/// ```mbt check
/// test {
///   let arr : Array[Int] = []
///   inspect(arr.length(), content="0")
///   inspect(arr.is_empty(), content="true")
/// }
/// ```
pub impl[T] Default for Array[T] with fn default() {
  []
}

///|
/// Removes a back element from an array.
///
/// # Example
/// ```mbt check
/// test {
///   let array = [1, 2, 3, 4, 5]
///   array.unsafe_pop_back()
///   @test.assert_eq(array.last(), Some(4))
/// }
/// ```
#internal(unsafe, "Panic if the array is empty on non-JS backend.")
#doc(hidden)
pub fn[A] Array::unsafe_pop_back(self : Array[A]) -> Unit {
  self.unsafe_pop() |> ignore
}

///|
/// Truncates the array in-place to the specified length.
///
/// If `len` is greater than or equal to the current array length,
/// the function does nothing. If `len` is 0, the array is cleared.
/// Otherwise, removes elements from the end until the array reaches the given length.
///
/// Parameters:
///
/// * `self` : The target array (modified in-place).
/// * `len` : The new desired length (must be non-negative).
///
/// Important:
///   - If `len` is negative, the function does nothing.
///   - If `len` exceeds current length, the array remains unchanged.
///
/// Example:
///
/// ```mbt check
/// test {
///   let arr = [1, 2, 3, 4, 5]
///   arr.truncate(3)
///   debug_inspect(arr, content="[1, 2, 3]")
/// }
/// ```
pub fn[A] Array::truncate(self : Array[A], len : Int) -> Unit {
  guard len >= 0 && len < self.length() else { return }
  self.unsafe_truncate_to_length(len)
}

///|
/// In-place filter and map for Array
///
/// # Example
/// ```mbt check
/// test {
///   let arr = [1, 2, 3, 4, 5]
///   arr.retain_map(fn(x) { if x % 2 == 0 { Some(x * 2) } else { None } })
///   debug_inspect(arr, content="[4, 8]")
/// }
/// ```
#locals(f)
pub fn[A] Array::retain_map(
  self : Array[A],
  f : (A) -> A? raise?,
) -> Unit raise? {
  if self.is_empty() {
    return
  }
  let buf = self.buffer()
  let len = self.length()
  for read_idx in 0.. {
        buf[write_idx] = new_val
        continue write_idx + 1
      }
      None => continue write_idx
    }
  } nobreak {
    self.unsafe_truncate_to_length(write_idx)
  }
}

///|
/// Creates a new array containing all elements from an iterator.
///
/// Parameters:
///
/// * `iterator` : An iterator containing elements of type `T`.
///
/// Returns a new array containing all elements from the iterator in the same
/// order.
///
/// Example:
///
/// ```mbt check
/// test {
///   let iter = Iter::singleton(42)
///   let arr = Array::from_iter(iter)
///   debug_inspect(arr, content="[42]")
/// }
/// ```
#alias(from_iterator, deprecated)
pub fn[T] Array::from_iter(iter : Iter[T]) -> Array[T] {
  iter.collect()
}

///|
/// Adds all elements from an iterator to the end of the array.
///
/// This function iterates over each element in the provided iterator
/// and adds them to the array using the `push` method.
///
/// # Example
/// ```mbt check
/// test {
///   let u = [1, 2, 3]
///   let v = [4, 5, 6]
///   u.push_iter(v.iter())
///   @test.assert_eq(u, [1, 2, 3, 4, 5, 6])
/// }
/// ```
pub fn[T] Array::push_iter(self : Self[T], iter : Iter[T]) -> Unit {
  // This function used by [Array spread operator](https://docs.moonbitlang.com/en/latest/language/fundamentals.html#spread-operator)
  // it can't be removed and deprecated
  if iter.size_hint() is Some(n) {
    self.reserve_capacity(self.length() + n)
  }
  for x in iter {
    self.push(x)
  }
}

///|
/// Shuffle the array using Knuth shuffle
///
/// To use this function, you need to provide a rand function, which takes an integer as it upper bound
/// and returns an integer.
/// *rand n* is expected to returns a uniformly distribution integer between 0 and n - 1
/// # Example
///
/// ```mbt check
/// test {
/// let arr = [1, 2, 3, 4, 5]
/// fn rand(upper : Int) -> Int {
///   let rng = @random.Rand::new()
///   rng.int(limit=upper)
/// }
///
/// Array::shuffle_in_place(arr, rand~)
/// }
/// ```
#locals(rand)
pub fn[T] Array::shuffle_in_place(
  self : Array[T],
  rand~ : (Int) -> Int,
) -> Unit {
  let n = self.length()
  for i in n>..1 {
    let j = rand(i + 1) % (i + 1)
    // for safety, perf is not a concern here
    // TODO: maybe return an error later
    self.swap(i, j)
  }
}

///|
/// Shuffle the array using Knuth shuffle
///
/// To use this function, you need to provide a rand function, which takes an integer as it upper bound
/// and returns an integer.
/// *rand n* is expected to returns a uniformly distribution integer between 0 and n - 1
/// # Example
///
/// ```mbt nocheck
/// let arr = [1, 2, 3, 4, 5]
///
/// fn rand(upper : Int) -> Int {
///   let rng = @random.Rand::new()
///   rng.int(limit=upper)
/// }
///
/// let _shuffled = Array::shuffle(arr, rand~)
/// ```
pub fn[T] Array::shuffle(self : Array[T], rand~ : (Int) -> Int) -> Array[T] {
  let new_arr = self.copy()
  Array::shuffle_in_place(new_arr, rand~)
  new_arr
}

///|
/// Returns a new array containing the elements of the original array that satisfy the given predicate.
///
/// # Arguments
///
/// * `self` - The array to filter.
/// * `f` - The predicate function.
///
/// # Returns
///
#locals(f)
pub fn[A, B] Array::filter_map(
  self : Array[A],
  f : (A) -> B? raise?,
) -> Array[B] raise? {
  let result = []
  for x in self {
    if f(x) is Some(x) {
      result.push(x)
    }
  }
  result
}

///|
/// Returns the last element of the array, or `None` if the array is empty.
///
/// Parameters:
///
/// * `array` : The array to get the last element from.
///
/// Returns an optional value containing the last element of the array. The
/// result is `None` if the array is empty, or `Some(x)` where `x` is the last
/// element of the array.
///
/// Example:
///
/// ```mbt check
/// test {
///   let arr = [1, 2, 3]
///   debug_inspect(arr.last(), content="Some(3)")
///   let empty : Array[Int] = []
///   debug_inspect(empty.last(), content="None")
/// }
/// ```
pub fn[A] Array::last(self : Array[A]) -> A? {
  match self {
    [] => None
    [.., last] => Some(last)
  }
}

///|
/// Zips two arrays into a single array of tuples.
///
/// Parameters:
///
/// * `self` : The first array.
/// * `other` : The second array.
///
/// Returns an array of tuples, where each tuple contains corresponding elements
/// from the two input arrays.
///
/// Example:
///
/// ```mbt check
/// test {
///   let arr1 = [1, 2, 3]
///   let arr2 = ['a', 'b', 'c']
///   debug_inspect(arr1.zip(arr2), content="[(1, 'a'), (2, 'b'), (3, 'c')]")
/// }
/// ```
pub fn[A, B] Array::zip(self : Array[A], other : Array[B]) -> Array[(A, B)] {
  let length = if self.length() < other.length() {
    self.length()
  } else {
    other.length()
  }
  Array::makei(length, i => (self[i], other[i]))
}

///|
/// Splits an array of pairs into two arrays, separating the first and second elements.
///
/// # Example
/// ```mbt check
/// test {
///   let arr = [(1, "a"), (2, "b"), (3, "c")]
///   let (nums, strs) = arr.unzip()
///   debug_inspect(nums, content="[1, 2, 3]")
///   debug_inspect(strs, content="[\"a\", \"b\", \"c\"]")
/// }
/// ```
pub fn[T1, T2] Array::unzip(self : Array[(T1, T2)]) -> (Array[T1], Array[T2]) {
  let arr1 : Array[T1] = Array::new(capacity=self.length())
  let arr2 : Array[T2] = Array::new(capacity=self.length())
  for pair in self {
    let (x, y) = pair
    arr1.push(x)
    arr2.push(y)
  }
  (arr1, arr2)
}

///|
/// Zips two arrays into an iterator that yields corresponding elements.
///
/// Parameters:
///
/// * `self` : The first array.
/// * `other` : The second array.
///
/// Returns an `Iter2` iterator that produces corresponding elements
/// from both arrays. The iteration continues until the shorter array is exhausted.
///
/// Example:
///
/// ```mbt check
/// test {
///   let arr1 = [1, 2, 3]
///   let arr2 = ['a', 'b', 'c']
///   debug_inspect(
///     arr1.zip_to_iter2(arr2).to_array(),
///     content="[(1, 'a'), (2, 'b'), (3, 'c')]",
///   )
/// }
/// ```
pub fn[A, B] Array::zip_to_iter2(
  self : Array[A],
  other : Array[B],
) -> Iter2[A, B] {
  let length = if self.length() < other.length() {
    self.length()
  } else {
    other.length()
  }
  let mut i = 0
  Iter2::new(
    () => {
      guard i < length else { None }
      let elem = (self[i], other[i])
      i += 1
      Some(elem)
    },
    size_hint=length,
  ).iter2()
}

///|
/// Join an array of strings using the provided `separator`.
///
/// Parameters:
///   * `separator` : The string inserted between each element.
///
/// Returns a single concatenated `String`.
/// # Example:
/// ```mbt check
/// test {
///   let s = "hello world"
///   inspect(s.split(" ").to_array().join(":"), content="hello:world")
/// }
/// ```
pub fn[A : ToStringView] Array::join(
  self : Array[A],
  separator : StringView,
) -> String {
  self[:].join(separator)
}

///|
/// Performs a lexicographical comparison of two arrays.
///
/// This method compares the arrays element by element until a difference is
/// found or one array is exhausted. Unlike the `Compare` trait implementation
/// which uses shortlex order (shorter arrays come first), this method compares
/// based purely on element values until a difference is found.
///
/// # Returns
///
/// - A negative integer if `self` is lexicographically less than `other`
/// - Zero if `self` is lexicographically equal to `other`
/// - A positive integer if `self` is lexicographically greater than `other`
///
/// # Example
///
/// ```mbt check
/// test {
///   inspect([1, 2].lexical_compare([1, 2, 3]), content="-1")
///   inspect([1, 2, 3].lexical_compare([1, 2]), content="1")
///   inspect([1, 2, 3].lexical_compare([1, 2, 3]), content="0")
///   inspect([1, 2, 3].lexical_compare([1, 2, 4]), content="-1")
/// }
/// ```
pub fn[T : Compare] Array::lexical_compare(
  self : Array[T],
  other : Array[T],
) -> Int {
  self[:].lexical_compare(other)
}