///|
/// Iterable and comprehension evaluation helpers.

///|
fn lookup_iterable_field(
  dict : Array[(String, Value)],
  name : String,
) -> Value? {
  for pair in dict {
    if pair.0 == name {
      return Some(pair.1)
    }
  }
  None
}

///|
fn set_iterable_field(
  dict : Array[(String, Value)],
  name : String,
  value : Value,
) -> Unit {
  for i = 0; i < dict.length(); i = i + 1 {
    if dict[i].0 == name {
      dict[i] = (name, value)
      return
    }
  }
  dict.push((name, value))
}

///|
fn iterable_values(value : Value) -> Result[Array[Value], RuntimeError] {
  match value {
    Value::List(values) => Ok(values)
    Value::Tuple(values) => Ok(values)
    Value::Set(values) => Ok(values)
    Value::Dict(values) => {
      let keys : Array[Value] = []
      for pair in values {
        keys.push(pair.0)
      }
      Ok(keys)
    }
    Value::Str(text) => {
      let items : Array[Value] = []
      for ch in text.to_array() {
        items.push(Value::Str(char_to_string(ch)))
      }
      Ok(items)
    }
    Value::Bytes(bytes) | Value::ByteArray(bytes) | Value::MemoryView(bytes) => {
      let items : Array[Value] = []
      for byte in bytes {
        items.push(Value::Int(@bigint.BigInt::from_int(byte)))
      }
      Ok(items)
    }
    Value::Instance(inst) =>
      if inst.class.name == "range" {
        let start = match get_named_value(inst.dict, "start") {
          Some(Value::Int(v)) => v
          _ => 0N
        }
        let stop = match get_named_value(inst.dict, "stop") {
          Some(Value::Int(v)) => v
          _ => 0N
        }
        let step = match get_named_value(inst.dict, "step") {
          Some(Value::Int(v)) => v
          _ => 1N
        }
        let len_big = range_length_bigint(start, stop, step)
        let len = match bigint_to_int_checked(len_big) {
          Ok(v) => v
          Err(err) => return Err(err)
        }
        let out : Array[Value] = []
        for i = 0; i < len; i = i + 1 {
          out.push(
            Value::Int(
              range_element_at(start, step, @bigint.BigInt::from_int(i)),
            ),
          )
        }
        Ok(out)
      } else if inst.class.name == "dict_keys" ||
        inst.class.name == "dict_values" ||
        inst.class.name == "dict_items" {
        let dict_value = lookup_iterable_field(inst.dict, "dict")
        match dict_value {
          Some(Value::Dict(pairs)) => {
            let out : Array[Value] = []
            for pair in pairs {
              if inst.class.name == "dict_keys" {
                out.push(pair.0)
              } else if inst.class.name == "dict_values" {
                out.push(pair.1)
              } else {
                out.push(Value::Tuple([pair.0, pair.1]))
              }
            }
            Ok(out)
          }
          _ =>
            Err(
              make_runtime_error(
                RuntimeErrorKind::Type,
                "object is not iterable".to_string(),
              ),
            )
        }
      } else if inst.class.name == "iterator" {
        let items = match lookup_iterable_field(inst.dict, "items") {
          Some(Value::List(items)) => items
          _ => []
        }
        let pos = match lookup_iterable_field(inst.dict, "pos") {
          Some(Value::Int(v)) => v
          Some(Value::Bool(v)) => if v { 1N } else { 0N }
          _ => 0N
        }
        let out : Array[Value] = []
        let start = match bigint_to_int_checked(pos) {
          Ok(v) => v
          Err(err) => return Err(err)
        }
        let mut i = start
        while i < items.length() {
          out.push(items[i])
          i = i + 1
        }
        set_iterable_field(
          inst.dict,
          "pos",
          Value::Int(@bigint.BigInt::from_int(items.length())),
        )
        Ok(out)
      } else if inst.class.name == "generator" {
        let out : Array[Value] = []
        while true {
          match generator_next(Value::Instance(inst), None) {
            Ok(v) => out.push(v)
            Err(err) =>
              if err.exc_type == "StopIteration" {
                break
              } else {
                return Err(err)
              }
          }
        }
        Ok(out)
      } else {
        Err(
          make_runtime_error(
            RuntimeErrorKind::Type,
            "object is not iterable".to_string(),
          ),
        )
      }
    _ =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "object is not iterable".to_string(),
        ),
      )
  }
}

///|
fn eval_comp_filters(
  filters : Array[Expr],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Bool, RuntimeError] {
  for filter in filters {
    let cond = match eval_expr_with_env(filter, locals, globals, builtins, io) {
      Ok(value) => value
      Err(err) => return Err(err)
    }
    let ok = match truthy_from_value_with_env(cond, globals, builtins, io) {
      Ok(v) => v
      Err(err) => return Err(err)
    }
    if !ok {
      return Ok(false)
    }
  }
  Ok(true)
}

///|
fn eval_list_comp_walk(
  index : Int32,
  elt : Expr,
  clauses : Array[CompClause],
  filters : Array[Expr],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
  results : Array[Value],
) -> Result[Unit, RuntimeError] {
  if index >= clauses.length() {
    let ok = match
      eval_comp_filters(
        unowned_comprehension_filters(clauses, filters),
        locals,
        globals,
        builtins,
        io,
      ) {
      Ok(value) => value
      Err(err) => return Err(err)
    }
    if ok {
      let value = match eval_expr_with_env(elt, locals, globals, builtins, io) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      results.push(value)
    }
    return Ok(())
  }
  let clause = clauses[index]
  let iter_value = match
    eval_expr_with_env(clause.iter, locals, globals, builtins, io) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  if clause.is_async {
    let aiter_value = match
      get_attr_from_value(iter_value, "__aiter__", globals, builtins, io) {
      Ok(v) => v
      Err(err) => return Err(err)
    }
    let iterator = match
      call_callable_with_env(aiter_value, [], [], globals, builtins, io) {
      Ok(v) => v
      Err(err) => return Err(err)
    }
    while true {
      let anext_value = match
        get_attr_from_value(iterator, "__anext__", globals, builtins, io) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      let next_awaitable = match
        call_callable_with_env(anext_value, [], [], globals, builtins, io) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      let item = match coroutine_await(next_awaitable) {
        Ok(v) => v
        Err(err) =>
          if err.exc_type == "StopAsyncIteration" {
            break
          } else {
            return Err(err)
          }
      }
      let _ = match
        assign_target_with_env(
          clause.target,
          item,
          locals,
          globals,
          builtins,
          io,
        ) {
        Ok(_) => ()
        Err(err) => return Err(err)
      }
      let keep = match
        eval_comp_filters(clause.filters, locals, globals, builtins, io) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      if !keep {
        continue
      }
      let _ = match
        eval_list_comp_walk(
          index + 1,
          elt,
          clauses,
          filters,
          locals,
          globals,
          builtins,
          io,
          results,
        ) {
        Ok(_) => ()
        Err(err) => return Err(err)
      }
    }
  } else {
    let iterator = match
      iter_value_to_iterator(iter_value, globals, builtins, io) {
      Ok(v) => v
      Err(err) => return Err(err)
    }
    while true {
      let item = match iterator_next(iterator, None, globals, builtins, io) {
        Ok(v) => v
        Err(err) =>
          if err.exc_type == "StopIteration" {
            break
          } else {
            return Err(err)
          }
      }
      let _ = match
        assign_target_with_env(
          clause.target,
          item,
          locals,
          globals,
          builtins,
          io,
        ) {
        Ok(_) => ()
        Err(err) => return Err(err)
      }
      let keep = match
        eval_comp_filters(clause.filters, locals, globals, builtins, io) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      if !keep {
        continue
      }
      let _ = match
        eval_list_comp_walk(
          index + 1,
          elt,
          clauses,
          filters,
          locals,
          globals,
          builtins,
          io,
          results,
        ) {
        Ok(_) => ()
        Err(err) => return Err(err)
      }
    }
  }
  Ok(())
}

///|
fn eval_dict_comp_walk(
  index : Int32,
  key : Expr,
  value : Expr,
  clauses : Array[CompClause],
  filters : Array[Expr],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
  results : Array[(Value, Value)],
) -> Result[Unit, RuntimeError] {
  if index >= clauses.length() {
    let ok = match
      eval_comp_filters(
        unowned_comprehension_filters(clauses, filters),
        locals,
        globals,
        builtins,
        io,
      ) {
      Ok(value) => value
      Err(err) => return Err(err)
    }
    if ok {
      let k = match eval_expr_with_env(key, locals, globals, builtins, io) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      let v = match eval_expr_with_env(value, locals, globals, builtins, io) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      let _ = match dict_set_item(results, k, v) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
    }
    return Ok(())
  }
  let clause = clauses[index]
  let iter_value = match
    eval_expr_with_env(clause.iter, locals, globals, builtins, io) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  if clause.is_async {
    let aiter_value = match
      get_attr_from_value(iter_value, "__aiter__", globals, builtins, io) {
      Ok(v) => v
      Err(err) => return Err(err)
    }
    let iterator = match
      call_callable_with_env(aiter_value, [], [], globals, builtins, io) {
      Ok(v) => v
      Err(err) => return Err(err)
    }
    while true {
      let anext_value = match
        get_attr_from_value(iterator, "__anext__", globals, builtins, io) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      let next_awaitable = match
        call_callable_with_env(anext_value, [], [], globals, builtins, io) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      let item = match coroutine_await(next_awaitable) {
        Ok(v) => v
        Err(err) =>
          if err.exc_type == "StopAsyncIteration" {
            break
          } else {
            return Err(err)
          }
      }
      let _ = match
        assign_target_with_env(
          clause.target,
          item,
          locals,
          globals,
          builtins,
          io,
        ) {
        Ok(_) => ()
        Err(err) => return Err(err)
      }
      let keep = match
        eval_comp_filters(clause.filters, locals, globals, builtins, io) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      if !keep {
        continue
      }
      let _ = match
        eval_dict_comp_walk(
          index + 1,
          key,
          value,
          clauses,
          filters,
          locals,
          globals,
          builtins,
          io,
          results,
        ) {
        Ok(_) => ()
        Err(err) => return Err(err)
      }
    }
  } else {
    let iterator = match
      iter_value_to_iterator(iter_value, globals, builtins, io) {
      Ok(v) => v
      Err(err) => return Err(err)
    }
    while true {
      let item = match iterator_next(iterator, None, globals, builtins, io) {
        Ok(v) => v
        Err(err) =>
          if err.exc_type == "StopIteration" {
            break
          } else {
            return Err(err)
          }
      }
      let _ = match
        assign_target_with_env(
          clause.target,
          item,
          locals,
          globals,
          builtins,
          io,
        ) {
        Ok(_) => ()
        Err(err) => return Err(err)
      }
      let keep = match
        eval_comp_filters(clause.filters, locals, globals, builtins, io) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      if !keep {
        continue
      }
      let _ = match
        eval_dict_comp_walk(
          index + 1,
          key,
          value,
          clauses,
          filters,
          locals,
          globals,
          builtins,
          io,
          results,
        ) {
        Ok(_) => ()
        Err(err) => return Err(err)
      }
    }
  }
  Ok(())
}

///|
// The flat filter view is retained for AST consumers and lexical validators;
// execution uses per-clause filters to avoid moving side effects inward.
fn unowned_comprehension_filters(
  clauses : Array[CompClause],
  filters : Array[Expr],
) -> Array[Expr] {
  if clauses.any(fn(clause) { !clause.filters.is_empty() }) {
    []
  } else {
    filters
  }
}