///|
/// Flatten array to specified depth
fn flatten_array_into(
  arr : Array[Json],
  depth : Int,
  out : Array[Json],
) -> Unit {
  guard depth > 0 else {
    for elem in arr {
      out.push(elem)
    }
    return
  }
  for elem in arr {
    match elem {
      Array(inner) => flatten_array_into(inner, depth - 1, out)
      _ => out.push(elem)
    }
  }
}

///|
fn flatten_array(arr : Array[Json], depth : Int) -> Array[Json] {
  guard depth > 0 else { return arr }
  let result : Array[Json] = []
  flatten_array_into(arr, depth, result)
  result
}

///|
/// Recursive descent: yield input and all nested values
fn recurse_all(input : Json) -> Iter[Json] {
  match input {
    Array(arr) =>
      Iter::singleton(input).concat(arr.iter().flat_map(recurse_all))
    Object(obj) =>
      Iter::singleton(input).concat(obj.values().flat_map(recurse_all))
    _ => Iter::singleton(input)
  }
}

///|
/// Evaluate RecurseWith
fn eval_recurse_with(
  f : Expr,
  _cond : Expr,
  input : Json,
  env : Env,
) -> Iter[Json] raise InterpreterError {
  let results : Array[Json] = [input]
  let to_process_ref : Ref[Array[Json]] = Ref([input])
  while !to_process_ref.val.is_empty() {
    let next_batch : Array[Json] = []
    for item in to_process_ref.val {
      for result in eval_with_env(f, item, env) {
        if result != item {
          results.push(result)
          next_batch.push(result)
        }
      }
    }
    to_process_ref.val = next_batch
    if to_process_ref.val.length() > 100 {
      break
    }
  }
  results.iter()
}

///|
/// Evaluate Walk
fn eval_walk(
  f : Expr,
  input : Json,
  env : Env,
) -> Iter[Json] raise InterpreterError {
  fn walk_value(v : Json) -> Json raise InterpreterError {
    match v {
      Array(arr) => {
        let walked = arr.map(walk_value)
        let wrapped = Json::array(walked)
        match eval_with_env(f, wrapped, env).collect() {
          [first, ..] => first
          [] => wrapped
        }
      }
      Object(obj) => {
        let walked : Map[String, Json] = Map([])
        for k, v in obj {
          walked[k] = walk_value(v)
        }
        let wrapped = Json::object(walked)
        match eval_with_env(f, wrapped, env).collect() {
          [first, ..] => first
          [] => wrapped
        }
      }
      _ =>
        match eval_with_env(f, v, env).collect() {
          [first, ..] => first
          [] => v
        }
    }
  }

  Iter::singleton(walk_value(input))
}