///|
/// Return an iterator's `next` method using the spec §7.4.1 `[[Get]]` path.
/// Per spec, callability is NOT checked here — GetIterator only gets the property.
/// The TypeError fires later in IteratorNext when Call(nextMethod, ...) is attempted.
pub fn Interpreter::get_iterator_next_method(
  self : Interpreter,
  iterator : Value,
  loc : @token.Loc,
) -> Value raise Error {
  if !is_object_value(iterator) {
    raise @errors.TypeError(message="Iterator is not an object")
  }
  self.get_property(iterator, "next", loc)
}

///|
/// Perform `IteratorNext(iteratorRecord)` with an already-resolved next method.
fn Interpreter::iterator_next_result(
  self : Interpreter,
  iterator : Value,
  next_method : Value,
  loc : @token.Loc,
) -> Value raise Error {
  let result = self.call_value(next_method, iterator, [], loc)
  if !is_object_value(result) {
    raise @errors.TypeError(message="Iterator result is not an object")
  }
  result
}

///|
/// Perform `IteratorStep(iteratorRecord)`.
pub fn Interpreter::iterator_step_result(
  self : Interpreter,
  iterator : Value,
  next_method : Value,
  loc : @token.Loc,
) -> Value? raise Error {
  let result = self.iterator_next_result(iterator, next_method, loc)
  if is_truthy(self.get_property(result, "done", loc)) {
    None
  } else {
    Some(result)
  }
}

///|
/// Perform `IteratorValue(iterResult)`.
pub fn Interpreter::iterator_result_value(
  self : Interpreter,
  iter_result : Value,
  loc : @token.Loc,
) -> Value raise Error {
  if !is_object_value(iter_result) {
    raise @errors.TypeError(message="Iterator result is not an object")
  }
  self.get_property(iter_result, "value", loc)
}

///|
/// Perform `IteratorStep` followed by `IteratorValue`.
pub fn Interpreter::iterator_step_value(
  self : Interpreter,
  iterator : Value,
  next_method : Value,
  loc : @token.Loc,
) -> Value? raise Error {
  match self.iterator_step_result(iterator, next_method, loc) {
    None => None
    Some(result) => Some(self.iterator_result_value(result, loc))
  }
}

///|
/// `IteratorClose(iteratorRecord, completion)` for a non-throw completion.
///
/// Resolves `"return"` via [[Get]], calls it if present, and raises on
/// GetMethod/Call/IsObject failures (§7.4.11 steps 6–7). Use
/// `iterator_close_throw` when `completion` is a throw completion.
pub fn Interpreter::iterator_close(
  self : Interpreter,
  iterator : Value,
  loc : @token.Loc,
) -> Unit raise Error {
  if !is_object_value(iterator) {
    return
  }
  let return_fn = self.get_property(iterator, "return", loc)
  match return_fn {
    Undefined | Null => ()
    _ =>
      if !is_callable(return_fn) {
        raise @errors.TypeError(message="iterator.return is not a function")
      } else {
        let result = self.call_value(return_fn, iterator, [], loc)
        if !is_object_value(result) {
          raise @errors.TypeError(
            message="Iterator return() must return an object",
          )
        }
      }
  }
}

///|
/// `IteratorClose(iteratorRecord, ThrowCompletion(...))` (§7.4.11 step 5).
///
/// Attempts `return()` for cleanup, then discards GetMethod/Call/IsObject
/// errors so the original ThrowCompletion remains the completion value.
/// Infallible: callers re-raise the original error after this returns.
pub fn Interpreter::iterator_close_throw(
  self : Interpreter,
  iterator : Value,
  loc : @token.Loc,
) -> Unit {
  if !is_object_value(iterator) {
    return
  }
  let return_fn = self.get_property(iterator, "return", loc) catch {
    _ => return
  }
  match return_fn {
    Undefined | Null => ()
    _ =>
      if is_callable(return_fn) {
        // Discard Call errors and skip IsObject: throw completion wins (§7.4.11 step 5).
        let _ = self.call_value(return_fn, iterator, [], loc) catch {
          _ => Undefined
        }
      }
  }
}