/*---
defines: [assertSetContainsExactOrderedItems, SetLike, SetIteratorLike, LoggingProxy]
allow_unused: True
---*/
(function(global) {
  // Save the primordial values.
  const {Array, Error, Object, Proxy, Reflect, Set} = global;

  const ArrayIsArray = Array.isArray;
  const ReflectApply = Reflect.apply;
  const ReflectDefineProperty = Reflect.defineProperty;
  const ReflectGet = Reflect.get;
  const ReflectGetPrototypeOf = Reflect.getPrototypeOf;
  const SetPrototype = Set.prototype;
  const SetPrototypeHas = SetPrototype.has;
  const SetPrototypeSize = Object.getOwnPropertyDescriptor(SetPrototype, "size").get;
  const SetPrototypeKeys = SetPrototype.keys;
  const SetIteratorPrototypeNext = new Set().values().next;

  function assertSetContainsExactOrderedItems(actual, expected) {
    assertEq(ReflectGetPrototypeOf(actual), SetPrototype, "actual is a native Set object");
    assertEq(ArrayIsArray(expected), true, "expected is an Array object");

    assertEq(ReflectApply(SetPrototypeSize, actual, []), expected.length);

    let index = 0;
    let keys = ReflectApply(SetPrototypeKeys, actual, []);

    while (true) {
      let {done, value: item} = ReflectApply(SetIteratorPrototypeNext, keys, []);
      if (done) {
        break;
      }
      assertEq(item, expected[index], `Element at index ${index}:`);
      index++;
    }
  }
  global.assertSetContainsExactOrderedItems = assertSetContainsExactOrderedItems;

  class SetLike {
    #set;

    constructor(values) {
      this.#set = new Set(values);
    }

    get size() {
      return ReflectApply(SetPrototypeSize, this.#set, []);
    }

    has(value) {
      return ReflectApply(SetPrototypeHas, this.#set, [value]);
    }

    keys() {
      let keys = ReflectApply(SetPrototypeKeys, this.#set, []);
      return new SetIteratorLike(keys);
    }
  }
  global.SetLike = SetLike;

  class SetIteratorLike {
    #keys;

    constructor(keys) {
      this.#keys = keys;
    }

    next() {
      return ReflectApply(SetIteratorPrototypeNext, this.#keys, []);
    }

    // The |return| method of the iterator protocol is never called.
    return() {
      throw new Error("Unexpected call to |return| method");
    }

    // The |throw| method of the iterator protocol is never called.
    throw() {
      throw new Error("Unexpected call to |throw| method");
    }
  }

  function LoggingProxy(obj, log) {
    assertEq(ArrayIsArray(log), true);

    let handler = new Proxy({
      get(t, pk, r) {
        ReflectDefineProperty(log, log.length, {
          value: pk, writable: true, enumerable: true, configurable: true,
        });
        return ReflectGet(t, pk, r);
      }
    }, {
      get(t, pk, r) {
        ReflectDefineProperty(log, log.length, {
          value: `[[${pk}]]`, writable: true, enumerable: true, configurable: true,
        });
        return ReflectGet(t, pk, r);
      }
    });

    return {obj, proxy: new Proxy(obj, handler)};
  }
  global.LoggingProxy = LoggingProxy;
})(this);
