///|
/// Object integrity abstract operations. All observable object operations go
/// through the canonical internal-method dispatchers so Proxy traps run in the
/// order required by SetIntegrityLevel / TestIntegrityLevel.

///|
priv enum IntegrityLevel {
  Sealed
  Frozen
}

///|
/// The descriptor applied by SetIntegrityLevel for one existing property.
/// Accessors have no writable attribute, so freezing only clears configurable.
fn integrity_descriptor(
  level : IntegrityLevel,
  current : PropDescriptor?,
) -> PartialDescriptor {
  match (level, current) {
    (Frozen, Some(desc)) if !desc.is_accessor =>
      {
        ..PartialDescriptor::empty(),
        writable: Some(false),
        configurable: Some(false),
      }
    _ => { ..PartialDescriptor::empty(), configurable: Some(false) }
  }
}

///|
/// Pure TestIntegrityLevel decision for one present own descriptor.
fn descriptor_has_integrity(
  level : IntegrityLevel,
  desc : PropDescriptor,
) -> Bool {
  guard !desc.configurable else { return false }
  match level {
    Frozen => desc.is_accessor || !desc.writable
    Sealed => true
  }
}

///|
/// Canonical `[[PreventExtensions]]` dispatcher used by Object integrity
/// builtins. Proxy paths invoke the trap and preserve its Boolean completion.
fn Interpreter::prevent_extensions_internal(
  self : Interpreter,
  obj : Value,
) -> Bool raise Error {
  match obj {
    Value::Proxy(proxy_data) => proxy_prevent_extensions(self, proxy_data)
    Object(data) => {
      data.extensible = false
      true
    }
    Array(data) => {
      data.extensible = false
      true
    }
    Map(data) => {
      data.extensible = false
      true
    }
    Set(data) => {
      data.extensible = false
      true
    }
    Promise(data) => {
      data.extensible = false
      true
    }
    _ => true
  }
}

///|
/// Canonical `[[IsExtensible]]` dispatcher used by TestIntegrityLevel and
/// Object.isExtensible. Proxy paths invoke the trap.
fn Interpreter::is_extensible_internal(
  self : Interpreter,
  obj : Value,
) -> Bool raise Error {
  match obj {
    Object(data) => data.extensible
    Array(data) => data.extensible
    Map(data) => data.extensible
    Set(data) => data.extensible
    Promise(data) => data.extensible
    Value::Proxy(proxy_data) => proxy_is_extensible(self, proxy_data)
    _ => false
  }
}

///|
/// ES §7.3.15 SetIntegrityLevel.
fn Interpreter::set_integrity_level(
  self : Interpreter,
  obj : Value,
  level : IntegrityLevel,
) -> Value raise Error {
  guard is_object_value(obj) else { return obj }
  guard self.prevent_extensions_internal(obj) else {
    raise @errors.TypeError(message="Cannot prevent extensions on object")
  }
  let keys = self.own_property_keys(obj)
  for key in keys {
    let current = match level {
      Sealed => None
      Frozen =>
        match self.get_own_property(obj, key) {
          Some((desc, _)) => Some(desc)
          None => None
        }
    }
    // Frozen skips keys that disappeared after [[OwnPropertyKeys]]. Sealed
    // follows the spec and still attempts DefinePropertyOrThrow for each key.
    if level is Frozen && current is None {
      continue
    }
    let succeeded = self.define_own_property(
      obj,
      key,
      integrity_descriptor(level, current),
      @token.Loc::default(),
    )
    guard succeeded else {
      raise @errors.TypeError(message="Cannot set object integrity level")
    }
  }
  obj
}

///|
/// ES §7.3.16 TestIntegrityLevel.
fn Interpreter::test_integrity_level(
  self : Interpreter,
  obj : Value,
  level : IntegrityLevel,
) -> Value raise Error {
  guard is_object_value(obj) else { return Bool(true) }
  guard !self.is_extensible_internal(obj) else { return Bool(false) }
  let keys = self.own_property_keys(obj)
  for key in keys {
    match self.get_own_property(obj, key) {
      Some((desc, _)) if !descriptor_has_integrity(level, desc) =>
        return Bool(false)
      _ => ()
    }
  }
  Bool(true)
}

///|
/// SetIntegrityLevel(O, frozen) backing for Object.freeze (§20.1.2.6).
pub fn set_integrity_frozen(
  interp : Interpreter,
  obj : Value,
) -> Value raise Error {
  interp.set_integrity_level(obj, Frozen)
}

///|
/// SetIntegrityLevel(O, sealed) backing for Object.seal (§20.1.2.20).
pub fn set_integrity_sealed(
  interp : Interpreter,
  obj : Value,
) -> Value raise Error {
  interp.set_integrity_level(obj, Sealed)
}

///|
/// O.`[[PreventExtensions]]`() backing for Object.preventExtensions
/// (§20.1.2.17). A falsy Proxy trap result is an exceptional completion here.
pub fn object_prevent_extensions(
  interp : Interpreter,
  obj : Value,
) -> Value raise Error {
  guard is_object_value(obj) else { return obj }
  guard interp.prevent_extensions_internal(obj) else {
    raise @errors.TypeError(message="Cannot prevent extensions on object")
  }
  obj
}

///|
/// TestIntegrityLevel(O, frozen) backing for Object.isFrozen (§20.1.2.13).
pub fn test_integrity_frozen(
  interp : Interpreter,
  obj : Value,
) -> Value raise Error {
  interp.test_integrity_level(obj, Frozen)
}

///|
/// TestIntegrityLevel(O, sealed) backing for Object.isSealed (§20.1.2.15).
pub fn test_integrity_sealed(
  interp : Interpreter,
  obj : Value,
) -> Value raise Error {
  interp.test_integrity_level(obj, Sealed)
}

///|
/// O.`[[IsExtensible]]`() backing for Object.isExtensible (§20.1.2.12).
pub fn object_is_extensible(
  interp : Interpreter,
  obj : Value,
) -> Value raise Error {
  Bool(interp.is_extensible_internal(obj))
}