///|
/// Prototype internal operations (`[[GetPrototypeOf]]` / `[[SetPrototypeOf]]`)
/// relocated from `interpreter/stdlib/builtins_reflect.mbt` so that stdlib calls
/// a runtime operation instead of reaching into PropertyBag / ArrayData /
/// prototype / extensibility representation internals (architecture redesign
/// Stage 8, following #335 own-property-keys and #338 integrity ops).
///
/// These operations currently handle Object / Array / Proxy targets. The
/// remaining latent asymmetry is that Map / Set / Promise targets raise
/// TypeError even though their `[[GetPrototypeOf]]` / `[[SetPrototypeOf]]`
/// operations should succeed. Object.getPrototypeOf / Object.setPrototypeOf
/// carry a richer coercing dispatch and intentionally do not route through
/// these operations. Aligning the Map/Set/Promise arms is behavior-changing
/// work tracked for the enumerator-unification / internal-slot follow-ups
/// (#336 / #337).

///|
/// Module-namespace exotic object `[[SetPrototypeOf]]` shortcut (§28.3 /
/// §10.4.6.2). Relocated from `interpreter/stdlib/module_namespace_helpers.mbt`;
/// the `stdlib_` name prefix is dropped because runtime is now the defining
/// package (necessary move fixup, not a redesign). Shared by
/// Reflect.setPrototypeOf and Object.setPrototypeOf.
pub fn module_namespace_set_prototype_result(
  data : ObjectData,
  proto : Value,
) -> Bool? {
  guard data.class_name == "Module" else { return None }
  // A module namespace exotic object accepts [[SetPrototypeOf]] only when the
  // new prototype is null (§10.4.6.2); any other proto fails.
  Some(proto is Null)
}

///|
/// OrdinarySetPrototypeOf cycle decision (§10.1.2.1 steps 5-7). The walk
/// follows Value variants whose [[GetPrototypeOf]] is ordinary and stops at
/// Proxy, whose internal method is exotic. A repeated ordinary object means
/// the input chain already contains a cycle, which is rejected to guarantee
/// termination. The visited state is local; this helper has no external effect.
fn ordinary_prototype_would_cycle(
  interp : Interpreter,
  obj : Value,
  proto : Value,
) -> Bool {
  let mut cursor = proto
  let visited : Array[Value] = []
  while true {
    if strict_equal(cursor, obj) {
      return true
    }
    match cursor {
      Null | Proxy(_) => return false
      Object(_) | Array(_) | Map(_) | Set(_) | Promise(_) => ()
      _ => return false
    }
    for seen in visited {
      if strict_equal(seen, cursor) {
        return true
      }
    }
    visited.push(cursor)
    match cursor {
      Object(data) => cursor = data.prototype
      Array(data) => cursor = get_array_prototype(interp.realm_state, data)
      Map(data) =>
        cursor = data.prototype.unwrap_or_else(fn() {
          interp.realm_state.get_map_proto()
        })
      Set(data) =>
        cursor = data.prototype.unwrap_or_else(fn() {
          interp.realm_state.get_set_proto()
        })
      Promise(data) =>
        cursor = data.prototype.unwrap_or_else(fn() {
          interp.realm_state.get_promise_proto()
        })
      _ => return false
    }
  }
  false
}

///|
/// [[SetPrototypeOf]] cycle detection and extensibility guard for ordinary objects
/// (§10.1.2.1). Returns true if the prototype was set, false if it would create a
/// cycle or the target is non-extensible. Does NOT handle immutable-prototype
/// exotic objects (Object.prototype) — callers check before reaching this.
pub fn ordinary_set_prototype(
  interp : Interpreter,
  data : ObjectData,
  proto : Value,
) -> Bool {
  if strict_equal(proto, data.prototype) {
    return true
  }
  if !data.extensible {
    return false
  }
  if ordinary_prototype_would_cycle(interp, Object(data), proto) {
    return false
  }
  data.prototype = proto
  true
}

///|
pub fn object_get_prototype_of(
  interp : Interpreter,
  target : Value,
) -> Value raise Error {
  match target {
    Object(data) => data.prototype
    Array(data) => get_array_prototype(interp.realm_state, data)
    Value::Proxy(proxy_data) => proxy_get_prototype_of(interp, proxy_data)
    _ =>
      raise @errors.TypeError(
        message="Reflect.getPrototypeOf called on non-object",
      )
  }
}

///|
/// target.`[[SetPrototypeOf]]`(proto) backing for Reflect.setPrototypeOf
/// (§28.1.13). Argument-count validation stays at the stdlib boundary; this op
/// performs the proto-type validation and the `match target` dispatch verbatim,
/// including the non-object TypeError arm and the Reflect-specific "return false"
/// (rather than throw) on a non-extensible target.
pub fn object_set_prototype_of(
  interp : Interpreter,
  target : Value,
  proto : Value,
) -> Value raise Error {
  guard proto is (Object(_) | Proxy(_) | Null) else {
    raise @errors.TypeError(
      message="Object prototype may only be an Object or null",
    )
  }
  match target {
    Value::Proxy(proxy_data) =>
      Bool(proxy_set_prototype_of(interp, proxy_data, proto))
    Object(data) =>
      match module_namespace_set_prototype_result(data, proto) {
        Some(result) => Value::Bool(result)
        None => Value::Bool(ordinary_set_prototype(interp, data, proto))
      }
    Array(data) => {
      let current = get_array_prototype(interp.realm_state, data)
      if strict_equal(proto, current) {
        Value::Bool(true)
      } else if !data.extensible {
        Value::Bool(false)
      } else if ordinary_prototype_would_cycle(interp, Array(data), proto) {
        Value::Bool(false)
      } else {
        set_array_prototype_override(data, proto)
        Value::Bool(true)
      }
    }
    _ =>
      raise @errors.TypeError(
        message="Reflect.setPrototypeOf called on non-object",
      )
  }
}