///|
/// Evaluate has for object keys or array indices
fn eval_has(key : String, input : Json) -> Iter[Json] {
  match input {
    Object(obj) => Iter::singleton(Json::boolean(obj.contains(key)))
    Array(arr) => {
      let idx_result = @string.parse_int(key) catch {
        _ => return Iter::singleton(Json::boolean(false))
      }
      let len = arr.length()
      let actual_idx = normalize_index(idx_result, len)
      Iter::singleton(Json::boolean(index_in_bounds(actual_idx, len)))
    }
    _ => Iter::singleton(Json::boolean(false))
  }
}

///|
/// Evaluate membership with container expression
fn eval_in(
  expr : Expr,
  input : Json,
  env : Env,
) -> Iter[Json] raise InterpreterError {
  match eval_with_env(expr, input, env).collect() {
    [] => Iter::singleton(Json::boolean(false))
    [Object(obj), ..] =>
      match input {
        String(key) => Iter::singleton(Json::boolean(obj.contains(key)))
        _ => Iter::singleton(Json::boolean(false))
      }
    [Array(arr), ..] =>
      match input {
        Number(n, ..) => {
          let idx = n.to_int()
          let len = arr.length()
          let actual_idx = normalize_index(idx, len)
          Iter::singleton(Json::boolean(index_in_bounds(actual_idx, len)))
        }
        _ => Iter::singleton(Json::boolean(false))
      }
    _ => Iter::singleton(Json::boolean(false))
  }
}

///|
/// Evaluate to_entries
fn eval_to_entries(input : Json) -> Iter[Json] raise InterpreterError {
  match input {
    Object(obj) => {
      let entries = obj
        .iter()
        .map(fn(pair) { entry_from_pair(Json::string(pair.0), pair.1) })
        .collect()
      Iter::singleton(Json::array(entries))
    }
    Array(arr) =>
      Iter::singleton(
        Json::array(
          arr.mapi(fn(i, v) { entry_from_pair(Json::number(i.to_double()), v) }),
        ),
      )
    _ => raise TypeMismatch("object/array", @ast_internal.json_type_name(input))
  }
}

///|
/// Evaluate from_entries
fn eval_from_entries(input : Json) -> Iter[Json] raise InterpreterError {
  match input {
    Array(arr) =>
      Iter::singleton(
        Json::object(Map::from_array(arr.filter_map(entry_to_pair))),
      )
    _ => raise TypeMismatch("array", @ast_internal.json_type_name(input))
  }
}

///|
fn entry_from_pair(key : Json, value : Json) -> Json {
  let entry : Map[String, Json] = { "key": key, "value": value }
  Json::object(entry)
}

///|
fn entry_to_pair(entry : Json) -> (String, Json)? {
  guard entry is Object(obj) else { return None }
  guard obj.get("value") is Some(value) else { return None }
  match obj.get("key") {
    Some(String(key_str)) => Some((key_str, value))
    Some(Number(n, ..)) => Some((n.to_int().to_string(), value))
    _ =>
      match obj.get("name") {
        Some(String(key_str)) => Some((key_str, value))
        Some(Number(n, ..)) => Some((n.to_int().to_string(), value))
        _ => None
      }
  }
}

///|
/// Evaluate with_entries
fn eval_with_entries(
  expr : Expr,
  input : Json,
  env : Env,
) -> Iter[Json] raise InterpreterError {
  match input {
    Object(_) | Array(_) =>
      match eval_with_env(ToEntries, input, env).collect() {
        [entries_array, ..] =>
          match eval_with_env(Map(expr), entries_array, env).collect() {
            [mapped, ..] => eval_with_env(FromEntries, mapped, env)
            [] => Iter::empty()
          }
        [] => Iter::empty()
      }
    _ => raise TypeMismatch("object/array", @ast_internal.json_type_name(input))
  }
}