// Matchers for maps.

///|
/// Assert a Map contains the given value under any key.
#callsite(autofill(loc))
pub fn[K : @debug.Debug, V : Eq + @debug.Debug] Expectation::to_contain_value(
  self : Expectation[Map[K, V]],
  value : V,
  loc~ : SourceLoc,
) -> Unit raise Error {
  self.assert_that(
    self.actual.values().any(v => v == value),
    "to_contain_value",
    args="value",
    expected=() => "containing value \{show(value)}",
    received=() => show(self.actual),
    loc~,
  )
}

///|
/// Assert a Map contains `key` with the given value.
#callsite(autofill(loc))
pub fn[K : Hash + Eq + @debug.Debug, V : Eq + @debug.Debug] Expectation::to_contain_entry(
  self : Expectation[Map[K, V]],
  key : K,
  value : V,
  loc~ : SourceLoc,
) -> Unit raise Error {
  let actual_value = self.actual.get(key)
  self.assert_that(
    actual_value is Some(v) && v == value,
    "to_contain_entry",
    args="key, value",
    expected=() => "containing \{show(key)}: \{show(value)}",
    received=() => show(self.actual),
    details=() => {
      if self.negated {
        return []
      }
      match actual_value {
        Some(v) => [("Value", show(v))]
        None => [("Value", "no key \{show(key)}")]
      }
    },
    loc~,
  )
}

///|
/// Assert a Map contains every entry of `expected`. Other entries are
/// allowed.
#callsite(autofill(loc))
pub fn[K : Hash + Eq + @debug.Debug, V : Eq + @debug.Debug] Expectation::to_contain_entries(
  self : Expectation[Map[K, V]],
  expected : Map[K, V],
  loc~ : SourceLoc,
) -> Unit raise Error {
  let missing : Map[K, V] = Map([])
  let changed = []
  for key, value in expected {
    match self.actual.get(key) {
      None => missing[key] = value
      Some(actual_value) if actual_value != value =>
        changed.push(
          "\{show(key)}: expected \{show(value)}, received \{show(actual_value)}",
        )
      Some(_) => ()
    }
  }
  self.assert_that(
    missing.is_empty() && changed.is_empty(),
    "to_contain_entries",
    args="expected",
    expected=() => "containing \{show(expected)}",
    received=() => show(self.actual),
    details=() => {
      let details = []
      if !self.negated && !missing.is_empty() {
        details.push(("Missing", show(missing)))
      }
      if !self.negated && !changed.is_empty() {
        details.push(("Changed", changed.join("\n")))
      }
      details
    },
    loc~,
  )
}