///|
/// Canonical `[[OwnPropertyKeys]]` enumeration for the runtime (ES §10.1.11
/// OrdinaryOwnPropertyKeys plus the Array §10.4.2.1, String, Module, TypedArray
/// §10.4.5, and Proxy §10.5.11 exotic variants).
///
/// `Interpreter::own_property_keys` is the single enumerator every consumer
/// routes through: Object.getOwnPropertyNames / getOwnPropertySymbols /
/// getOwnPropertyDescriptors / defineProperties (this file), Reflect.ownKeys
/// (builtins_reflect.mbt), and the Proxy ownKeys target-key enumeration
/// (proxy_helpers.mbt). Key order is integer indices ascending, then string
/// keys in insertion order, then symbol keys in insertion order; Array holes
/// are omitted everywhere (§10.4.2.1).
///
/// Sparse Array index keys stored on the bag are merged into the ascending
/// integer-index run before the exotic "length" key.

///|
/// Append the own symbol keys of a property bag (resolved id -> SymbolData) to
/// `out`, in insertion order. Unresolvable ids are skipped.
fn append_resolved_symbol_keys(
  out : Array[Value],
  symbol_props : Map[Int, Value],
  sym_state : SymbolState,
) -> Unit {
  symbol_props.each(fn(sym_id, _raw) {
    match sym_state.get_symbol_by_id(sym_id) {
      Some(sym) => out.push(Value::Symbol(sym))
      None => ()
    }
  })
}

///|
fn append_bag_own_keys(
  keys : Array[Value],
  bag : PropertyBag,
  sym_state : SymbolState,
) -> Unit {
  for prop_name in sort_property_keys(bag.properties) {
    keys.push(Value::String_(prop_name))
  }
  append_resolved_symbol_keys(keys, bag.symbol_properties, sym_state)
}

///|
fn append_array_own_keys(
  keys : Array[Value],
  data : ArrayData,
  sym_state : SymbolState,
) -> Unit {
  let element_len = data.elements.length()
  for i in 0..= element_len.to_int64() =>
        keys.push(Value::String_(prop_name))
      _ => ()
    }
  }
  keys.push(Value::String_("length"))
  for prop_name in sorted_bag_keys {
    match array_index_of_key(prop_name) {
      Some(_) => ()
      None => keys.push(Value::String_(prop_name))
    }
  }
  append_resolved_symbol_keys(keys, data.bag.symbol_properties, sym_state)
}

///|
/// Append a TypedArray's own keys (§10.4.5.2 IntegerIndexedExoticObject
/// `[[OwnPropertyKeys]]`): the canonical numeric indices `0 .. [[ArrayLength]]`
/// that are still valid (the buffer may have detached), then non-index string
/// bag keys in insertion order, then symbol keys.
fn append_typedarray_own_keys(
  interp : Interpreter,
  keys : Array[Value],
  data : ObjectData,
  sym_state : SymbolState,
) -> Unit {
  let length = typedarray_declared_length(data)
  for i in 0.. Array[Value] raise Error {
  let sym_state = self.realm_state.symbols
  let keys : Array[Value] = []
  match val {
    Object(data) =>
      if data.class_name == "Module" {
        for prop_name in module_namespace_own_string_keys(data) {
          keys.push(Value::String_(prop_name))
        }
        append_resolved_symbol_keys(keys, data.bag.symbol_properties, sym_state)
      } else if is_typedarray_class(data.class_name) {
        append_typedarray_own_keys(self, keys, data, sym_state)
      } else {
        append_bag_own_keys(keys, data.bag, sym_state)
      }
    Array(data) => append_array_own_keys(keys, data, sym_state)
    Map(data) => append_bag_own_keys(keys, data.bag, sym_state)
    Set(data) => append_bag_own_keys(keys, data.bag, sym_state)
    Promise(data) => append_bag_own_keys(keys, data.bag, sym_state)
    Value::String_(s) => {
      // ToObject(String) characterization (§10.4.3): indexed character slots
      // then "length". Only reached through getOwnPropertyNames coercion.
      let length = utf16_length(s)
      for i in 0..
      match proxy_own_property_keys(self, proxy_data) {
        Value::Array(proxy_keys) =>
          for key in proxy_keys.elements {
            keys.push(key)
          }
        _ => ()
      }
    _ => ()
  }
  keys
}

///|
/// Object.getOwnPropertyNames (§20.1.2.11): the String-typed own keys from the
/// canonical `[[OwnPropertyKeys]]` enumeration. Array holes and TypedArray
/// validity are handled by the canonical op.
pub fn own_string_property_names(
  interp : Interpreter,
  source : Value,
) -> Value raise Error {
  match source {
    Null | Undefined =>
      raise @errors.TypeError(
        message="Cannot convert undefined or null to object",
      )
    _ => {
      let result : Array[Value] = []
      for key in interp.own_property_keys(source) {
        if key is Value::String_(_) {
          result.push(key)
        }
      }
      make_array(result)
    }
  }
}

///|
/// Object.getOwnPropertySymbols (§20.1.2.12): the Symbol-typed own keys from the
/// canonical `[[OwnPropertyKeys]]` enumeration.
pub fn own_symbol_properties(
  interp : Interpreter,
  source : Value,
) -> Value raise Error {
  match source {
    Null | Undefined =>
      raise @errors.TypeError(
        message="Cannot convert undefined or null to object",
      )
    _ => {
      let result : Array[Value] = []
      for key in interp.own_property_keys(source) {
        if key is Value::Symbol(_) {
          result.push(key)
        }
      }
      make_array(result)
    }
  }
}