///|
/// Async generator-expression runtime.

///|
priv struct AsyncGenExpState {
  elt : Expr
  scope : ScopeDecls
  closure : Array[(String, Value)]
  clauses : Array[CompClause]
  filters : Array[Expr]
  locals : Array[(String, Value)]
  globals : Array[(String, Value)]
  builtins : Array[(String, Value)]
  io : MockIO
  iters : Array[Value?]
  mut depth : Int32
  mut done : Bool
}

///|
let async_genexp_registry : Ref[Array[Ref[AsyncGenExpState]]] = { val: [], }

///|
fn make_async_genexp_instance(id : Int32) -> Value {
  Value::Instance(InstanceValue::{
    class: ClassValue::{ name: "async_generator", bases: [], dict: [], },
    dict: [("id", Value::Int(@bigint.BigInt::from_int(id)))],
  })
}

///|
fn async_genexp_id_from_instance(
  inst : InstanceValue,
) -> Result[Int32?, RuntimeError] {
  match get_named_value(inst.dict, "id") {
    Some(Value::Int(v)) =>
      match bigint_to_int_checked(v) {
        Ok(i) => Ok(Some(i))
        Err(err) => Err(err)
      }
    Some(Value::Bool(v)) => Ok(Some(if v { 1 } else { 0 }))
    _ => Ok(None)
  }
}

///|
fn stop_async_iteration() -> RuntimeError {
  make_runtime_error(
    RuntimeErrorKind::Runtime,
    "StopAsyncIteration".to_string(),
  )
}

///|
fn async_genexp_state_from_value(
  async_genexp_value : Value,
) -> Result[Ref[AsyncGenExpState], RuntimeError] {
  match async_genexp_value {
    Value::Instance(inst) =>
      if inst.class.name != "async_generator" {
        Err(
          make_runtime_error(
            RuntimeErrorKind::Type,
            "'" + inst.class.name + "' object is not an async generator",
          ),
        )
      } else {
        let id_opt = match async_genexp_id_from_instance(inst) {
          Ok(v) => v
          Err(err) => return Err(err)
        }
        let id = match id_opt {
          Some(id) => id
          None =>
            return Err(
              make_runtime_error(
                RuntimeErrorKind::Runtime,
                "RuntimeError: invalid async generator".to_string(),
              ),
            )
        }
        if id < 0 || id >= async_genexp_registry.val.length() {
          return Err(
            make_runtime_error(
              RuntimeErrorKind::Runtime,
              "RuntimeError: invalid async generator id".to_string(),
            ),
          )
        }
        Ok(async_genexp_registry.val[id])
      }
    _ =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "'" +
          type_name_from_value(async_genexp_value) +
          "' object is not an async generator",
        ),
      )
  }
}

///|
fn async_genexp_new(
  elt : Expr,
  clauses : Array[CompClause],
  filters : Array[Expr],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Value {
  let iters : Array[Value?] = []
  for _ in clauses {
    iters.push(None)
  }
  let state : Ref[AsyncGenExpState] = {
    val: AsyncGenExpState::{
      elt,
      scope: comprehension_scope_decls(clauses),
      closure: current_closure_env(),
      clauses,
      filters: unowned_comprehension_filters(clauses, filters),
      locals,
      globals,
      builtins,
      io,
      iters,
      depth: 0,
      done: false,
    },
  }
  let id = async_genexp_registry.val.length()
  async_genexp_registry.val.push(state)
  make_async_genexp_instance(id)
}

///|
fn async_genexp_next(async_genexp_value : Value) -> Result[Value, RuntimeError] {
  let state = match async_genexp_state_from_value(async_genexp_value) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  push_closure_env(state.val.closure)
  scope_decls_stack.val.push(state.val.scope)
  let result = async_genexp_resume(state)
  pop_scope_decls()
  pop_closure_env()
  result
}

///|
fn async_genexp_resume(
  state : Ref[AsyncGenExpState],
) -> Result[Value, RuntimeError] {
  if state.val.done {
    return Err(stop_async_iteration())
  }
  let n = state.val.clauses.length()
  if n == 0 {
    state.val.done = true
    return eval_expr_with_env(
      state.val.elt,
      state.val.locals,
      state.val.globals,
      state.val.builtins,
      state.val.io,
    )
  }
  while true {
    if state.val.depth >= n {
      let ok = match
        eval_comp_filters(
          state.val.filters,
          state.val.locals,
          state.val.globals,
          state.val.builtins,
          state.val.io,
        ) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      state.val.depth = n - 1
      if ok {
        return eval_expr_with_env(
          state.val.elt,
          state.val.locals,
          state.val.globals,
          state.val.builtins,
          state.val.io,
        )
      }
      continue
    }
    let depth = state.val.depth
    let clause = state.val.clauses[depth]
    let iterator = match state.val.iters[depth] {
      Some(v) => v
      None => {
        let iter_value = match
          eval_expr_with_env(
            clause.iter,
            state.val.locals,
            state.val.globals,
            state.val.builtins,
            state.val.io,
          ) {
          Ok(v) => v
          Err(err) => return Err(err)
        }
        let iterator = if clause.is_async {
          let aiter_value = match
            get_attr_from_value(
              iter_value,
              "__aiter__",
              state.val.globals,
              state.val.builtins,
              state.val.io,
            ) {
            Ok(v) => v
            Err(err) => return Err(err)
          }
          match
            call_callable_with_env(
              aiter_value,
              [],
              [],
              state.val.globals,
              state.val.builtins,
              state.val.io,
            ) {
            Ok(v) => v
            Err(err) => return Err(err)
          }
        } else {
          match
            iter_value_to_iterator(
              iter_value,
              state.val.globals,
              state.val.builtins,
              state.val.io,
            ) {
            Ok(v) => v
            Err(err) => return Err(err)
          }
        }
        state.val.iters[depth] = Some(iterator)
        iterator
      }
    }
    let item_res = if clause.is_async {
      let anext_value = match
        get_attr_from_value(
          iterator,
          "__anext__",
          state.val.globals,
          state.val.builtins,
          state.val.io,
        ) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      let next_awaitable = match
        call_callable_with_env(
          anext_value,
          [],
          [],
          state.val.globals,
          state.val.builtins,
          state.val.io,
        ) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      match coroutine_await(next_awaitable) {
        Ok(v) => Ok(Some(v))
        Err(err) =>
          if err.exc_type == "StopAsyncIteration" {
            Ok(None)
          } else {
            Err(err)
          }
      }
    } else {
      match
        iterator_next(
          iterator,
          None,
          state.val.globals,
          state.val.builtins,
          state.val.io,
        ) {
        Ok(v) => Ok(Some(v))
        Err(err) =>
          if err.exc_type == "StopIteration" {
            Ok(None)
          } else {
            Err(err)
          }
      }
    }
    match item_res {
      Ok(Some(item)) => {
        let _ = match
          assign_target_with_env(
            clause.target,
            item,
            state.val.locals,
            state.val.globals,
            state.val.builtins,
            state.val.io,
          ) {
          Ok(v) => v
          Err(err) => return Err(err)
        }
        let keep = match
          eval_comp_filters(
            clause.filters,
            state.val.locals,
            state.val.globals,
            state.val.builtins,
            state.val.io,
          ) {
          Ok(value) => value
          Err(err) => return Err(err)
        }
        if !keep {
          continue
        }
        for i = depth + 1; i < n; i = i + 1 {
          state.val.iters[i] = None
        }
        state.val.depth = depth + 1
      }
      Ok(None) => {
        state.val.iters[depth] = None
        if depth == 0 {
          state.val.done = true
          return Err(stop_async_iteration())
        }
        state.val.depth = depth - 1
      }
      Err(err) => return Err(err)
    }
  }
  return Err(stop_async_iteration())
}

///|
fn async_genexp_make_anext_coroutine(
  async_genexp_value : Value,
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Value {
  let body : Array[Stmt] = [
    Stmt::Return(
      Some(
        Expr::Call(callee=Expr::Name("__mpython_asyncgenexp_next"), args=[
          Expr::Name("self"),
        ]),
      ),
    ),
  ]
  let locals : Array[(String, Value)] = [("self", async_genexp_value)]
  let filename = current_traceback_filename()
  coroutine_new(
    body,
    locals,
    globals,
    builtins,
    io,
    [],
    [],
    [],
    "".to_string(),
    filename,
    Config::default(),
  )
}