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

///|
fn[K : Show, V : Show] HashMap::_debug_entries(self : HashMap[K, V]) -> String {
  for s = "", i = 0; i < self.entries.length(); {
    let s = if i > 0 { s + "," } else { s }
    match self.entries[i] {
      None => continue s + "_", i + 1
      Some({ psl, key, value, .. }) =>
        continue s + "(\{psl},\{key},\{value})", i + 1
    }
  } nobreak {
    s
  }
}

///|
/// Removes all key-value pairs from the map while retaining the allocated
/// capacity. After calling this method, the size of the map will be zero but the
/// capacity remains unchanged.
///
/// Parameters:
///
/// * `self` : The hash map to be cleared.
///
/// Example:
///
/// ```mbt check
/// test {
///   let map = @hashmap.from_array([("a", 1), ("b", 2)])
///   map.clear()
///   inspect(map.length(), content="0")
///   debug_inspect(map.get("a"), content="None")
/// }
/// ```
pub fn[K, V] HashMap::clear(self : HashMap[K, V]) -> Unit {
  self.entries.fill(None)
  self.size = 0
}

///|
/// Returns an iterator over the key-value pairs in the map.
///
/// Parameters:
///
/// * `map` : The hash map to iterate over.
///
/// Returns an iterator that yields tuples of `(key, value)` for each entry in
/// the map, in unspecified order.
///
/// Example:
///
/// ```mbt check
/// test {
///   let map = @hashmap.from_array([(1, "one"), (2, "two")])
///   let pairs = map.iter().to_array()
///   inspect(pairs.length(), content="2")
///   inspect(pairs.contains((1, "one")), content="true")
///   inspect(pairs.contains((2, "two")), content="true")
/// }
/// ```
#alias(iterator, deprecated)
pub fn[K, V] HashMap::iter(self : HashMap[K, V]) -> Iter[(K, V)] {
  let mut i = 0
  let len = self.entries.length()
  Iter::new(
    fn() {
      while i < len {
        let entry = self.entries.unsafe_get(i)
        i += 1
        if entry is Some({ key, value, .. }) {
          return Some((key, value))
        }
      } nobreak {
        None
      }
    },
    size_hint=self.size,
  )
}

///|
/// Returns an iterator over the key-value pairs in the map.
///
/// Parameters:
///
/// * `map` : The hash map to iterate over.
///
/// Returns an iterator that yields tuples of `(key, value)` for each entry in
/// the map, in unspecified order.
/// This is mainly used for `for _, _ in ..` loops.
#alias(iterator2, deprecated)
pub fn[K, V] HashMap::iter2(self : HashMap[K, V]) -> Iter2[K, V] {
  self.iter()
}

///|
/// Creates a new hash map from an iterator of key-value pairs.
///
/// Parameters:
///
/// * `iter` : An iterator that yields key-value pairs. The key type must
/// implement both `Hash` and `Eq` traits.
///
/// Returns a new hash map containing all key-value pairs from the iterator. If
/// the iterator yields multiple pairs with the same key, the later value will
/// overwrite the earlier one.
///
/// Example:
///
/// ```mbt check
/// test {
///   let iter = Iter::singleton((1, "one")) + Iter::singleton((2, "two"))
///   let map = @hashmap.from_iter(iter)
///   debug_inspect(map.get(1), content="Some(\"one\")")
///   debug_inspect(map.get(2), content="Some(\"two\")")
/// }
/// ```
#as_free_fn
#alias(from_iterator, deprecated)
#as_free_fn(from_iterator, deprecated)
pub fn[K : Hash + Eq, V] HashMap::from_iter(
  iter : Iter[(K, V)],
) -> HashMap[K, V] {
  let m = new_hashmap(default_init_capacity)
  while iter.next() is Some((k, v)) {
    m[k] = v
  }
  m
}

///|
/// Converts the hash map into an array of key-value pairs. The order of elements
/// in the resulting array follows the internal storage order of the hash map.
///
/// Parameters:
///
/// * `self` : The hash map to be converted.
///
/// Returns an array containing tuples of key-value pairs from the hash map.
///
/// Example:
///
/// ```mbt check
/// test {
///   let map = @hashmap.from_array([(1, "one"), (2, "two")])
///   let arr = map.to_array()
///   arr.sort()
///   debug_inspect(
///     arr,
///     content=(
///       #|[(1, "one"), (2, "two")]
///     ),
///   )
/// }
/// ```
pub fn[K, V] HashMap::to_array(self : HashMap[K, V]) -> Array[(K, V)] {
  let mut i = 0
  let res = while i < self.capacity {
    if self.entries[i] is Some({ key, value, .. }) {
      i += 1
      break Array::make(self.size, (key, value))
    }
    i += 1
  } nobreak {
    []
  }
  if !res.is_empty() {
    let mut res_idx = 1
    while res_idx < res.length() && i < self.capacity {
      if self.entries[i] is Some({ key, value, .. }) {
        res[res_idx] = (key, value)
        res_idx += 1
      }
      i += 1
    }
  }
  res
}

///|
/// Returns the number of key-value pairs currently stored in the hash map.
///
/// Parameters:
///
/// * `self` : The hash map to get the size from.
///
/// Returns the number of key-value pairs in the hash map.
///
/// Example:
///
/// ```mbt check
/// test {
///   let map = @hashmap.from_array([("a", 1), ("b", 2), ("c", 3)])
///   inspect(map.length(), content="3")
/// }
/// ```
#alias(size, deprecated)
pub fn[K, V] HashMap::length(self : HashMap[K, V]) -> Int {
  self.size
}

///|
/// Returns the current capacity of the hash map. The capacity is the number of
/// key-value pairs the hash map can hold before it needs to reallocate its
/// internal storage.
///
/// Parameters:
///
/// * `map` : The hash map whose capacity is to be queried.
///
/// Returns the number of key-value pairs that can be stored in the hash map
/// before triggering a reallocation.
///
/// Example:
///
/// ```mbt check
/// test {
///   let map : @hashmap.HashMap[Int, String] = HashMap([], capacity=16)
///   inspect(map.capacity(), content="16")
/// }
/// ```
pub fn[K, V] HashMap::capacity(self : HashMap[K, V]) -> Int {
  self.capacity
}

///|
/// Returns whether the hash map contains no key-value pairs.
///
/// Parameters:
///
/// * `map` : The hash map to check.
///
/// Returns `true` if the hash map contains no key-value pairs, `false`
/// otherwise.
///
/// Example:
///
/// ```mbt check
/// test {
///   let map : @hashmap.HashMap[String, Int] = HashMap([])
///   inspect(map.is_empty(), content="true")
///   map.set("key", 42)
///   inspect(map.is_empty(), content="false")
/// }
/// ```
pub fn[K, V] HashMap::is_empty(self : HashMap[K, V]) -> Bool {
  self.size == 0
}

///|
/// Iterates over all key-value pairs in the hash map and applies the given
/// function to each pair.
///
/// Parameters:
///
/// * `map` : The hash map to iterate over.
/// * `action` : A function that takes a key and a value as arguments and
/// performs some action. The function should not return any value.
///
/// Example:
///
/// ```mbt check
/// test {
///   let map = @hashmap.from_array([(1, "one"), (2, "two")])
///   let array = []
///   map.each((k, v) => array.push((k, v)))
///   array.sort()
///   debug_inspect(
///     array,
///     content=(
///       #|[(1, "one"), (2, "two")]
///     ),
///   )
/// }
/// ```
#locals(f)
pub fn[K, V] HashMap::each(
  self : HashMap[K, V],
  f : (K, V) -> Unit raise?,
) -> Unit raise? {
  for i in 0.. Unit raise?,
) -> Unit raise? {
  for i = 0, idx = 0; i < self.capacity; {
    match self.entries[i] {
      Some({ key, value, .. }) => {
        f(idx, key, value)
        continue i + 1, idx + 1
      }
      None => continue i + 1, idx
    }
  }
}

///|
/// Provides string representation for hash maps.
/// 
/// Notice that the order of key-value pairs in the output string is not guaranteed
///
/// Parameters:
///
/// * `self` : The hash map to be converted to string.
/// * `logger` : The buffer to write the string representation to.
#deprecated("Use @debug.Debug instead of Show for debugging purposes. See https://github.com/moonbitlang/core/blob/main/debug/README.mbt.md")
pub impl[K : Show, V : Show] Show for HashMap[K, V]

///|
pub impl[K : Show, V : Show] Show for HashMap[K, V] with fn output(self, logger) {
  logger.write_string("HashMap::from_array([")
  self.eachi((i, k, v) => {
    if i > 0 {
      logger.write_string(", ")
    }
    logger.write_string("(")
    logger.write_object(k)
    logger.write_string(", ")
    logger.write_object(v)
    logger.write_string(")")
  })
  logger.write_string("])")
}

///|
/// Returns an iterator over all keys in the hash map.
///
/// Parameters:
///
/// * `self` : The hash map to iterate over.
///
/// Returns an iterator that yields each key in the hash map in unspecified order.
/// The keys are yielded in the same order as they appear in the internal storage.
///
/// Example:
///
/// ```mbt check
/// test {
///   let map = @hashmap.from_array([(1, "one"), (2, "two"), (3, "three")])
///   let keys = map.keys().to_array()
///   inspect(keys.length(), content="3")
///   inspect(keys.contains(1), content="true")
///   inspect(keys.contains(2), content="true")
///   inspect(keys.contains(3), content="true")
/// }
/// ```
pub fn[K, V] HashMap::keys(self : HashMap[K, V]) -> Iter[K] {
  let mut i = 0
  let iter = Iter::new(
    () => {
      while i < self.entries.length() {
        let entry = self.entries[i]
        i += 1
        if entry is Some({ key, .. }) {
          break Some(key)
        }
      } nobreak {
        None
      }
    },
    size_hint=self.size,
  )
  iter.iter()
}

///|
/// Returns an iterator over all values in the hash map.
///
/// Parameters:
///
/// * `self` : The hash map to iterate over.
///
/// Returns an iterator that yields each value in the hash map in unspecified order.
/// The values are yielded in the same order as they appear in the internal storage.
///
/// Example:
///
/// ```mbt check
/// test {
///   let map = @hashmap.from_array([("a", 1), ("b", 2), ("c", 3)])
///   let values = map.values().to_array()
///   inspect(values.length(), content="3")
///   inspect(values.contains(1), content="true")
///   inspect(values.contains(2), content="true")
///   inspect(values.contains(3), content="true")
/// }
/// ```
pub fn[K, V] HashMap::values(self : HashMap[K, V]) -> Iter[V] {
  let mut i = 0
  let iter = Iter::new(
    () => {
      while i < self.entries.length() {
        let entry = self.entries[i]
        i += 1
        if entry is Some({ value, .. }) {
          break Some(value)
        }
      } nobreak {
        None
      }
    },
    size_hint=self.size,
  )
  iter.iter()
}

///|
/// Retains only the key-value pairs that satisfy the given predicate function.
/// This method modifies the hash map in-place, removing all entries for which
/// the predicate returns `false`.
///
/// Parameters:
///
/// * `self` : The hash map to be filtered.
/// * `predicate` : A function that takes a key and value as arguments and returns
/// `true` if the key-value pair should be kept, `false` if it should be removed.
///
/// Example:
///
/// ```mbt check
/// test {
///   let map = @hashmap.from_array([("a", 1), ("b", 2), ("c", 3), ("d", 4)])
///   map.retain((_k, v) => v % 2 == 0) // Keep only even values
///   inspect(map.length(), content="2")
///   debug_inspect(map.get("a"), content="None")
///   debug_inspect(map.get("b"), content="Some(2)")
///   debug_inspect(map.get("c"), content="None")
///   debug_inspect(map.get("d"), content="Some(4)")
/// }
/// ```
#locals(f)
pub fn[K, V] HashMap::retain(self : HashMap[K, V], f : (K, V) -> Bool) -> Unit {
  let size = self.size
  let mut j = 0
  for i = 0; j < size; i = i + 1 {
    while self.entries[i] is Some(entry) {
      j += 1
      if f(entry.key, entry.value) {
        break
      } else {
        self.shift_back(i)
        self.size -= 1
      }
    }
  }
}

///|
test "retain" {
  // seed = 1827982181
  let hashmap : HashMap[String, Int] = {
    capacity: 8,
    capacity_mask: 7,
    size: 4,
    entries: [
      Some({ psl: 2, hash: 448974246, key: "c", value: 3 }),
      Some({ psl: 2, hash: -136509641, key: "d", value: 4 }),
      None,
      None,
      None,
      None,
      Some({ psl: 0, hash: 1614946358, key: "a", value: 1 }),
      Some({ psl: 1, hash: -765931946, key: "b", value: 2 }),
    ],
  }
  hashmap.retain((_k, v) => v % 2 == 0)
  @debug.debug_inspect(
    hashmap.entries,
    content=(
      #|
    ),
  )
}