///|
// Engine-private negative symbol IDs for Array Iterator internal slots.
// These IDs are not registered in SymbolState, so JavaScript cannot obtain
// or enumerate keys for them via string or symbol property access.
const ARRAY_ITERATOR_NEXT_INDEX_SYMBOL_ID = -3

///|
const ARRAY_ITERATOR_TARGET_SYMBOL_ID = -4

///|
const ARRAY_ITERATOR_KIND_SYMBOL_ID = -5

///|
priv enum ArrayIteratorKind {
  Keys
  Values
  Entries
}

///|
// Hidden slot on the realm-owned Array.prototype that preserves the original
// %ArrayProto_values% intrinsic even if user code later mutates the public
// `Array.prototype.values` property.
const ARRAY_PROTO_VALUES_INTRINSIC_SYMBOL_ID = -112

///|
// Engine-private negative symbol IDs for String Iterator internal slots.
const STRING_ITERATOR_NEXT_INDEX_SYMBOL_ID = -136

///|
const STRING_ITERATOR_STRING_SYMBOL_ID = -137

///|
fn array_iterator_internal_slot_descriptor() -> PropDescriptor {
  {
    writable: false,
    enumerable: false,
    configurable: false,
    getter: None,
    setter: None,
    is_accessor: false,
  }
}

///|
pub fn RealmState::set_array_proto_values_intrinsic(
  self : RealmState,
  values_fn : Value,
) -> Unit {
  match self.get_array_proto() {
    Object(proto_data) => {
      proto_data.bag.symbol_properties[ARRAY_PROTO_VALUES_INTRINSIC_SYMBOL_ID] = values_fn
      proto_data.bag.symbol_descriptors[ARRAY_PROTO_VALUES_INTRINSIC_SYMBOL_ID] = array_iterator_internal_slot_descriptor()
    }
    _ => ()
  }
}

///|
pub fn RealmState::get_array_proto_values_intrinsic(
  self : RealmState,
) -> Value? {
  match self.get_array_proto() {
    Object(proto_data) =>
      proto_data.bag.symbol_properties.get(
        ARRAY_PROTO_VALUES_INTRINSIC_SYMBOL_ID,
      )
    _ => None
  }
}

///|
fn make_iterator_result_object(value : Value, done : Bool) -> Value {
  Object({
    bag: {
      properties: { "value": value, "done": Bool(done) },
      symbol_properties: Map([]),
      descriptors: Map([]),
      symbol_descriptors: Map([]),
      internal_slots: Map([]),
      host_slots: Map([]),
    },
    prototype: Null,
    callable: None,
    class_name: "Object",
    extensible: true,
    arraybuffer_state: None,
  })
}

///|
fn make_iterator_result_object_with_proto(
  value : Value,
  done : Bool,
  proto : Value,
) -> Value {
  Object({
    bag: {
      properties: { "value": value, "done": Bool(done) },
      symbol_properties: Map([]),
      descriptors: Map([]),
      symbol_descriptors: Map([]),
      internal_slots: Map([]),
      host_slots: Map([]),
    },
    prototype: proto,
    callable: None,
    class_name: "Object",
    extensible: true,
    arraybuffer_state: None,
  })
}

///|
fn array_iterator_kind_to_slot_value(kind : ArrayIteratorKind) -> Value {
  match kind {
    Keys => Number(0.0)
    Values => Number(1.0)
    Entries => Number(2.0)
  }
}

///|
fn array_iterator_kind_from_slot_value(value : Value) -> ArrayIteratorKind? {
  match value {
    Number(n) =>
      match n.to_int() {
        0 => Some(Keys)
        1 => Some(Values)
        2 => Some(Entries)
        _ => None
      }
    _ => None
  }
}

///|
fn array_iterator_type_error() -> Value raise Error {
  raise @errors.TypeError(
    message="Array Iterator.prototype.next requires that |this| be an Array Iterator",
  )
}

///|
fn array_iterator_result_value(
  kind : ArrayIteratorKind,
  current : Int64,
  target : Value,
  interp : Interpreter,
) -> Value raise Error {
  match kind {
    Keys => Number(current.to_double())
    Values => get_array_like_element_interp(interp, target, current)
    Entries =>
      make_array([
        Number(current.to_double()),
        get_array_like_element_interp(interp, target, current),
      ])
  }
}

///|
fn array_iterator_next(
  interp : Interpreter,
  this_val : Value,
) -> Value raise Error {
  match this_val {
    Object(data) => {
      guard data.class_name == "Array Iterator" else {
        return array_iterator_type_error()
      }
      let (current, target, kind) = match
        (
          data.bag.symbol_properties.get(ARRAY_ITERATOR_NEXT_INDEX_SYMBOL_ID),
          data.bag.symbol_properties.get(ARRAY_ITERATOR_TARGET_SYMBOL_ID),
          data.bag.symbol_properties.get(ARRAY_ITERATOR_KIND_SYMBOL_ID),
        ) {
        (Some(Number(n)), Some(target), Some(kind_value)) =>
          match array_iterator_kind_from_slot_value(kind_value) {
            Some(kind) => (n.to_int64(), target, kind)
            None => return array_iterator_type_error()
          }
        _ => return array_iterator_type_error()
      }
      if target is Undefined {
        return make_iterator_result_object(Undefined, true)
      }
      let total = to_array_like_length_interp(target, interp)
      if current >= total {
        data.bag.symbol_properties[ARRAY_ITERATOR_TARGET_SYMBOL_ID] = Undefined
        return make_iterator_result_object(Undefined, true)
      }
      data.bag.symbol_properties[ARRAY_ITERATOR_NEXT_INDEX_SYMBOL_ID] = Number(
        (current + 1L).to_double(),
      )
      let value = array_iterator_result_value(kind, current, target, interp)
      make_iterator_result_object(value, false)
    }
    _ => array_iterator_type_error()
  }
}

///|
/// Per ES spec, %IteratorPrototype% has [Symbol.iterator] that returns `this`
/// and its [[Prototype]] is %Object.prototype% (§27.1.2).
fn make_iterator_proto_value(
  well_known_symbols : WellKnownSymbols,
  realm_state : RealmState,
) -> Value {
  let iterator_sym = well_known_symbols.iterator
  let proto_sym_props : Map[Int, Value] = Map([])
  proto_sym_props[iterator_sym.id] = make_method_func(
    name="[Symbol.iterator]",
    length=0,
    realm_state=Some(realm_state),
    fn(this_val, _args) { this_val },
  )
  let proto_sym_descs : Map[Int, PropDescriptor] = Map([])
  proto_sym_descs[iterator_sym.id] = {
    writable: true,
    enumerable: false,
    configurable: true,
    getter: None,
    setter: None,
    is_accessor: false,
  }
  Object({
    bag: {
      properties: Map([]),
      symbol_properties: proto_sym_props,
      descriptors: Map([]),
      symbol_descriptors: proto_sym_descs,
      internal_slots: Map([]),
      host_slots: Map([]),
    },
    prototype: realm_state.get_obj_proto(),
    callable: None,
    class_name: "Iterator",
    extensible: true,
    arraybuffer_state: None,
  })
}

///|
/// Get or create the realm-owned %IteratorPrototype%.
pub fn RealmState::get_iterator_proto(self : RealmState) -> Value {
  match self.runtime_iterator_prototypes.iterator_proto.val {
    Some(proto) => proto
    None => {
      let proto = make_iterator_proto_value(self.well_known_symbols, self)
      self.runtime_iterator_prototypes.iterator_proto.val = Some(proto)
      proto
    }
  }
}

///|
fn make_array_iterator_proto_value(
  well_known_symbols : WellKnownSymbols,
  iterator_proto : Value,
  realm_state : RealmState,
) -> Value {
  let iterator_sym = well_known_symbols.iterator
  let tostringtag_sym = well_known_symbols.to_string_tag
  let proto_sym_props : Map[Int, Value] = Map([])
  proto_sym_props[tostringtag_sym.id] = String_("Array Iterator")
  let proto_props : Map[String, Value] = Map([])
  let proto_sym_descs : Map[Int, PropDescriptor] = Map([])
  proto_sym_descs[tostringtag_sym.id] = {
    writable: false,
    enumerable: false,
    configurable: true,
    getter: None,
    setter: None,
    is_accessor: false,
  }
  proto_sym_descs[iterator_sym.id] = {
    writable: true,
    enumerable: false,
    configurable: true,
    getter: None,
    setter: None,
    is_accessor: false,
  }
  proto_sym_props[iterator_sym.id] = make_method_func(
    name="[Symbol.iterator]",
    length=0,
    realm_state=Some(realm_state),
    fn(this_val, _args) { this_val },
  )
  proto_props["next"] = make_interp_method_func(
    name="next",
    length=0,
    realm_state=Some(realm_state),
    fn(interp, this_val, _args) raise { array_iterator_next(interp, this_val) },
  )
  let proto_descs : Map[String, PropDescriptor] = Map([])
  proto_descs["next"] = {
    writable: true,
    enumerable: false,
    configurable: true,
    getter: None,
    setter: None,
    is_accessor: false,
  }
  Object({
    bag: {
      properties: proto_props,
      symbol_properties: proto_sym_props,
      descriptors: proto_descs,
      symbol_descriptors: proto_sym_descs,
      internal_slots: Map([]),
      host_slots: Map([]),
    },
    prototype: iterator_proto,
    callable: None,
    class_name: "Array Iterator",
    extensible: true,
    arraybuffer_state: None,
  })
}

///|
/// Get or create the realm-owned %ArrayIteratorPrototype%.
pub fn RealmState::get_array_iterator_proto(self : RealmState) -> Value {
  match self.runtime_iterator_prototypes.array_iterator_proto.val {
    Some(proto) => proto
    None => {
      let proto = make_array_iterator_proto_value(
        self.well_known_symbols,
        self.get_iterator_proto(),
        self,
      )
      self.runtime_iterator_prototypes.array_iterator_proto.val = Some(proto)
      proto
    }
  }
}

///|
/// Create an Array Iterator object with a live target and iteration kind.
fn make_array_iterator_value_with_proto(
  arr_iter_proto : Value,
  target : Value,
  kind : ArrayIteratorKind,
) -> Value {
  let iter_sym_props : Map[Int, Value] = Map([])
  iter_sym_props[ARRAY_ITERATOR_NEXT_INDEX_SYMBOL_ID] = Number(0.0)
  iter_sym_props[ARRAY_ITERATOR_TARGET_SYMBOL_ID] = target
  iter_sym_props[ARRAY_ITERATOR_KIND_SYMBOL_ID] = array_iterator_kind_to_slot_value(
    kind,
  )
  let iter_sym_descs : Map[Int, PropDescriptor] = Map([])
  let slot_desc = array_iterator_internal_slot_descriptor()
  iter_sym_descs[ARRAY_ITERATOR_NEXT_INDEX_SYMBOL_ID] = slot_desc
  iter_sym_descs[ARRAY_ITERATOR_TARGET_SYMBOL_ID] = slot_desc
  iter_sym_descs[ARRAY_ITERATOR_KIND_SYMBOL_ID] = slot_desc

  Object({
    bag: {
      properties: Map([]),
      symbol_properties: iter_sym_props,
      descriptors: Map([]),
      symbol_descriptors: iter_sym_descs,
      internal_slots: Map([]),
      host_slots: Map([]),
    },
    prototype: arr_iter_proto,
    callable: None,
    class_name: "Array Iterator",
    extensible: true,
    arraybuffer_state: None,
  })
}

///|
pub fn RealmState::make_array_iterator_value(
  self : RealmState,
  arr : ArrayData,
) -> Value {
  make_array_iterator_value_with_proto(
    self.get_array_iterator_proto(),
    Array(arr),
    Values,
  )
}

///|
fn RealmState::make_array_iterator_for_value(
  self : RealmState,
  val : Value,
  kind : ArrayIteratorKind,
) -> Value raise Error {
  match val {
    Null | Undefined =>
      raise @errors.TypeError(
        message="Cannot convert undefined or null to object",
      )
    _ => ()
  }
  make_array_iterator_value_with_proto(
    self.get_array_iterator_proto(),
    val,
    kind,
  )
}

///|
/// Create an array-like values iterator for objects with a `length` property.
pub fn RealmState::make_array_like_iterator_value(
  self : RealmState,
  val : Value,
) -> Value raise Error {
  self.make_array_values_iterator_value(val)
}

///|
pub fn RealmState::make_array_keys_iterator_value(
  self : RealmState,
  val : Value,
) -> Value raise Error {
  self.make_array_iterator_for_value(val, Keys)
}

///|
pub fn RealmState::make_array_values_iterator_value(
  self : RealmState,
  val : Value,
) -> Value raise Error {
  self.make_array_iterator_for_value(val, Values)
}

///|
pub fn RealmState::make_array_entries_iterator_value(
  self : RealmState,
  val : Value,
) -> Value raise Error {
  self.make_array_iterator_for_value(val, Entries)
}

///|
fn string_iterator_type_error() -> Value raise Error {
  raise @errors.TypeError(
    message="String Iterator.prototype.next requires that |this| be a String Iterator",
  )
}

///|
// §22.1.5.2.1 %StringIteratorPrototype%.next()
// Reads [[StringIteratorNextIndex]] and [[IteratedString]] from receiver slots.
// Missing STRING slot → TypeError (catches %StringIteratorPrototype% itself).
fn string_iterator_next(this_val : Value) -> Value raise Error {
  guard this_val is Object(data) else { return string_iterator_type_error() }
  guard data.class_name == "String Iterator" else {
    return string_iterator_type_error()
  }
  let index = match
    data.bag.symbol_properties.get(STRING_ITERATOR_NEXT_INDEX_SYMBOL_ID) {
    Some(Number(n)) => n.to_int()
    _ => return string_iterator_type_error()
  }
  match data.bag.symbol_properties.get(STRING_ITERATOR_STRING_SYMBOL_ID) {
    None => return string_iterator_type_error()
    // §22.1.5.2.1 step 7: [[IteratedString]] is undefined → already done
    Some(Undefined) => return create_iter_result(Undefined, true)
    Some(String_(s)) => {
      let len = s.length()
      guard index < len else {
        data.bag.symbol_properties[STRING_ITERATOR_STRING_SYMBOL_ID] = Undefined
        return create_iter_result(Undefined, true)
      }
      let code = s[index].to_int()
      let next_index = if code >= 0xD800 && code <= 0xDBFF && index + 1 < len {
        let next_code = s[index + 1].to_int()
        if next_code >= 0xDC00 && next_code <= 0xDFFF {
          index + 2
        } else {
          index + 1
        }
      } else {
        index + 1
      }
      data.bag.symbol_properties[STRING_ITERATOR_NEXT_INDEX_SYMBOL_ID] = Value::Number(
        next_index.to_double(),
      )
      create_iter_result(
        String_(s.unsafe_substring(start=index, end=next_index)),
        false,
      )
    }
    _ => return string_iterator_type_error()
  }
}

///|
fn make_string_iterator_proto_value(
  well_known_symbols : WellKnownSymbols,
  iterator_proto : Value,
  realm_state : RealmState,
) -> Value {
  let iterator_sym = well_known_symbols.iterator
  let tostringtag_sym = well_known_symbols.to_string_tag
  let proto_sym_props : Map[Int, Value] = Map([])
  proto_sym_props[tostringtag_sym.id] = String_("String Iterator")
  let proto_props : Map[String, Value] = Map([])
  let proto_sym_descs : Map[Int, PropDescriptor] = Map([])
  proto_sym_descs[tostringtag_sym.id] = {
    writable: false,
    enumerable: false,
    configurable: true,
    getter: None,
    setter: None,
    is_accessor: false,
  }
  proto_sym_props[iterator_sym.id] = make_method_func(
    name="[Symbol.iterator]",
    length=0,
    realm_state=Some(realm_state),
    fn(this_val, _args) { this_val },
  )
  proto_props["next"] = make_method_func(
    name="next",
    length=0,
    realm_state=Some(realm_state),
    fn(this_val, _args) raise { string_iterator_next(this_val) },
  )
  let str_proto_descs : Map[String, PropDescriptor] = Map([])
  str_proto_descs["next"] = {
    writable: true,
    enumerable: false,
    configurable: true,
    getter: None,
    setter: None,
    is_accessor: false,
  }
  Object({
    bag: {
      properties: proto_props,
      symbol_properties: proto_sym_props,
      descriptors: str_proto_descs,
      symbol_descriptors: proto_sym_descs,
      internal_slots: Map([]),
      host_slots: Map([]),
    },
    prototype: iterator_proto,
    callable: None,
    class_name: "String Iterator",
    extensible: true,
    arraybuffer_state: None,
  })
}

///|
/// Get or create the realm-owned %StringIteratorPrototype%.
pub fn RealmState::get_string_iterator_proto(self : RealmState) -> Value {
  match self.runtime_iterator_prototypes.string_iterator_proto.val {
    Some(proto) => proto
    None => {
      let proto = make_string_iterator_proto_value(
        self.well_known_symbols,
        self.get_iterator_proto(),
        self,
      )
      self.runtime_iterator_prototypes.string_iterator_proto.val = Some(proto)
      proto
    }
  }
}

///|
// §22.1.5.1 CreateStringIterator: stores [[IteratedString]] and
// [[StringIteratorNextIndex]] as hidden symbol slots; next() lives on the proto.
fn make_string_iterator_value_with_proto(
  str_iter_proto : Value,
  s : String,
  _realm_state : RealmState,
) -> Value {
  let slot_desc = array_iterator_internal_slot_descriptor()
  let iter_sym_props : Map[Int, Value] = Map([])
  iter_sym_props[STRING_ITERATOR_NEXT_INDEX_SYMBOL_ID] = Value::Number(0.0)
  iter_sym_props[STRING_ITERATOR_STRING_SYMBOL_ID] = Value::String_(s)
  let iter_sym_descs : Map[Int, PropDescriptor] = Map([])
  iter_sym_descs[STRING_ITERATOR_NEXT_INDEX_SYMBOL_ID] = slot_desc
  iter_sym_descs[STRING_ITERATOR_STRING_SYMBOL_ID] = slot_desc
  Object({
    bag: {
      properties: Map([]),
      symbol_properties: iter_sym_props,
      descriptors: Map([]),
      symbol_descriptors: iter_sym_descs,
      internal_slots: Map([]),
      host_slots: Map([]),
    },
    prototype: str_iter_proto,
    callable: None,
    class_name: "String Iterator",
    extensible: true,
    arraybuffer_state: None,
  })
}

///|
pub fn RealmState::make_string_iterator_value(
  self : RealmState,
  s : String,
) -> Value {
  make_string_iterator_value_with_proto(
    self.get_string_iterator_proto(),
    s,
    self,
  )
}

///|
fn make_async_iterator_proto_value(
  well_known_symbols : WellKnownSymbols,
  realm_state : RealmState,
) -> Value {
  let async_iterator_sym = well_known_symbols.async_iterator
  let async_iter_sym_props : Map[Int, Value] = Map([])
  async_iter_sym_props[async_iterator_sym.id] = make_method_func(
    name="[Symbol.asyncIterator]",
    length=0,
    realm_state=Some(realm_state),
    fn(this_val, _args) { this_val },
  )
  let async_iter_sym_descs : Map[Int, PropDescriptor] = Map([])
  async_iter_sym_descs[async_iterator_sym.id] = {
    writable: true,
    enumerable: false,
    configurable: true,
    getter: None,
    setter: None,
    is_accessor: false,
  }
  Object({
    bag: {
      properties: Map([]),
      symbol_properties: async_iter_sym_props,
      descriptors: Map([]),
      symbol_descriptors: async_iter_sym_descs,
      internal_slots: Map([]),
      host_slots: Map([]),
    },
    prototype: realm_state.get_obj_proto(),
    callable: None,
    class_name: "AsyncIterator",
    extensible: true,
    arraybuffer_state: None,
  })
}

///|
/// Optionally close the sync iterator when closeOnRejection is true and done is false.
/// ThrowCompletion path only (§7.4.11 step 5) — close errors are discarded.
fn try_close_on_rejection(
  ip : Interpreter,
  sync_iterator : Value,
  loc : @token.Loc,
  done : Bool,
  close_on_rejection : Bool,
) -> Unit {
  if !done && close_on_rejection {
    ip.iterator_close_throw(sync_iterator, loc)
  }
}

///|
/// Perform Promise.prototype.then with error handling.
/// Returns the result promise, or a rejected promise on error.
fn perform_promise_then(
  ip : Interpreter,
  promise : Value,
  on_fulfill : Value,
  on_reject : Value,
  loc : @token.Loc,
) -> Value {
  try {
    let then_m = ip.get_property(promise, "then", loc)
    ip.call_value(then_m, promise, [on_fulfill, on_reject], loc)
  } catch {
    e => make_rejected_promise(ip, ip.error_to_js_value(e))
  }
}

///|
/// AsyncFromSyncIteratorContinuation (§27.1.4.1.1)
/// Resolves the sync iterator result value through a Promise chain,
/// and optionally closes the sync iterator on rejection.
/// closeOnRejection=true: close sync iterator before rejecting the promise.
/// closeOnRejection=false: only the onFulfilled handler is attached.
fn async_from_sync_continuation(
  ip : Interpreter,
  result : Value,
  sync_iterator : Value,
  close_on_rejection : Bool,
) -> Value {
  let loc = @token.Loc::default()

  // Steps 1-2: IteratorComplete(result)
  let done = extract_iterator_done(ip, result, loc) catch {
    e => return make_rejected_promise(ip, ip.error_to_js_value(e))
  }

  // Steps 3-4: IteratorValue(result)
  let value = extract_iterator_value(ip, result, loc) catch {
    e => return make_rejected_promise(ip, ip.error_to_js_value(e))
  }

  // Step 5: PromiseResolve(%Promise%, value)
  let promise_ctor = ip.global.get("Promise") catch {
    e => {
      try_close_on_rejection(ip, sync_iterator, loc, done, close_on_rejection)
      return make_rejected_promise(ip, ip.error_to_js_value(e))
    }
  }
  let resolve_m = ip.get_property(promise_ctor, "resolve", loc) catch {
    e => {
      try_close_on_rejection(ip, sync_iterator, loc, done, close_on_rejection)
      return make_rejected_promise(ip, ip.error_to_js_value(e))
    }
  }
  let resolved = ip.call_value(resolve_m, promise_ctor, [value], loc) catch {
    e => {
      // Step 6: If abrupt, done is false, and closeOnRejection is true → close iterator
      try_close_on_rejection(ip, sync_iterator, loc, done, close_on_rejection)
      // Step 7: IfAbruptRejectPromise
      return make_rejected_promise(ip, ip.error_to_js_value(e))
    }
  }

  // Create onFulfilled — common for both paths (steps 12a/13)
  let on_fulfill = make_interp_method_func(
    name="",
    length=1,
    realm_state=Some(ip.realm_state),
    fn(i : Interpreter, _this : Value, args : Array[Value]) -> Value {
      let v = if args.length() > 0 { args[0] } else { Undefined }
      make_iterator_result_object_with_proto(
        v,
        done,
        i.realm_state.get_obj_proto(),
      )
    },
  )

  // Steps 12-14: onRejected depends on closeOnRejection and done
  if done || !close_on_rejection {
    // Step 12: onRejected is undefined
    perform_promise_then(ip, resolved, on_fulfill, Undefined, loc)
  } else {
    // Step 13: Create onRejected that closes the iterator first
    let on_reject = make_interp_method_func(
      name="",
      length=1,
      realm_state=Some(ip.realm_state),
      fn(i : Interpreter, _this : Value, args : Array[Value]) -> Value raise {
        let e = if args.length() > 0 { args[0] } else { Undefined }
        // Step 13.a.i: Return ? IteratorClose(syncIteratorRecord, ThrowCompletion(error))
        i.iterator_close_throw(sync_iterator, loc)
        raise JsException(e)
      },
    )
    // Step 14: PerformPromiseThen
    perform_promise_then(ip, resolved, on_fulfill, on_reject, loc)
  }
}

///|
fn Interpreter::error_to_js_value(self : Interpreter, e : Error) -> Value {
  match e {
    JsException(v) => v
    _ => js_error_to_value_with_env(e, Some(self.global))
  }
}

///|
fn extract_iterator_done(
  ip : Interpreter,
  result : Value,
  loc : @token.Loc,
) -> Bool raise Error {
  let done_val = ip.get_property(result, "done", loc)
  is_truthy(done_val)
}

///|
fn extract_iterator_value(
  ip : Interpreter,
  result : Value,
  loc : @token.Loc,
) -> Value raise Error {
  ip.get_property(result, "value", loc)
}

///|
/// Helper: wrap a raise-bound operation with promise-based error handling.
/// Catches any raise and returns a rejected promise instead.
fn reject_on_error(ip : Interpreter, action : () -> Value raise Error) -> Value {
  action() catch {
    e => make_rejected_promise(ip, ip.error_to_js_value(e))
  }
}

///|
/// %AsyncFromSyncIteratorPrototype%.next ( value )
fn async_from_sync_next(
  ip : Interpreter,
  this_val : Value,
  _args : Array[Value],
) -> Value {
  reject_on_error(ip, fn() -> Value raise Error {
    let data = match this_val {
      Object(d) => d
      _ =>
        raise @errors.TypeError(
          message="%AsyncFromSyncIteratorPrototype%.next called on incompatible receiver",
        )
    }
    let sync_iterator = match get_sync_iterator(data) {
      Some(v) => v
      None => raise @errors.TypeError(message="SyncIterator slot missing")
    }
    let sync_next = match get_sync_next_method(data) {
      Some(v) => v
      None => raise @errors.TypeError(message="SyncNextMethod slot missing")
    }
    let loc = @token.Loc::default()
    let result = ip.call_value(sync_next, sync_iterator, [], loc)
    async_from_sync_continuation(ip, result, sync_iterator, true)
  })
}

///|
/// %AsyncFromSyncIteratorPrototype%.return ( value )
fn async_from_sync_return(
  ip : Interpreter,
  this_val : Value,
  args : Array[Value],
) -> Value {
  let arg = if args.length() > 0 { args[0] } else { Undefined }
  reject_on_error(ip, fn() -> Value raise Error {
    let data = match this_val {
      Object(d) => d
      _ =>
        raise @errors.TypeError(
          message="%AsyncFromSyncIteratorPrototype%.return called on incompatible receiver",
        )
    }
    let sync_iterator = match get_sync_iterator(data) {
      Some(v) => v
      None => raise @errors.TypeError(message="SyncIterator slot missing")
    }
    let loc = @token.Loc::default()
    let return_method = ip.get_property(sync_iterator, "return", loc)
    match return_method {
      Undefined | Null => {
        let obj_proto = ip.realm_state.get_obj_proto()
        let result = make_iterator_result_object_with_proto(
          arg, true, obj_proto,
        )
        make_resolved_promise(ip, result)
      }
      _ => {
        let call_result = if args.length() > 0 {
          ip.call_value(return_method, sync_iterator, [arg], loc)
        } else {
          ip.call_value(return_method, sync_iterator, [], loc)
        }
        guard is_object_value(call_result) else {
          raise @errors.TypeError(message="Iterator result is not an object")
        }
        async_from_sync_continuation(ip, call_result, sync_iterator, false)
      }
    }
  })
}

///|
/// %AsyncFromSyncIteratorPrototype%.throw ( value )
fn async_from_sync_throw(
  ip : Interpreter,
  this_val : Value,
  args : Array[Value],
) -> Value {
  let arg = if args.length() > 0 { args[0] } else { Undefined }
  reject_on_error(ip, fn() -> Value raise Error {
    let data = match this_val {
      Object(d) => d
      _ =>
        raise @errors.TypeError(
          message="%AsyncFromSyncIteratorPrototype%.throw called on incompatible receiver",
        )
    }
    let sync_iterator = match get_sync_iterator(data) {
      Some(v) => v
      None => raise @errors.TypeError(message="SyncIterator slot missing")
    }
    let loc = @token.Loc::default()
    let throw_method = ip.get_property(sync_iterator, "throw", loc)
    match throw_method {
      Undefined | Null => {
        // Step 7a: IteratorClose(syncIteratorRecord, closeCompletion)
        ip.iterator_close(sync_iterator, loc)
        // If close succeeded, reject with TypeError (step 7b-c)
        raise @errors.TypeError(
          message="%AsyncFromSyncIteratorPrototype%.throw: sync iterator has no throw method",
        )
      }
      _ => {
        let call_result = ip.call_value(throw_method, sync_iterator, [arg], loc)
        guard is_object_value(call_result) else {
          raise @errors.TypeError(message="Iterator result is not an object")
        }
        async_from_sync_continuation(ip, call_result, sync_iterator, true)
      }
    }
  })
}

///|
fn make_async_from_sync_iterator_proto_value(
  well_known_symbols : WellKnownSymbols,
  realm_state : RealmState,
) -> Value {
  let tostringtag_sym = well_known_symbols.to_string_tag
  let async_iterator_sym = well_known_symbols.async_iterator
  let proto_props : Map[String, Value] = Map([])
  let proto_descs : Map[String, PropDescriptor] = Map([])
  let method_desc : PropDescriptor = {
    writable: true,
    enumerable: false,
    configurable: true,
    getter: None,
    setter: None,
    is_accessor: false,
  }
  proto_props["next"] = make_interp_method_func(
    name="next",
    length=1,
    realm_state=Some(realm_state),
    fn(ip : Interpreter, this_val : Value, args : Array[Value]) -> Value {
      async_from_sync_next(ip, this_val, args)
    },
  )
  proto_descs["next"] = { ..method_desc }
  proto_props["return"] = make_interp_method_func(
    name="return",
    length=1,
    realm_state=Some(realm_state),
    fn(ip : Interpreter, this_val : Value, args : Array[Value]) -> Value {
      async_from_sync_return(ip, this_val, args)
    },
  )
  proto_descs["return"] = { ..method_desc }
  proto_props["throw"] = make_interp_method_func(
    name="throw",
    length=1,
    realm_state=Some(realm_state),
    fn(ip : Interpreter, this_val : Value, args : Array[Value]) -> Value {
      async_from_sync_throw(ip, this_val, args)
    },
  )
  proto_descs["throw"] = { ..method_desc }
  let sym_props : Map[Int, Value] = Map([])
  let sym_descs : Map[Int, PropDescriptor] = Map([])
  sym_props[async_iterator_sym.id] = make_method_func(
    name="[Symbol.asyncIterator]",
    length=0,
    realm_state=Some(realm_state),
    fn(this_val, _args) { this_val },
  )
  sym_descs[async_iterator_sym.id] = {
    writable: true,
    enumerable: false,
    configurable: true,
    getter: None,
    setter: None,
    is_accessor: false,
  }
  sym_props[tostringtag_sym.id] = String_("Async-from-Sync Iterator")
  sym_descs[tostringtag_sym.id] = {
    writable: false,
    enumerable: false,
    configurable: true,
    getter: None,
    setter: None,
    is_accessor: false,
  }
  Object({
    bag: {
      properties: proto_props,
      symbol_properties: sym_props,
      descriptors: proto_descs,
      symbol_descriptors: sym_descs,
      internal_slots: Map([]),
      host_slots: Map([]),
    },
    prototype: realm_state.get_async_iterator_proto(),
    callable: None,
    class_name: "Async-from-Sync Iterator",
    extensible: true,
    arraybuffer_state: None,
  })
}

///|
/// Create an Async-from-Sync Iterator wrapper per ES262 §27.1.4.
/// Wraps a sync iterator record so it can be used with for-await-of.
/// The instance inherits next/return/throw from %AsyncFromSyncIteratorPrototype%
/// and stores sync iterator data in internal slots.
fn create_async_from_sync_iterator(
  interp : Interpreter,
  sync_iterator : Value,
  sync_next_method : Value,
  _loc : @token.Loc,
) -> Value {
  let async_iter_sym = interp.realm_state.well_known_symbols.async_iterator
  let async_sym_props : Map[Int, Value] = Map([])
  let async_sym_descs : Map[Int, PropDescriptor] = Map([])
  async_sym_props[async_iter_sym.id] = make_method_func(
    name="[Symbol.asyncIterator]",
    length=0,
    realm_state=Some(interp.realm_state),
    fn(this_val, _args) { this_val },
  )
  async_sym_descs[async_iter_sym.id] = {
    writable: true,
    enumerable: false,
    configurable: true,
    getter: None,
    setter: None,
    is_accessor: false,
  }
  let internal_slots : Map[InternalSlotKey, Value] = Map([])
  internal_slots[SyncIterator] = sync_iterator
  internal_slots[SyncNextMethod] = sync_next_method
  let async_from_sync_proto = interp.realm_state.get_async_from_sync_iterator_proto()
  Object({
    bag: {
      properties: Map([]),
      symbol_properties: async_sym_props,
      descriptors: Map([]),
      symbol_descriptors: async_sym_descs,
      internal_slots,
      host_slots: Map([]),
    },
    prototype: async_from_sync_proto,
    callable: None,
    class_name: "Async-from-Sync Iterator",
    extensible: true,
    arraybuffer_state: None,
  })
}

///|
fn make_resolved_promise(_interp : Interpreter, value : Value) -> Value {
  let promise_data = new_promise_data()
  promise_data.state = Fulfilled
  promise_data.result = value
  Promise(promise_data)
}

///|
fn make_rejected_promise(_interp : Interpreter, value : Value) -> Value {
  let promise_data = new_promise_data()
  promise_data.state = Rejected
  promise_data.result = value
  Promise(promise_data)
}