#if !POLYFILL
#error "this file should never be included unless POLYFILL is set"
#endif

if (!globalThis.BigInt64Array) {
  // BigInt64Array polyfill for Safari versions between v14.0 and v15.0.
  // All browsers other than Safari added BigInt and BigInt64Array at the same
  // time, but Safari introduced BigInt in v14.0 and introduced BigInt64Array in
  // v15.0

  function partsToBigIntSigned(lower, upper) {
    return BigInt(lower) | (BigInt(upper + 2 * (upper & 0x80000000)) << 32n);
  }

  function partsToBigIntUnsigned(lower, upper) {
    return BigInt(lower) | (BigInt(upper) << 32n);
  }

  function bigIntToParts(value) {
    var lower = Number(BigInt(value) & BigInt(0xffffffff)) | 0;
    var upper = Number(BigInt(value) >> 32n) | 0;
    return [lower, upper];
  }

  function createBigIntArrayShim(partsToBigInt) {
    function createBigInt64Array(array) {
      if (typeof array === "number") {
        array = new Uint32Array(2 * array);
      }
      var orig_array;
      if (!ArrayBuffer.isView(array)) {
        if (array.constructor?.name === "ArrayBuffer") {
          array = new Uint32Array(array);
        } else {
          orig_array = array;
          array = new Uint32Array(array.length * 2);
        }
      }
      var proxy = new Proxy(
        {
          slice(min, max) {
            max ??= array.length;
            var new_buf = array.slice(min * 2, max * 2);
            return createBigInt64Array(new_buf);
          },
          subarray(min, max) {
            var new_buf = array.subarray(min * 2, max * 2);
            return createBigInt64Array(new_buf);
          },
          [Symbol.iterator]: function* () {
            for (var i = 0; i < array.length / 2; i++) {
              yield partsToBigInt(array[2 * i], array[2 * i + 1]);
            }
          },
          BYTES_PER_ELEMENT: 2 * array.BYTES_PER_ELEMENT,
          buffer: array.buffer,
          byteLength: array.byteLength,
          byteOffset: array.byteOffset,
          length: array.length / 2,
          copyWithin: function (target, start, end) {
            array.copyWithin(target * 2, start * 2, end * 2);
            return proxy;
          },
          set(source, targetOffset) {
            targetOffset ??= 0;
            if (2 * (source.length + targetOffset) > array.length) {
              // This is the Chrome error message
              // Firefox: "invalid or out-of-range index"
              throw new RangeError("offset is out of bounds");
            }
            for (var i = 0; i < source.length; i++) {
              var value = source[i];
              var pair = bigIntToParts(value);
              array.set(pair, 2 * (targetOffset + i));
            }
          },
        },
        {
          get(target, idx, receiver) {
            if (typeof idx !== "string" || !/^\d+$/.test(idx)) {
              return Reflect.get(target, idx, receiver);
            }
            var lower = array[idx * 2];
            var upper = array[idx * 2 + 1];
            return partsToBigInt(lower, upper);
          },
          set(target, idx, value, receiver) {
            if (typeof idx !== "string" || !/^\d+$/.test(idx)) {
              return Reflect.set(target, idx, value, receiver);
            }
            if (typeof value !== "bigint") {
              // Chrome error message, Firefox has no "a" in front of "BigInt".
              throw new TypeError(`Cannot convert ${value} to a BigInt`);
            }
            var pair = bigIntToParts(value);
            array.set(pair, 2 * idx);
            return true;
          },
        }
      );
      if (orig_array) {
        proxy.set(orig_array);
      }
      return proxy;
    }
    return createBigInt64Array;
  }

  globalThis.BigUint64Array = createBigIntArrayShim(partsToBigIntUnsigned);
  globalThis.BigInt64Array = createBigIntArrayShim(partsToBigIntSigned);
}
