///|
/// Accumulator collects values during query computation.
/// Useful for gathering diagnostics, warnings, or other side-effect data
/// without affecting the main computation result.
///
/// Example use cases:
/// - Collecting compiler warnings during type checking
/// - Gathering dependency information during module resolution
/// - Accumulating metrics during computation
pub struct Accumulator[V] {
  /// Unique index for this accumulator
  ingredient_index : Int
  /// Values accumulated per query execution
  /// Key: (query_ingredient_index, key_hash)
  /// Value: Array of accumulated values
  values : @hashmap.HashMap[(Int, Int), Array[V]]
}

///|
/// Create a new Accumulator with the given ingredient index.
pub fn[V] Accumulator::new(ingredient_index : Int) -> Accumulator[V] {
  { ingredient_index, values: @hashmap.HashMap::default() }
}

///|
/// Get the ingredient index.
pub fn[V] Accumulator::get_index(self : Accumulator[V]) -> Int {
  self.ingredient_index
}

///|
/// Push a value to the accumulator in the context of the current query.
/// The value is associated with the currently executing query.
/// If no query is executing, the value is silently ignored.
pub fn[V] Accumulator::push(
  self : Accumulator[V],
  rt : Runtime,
  value : V,
) -> Unit {
  match rt.get_current_query() {
    Some((query_index, key_hash)) => {
      let key = (query_index, key_hash)
      match self.values.get(key) {
        Some(arr) => arr.push(value)
        None => self.values.set(key, [value])
      }
    }
    None => () // No active query, ignore
  }
}

///|
/// Collect all values accumulated for a specific query execution.
/// Returns an empty array if no values were accumulated.
pub fn[V] Accumulator::get(
  self : Accumulator[V],
  query_index : Int,
  key_hash : Int,
) -> Array[V] {
  match self.values.get((query_index, key_hash)) {
    Some(arr) => arr
    None => []
  }
}

///|
/// Collect all values accumulated for a query, given its Query instance and key.
pub fn[K : Hash, Q, V] Accumulator::get_for_query(
  self : Accumulator[V],
  query : Query[K, Q],
  key : K,
) -> Array[V] {
  ignore(query.compute)
  self.get(query.get_index(), key.hash())
}

///|
/// Collect all values accumulated for a CycleQuery, given its instance and key.
pub fn[K : Hash, Q, V] Accumulator::get_for_cycle_query(
  self : Accumulator[V],
  query : CycleQuery[K, Q],
  key : K,
) -> Array[V] {
  ignore(query.compute)
  self.get(query.get_index(), key.hash())
}

///|
/// Clear all accumulated values for a specific query execution.
/// Called when the query is invalidated and needs to be recomputed.
pub fn[V] Accumulator::clear(
  self : Accumulator[V],
  query_index : Int,
  key_hash : Int,
) -> Unit {
  self.values.remove((query_index, key_hash))
}

///|
/// Clear all accumulated values for a query, given its Query instance and key.
pub fn[K : Hash, Q, V] Accumulator::clear_for_query(
  self : Accumulator[V],
  query : Query[K, Q],
  key : K,
) -> Unit {
  ignore(query.compute)
  self.clear(query.get_index(), key.hash())
}

///|
/// Clear all accumulated values.
pub fn[V] Accumulator::clear_all(self : Accumulator[V]) -> Unit {
  // Remove all entries
  let keys_to_remove : Array[(Int, Int)] = []
  for entry in self.values.iter() {
    let (key, _) = entry
    keys_to_remove.push(key)
  }
  for key in keys_to_remove {
    self.values.remove(key)
  }
}

///|
/// Check if any values have been accumulated for a specific query execution.
pub fn[V] Accumulator::has_values(
  self : Accumulator[V],
  query_index : Int,
  key_hash : Int,
) -> Bool {
  match self.values.get((query_index, key_hash)) {
    Some(arr) => !arr.is_empty()
    None => false
  }
}

///|
/// Get the count of accumulated values for a specific query execution.
pub fn[V] Accumulator::count(
  self : Accumulator[V],
  query_index : Int,
  key_hash : Int,
) -> Int {
  match self.values.get((query_index, key_hash)) {
    Some(arr) => arr.length()
    None => 0
  }
}

///|
/// Iterate over all accumulated values across all queries.
pub fn[V] Accumulator::iter(
  self : Accumulator[V],
) -> Iter[((Int, Int), Array[V])] {
  self.values.iter()
}