///|
/// A string-keyed dict of `Value`s; used by the embedding API to pass named
/// globals to `eval_expr` and to inspect module exports.
pub struct StringDict {
  priv data : Map[String, Value]
}

///|
/// Creates an empty `StringDict`.
///
/// Returns a new `StringDict` with no bindings.
pub fn StringDict::new() -> StringDict {
  { data: Map([]) }
}

///|
/// Creates a `StringDict` wrapping an existing `Map[String, Value]`.
///
/// Parameters:
///
/// - `m` : The map to wrap; ownership is transferred.
///
/// Returns a `StringDict` backed by `m`.
pub fn StringDict::from_map(m : Map[String, Value]) -> StringDict {
  { data: m }
}

///|
/// Inserts or updates the binding for `key`.
///
/// Parameters:
///
/// - `self` : The dict to modify.
/// - `key` : The string key to bind.
/// - `v` : The value to associate with `key`.
pub fn StringDict::set(self : StringDict, key : String, v : Value) -> Unit {
  self.data[key] = v
}

///|
/// Returns the value bound to `key`, or `None`.
///
/// Parameters:
///
/// - `self` : The dict to look up in.
/// - `key` : The string key to retrieve.
///
/// Returns `Some(value)` if `key` is present, `None` otherwise.
pub fn StringDict::get(self : StringDict, key : String) -> Value? {
  self.data.get(key)
}

///|
/// Returns `true` if `key` is present.
///
/// Parameters:
///
/// - `self` : The dict to query.
/// - `key` : The string key to test for presence.
///
/// Returns `true` if `key` exists in the dict.
pub fn StringDict::has(self : StringDict, key : String) -> Bool {
  self.data.contains(key)
}

///|
/// Returns the keys in lexicographic order.
///
/// Parameters:
///
/// - `self` : The dict whose keys to retrieve.
///
/// Returns an array of all keys sorted lexicographically.
pub fn StringDict::keys(self : StringDict) -> Array[String] {
  let ks = self.data.keys().collect()
  ks.sort_by(fn(a, b) {
    let la = a.length()
    let lb = b.length()
    let min_len = if la < lb { la } else { lb }
    let mut result = 0
    let mut i = 0
    while i < min_len && result == 0 {
      let ca = a.get_char(i).unwrap()
      let cb = b.get_char(i).unwrap()
      result = ca.compare(cb)
      i = i + 1
    }
    if result != 0 {
      result
    } else {
      la.compare(lb)
    }
  })
  ks
}

///|
/// Removes the binding for `key`.
///
/// Parameters:
///
/// - `self` : The dict to modify.
/// - `key` : The string key to remove.
///
/// Returns `true` if `key` was present (and removed), `false` if absent.
pub fn StringDict::delete(self : StringDict, key : String) -> Bool {
  if self.data.contains(key) {
    self.data.remove(key)
    true
  } else {
    false
  }
}

///|
/// Calls `f(key, value)` for every entry in the dict.
///
/// Parameters:
///
/// - `self` : The dict to iterate.
/// - `f` : Callback receiving each key-value pair.
pub fn StringDict::each(self : StringDict, f : (String, Value) -> Unit) -> Unit {
  self.data.each(f)
}

///|
/// Returns all values in the dict.
///
/// Parameters:
///
/// - `self` : The dict whose values to retrieve.
///
/// Returns an array of all values.
pub fn StringDict::values(self : StringDict) -> Array[Value] {
  self.data.values().collect()
}

///|
/// Recursively deep-freezes all values in the dict.
///
/// Parameters:
///
/// - `self` : The dict whose values to freeze.
pub fn StringDict::freeze(self : StringDict) -> Unit {
  self.data.each(fn(_, v) { v.freeze() })
}