// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

part of "dart:_js_types";

/// Container class for constants that represent the possible types of a
/// [WasmExternRef] that can be passed to [JSArrayBufferImpl].
///
/// Constants are preferred over enums for performance.
abstract final class _ArrayBufferType {
  static const int nullOrUndefined = 0;
  static const int arrayBuffer = 1;
  static const int sharedArrayBuffer = 2;
  static const int unknown = 3;
}

/// A JS `ArrayBuffer` or `SharedArrayBuffer`.
final class JSArrayBufferImpl extends js.JSExternWrapper implements ByteBuffer {
  final bool _immutable;

  late int _refType = _getRefType(wrappedExternRef);

  static int _getRefType(WasmExternRef? ref) =>
      // Feature check for `SharedArrayBuffer` before doing a type-check.
      js.JS<WasmI32>('''o => {
          if (o === null || o === undefined) return 0;
          if (o instanceof ArrayBuffer) return 1;
          if (globalThis.SharedArrayBuffer !== undefined &&
              o instanceof SharedArrayBuffer) {
            return 2;
          }
          return 3;
        }''', ref).toIntUnsigned();

  bool get isArrayBuffer => _refType == _ArrayBufferType.arrayBuffer;

  bool get isSharedArrayBuffer =>
      _refType == _ArrayBufferType.sharedArrayBuffer;

  JSArrayBufferImpl.fromRefUnchecked(WasmExternRef? ref)
    : _immutable = false,
      super(ref) {
    assert(isArrayBuffer || isSharedArrayBuffer);
  }

  JSArrayBufferImpl.fromRefImmutableUnchecked(WasmExternRef? ref)
    : _immutable = true,
      super(ref);

  factory JSArrayBufferImpl.fromRef(WasmExternRef? ref) {
    final refType = _getRefType(ref);
    if (refType == _ArrayBufferType.unknown) {
      return _throwConversionFailureError("ByteBuffer");
    }
    return JSArrayBufferImpl.fromRefUnchecked(ref);
  }

  static JSArrayBufferImpl? fromRefNullable(WasmExternRef? ref) {
    final refType = _getRefType(ref);
    if (refType == _ArrayBufferType.nullOrUndefined) return null;
    if (refType == _ArrayBufferType.unknown)
      return _throwConversionFailureError("ByteBuffer");
    return JSArrayBufferImpl.fromRefUnchecked(ref);
  }

  /// Get a JS `DataView` of this `ArrayBuffer` or `SharedArrayBuffer`.
  WasmExternRef? _jsDataView(int offsetInBytes, int? length) =>
      _newDataViewFromArrayBufferOrSharedArrayBuffer(
        wrappedExternRef,
        offsetInBytes,
        length,
      );

  WasmExternRef? _cloneAsDataView(int offsetInBytes, int? lengthInBytes) {
    lengthInBytes ??= this.lengthInBytes;
    return js.JS<WasmExternRef?>(
      '''(o, offsetInBytes, lengthInBytes) => {
      var dst = new ArrayBuffer(lengthInBytes);
      new Uint8Array(dst).set(new Uint8Array(o, offsetInBytes, lengthInBytes));
      return new DataView(dst);
    }''',
      wrappedExternRef,
      offsetInBytes.toDouble(),
      lengthInBytes.toDouble(),
    );
  }

  @override
  @pragma("wasm:prefer-inline")
  int get lengthInBytes => _arrayBufferByteLength(wrappedExternRef);

  @override
  Uint8List asUint8List([int offsetInBytes = 0, int? length]) {
    final view = JSUint8ArrayImpl.view(this, offsetInBytes, length);
    return _immutable ? view.asUnmodifiableView() : view;
  }

  @override
  Int8List asInt8List([int offsetInBytes = 0, int? length]) {
    final view = JSInt8ArrayImpl.view(this, offsetInBytes, length);
    return _immutable ? view.asUnmodifiableView() : view;
  }

  @override
  Uint8ClampedList asUint8ClampedList([int offsetInBytes = 0, int? length]) {
    final view = JSUint8ClampedArrayImpl.view(this, offsetInBytes, length);
    return _immutable ? view.asUnmodifiableView() : view;
  }

  @override
  Uint16List asUint16List([int offsetInBytes = 0, int? length]) {
    final view = JSUint16ArrayImpl.view(this, offsetInBytes, length);
    return _immutable ? view.asUnmodifiableView() : view;
  }

  @override
  Int16List asInt16List([int offsetInBytes = 0, int? length]) {
    final view = JSInt16ArrayImpl.view(this, offsetInBytes, length);
    return _immutable ? view.asUnmodifiableView() : view;
  }

  @override
  Uint32List asUint32List([int offsetInBytes = 0, int? length]) {
    final view = JSUint32ArrayImpl.view(this, offsetInBytes, length);
    return _immutable ? view.asUnmodifiableView() : view;
  }

  @override
  Int32List asInt32List([int offsetInBytes = 0, int? length]) {
    final view = JSInt32ArrayImpl.view(this, offsetInBytes, length);
    return _immutable ? view.asUnmodifiableView() : view;
  }

  @override
  Uint64List asUint64List([int offsetInBytes = 0, int? length]) {
    final view = JSBigUint64ArrayImpl.view(this, offsetInBytes, length);
    return _immutable ? view.asUnmodifiableView() : view;
  }

  @override
  Int64List asInt64List([int offsetInBytes = 0, int? length]) {
    final view = JSBigInt64ArrayImpl.view(this, offsetInBytes, length);
    return _immutable ? view.asUnmodifiableView() : view;
  }

  @override
  Int32x4List asInt32x4List([int offsetInBytes = 0, int? length]) {
    RangeErrorUtils.checkAlignment(offsetInBytes, Int32x4List.bytesPerElement);
    length ??= (lengthInBytes - offsetInBytes) ~/ Int32x4List.bytesPerElement;
    final storage = JSInt32ArrayImpl.view(this, offsetInBytes, length * 4);
    final view = JSInt32x4ArrayImpl.externalStorage(storage);
    return _immutable ? view.asUnmodifiableView() : view;
  }

  @override
  Float32List asFloat32List([int offsetInBytes = 0, int? length]) {
    final view = JSFloat32ArrayImpl.view(this, offsetInBytes, length);
    return _immutable ? view.asUnmodifiableView() : view;
  }

  @override
  Float64List asFloat64List([int offsetInBytes = 0, int? length]) {
    final view = JSFloat64ArrayImpl.view(this, offsetInBytes, length);
    return _immutable ? view.asUnmodifiableView() : view;
  }

  @override
  Float32x4List asFloat32x4List([int offsetInBytes = 0, int? length]) {
    RangeErrorUtils.checkAlignment(
      offsetInBytes,
      Float32x4List.bytesPerElement,
    );
    length ??= (lengthInBytes - offsetInBytes) ~/ Float32x4List.bytesPerElement;
    final storage = JSFloat32ArrayImpl.view(this, offsetInBytes, length * 4);
    final view = JSFloat32x4ArrayImpl.externalStorage(storage);
    return _immutable ? view.asUnmodifiableView() : view;
  }

  @override
  Float64x2List asFloat64x2List([int offsetInBytes = 0, int? length]) {
    RangeErrorUtils.checkAlignment(
      offsetInBytes,
      Float64x2List.bytesPerElement,
    );
    length ??= (lengthInBytes - offsetInBytes) ~/ Float64x2List.bytesPerElement;
    final storage = JSFloat64ArrayImpl.view(this, offsetInBytes, length * 2);
    final view = JSFloat64x2ArrayImpl.externalStorage(storage);
    return _immutable ? view.asUnmodifiableView() : view;
  }

  @override
  ByteData asByteData([int offsetInBytes = 0, int? length]) {
    final view = JSDataViewImpl.view(this, offsetInBytes, length);
    return _immutable ? view.asUnmodifiableView() : view;
  }

  @override
  bool operator ==(Object that) =>
      that is JSArrayBufferImpl &&
      js.areEqualInJS(wrappedExternRef, that.wrappedExternRef);
}

/// Base class for all JS typed array classes.
abstract class JSArrayBase extends js.JSExternWrapper implements TypedData {
  /// List length
  final int length;

  JSArrayBase(WasmExternRef? ref, int elementSizeShift)
    : length = _dataViewByteLength(ref) >>> elementSizeShift,
      super(ref);

  WasmExternRef? get _toExternRef => wrappedExternRef;

  WasmExternRef? toJSArrayExternRef([int start = 0, int? length]);

  @override
  JSArrayBufferImpl get buffer =>
      JSArrayBufferImpl.fromRefUnchecked(_dataViewBuffer(wrappedExternRef));

  @override
  @pragma("wasm:prefer-inline")
  int get lengthInBytes => _dataViewByteLength(wrappedExternRef);

  @override
  @pragma("wasm:prefer-inline")
  int get offsetInBytes => _dataViewByteOffset(wrappedExternRef);

  @override
  bool operator ==(Object that) =>
      that is JSArrayBase &&
      js.areEqualInJS(wrappedExternRef, that.wrappedExternRef);

  void clear() {
    throw UnsupportedError("Cannot remove from a fixed-length list");
  }

  bool remove(Object? element) {
    throw UnsupportedError("Cannot remove from a fixed-length list");
  }

  void removeRange(int start, int end) {
    throw UnsupportedError("Cannot remove from a fixed-length list");
  }

  void replaceRange(int start, int end, Iterable iterable) {
    throw UnsupportedError("Cannot remove from a fixed-length list");
  }

  set length(int newLength) {
    throw UnsupportedError("Cannot resize a fixed-length list");
  }

  void add(dynamic value) {
    throw UnsupportedError("Cannot add to a fixed-length list");
  }

  void addAll(Iterable value) {
    throw UnsupportedError("Cannot add to a fixed-length list");
  }

  void insert(int index, dynamic value) {
    throw UnsupportedError("Cannot insert into a fixed-length list");
  }

  void insertAll(int index, Iterable values) {
    throw UnsupportedError("Cannot insert into a fixed-length list");
  }
}

/// Base class for all JS typed integer-based array classes.
abstract class JSIntegerArrayBase extends JSArrayBase {
  /// The original typed array this wrapped, if any.
  final WasmExternRef? _arrayRef;

  JSIntegerArrayBase(super._ref, super.elementSizeShift, this._arrayRef);
}

/// Base class for all JS typed float-based array classes.
abstract class JSFloatArrayBase extends JSArrayBase {
  /// The original typed array this wrapped, if any.
  final WasmExternRef? _arrayRef;

  JSFloatArrayBase(super._ref, super.elementSizeShift, this._arrayRef);
}

/// A JS `DataView`.
final class JSDataViewImpl extends js.JSExternWrapper implements ByteData {
  final int lengthInBytes;

  final bool _immutable;

  static int _getRefType(WasmExternRef? ref) => js.JS<WasmI32>('''o => {
          if (o === null || o === undefined) return 0;
          if (o instanceof DataView) return 1;
          return 2;
        }''', ref).toIntUnsigned();

  JSDataViewImpl._(WasmExternRef? ref, this.lengthInBytes, this._immutable)
    : super(ref) {
    assert(_getRefType(ref) == 1);
  }

  JSDataViewImpl(this.lengthInBytes)
    : _immutable = false,
      super(_newDataView(lengthInBytes));

  JSDataViewImpl.fromRefUnchecked(WasmExternRef? ref)
    : lengthInBytes = _dataViewByteLength(ref),
      _immutable = false,
      super(ref);

  factory JSDataViewImpl.fromRef(WasmExternRef? ref) {
    final refType = _getRefType(ref);
    if (refType != 1) return _throwConversionFailureError("ByteData");
    return JSDataViewImpl.fromRefUnchecked(ref);
  }

  static JSDataViewImpl? fromRefNullable(WasmExternRef? ref) {
    final refType = _getRefType(ref);
    if (refType == 0) return null;
    if (refType == 2) return JSDataViewImpl.fromRefUnchecked(ref);
    return _throwConversionFailureError("ByteData");
  }

  JSDataViewImpl.immutable(WasmExternRef? ref, this.lengthInBytes)
    : _immutable = true,
      super(ref);

  factory JSDataViewImpl.view(
    JSArrayBufferImpl buffer,
    int offsetInBytes,
    int? length,
  ) => JSDataViewImpl.fromRefUnchecked(
    _newDataViewFromArrayBufferOrSharedArrayBuffer(
      buffer.wrappedExternRef,
      offsetInBytes,
      length,
    ),
  );

  @override
  JSArrayBufferImpl get buffer => _immutable
      ? JSArrayBufferImpl.fromRefImmutableUnchecked(
          _dataViewBuffer(wrappedExternRef),
        )
      : JSArrayBufferImpl.fromRefUnchecked(_dataViewBuffer(wrappedExternRef));

  @override
  @pragma("wasm:prefer-inline")
  int get offsetInBytes => _dataViewByteOffset(wrappedExternRef);

  @override
  @pragma("wasm:prefer-inline")
  int get elementSizeInBytes => 1;

  @override
  ByteData asUnmodifiableView() =>
      JSDataViewImpl.immutable(wrappedExternRef, lengthInBytes);

  @override
  double getFloat32(int byteOffset, [Endian endian = Endian.big]) =>
      _getFloat32(wrappedExternRef, byteOffset, Endian.little == endian);

  @override
  double getFloat64(int byteOffset, [Endian endian = Endian.big]) =>
      _getFloat64(wrappedExternRef, byteOffset, Endian.little == endian);

  @override
  int getInt16(int byteOffset, [Endian endian = Endian.big]) =>
      _getInt16(wrappedExternRef, byteOffset, Endian.little == endian);

  @override
  int getInt32(int byteOffset, [Endian endian = Endian.big]) =>
      _getInt32(wrappedExternRef, byteOffset, Endian.little == endian);

  @override
  int getInt64(int byteOffset, [Endian endian = Endian.big]) =>
      _getBigInt64(wrappedExternRef, byteOffset, Endian.little == endian);

  @override
  int getInt8(int byteOffset) => _getInt8(wrappedExternRef, byteOffset);

  @override
  int getUint16(int byteOffset, [Endian endian = Endian.big]) =>
      _getUint16(wrappedExternRef, byteOffset, Endian.little == endian);

  @override
  int getUint32(int byteOffset, [Endian endian = Endian.big]) =>
      _getUint32(wrappedExternRef, byteOffset, Endian.little == endian);

  @override
  int getUint64(int byteOffset, [Endian endian = Endian.big]) =>
      _getBigUint64(wrappedExternRef, byteOffset, Endian.little == endian);

  @override
  int getUint8(int byteOffset) => _getUint8(wrappedExternRef, byteOffset);

  @override
  void setFloat32(int byteOffset, num value, [Endian endian = Endian.big]) {
    if (_immutable) {
      throw UnsupportedError("Cannot modify an unmodifiable byte data");
    }
    _setFloat32(wrappedExternRef, byteOffset, value, Endian.little == endian);
  }

  @override
  void setFloat64(int byteOffset, num value, [Endian endian = Endian.big]) {
    if (_immutable) {
      throw UnsupportedError("Cannot modify an unmodifiable byte data");
    }
    _setFloat64(wrappedExternRef, byteOffset, value, Endian.little == endian);
  }

  @override
  void setInt16(int byteOffset, int value, [Endian endian = Endian.big]) {
    if (_immutable) {
      throw UnsupportedError("Cannot modify an unmodifiable byte data");
    }
    _setInt16(wrappedExternRef, byteOffset, value, Endian.little == endian);
  }

  @override
  void setInt32(int byteOffset, int value, [Endian endian = Endian.big]) {
    if (_immutable) {
      throw UnsupportedError("Cannot modify an unmodifiable byte data");
    }
    _setInt32(wrappedExternRef, byteOffset, value, Endian.little == endian);
  }

  @override
  void setInt64(int byteOffset, int value, [Endian endian = Endian.big]) {
    if (_immutable) {
      throw UnsupportedError("Cannot modify an unmodifiable byte data");
    }
    _setBigInt64(wrappedExternRef, byteOffset, value, Endian.little == endian);
  }

  @override
  void setInt8(int byteOffset, int value) {
    if (_immutable) {
      throw UnsupportedError("Cannot modify an unmodifiable byte data");
    }
    _setInt8(wrappedExternRef, byteOffset, value);
  }

  @override
  void setUint16(int byteOffset, int value, [Endian endian = Endian.big]) {
    if (_immutable) {
      throw UnsupportedError("Cannot modify an unmodifiable byte data");
    }
    _setUint16(wrappedExternRef, byteOffset, value, Endian.little == endian);
  }

  @override
  void setUint32(int byteOffset, int value, [Endian endian = Endian.big]) {
    if (_immutable) {
      throw UnsupportedError("Cannot modify an unmodifiable byte data");
    }
    _setUint32(wrappedExternRef, byteOffset, value, Endian.little == endian);
  }

  @override
  void setUint64(int byteOffset, int value, [Endian endian = Endian.big]) {
    if (_immutable) {
      throw UnsupportedError("Cannot modify an unmodifiable byte data");
    }
    _setBigUint64(wrappedExternRef, byteOffset, value, Endian.little == endian);
  }

  @override
  void setUint8(int byteOffset, int value) {
    if (_immutable) {
      throw UnsupportedError("Cannot modify an unmodifiable byte data");
    }
    _setUint8(wrappedExternRef, byteOffset, value);
  }
}

abstract class _IntArrayIteratorBase extends js.JSExternWrapper
    implements Iterator<int> {
  final int _length;
  int _position = -1;
  int _current = 0;

  _IntArrayIteratorBase(WasmExternRef? ref, this._length) : super(ref);

  @pragma("wasm:prefer-inline")
  int get current => _current;
}

mixin _IntListMixin implements List<int> {
  WasmExternRef? toJSArrayExternRef([int start = 0, int? length]);

  void _setUnchecked(int index, int value);

  int _getUnchecked(int index);

  Iterable<T> whereType<T>() => WhereTypeIterable<T>(this);

  Iterable<int> followedBy(Iterable<int> other) =>
      FollowedByIterable<int>.firstEfficient(this, other);

  List<R> cast<R>() => List.castFrom<int, R>(this);

  void set first(int value) {
    this[0] = value;
  }

  void set last(int value) {
    this[length - 1] = value;
  }

  int indexWhere(bool test(int element), [int start = 0]) {
    final length = this.length;
    if (start < 0) start = 0;
    for (int i = start; i < length; i++) {
      if (test(_getUnchecked(i))) return i;
    }
    return -1;
  }

  int lastIndexWhere(bool test(int element), [int? start]) {
    final length = this.length;
    int startIndex = (start == null || start >= length) ? length - 1 : start;
    for (int i = startIndex; i >= 0; i--) {
      if (test(_getUnchecked(i))) return i;
    }
    return -1;
  }

  List<int> operator +(List<int> other) => [...this, ...other];

  bool contains(Object? element) {
    final length = this.length;
    for (var i = 0; i < length; ++i) {
      if (_getUnchecked(i) == element) return true;
    }
    return false;
  }

  void shuffle([Random? random]) {
    random ??= Random();
    var i = length;
    while (i > 1) {
      int pos = random.nextInt(i);
      i -= 1;
      var tmp = _getUnchecked(i);
      _setUnchecked(i, _getUnchecked(pos));
      _setUnchecked(pos, tmp);
    }
  }

  Iterable<int> where(bool f(int element)) => WhereIterable<int>(this, f);

  Iterable<int> take(int n) => SubListIterable<int>(this, 0, n);

  Iterable<int> takeWhile(bool test(int element)) =>
      TakeWhileIterable<int>(this, test);

  Iterable<int> skip(int n) => SubListIterable<int>(this, n, null);

  Iterable<int> skipWhile(bool test(int element)) =>
      SkipWhileIterable<int>(this, test);

  Iterable<int> get reversed => ReversedListIterable<int>(this);

  Map<int, int> asMap() => ListMapView<int>(this);

  Iterable<int> getRange(int start, [int? end]) {
    end = RangeErrorUtils.checkValidRange(start, end, length);
    return SubListIterable<int>(this, start, end);
  }

  List<int> toList({bool growable = true}) {
    return List<int>.of(this, growable: growable);
  }

  Set<int> toSet() {
    return Set<int>.of(this);
  }

  void forEach(void f(int element)) {
    final length = this.length;
    for (var i = 0; i < length; i++) {
      f(_getUnchecked(i));
    }
  }

  int reduce(int combine(int value, int element)) {
    final length = this.length;
    if (length == 0) throw IterableElementError.noElement();
    var value = _getUnchecked(0);
    for (var i = 1; i < length; ++i) {
      value = combine(value, _getUnchecked(i));
    }
    return value;
  }

  T fold<T>(T initialValue, T combine(T initialValue, int element)) {
    final length = this.length;
    for (var i = 0; i < length; ++i) {
      initialValue = combine(initialValue, _getUnchecked(i));
    }
    return initialValue;
  }

  Iterable<T> map<T>(T f(int element)) => MappedIterable<int, T>(this, f);

  Iterable<T> expand<T>(Iterable<T> f(int element)) =>
      ExpandIterable<int, T>(this, f);

  bool every(bool f(int element)) {
    final length = this.length;
    for (var i = 0; i < length; ++i) {
      if (!f(_getUnchecked(i))) return false;
    }
    return true;
  }

  bool any(bool f(int element)) {
    final length = this.length;
    for (var i = 0; i < length; ++i) {
      if (f(_getUnchecked(i))) return true;
    }
    return false;
  }

  int firstWhere(bool test(int element), {int orElse()?}) {
    final length = this.length;
    for (var i = 0; i < length; ++i) {
      final element = _getUnchecked(i);
      if (test(element)) return element;
    }
    if (orElse != null) return orElse();
    throw IterableElementError.noElement();
  }

  int lastWhere(bool test(int element), {int orElse()?}) {
    final length = this.length;
    for (var i = length - 1; i >= 0; --i) {
      final element = _getUnchecked(i);
      if (test(element)) {
        return element;
      }
    }
    if (orElse != null) return orElse();
    throw IterableElementError.noElement();
  }

  int singleWhere(bool test(int element), {int orElse()?}) {
    var result = null;
    bool foundMatching = false;
    final length = this.length;
    for (var i = 0; i < length; ++i) {
      final element = _getUnchecked(i);
      if (test(element)) {
        if (foundMatching) {
          throw IterableElementError.tooMany();
        }
        result = element;
        foundMatching = true;
      }
    }
    if (foundMatching) return result;
    if (orElse != null) return orElse();
    throw IterableElementError.noElement();
  }

  int elementAt(int index) {
    return this[index];
  }

  void sort([int compare(int a, int b)?]) {
    Sort.sort(this, compare ?? Comparable.compare);
  }

  int indexOf(int element, [int start = 0]) {
    final length = this.length;
    if (start >= length) {
      return -1;
    } else if (start < 0) {
      start = 0;
    }
    for (int i = start; i < length; i++) {
      if (_getUnchecked(i) == element) return i;
    }
    return -1;
  }

  int lastIndexOf(int element, [int? start]) {
    final length = this.length;
    int startIndex = (start == null || start >= length) ? length - 1 : start;
    for (int i = startIndex; i >= 0; i--) {
      if (_getUnchecked(i) == element) return i;
    }
    return -1;
  }

  int removeLast() {
    throw UnsupportedError("Cannot remove from a fixed-length list");
  }

  int removeAt(int index) {
    throw UnsupportedError("Cannot remove from a fixed-length list");
  }

  void removeWhere(bool test(int element)) {
    throw UnsupportedError("Cannot remove from a fixed-length list");
  }

  void retainWhere(bool test(int element)) {
    throw UnsupportedError("Cannot remove from a fixed-length list");
  }

  int get first {
    if (length > 0) return _getUnchecked(0);
    throw IterableElementError.noElement();
  }

  int get last {
    final length = this.length;
    if (length > 0) return _getUnchecked(length - 1);
    throw IterableElementError.noElement();
  }

  int get single {
    final length = this.length;
    if (length == 1) return _getUnchecked(0);
    if (length == 0) throw IterableElementError.noElement();
    throw IterableElementError.tooMany();
  }

  void fillRange(int start, int end, [int? fillValue]) {
    RangeErrorUtils.checkValidRange(start, end, this.length);
    if (start == end) return;
    if (fillValue == null) {
      throw ArgumentError.notNull("fillValue");
    }
    for (var i = start; i < end; ++i) {
      _setUnchecked(i, fillValue);
    }
  }

  void setAll(int index, Iterable<int> iterable) {
    final end = iterable.length + index;
    setRange(index, end, iterable);
  }

  void setRange(
    int start,
    int end,
    Iterable<int> iterable, [
    int skipCount = 0,
  ]) {
    RangeErrorUtils.checkValidRange(start, end, this.length);

    if (skipCount < 0) {
      throw ArgumentError(skipCount);
    }

    if (iterable is JSArrayBase) {
      final JSArrayBase source = unsafeCast<JSArrayBase>(iterable);

      // JS `TypedArray.prototype.set` does not allow mixing `BigInt` and other
      // types. Check that either both of the arrays are `BigInt`s (signed or
      // unsigned), or none of them are.
      final sourceBigInt = source.elementSizeInBytes == 8;
      final targetBigInt = elementSizeInBytes == 8;
      if (!(sourceBigInt ^ targetBigInt)) {
        final length = end - start;
        final sourceArray = source.toJSArrayExternRef(skipCount, length);
        final targetArray = toJSArrayExternRef(start, length);
        return _setRangeFast(targetArray, sourceArray);
      }
    }

    List<int> otherList = iterable.skip(skipCount).toList(growable: false);

    final count = end - start;
    if (otherList.length < count) {
      throw IterableElementError.tooFew();
    }

    // TODO(omersa): Use unchecked read.
    for (int i = 0, j = start; i < count; i++, j++) {
      _setUnchecked(j, otherList[i]);
    }
  }

  int get length;

  int get elementSizeInBytes;

  int get lengthInBytes;

  @override
  bool get isEmpty => length == 0;

  @override
  bool get isNotEmpty => !isEmpty;

  @override
  String join([String separator = ""]) =>
      (StringBuffer()..writeAll(this, separator)).toString();

  @override
  String toString() => ListBase.listToString(this);
}

// TODO(omersa): This mixin should override other update methods (probably just
// setRange) that don't use `[]=` to modify the list.
mixin _UnmodifiableIntListMixin {
  WasmExternRef? get _toExternRef;

  JSArrayBufferImpl get buffer => JSArrayBufferImpl.fromRefImmutableUnchecked(
    _dataViewBuffer(_toExternRef),
  );

  void operator []=(int index, int value) {
    throw UnsupportedError("Cannot modify an unmodifiable list");
  }

  void setRange(
    int start,
    int end,
    Iterable<int> iterable, [
    int skipCount = 0,
  ]) {
    throw UnsupportedError("Cannot modify an unmodifiable list");
  }
}

final class JSUint8ArrayImpl extends JSIntegerArrayBase
    with _IntListMixin
    implements Uint8List {
  JSUint8ArrayImpl._(WasmExternRef? _dataViewRef, [WasmExternRef? _arrayRef])
    : super(_dataViewRef, 0, _arrayRef);

  factory JSUint8ArrayImpl(int length) =>
      JSUint8ArrayImpl._(_newDataView(length));

  static int _getRefType(WasmExternRef? ref) => js.JS<WasmI32>('''o => {
          if (o === null || o === undefined) return 0;
          if (o instanceof Uint8Array) return 1;
          return 2;
        }''', ref).toIntUnsigned();

  factory JSUint8ArrayImpl.fromRefUnchecked(WasmExternRef? ref) {
    assert(_getRefType(ref) == 1);
    return JSUint8ArrayImpl._(_dataViewFromJSArray(ref), ref);
  }

  factory JSUint8ArrayImpl.fromRef(WasmExternRef? ref) {
    if (_getRefType(ref) != 1) {
      return _throwConversionFailureError("Uint8List");
    }
    return JSUint8ArrayImpl.fromRefUnchecked(ref);
  }

  static JSUint8ArrayImpl? fromRefNullable(WasmExternRef? ref) {
    final refType = _getRefType(ref);
    if (refType == 0) return null;
    if (refType == 1) return JSUint8ArrayImpl.fromRefUnchecked(ref);
    return _throwConversionFailureError("Uint8List");
  }

  factory JSUint8ArrayImpl.view(
    JSArrayBufferImpl buffer,
    int offsetInBytes,
    int? length,
  ) => JSUint8ArrayImpl._(buffer._jsDataView(offsetInBytes, length));

  @override
  @pragma("wasm:prefer-inline")
  int get elementSizeInBytes => 1;

  @override
  WasmExternRef? toJSArrayExternRef([int start = 0, int? length]) {
    length = length ?? (this.length - start);
    if (!_arrayRef.isNull && start == 0 && length == this.length) {
      return _arrayRef;
    }
    return js.JS<WasmExternRef?>(
      '(o, start, length) => new Uint8Array(o.buffer, o.byteOffset + start, length)',
      wrappedExternRef,
      WasmI32.fromInt(start),
      WasmI32.fromInt(length),
    );
  }

  @pragma("wasm:prefer-inline")
  int _getUnchecked(int index) => _getUint8(wrappedExternRef, index);

  @pragma("wasm:prefer-inline")
  void _setUnchecked(int index, int value) =>
      _setUint8(wrappedExternRef, index, value);

  @override
  @pragma("wasm:prefer-inline")
  int operator [](int index) {
    IndexErrorUtils.checkIndexBCE(index, length);
    return _getUint8(wrappedExternRef, index);
  }

  @override
  @pragma("wasm:prefer-inline")
  void operator []=(int index, int value) {
    IndexErrorUtils.checkIndexBCE(index, length);
    _setUint8(wrappedExternRef, index, value);
  }

  @override
  UnmodifiableJSUint8Array asUnmodifiableView() =>
      UnmodifiableJSUint8Array._(wrappedExternRef);

  @override
  JSUint8ArrayImpl sublist(int start, [int? end]) {
    final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
    final newOffsetInBytes = offsetInBytes + start;
    final newLengthInBytes = newEnd - start;
    return JSUint8ArrayImpl._(
      buffer._cloneAsDataView(newOffsetInBytes, newLengthInBytes),
    );
  }

  @override
  _JSUint8ArrayIterator get iterator =>
      _JSUint8ArrayIterator(wrappedExternRef, length);
}

final class _JSUint8ArrayIterator extends _IntArrayIteratorBase {
  _JSUint8ArrayIterator(WasmExternRef? ref, int length) : super(ref, length);

  @pragma("wasm:prefer-inline")
  bool moveNext() {
    _position += 1;
    if (_position < _length) {
      _current = _getUint8(wrappedExternRef, _position);
      return true;
    }
    return false;
  }
}

final class UnmodifiableJSUint8Array extends JSUint8ArrayImpl
    with _UnmodifiableIntListMixin {
  UnmodifiableJSUint8Array._(WasmExternRef? ref) : super._(ref);
}

final class JSInt8ArrayImpl extends JSIntegerArrayBase
    with _IntListMixin
    implements Int8List {
  JSInt8ArrayImpl._(WasmExternRef? _dataViewRef, [WasmExternRef? _arrayRef])
    : super(_dataViewRef, 0, _arrayRef);

  factory JSInt8ArrayImpl(int length) =>
      JSInt8ArrayImpl._(_newDataView(length));

  static int _getRefType(WasmExternRef? ref) => js.JS<WasmI32>('''o => {
          if (o === null || o === undefined) return 0;
          if (o instanceof Int8Array) return 1;
          return 2;
        }''', ref).toIntUnsigned();

  factory JSInt8ArrayImpl.fromRefUnchecked(WasmExternRef? ref) {
    assert(_getRefType(ref) == 1);
    return JSInt8ArrayImpl._(_dataViewFromJSArray(ref), ref);
  }

  factory JSInt8ArrayImpl.fromRef(WasmExternRef? ref) {
    if (_getRefType(ref) != 1) {
      return _throwConversionFailureError("Int8List");
    }
    return JSInt8ArrayImpl.fromRefUnchecked(ref);
  }

  static JSInt8ArrayImpl? fromRefNullable(WasmExternRef? ref) {
    final refType = _getRefType(ref);
    if (refType == 0) return null;
    if (refType == 1) return JSInt8ArrayImpl.fromRefUnchecked(ref);
    return _throwConversionFailureError("Int8List");
  }

  factory JSInt8ArrayImpl.view(
    JSArrayBufferImpl buffer,
    int offsetInBytes,
    int? length,
  ) => JSInt8ArrayImpl._(buffer._jsDataView(offsetInBytes, length));

  @override
  @pragma("wasm:prefer-inline")
  int get elementSizeInBytes => 1;

  @override
  WasmExternRef? toJSArrayExternRef([int start = 0, int? length]) {
    length = length ?? (this.length - start);
    if (!_arrayRef.isNull && start == 0 && length == this.length) {
      return _arrayRef;
    }
    return js.JS<WasmExternRef?>(
      '(o, start, length) => new Int8Array(o.buffer, o.byteOffset + start, length)',
      wrappedExternRef,
      WasmI32.fromInt(start),
      WasmI32.fromInt(length),
    );
  }

  @pragma("wasm:prefer-inline")
  int _getUnchecked(int index) => _getInt8(wrappedExternRef, index);

  @pragma("wasm:prefer-inline")
  void _setUnchecked(int index, int value) =>
      _setInt8(wrappedExternRef, index, value);

  @override
  @pragma("wasm:prefer-inline")
  int operator [](int index) {
    IndexErrorUtils.checkIndexBCE(index, length);
    return _getInt8(wrappedExternRef, index);
  }

  @override
  @pragma("wasm:prefer-inline")
  void operator []=(int index, int value) {
    IndexErrorUtils.checkIndexBCE(index, length);
    _setInt8(wrappedExternRef, index, value);
  }

  @override
  UnmodifiableJSInt8Array asUnmodifiableView() =>
      UnmodifiableJSInt8Array._(wrappedExternRef);

  @override
  JSInt8ArrayImpl sublist(int start, [int? end]) {
    final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
    final newOffsetInBytes = offsetInBytes + start;
    final newLengthInBytes = newEnd - start;
    return JSInt8ArrayImpl._(
      buffer._cloneAsDataView(newOffsetInBytes, newLengthInBytes),
    );
  }

  @override
  _JSInt8ArrayIterator get iterator => _JSInt8ArrayIterator(this);
}

final class _JSInt8ArrayIterator extends _IntArrayIteratorBase {
  _JSInt8ArrayIterator(JSInt8ArrayImpl array)
    : super(array.wrappedExternRef, array.length);

  @pragma("wasm:prefer-inline")
  bool moveNext() {
    _position += 1;
    if (_position < _length) {
      _current = _getInt8(wrappedExternRef, _position);
      return true;
    }
    return false;
  }
}

final class UnmodifiableJSInt8Array extends JSInt8ArrayImpl
    with _UnmodifiableIntListMixin {
  UnmodifiableJSInt8Array._(WasmExternRef? ref) : super._(ref);
}

final class JSUint8ClampedArrayImpl extends JSIntegerArrayBase
    with _IntListMixin
    implements Uint8ClampedList {
  JSUint8ClampedArrayImpl._(
    WasmExternRef? _dataViewRef, [
    WasmExternRef? _arrayRef,
  ]) : super(_dataViewRef, 0, _arrayRef);

  factory JSUint8ClampedArrayImpl(int length) =>
      JSUint8ClampedArrayImpl._(_newDataView(length));

  static bool _checkArrayRefType(WasmExternRef? ref) =>
      js.JS<bool>('o => o instanceof Uint8ClampedArray', ref);

  factory JSUint8ClampedArrayImpl.fromRefUnchecked(WasmExternRef? ref) {
    assert(_checkArrayRefType(ref));
    return JSUint8ClampedArrayImpl._(_dataViewFromJSArray(ref), ref);
  }

  factory JSUint8ClampedArrayImpl.fromRef(WasmExternRef? ref) {
    if (!_checkArrayRefType(ref)) {
      return _throwConversionFailureError("Uint8ClampedList");
    }
    return JSUint8ClampedArrayImpl.fromRefUnchecked(ref);
  }

  static JSUint8ClampedArrayImpl? fromRefNullable(WasmExternRef? ref) {
    if (!_checkArrayRefType(ref)) {
      if (ref.isNull) {
        return null;
      }
      return _throwConversionFailureError("Uint8ClampedList");
    }
    return JSUint8ClampedArrayImpl.fromRefUnchecked(ref);
  }

  factory JSUint8ClampedArrayImpl.view(
    JSArrayBufferImpl buffer,
    int offsetInBytes,
    int? length,
  ) => JSUint8ClampedArrayImpl._(buffer._jsDataView(offsetInBytes, length));

  @override
  @pragma("wasm:prefer-inline")
  int get elementSizeInBytes => 1;

  @override
  WasmExternRef? toJSArrayExternRef([int start = 0, int? length]) {
    length = length ?? (this.length - start);
    if (!_arrayRef.isNull && start == 0 && length == this.length) {
      return _arrayRef;
    }
    return js.JS<WasmExternRef?>(
      '(o, start, length) => new Uint8ClampedArray(o.buffer, o.byteOffset + start, length)',
      wrappedExternRef,
      WasmI32.fromInt(start),
      WasmI32.fromInt(length),
    );
  }

  @pragma("wasm:prefer-inline")
  int _getUnchecked(int index) => _getUint8(wrappedExternRef, index);

  @pragma("wasm:prefer-inline")
  void _setUnchecked(int index, int value) =>
      _setUint8(wrappedExternRef, index, value.clamp(0, 255));

  @override
  @pragma("wasm:prefer-inline")
  int operator [](int index) {
    IndexErrorUtils.checkIndexBCE(index, length);
    return _getUint8(wrappedExternRef, index);
  }

  @override
  @pragma("wasm:prefer-inline")
  void operator []=(int index, int value) {
    IndexErrorUtils.checkIndexBCE(index, length);
    _setUint8(wrappedExternRef, index, value.clamp(0, 255));
  }

  @override
  UnmodifiableJSUint8ClampedArray asUnmodifiableView() =>
      UnmodifiableJSUint8ClampedArray._(wrappedExternRef);

  @override
  JSUint8ClampedArrayImpl sublist(int start, [int? end]) {
    final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
    final newOffsetInBytes = offsetInBytes + start;
    final newLengthInBytes = newEnd - start;
    return JSUint8ClampedArrayImpl._(
      buffer._cloneAsDataView(newOffsetInBytes, newLengthInBytes),
    );
  }

  @override
  _JSUint8ArrayIterator get iterator =>
      _JSUint8ArrayIterator(wrappedExternRef, length);
}

final class UnmodifiableJSUint8ClampedArray extends JSUint8ClampedArrayImpl
    with _UnmodifiableIntListMixin {
  UnmodifiableJSUint8ClampedArray._(WasmExternRef? ref) : super._(ref);
}

final class JSUint16ArrayImpl extends JSIntegerArrayBase
    with _IntListMixin
    implements Uint16List {
  JSUint16ArrayImpl._(WasmExternRef? _dataViewRef, [WasmExternRef? _arrayRef])
    : super(_dataViewRef, 1, _arrayRef);

  factory JSUint16ArrayImpl(int length) =>
      JSUint16ArrayImpl._(_newDataView(length * 2));

  static bool _checkArrayRefType(WasmExternRef? ref) =>
      js.JS<bool>('o => o instanceof Uint16Array', ref);

  factory JSUint16ArrayImpl.fromRefUnchecked(WasmExternRef? ref) {
    assert(_checkArrayRefType(ref));
    return JSUint16ArrayImpl._(_dataViewFromJSArray(ref), ref);
  }

  factory JSUint16ArrayImpl.fromRef(WasmExternRef? ref) {
    if (!_checkArrayRefType(ref)) {
      return _throwConversionFailureError("Uint16List");
    }
    return JSUint16ArrayImpl.fromRefUnchecked(ref);
  }

  static JSUint16ArrayImpl? fromRefNullable(WasmExternRef? ref) {
    if (!_checkArrayRefType(ref)) {
      if (ref.isNull) {
        return null;
      }
      return _throwConversionFailureError("Uint16List");
    }
    return JSUint16ArrayImpl.fromRefUnchecked(ref);
  }

  factory JSUint16ArrayImpl.view(
    JSArrayBufferImpl buffer,
    int offsetInBytes,
    int? length,
  ) {
    RangeErrorUtils.checkAlignment(offsetInBytes, Uint16List.bytesPerElement);
    final lengthInBytes = (length == null
        ? ((buffer.lengthInBytes - offsetInBytes) & -2)
        : length * 2);
    return JSUint16ArrayImpl._(
      buffer._jsDataView(offsetInBytes, lengthInBytes),
    );
  }

  @override
  @pragma("wasm:prefer-inline")
  int get elementSizeInBytes => 2;

  @override
  WasmExternRef? toJSArrayExternRef([int start = 0, int? length]) {
    length = length ?? (this.length - start);
    if (!_arrayRef.isNull && start == 0 && length == this.length) {
      return _arrayRef;
    }
    return js.JS<WasmExternRef?>(
      '(o, start, length) => new Uint16Array(o.buffer, o.byteOffset + start, length)',
      wrappedExternRef,
      WasmI32.fromInt(start * 2),
      WasmI32.fromInt(length),
    );
  }

  @pragma("wasm:prefer-inline")
  int _getUnchecked(int index) => _getUint16(wrappedExternRef, index * 2, true);

  @pragma("wasm:prefer-inline")
  void _setUnchecked(int index, int value) =>
      _setUint16(wrappedExternRef, index * 2, value, true);

  @override
  @pragma("wasm:prefer-inline")
  int operator [](int index) {
    IndexErrorUtils.checkIndexBCE(index, length);
    return _getUint16(wrappedExternRef, index * 2, true);
  }

  @override
  @pragma("wasm:prefer-inline")
  void operator []=(int index, int value) {
    IndexErrorUtils.checkIndexBCE(index, length);
    _setUint16(wrappedExternRef, index * 2, value, true);
  }

  @override
  UnmodifiableJSUint16Array asUnmodifiableView() =>
      UnmodifiableJSUint16Array._(wrappedExternRef);

  @override
  JSUint16ArrayImpl sublist(int start, [int? end]) {
    final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
    final newOffsetInBytes = offsetInBytes + 2 * start;
    final newLengthInBytes = 2 * (newEnd - start);
    return JSUint16ArrayImpl._(
      buffer._cloneAsDataView(newOffsetInBytes, newLengthInBytes),
    );
  }

  @override
  _JSUint16ArrayIterator get iterator => _JSUint16ArrayIterator(this);
}

final class _JSUint16ArrayIterator extends _IntArrayIteratorBase {
  _JSUint16ArrayIterator(JSUint16ArrayImpl array)
    : super(array.wrappedExternRef, array.length);

  @pragma("wasm:prefer-inline")
  bool moveNext() {
    _position += 1;
    if (_position < _length) {
      _current = _getUint16(wrappedExternRef, _position * 2, true);
      return true;
    }
    return false;
  }
}

final class UnmodifiableJSUint16Array extends JSUint16ArrayImpl
    with _UnmodifiableIntListMixin {
  UnmodifiableJSUint16Array._(WasmExternRef? ref) : super._(ref);
}

final class JSInt16ArrayImpl extends JSIntegerArrayBase
    with _IntListMixin
    implements Int16List {
  JSInt16ArrayImpl._(WasmExternRef? _dataViewRef, [WasmExternRef? _arrayRef])
    : super(_dataViewRef, 1, _arrayRef);

  factory JSInt16ArrayImpl(int length) =>
      JSInt16ArrayImpl._(_newDataView(length * 2));

  static bool _checkArrayRefType(WasmExternRef? ref) =>
      js.JS<bool>('o => o instanceof Int16Array', ref);

  factory JSInt16ArrayImpl.fromRefUnchecked(WasmExternRef? ref) {
    assert(_checkArrayRefType(ref));
    return JSInt16ArrayImpl._(_dataViewFromJSArray(ref), ref);
  }

  factory JSInt16ArrayImpl.fromRef(WasmExternRef? ref) {
    if (!_checkArrayRefType(ref)) {
      return _throwConversionFailureError("Int16List");
    }
    return JSInt16ArrayImpl.fromRefUnchecked(ref);
  }

  static JSInt16ArrayImpl? fromRefNullable(WasmExternRef? ref) {
    if (!_checkArrayRefType(ref)) {
      if (ref.isNull) {
        return null;
      }
      return _throwConversionFailureError("Int16List");
    }
    return JSInt16ArrayImpl.fromRefUnchecked(ref);
  }

  factory JSInt16ArrayImpl.view(
    JSArrayBufferImpl buffer,
    int offsetInBytes,
    int? length,
  ) {
    RangeErrorUtils.checkAlignment(offsetInBytes, Int16List.bytesPerElement);
    final lengthInBytes = (length == null
        ? ((buffer.lengthInBytes - offsetInBytes) & -2)
        : length * 2);
    return JSInt16ArrayImpl._(buffer._jsDataView(offsetInBytes, lengthInBytes));
  }

  @override
  @pragma("wasm:prefer-inline")
  int get elementSizeInBytes => 2;

  @override
  WasmExternRef? toJSArrayExternRef([int start = 0, int? length]) {
    length = length ?? (this.length - start);
    if (!_arrayRef.isNull && start == 0 && length == this.length) {
      return _arrayRef;
    }
    return js.JS<WasmExternRef?>(
      '(o, start, length) => new Int16Array(o.buffer, o.byteOffset + start, length)',
      wrappedExternRef,
      WasmI32.fromInt(start * 2),
      WasmI32.fromInt(length),
    );
  }

  @pragma("wasm:prefer-inline")
  int _getUnchecked(int index) => _getInt16(wrappedExternRef, index * 2, true);

  @pragma("wasm:prefer-inline")
  void _setUnchecked(int index, int value) =>
      _setInt16(wrappedExternRef, index * 2, value, true);

  @override
  @pragma("wasm:prefer-inline")
  int operator [](int index) {
    IndexErrorUtils.checkIndexBCE(index, length);
    return _getInt16(wrappedExternRef, index * 2, true);
  }

  @override
  @pragma("wasm:prefer-inline")
  void operator []=(int index, int value) {
    IndexErrorUtils.checkIndexBCE(index, length);
    _setInt16(wrappedExternRef, index * 2, value, true);
  }

  @override
  UnmodifiableJSInt16Array asUnmodifiableView() =>
      UnmodifiableJSInt16Array._(wrappedExternRef);

  @override
  JSInt16ArrayImpl sublist(int start, [int? end]) {
    final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
    final newOffsetInBytes = offsetInBytes + 2 * start;
    final newLengthInBytes = 2 * (newEnd - start);
    return JSInt16ArrayImpl._(
      buffer._cloneAsDataView(newOffsetInBytes, newLengthInBytes),
    );
  }

  @override
  _JSInt16ArrayIterator get iterator => _JSInt16ArrayIterator(this);
}

final class _JSInt16ArrayIterator extends _IntArrayIteratorBase {
  _JSInt16ArrayIterator(JSInt16ArrayImpl array)
    : super(array.wrappedExternRef, array.length);

  @pragma("wasm:prefer-inline")
  bool moveNext() {
    _position += 1;
    if (_position < _length) {
      _current = _getInt16(wrappedExternRef, _position * 2, true);
      return true;
    }
    return false;
  }
}

final class UnmodifiableJSInt16Array extends JSInt16ArrayImpl
    with _UnmodifiableIntListMixin {
  UnmodifiableJSInt16Array._(WasmExternRef? ref) : super._(ref);
}

final class JSUint32ArrayImpl extends JSIntegerArrayBase
    with _IntListMixin
    implements Uint32List {
  JSUint32ArrayImpl._(WasmExternRef? _dataViewRef, [WasmExternRef? _arrayRef])
    : super(_dataViewRef, 2, _arrayRef);

  factory JSUint32ArrayImpl(int length) =>
      JSUint32ArrayImpl._(_newDataView(length * 4));

  static int _getRefType(WasmExternRef? ref) => js.JS<WasmI32>('''o => {
          if (o === null || o === undefined) return 0;
          if (o instanceof Uint32Array) return 1;
          return 2;
        }''', ref).toIntUnsigned();

  factory JSUint32ArrayImpl.fromRefUnchecked(WasmExternRef? ref) {
    assert(_getRefType(ref) == 1);
    return JSUint32ArrayImpl._(_dataViewFromJSArray(ref), ref);
  }

  factory JSUint32ArrayImpl.fromRef(WasmExternRef? ref) {
    if (_getRefType(ref) != 1) {
      return _throwConversionFailureError("Uint32List");
    }
    return JSUint32ArrayImpl.fromRefUnchecked(ref);
  }

  static JSUint32ArrayImpl? fromRefNullable(WasmExternRef? ref) {
    final refType = _getRefType(ref);
    if (refType == 0) return null;
    if (refType == 1) return JSUint32ArrayImpl.fromRefUnchecked(ref);
    return _throwConversionFailureError("Uint32List");
  }

  factory JSUint32ArrayImpl.view(
    JSArrayBufferImpl buffer,
    int offsetInBytes,
    int? length,
  ) {
    RangeErrorUtils.checkAlignment(offsetInBytes, Uint32List.bytesPerElement);
    final lengthInBytes = (length == null
        ? ((buffer.lengthInBytes - offsetInBytes) & -4)
        : length * 4);
    return JSUint32ArrayImpl._(
      buffer._jsDataView(offsetInBytes, lengthInBytes),
    );
  }

  @override
  @pragma("wasm:prefer-inline")
  int get elementSizeInBytes => 4;

  @override
  WasmExternRef? toJSArrayExternRef([int start = 0, int? length]) {
    length = length ?? (this.length - start);
    if (!_arrayRef.isNull && start == 0 && length == this.length) {
      return _arrayRef;
    }
    return js.JS<WasmExternRef?>(
      '(o, start, length) => new Uint32Array(o.buffer, o.byteOffset + start, length)',
      wrappedExternRef,
      WasmI32.fromInt(start * 4),
      WasmI32.fromInt(length),
    );
  }

  @pragma("wasm:prefer-inline")
  int _getUnchecked(int index) => _getUint32(wrappedExternRef, index * 4, true);

  @pragma("wasm:prefer-inline")
  void _setUnchecked(int index, int value) =>
      _setUint32(wrappedExternRef, index * 4, value, true);

  @override
  @pragma("wasm:prefer-inline")
  int operator [](int index) {
    IndexErrorUtils.checkIndexBCE(index, length);
    return _getUint32(wrappedExternRef, index * 4, true);
  }

  @override
  @pragma("wasm:prefer-inline")
  void operator []=(int index, int value) {
    IndexErrorUtils.checkIndexBCE(index, length);
    _setUint32(wrappedExternRef, index * 4, value, true);
  }

  @override
  UnmodifiableJSUint32Array asUnmodifiableView() =>
      UnmodifiableJSUint32Array._(wrappedExternRef);

  @override
  JSUint32ArrayImpl sublist(int start, [int? end]) {
    final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
    final newOffsetInBytes = offsetInBytes + 4 * start;
    final newLengthInBytes = 4 * (newEnd - start);
    return JSUint32ArrayImpl._(
      buffer._cloneAsDataView(newOffsetInBytes, newLengthInBytes),
    );
  }

  @override
  _JSUint32ArrayIterator get iterator => _JSUint32ArrayIterator(this);
}

final class _JSUint32ArrayIterator extends _IntArrayIteratorBase {
  _JSUint32ArrayIterator(JSUint32ArrayImpl array)
    : super(array.wrappedExternRef, array.length);

  @pragma("wasm:prefer-inline")
  bool moveNext() {
    _position += 1;
    if (_position < _length) {
      _current = _getUint32(wrappedExternRef, _position * 4, true);
      return true;
    }
    return false;
  }
}

final class UnmodifiableJSUint32Array extends JSUint32ArrayImpl
    with _UnmodifiableIntListMixin {
  UnmodifiableJSUint32Array._(WasmExternRef? ref) : super._(ref);
}

final class JSInt32ArrayImpl extends JSIntegerArrayBase
    with _IntListMixin
    implements Int32List {
  JSInt32ArrayImpl._(WasmExternRef? _dataViewRef, [WasmExternRef? _arrayRef])
    : super(_dataViewRef, 2, _arrayRef);

  factory JSInt32ArrayImpl(int length) =>
      JSInt32ArrayImpl._(_newDataView(length * 4));

  static int _getRefType(WasmExternRef? ref) => js.JS<WasmI32>('''o => {
          if (o === null || o === undefined) return 0;
          if (o instanceof Int32Array) return 1;
          return 2;
        }''', ref).toIntUnsigned();

  factory JSInt32ArrayImpl.fromRefUnchecked(WasmExternRef? ref) {
    assert(_getRefType(ref) == 1);
    return JSInt32ArrayImpl._(_dataViewFromJSArray(ref), ref);
  }

  factory JSInt32ArrayImpl.fromRef(WasmExternRef? ref) {
    if (_getRefType(ref) != 1) {
      return _throwConversionFailureError("Int32List");
    }
    return JSInt32ArrayImpl.fromRefUnchecked(ref);
  }

  static JSInt32ArrayImpl? fromRefNullable(WasmExternRef? ref) {
    final refType = _getRefType(ref);
    if (refType == 0) return null;
    if (refType == 1) return JSInt32ArrayImpl.fromRefUnchecked(ref);
    return _throwConversionFailureError("Int32List");
  }

  factory JSInt32ArrayImpl.view(
    JSArrayBufferImpl buffer,
    int offsetInBytes,
    int? length,
  ) {
    RangeErrorUtils.checkAlignment(offsetInBytes, Int32List.bytesPerElement);
    final lengthInBytes = (length == null
        ? ((buffer.lengthInBytes - offsetInBytes) & -4)
        : length * 4);
    return JSInt32ArrayImpl._(buffer._jsDataView(offsetInBytes, lengthInBytes));
  }

  @override
  @pragma("wasm:prefer-inline")
  int get elementSizeInBytes => 4;

  @override
  WasmExternRef? toJSArrayExternRef([int start = 0, int? length]) {
    length = length ?? (this.length - start);
    if (!_arrayRef.isNull && start == 0 && length == this.length) {
      return _arrayRef;
    }
    return js.JS<WasmExternRef?>(
      '(o, start, length) => new Int32Array(o.buffer, o.byteOffset + start, length)',
      wrappedExternRef,
      WasmI32.fromInt(start * 4),
      WasmI32.fromInt(length),
    );
  }

  @pragma("wasm:prefer-inline")
  int _getUnchecked(int index) => _getInt32(wrappedExternRef, index * 4, true);

  @pragma("wasm:prefer-inline")
  void _setUnchecked(int index, int value) =>
      _setInt32(wrappedExternRef, index * 4, value, true);

  @override
  @pragma("wasm:prefer-inline")
  int operator [](int index) {
    IndexErrorUtils.checkIndexBCE(index, length);
    return _getInt32(wrappedExternRef, index * 4, true);
  }

  @override
  @pragma("wasm:prefer-inline")
  void operator []=(int index, int value) {
    IndexErrorUtils.checkIndexBCE(index, length);
    _setInt32(wrappedExternRef, index * 4, value, true);
  }

  @override
  UnmodifiableJSInt32Array asUnmodifiableView() =>
      UnmodifiableJSInt32Array._(wrappedExternRef);

  @override
  JSInt32ArrayImpl sublist(int start, [int? end]) {
    final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
    final newOffsetInBytes = offsetInBytes + 4 * start;
    final newLengthInBytes = 4 * (newEnd - start);
    return JSInt32ArrayImpl._(
      buffer._cloneAsDataView(newOffsetInBytes, newLengthInBytes),
    );
  }

  @override
  _JSInt32ArrayIterator get iterator => _JSInt32ArrayIterator(this);
}

final class _JSInt32ArrayIterator extends _IntArrayIteratorBase {
  _JSInt32ArrayIterator(JSInt32ArrayImpl array)
    : super(array.wrappedExternRef, array.length);

  @pragma("wasm:prefer-inline")
  bool moveNext() {
    _position += 1;
    if (_position < _length) {
      _current = _getInt32(wrappedExternRef, _position * 4, true);
      return true;
    }
    return false;
  }
}

final class UnmodifiableJSInt32Array extends JSInt32ArrayImpl
    with _UnmodifiableIntListMixin {
  UnmodifiableJSInt32Array._(WasmExternRef? ref) : super._(ref);
}

final class JSInt32x4ArrayImpl
    with ListMixin<Int32x4>, FixedLengthListMixin<Int32x4>
    implements Int32x4List {
  final JSInt32ArrayImpl _storage;

  JSInt32x4ArrayImpl.externalStorage(JSInt32ArrayImpl storage)
    : _storage = storage;

  @override
  ByteBuffer get buffer => _storage.buffer;

  @override
  @pragma("wasm:prefer-inline")
  int get lengthInBytes => _storage.lengthInBytes;

  @override
  int get offsetInBytes => _storage.offsetInBytes;

  @override
  int get elementSizeInBytes => Int32x4List.bytesPerElement;

  @override
  @pragma("wasm:prefer-inline")
  int get length => _storage.length ~/ 4;

  @override
  @pragma("wasm:prefer-inline")
  Int32x4 operator [](int index) {
    IndexErrorUtils.checkIndexBCE(index, length);
    int _x = _storage[(index * 4) + 0];
    int _y = _storage[(index * 4) + 1];
    int _z = _storage[(index * 4) + 2];
    int _w = _storage[(index * 4) + 3];
    return Int32x4(_x, _y, _z, _w);
  }

  @override
  @pragma("wasm:prefer-inline")
  void operator []=(int index, Int32x4 value) {
    IndexErrorUtils.checkIndexBCE(index, length);
    _storage[(index * 4) + 0] = value.x;
    _storage[(index * 4) + 1] = value.y;
    _storage[(index * 4) + 2] = value.z;
    _storage[(index * 4) + 3] = value.w;
  }

  @override
  Int32x4List asUnmodifiableView() =>
      NaiveUnmodifiableInt32x4List.externalStorage(_storage);

  @override
  JSInt32x4ArrayImpl sublist(int start, [int? end]) {
    final stop = RangeErrorUtils.checkValidRange(start, end, length);
    return JSInt32x4ArrayImpl.externalStorage(
      _storage.sublist(start * 4, stop * 4),
    );
  }

  @override
  void setAll(int index, Iterable<Int32x4> iterable) {
    final end = iterable.length + index;
    setRange(index, end, iterable);
  }

  @override
  void setRange(
    int start,
    int end,
    Iterable<Int32x4> iterable, [
    int skipCount = 0,
  ]) {
    RangeErrorUtils.checkValidRange(start, end, length);

    if (skipCount < 0) {
      throw ArgumentError(skipCount);
    }

    List<Int32x4> otherList = iterable.skip(skipCount).toList(growable: false);

    final count = end - start;
    if (otherList.length < count) {
      throw IterableElementError.tooFew();
    }

    // TODO(omersa): Use unchecked operations here.
    for (int i = 0, j = start; i < count; i++, j++) {
      this[j] = otherList[i];
    }
  }
}

final class JSBigUint64ArrayImpl extends JSIntegerArrayBase
    with _IntListMixin
    implements Uint64List {
  JSBigUint64ArrayImpl._(
    WasmExternRef? _dataViewRef, [
    WasmExternRef? _arrayRef,
  ]) : super(_dataViewRef, 3, _arrayRef);

  factory JSBigUint64ArrayImpl(int length) =>
      JSBigUint64ArrayImpl._(_newDataView(length * 8));

  factory JSBigUint64ArrayImpl.fromJSArray(WasmExternRef? jsArrayRef) =>
      JSBigUint64ArrayImpl._(_dataViewFromJSArray(jsArrayRef), jsArrayRef);

  factory JSBigUint64ArrayImpl.view(
    JSArrayBufferImpl buffer,
    int offsetInBytes,
    int? length,
  ) {
    RangeErrorUtils.checkAlignment(offsetInBytes, Uint64List.bytesPerElement);
    final lengthInBytes = (length == null
        ? ((buffer.lengthInBytes - offsetInBytes) & -8)
        : length * 8);
    return JSBigUint64ArrayImpl._(
      buffer._jsDataView(offsetInBytes, lengthInBytes),
    );
  }

  @override
  @pragma("wasm:prefer-inline")
  int get elementSizeInBytes => 8;

  @override
  WasmExternRef? toJSArrayExternRef([int start = 0, int? length]) {
    length = length ?? (this.length - start);
    if (!_arrayRef.isNull && start == 0 && length == this.length) {
      return _arrayRef;
    }
    return js.JS<WasmExternRef?>(
      '(o, start, length) => new BigUint64Array(o.buffer, o.byteOffset + start, length)',
      wrappedExternRef,
      WasmI32.fromInt(start * 8),
      WasmI32.fromInt(length),
    );
  }

  @pragma("wasm:prefer-inline")
  int _getUnchecked(int index) =>
      _getBigUint64(wrappedExternRef, index * 8, true);

  @pragma("wasm:prefer-inline")
  void _setUnchecked(int index, int value) =>
      _setBigUint64(wrappedExternRef, index * 8, value, true);

  @override
  @pragma("wasm:prefer-inline")
  int operator [](int index) {
    IndexErrorUtils.checkIndexBCE(index, length);
    return _getBigUint64(wrappedExternRef, index * 8, true);
  }

  @override
  @pragma("wasm:prefer-inline")
  void operator []=(int index, int value) {
    IndexErrorUtils.checkIndexBCE(index, length);
    return _setBigUint64(wrappedExternRef, index * 8, value, true);
  }

  @override
  UnmodifiableJSBigUint64Array asUnmodifiableView() =>
      UnmodifiableJSBigUint64Array._(wrappedExternRef);

  @override
  JSBigUint64ArrayImpl sublist(int start, [int? end]) {
    final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
    final newOffsetInBytes = offsetInBytes + 8 * start;
    final newLengthInBytes = 8 * (newEnd - start);
    return JSBigUint64ArrayImpl._(
      buffer._cloneAsDataView(newOffsetInBytes, newLengthInBytes),
    );
  }

  @override
  _JSUint64ArrayIterator get iterator => _JSUint64ArrayIterator(this);
}

final class _JSUint64ArrayIterator extends _IntArrayIteratorBase {
  _JSUint64ArrayIterator(JSBigUint64ArrayImpl array)
    : super(array.wrappedExternRef, array.length);

  @pragma("wasm:prefer-inline")
  bool moveNext() {
    _position += 1;
    if (_position < _length) {
      _current = _getBigUint64(wrappedExternRef, _position * 8, true);
      return true;
    }
    return false;
  }
}

final class UnmodifiableJSBigUint64Array extends JSBigUint64ArrayImpl
    with _UnmodifiableIntListMixin {
  UnmodifiableJSBigUint64Array._(WasmExternRef? ref) : super._(ref);
}

final class JSBigInt64ArrayImpl extends JSIntegerArrayBase
    with _IntListMixin
    implements Int64List {
  JSBigInt64ArrayImpl._(WasmExternRef? _dataViewRef, [WasmExternRef? _arrayRef])
    : super(_dataViewRef, 3, _arrayRef);

  factory JSBigInt64ArrayImpl(int length) =>
      JSBigInt64ArrayImpl._(_newDataView(length * 8));

  factory JSBigInt64ArrayImpl.fromJSArray(WasmExternRef? jsArrayRef) =>
      JSBigInt64ArrayImpl._(_dataViewFromJSArray(jsArrayRef), jsArrayRef);

  factory JSBigInt64ArrayImpl.view(
    JSArrayBufferImpl buffer,
    int offsetInBytes,
    int? length,
  ) {
    RangeErrorUtils.checkAlignment(offsetInBytes, Int64List.bytesPerElement);
    final lengthInBytes = (length == null
        ? ((buffer.lengthInBytes - offsetInBytes) & -8)
        : length * 8);
    return JSBigInt64ArrayImpl._(
      buffer._jsDataView(offsetInBytes, lengthInBytes),
    );
  }

  @override
  @pragma("wasm:prefer-inline")
  int get elementSizeInBytes => 8;

  @override
  WasmExternRef? toJSArrayExternRef([int start = 0, int? length]) {
    length = length ?? (this.length - start);
    if (!_arrayRef.isNull && start == 0 && length == this.length) {
      return _arrayRef;
    }
    return js.JS<WasmExternRef?>(
      '(o, start, length) => new BigInt64Array(o.buffer, o.byteOffset + start, length)',
      wrappedExternRef,
      WasmI32.fromInt(start * 8),
      WasmI32.fromInt(length),
    );
  }

  @pragma("wasm:prefer-inline")
  int _getUnchecked(int index) =>
      _getBigInt64(wrappedExternRef, index * 8, true);

  @pragma("wasm:prefer-inline")
  void _setUnchecked(int index, int value) =>
      _setBigInt64(wrappedExternRef, index * 8, value, true);

  @override
  @pragma("wasm:prefer-inline")
  int operator [](int index) {
    IndexErrorUtils.checkIndexBCE(index, length);
    return _getBigInt64(wrappedExternRef, index * 8, true);
  }

  @override
  @pragma("wasm:prefer-inline")
  void operator []=(int index, int value) {
    IndexErrorUtils.checkIndexBCE(index, length);
    _setBigInt64(wrappedExternRef, index * 8, value, true);
  }

  @override
  UnmodifiableJSBigInt64Array asUnmodifiableView() =>
      UnmodifiableJSBigInt64Array._(wrappedExternRef);

  @override
  JSBigInt64ArrayImpl sublist(int start, [int? end]) {
    final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
    final newOffsetInBytes = offsetInBytes + 8 * start;
    final newLengthInBytes = 8 * (newEnd - start);
    return JSBigInt64ArrayImpl._(
      buffer._cloneAsDataView(newOffsetInBytes, newLengthInBytes),
    );
  }

  @override
  _JSInt64ArrayIterator get iterator => _JSInt64ArrayIterator(this);
}

final class _JSInt64ArrayIterator extends _IntArrayIteratorBase {
  _JSInt64ArrayIterator(JSBigInt64ArrayImpl array)
    : super(array.wrappedExternRef, array.length);

  @pragma("wasm:prefer-inline")
  bool moveNext() {
    _position += 1;
    if (_position < _length) {
      _current = _getBigInt64(wrappedExternRef, _position * 8, true);
      return true;
    }
    return false;
  }
}

final class UnmodifiableJSBigInt64Array extends JSBigInt64ArrayImpl
    with _UnmodifiableIntListMixin {
  UnmodifiableJSBigInt64Array._(WasmExternRef? ref) : super._(ref);
}

abstract class _DoubleArrayIteratorBase extends js.JSExternWrapper
    implements Iterator<double> {
  final int _length;
  int _position = -1;
  double _current = 0;

  _DoubleArrayIteratorBase(WasmExternRef? ref, this._length) : super(ref);

  @pragma("wasm:prefer-inline")
  double get current => _current;
}

mixin _DoubleListMixin implements List<double> {
  WasmExternRef? toJSArrayExternRef([int start = 0, int? length]);

  void _setUnchecked(int index, double value);

  double _getUnchecked(int index);

  Iterable<T> whereType<T>() => WhereTypeIterable<T>(this);

  Iterable<double> followedBy(Iterable<double> other) =>
      FollowedByIterable<double>.firstEfficient(this, other);

  List<R> cast<R>() => List.castFrom<double, R>(this);

  void set first(double value) {
    this[0] = value;
  }

  void set last(double value) {
    this[length - 1] = value;
  }

  int indexWhere(bool test(double element), [int start = 0]) {
    final length = this.length;
    if (start < 0) start = 0;
    for (int i = start; i < length; i++) {
      if (test(_getUnchecked(i))) return i;
    }
    return -1;
  }

  int lastIndexWhere(bool test(double element), [int? start]) {
    final length = this.length;
    int startIndex = (start == null || start >= length) ? length - 1 : start;
    for (int i = startIndex; i >= 0; i--) {
      if (test(_getUnchecked(i))) return i;
    }
    return -1;
  }

  List<double> operator +(List<double> other) => [...this, ...other];

  bool contains(Object? element) {
    final length = this.length;
    for (var i = 0; i < length; ++i) {
      if (_getUnchecked(i) == element) return true;
    }
    return false;
  }

  void shuffle([Random? random]) {
    random ??= Random();
    var i = length;
    while (i > 1) {
      int pos = random.nextInt(i);
      i -= 1;
      var tmp = _getUnchecked(i);
      _setUnchecked(i, _getUnchecked(pos));
      _setUnchecked(pos, tmp);
    }
  }

  Iterable<double> where(bool f(double element)) =>
      WhereIterable<double>(this, f);

  Iterable<double> take(int n) => SubListIterable<double>(this, 0, n);

  Iterable<double> takeWhile(bool test(double element)) =>
      TakeWhileIterable<double>(this, test);

  Iterable<double> skip(int n) => SubListIterable<double>(this, n, null);

  Iterable<double> skipWhile(bool test(double element)) =>
      SkipWhileIterable<double>(this, test);

  Iterable<double> get reversed => ReversedListIterable<double>(this);

  Map<int, double> asMap() => ListMapView<double>(this);

  Iterable<double> getRange(int start, [int? end]) {
    end = RangeErrorUtils.checkValidRange(start, end, length);
    return SubListIterable<double>(this, start, end);
  }

  List<double> toList({bool growable = true}) {
    return List<double>.of(this, growable: growable);
  }

  Set<double> toSet() {
    return Set<double>.of(this);
  }

  void forEach(void f(double element)) {
    final length = this.length;
    for (var i = 0; i < length; i++) {
      f(_getUnchecked(i));
    }
  }

  double reduce(double combine(double value, double element)) {
    final length = this.length;
    if (length == 0) throw IterableElementError.noElement();
    var value = _getUnchecked(0);
    for (var i = 1; i < length; ++i) {
      value = combine(value, _getUnchecked(i));
    }
    return value;
  }

  T fold<T>(T initialValue, T combine(T initialValue, double element)) {
    final length = this.length;
    for (var i = 0; i < length; ++i) {
      initialValue = combine(initialValue, _getUnchecked(i));
    }
    return initialValue;
  }

  Iterable<T> map<T>(T f(double element)) => MappedIterable<double, T>(this, f);

  Iterable<T> expand<T>(Iterable<T> f(double element)) =>
      ExpandIterable<double, T>(this, f);

  bool every(bool f(double element)) {
    final length = this.length;
    for (var i = 0; i < length; ++i) {
      if (!f(_getUnchecked(i))) return false;
    }
    return true;
  }

  bool any(bool f(double element)) {
    final length = this.length;
    for (var i = 0; i < length; ++i) {
      if (f(_getUnchecked(i))) return true;
    }
    return false;
  }

  double firstWhere(bool test(double element), {double orElse()?}) {
    final length = this.length;
    for (var i = 0; i < length; ++i) {
      final element = _getUnchecked(i);
      if (test(element)) return element;
    }
    if (orElse != null) return orElse();
    throw IterableElementError.noElement();
  }

  double lastWhere(bool test(double element), {double orElse()?}) {
    final length = this.length;
    for (var i = length - 1; i >= 0; --i) {
      final element = _getUnchecked(i);
      if (test(element)) {
        return element;
      }
    }
    if (orElse != null) return orElse();
    throw IterableElementError.noElement();
  }

  double singleWhere(bool test(double element), {double orElse()?}) {
    var result = null;
    bool foundMatching = false;
    final length = this.length;
    for (var i = 0; i < length; ++i) {
      final element = _getUnchecked(i);
      if (test(element)) {
        if (foundMatching) {
          throw IterableElementError.tooMany();
        }
        result = element;
        foundMatching = true;
      }
    }
    if (foundMatching) return result;
    if (orElse != null) return orElse();
    throw IterableElementError.noElement();
  }

  double elementAt(int index) {
    return this[index];
  }

  void sort([int compare(double a, double b)?]) {
    Sort.sort(this, compare ?? Comparable.compare);
  }

  int indexOf(double element, [int start = 0]) {
    final length = this.length;
    if (start >= length) {
      return -1;
    } else if (start < 0) {
      start = 0;
    }
    for (int i = start; i < length; i++) {
      if (_getUnchecked(i) == element) return i;
    }
    return -1;
  }

  int lastIndexOf(double element, [int? start]) {
    final length = this.length;
    int startIndex = (start == null || start >= length) ? length - 1 : start;
    for (int i = startIndex; i >= 0; i--) {
      if (_getUnchecked(i) == element) return i;
    }
    return -1;
  }

  double removeLast() {
    throw UnsupportedError("Cannot remove from a fixed-length list");
  }

  double removeAt(int index) {
    throw UnsupportedError("Cannot remove from a fixed-length list");
  }

  void removeWhere(bool test(double element)) {
    throw UnsupportedError("Cannot remove from a fixed-length list");
  }

  void retainWhere(bool test(double element)) {
    throw UnsupportedError("Cannot remove from a fixed-length list");
  }

  double get first {
    if (length > 0) return _getUnchecked(0);
    throw IterableElementError.noElement();
  }

  double get last {
    final length = this.length;
    if (length > 0) return _getUnchecked(length - 1);
    throw IterableElementError.noElement();
  }

  double get single {
    final length = this.length;
    if (length == 1) return _getUnchecked(0);
    if (length == 0) throw IterableElementError.noElement();
    throw IterableElementError.tooMany();
  }

  void fillRange(int start, int end, [double? fillValue]) {
    RangeErrorUtils.checkValidRange(start, end, length);
    if (start == end) return;
    if (fillValue == null) {
      throw ArgumentError.notNull("fillValue");
    }
    for (var i = start; i < end; ++i) {
      _setUnchecked(i, fillValue);
    }
  }

  void setAll(int index, Iterable<double> iterable) {
    final end = iterable.length + index;
    setRange(index, end, iterable);
  }

  void setRange(
    int start,
    int end,
    Iterable<double> iterable, [
    int skipCount = 0,
  ]) {
    RangeErrorUtils.checkValidRange(start, end, length);

    if (skipCount < 0) {
      throw ArgumentError(skipCount);
    }

    if (iterable is JSArrayBase) {
      final JSArrayBase source = unsafeCast<JSArrayBase>(iterable);
      final length = end - start;
      final sourceArray = source.toJSArrayExternRef(skipCount, length);
      final targetArray = toJSArrayExternRef(start, length);
      return _setRangeFast(targetArray, sourceArray);
    }

    List<double> otherList = iterable.skip(skipCount).toList(growable: false);

    final count = end - start;
    if (otherList.length < count) {
      throw IterableElementError.tooFew();
    }

    // TODO(omersa): Use unchecked read.
    for (int i = 0, j = start; i < count; i++, j++) {
      _setUnchecked(j, otherList[i]);
    }
  }

  int get length;

  int get elementSizeInBytes;

  int get lengthInBytes;

  @override
  bool get isEmpty => length == 0;

  @override
  bool get isNotEmpty => !isEmpty;

  @override
  String join([String separator = ""]) =>
      (StringBuffer()..writeAll(this, separator)).toString();

  @override
  String toString() => ListBase.listToString(this);
}

mixin _UnmodifiableDoubleListMixin {
  WasmExternRef? get _toExternRef;

  JSArrayBufferImpl get buffer => JSArrayBufferImpl.fromRefImmutableUnchecked(
    _dataViewBuffer(_toExternRef),
  );

  void operator []=(int index, double value) {
    throw UnsupportedError("Cannot modify an unmodifiable list");
  }

  void setRange(
    int start,
    int end,
    Iterable<double> iterable, [
    int skipCount = 0,
  ]) {
    throw UnsupportedError("Cannot modify an unmodifiable list");
  }
}

final class JSFloat32ArrayImpl extends JSFloatArrayBase
    with _DoubleListMixin
    implements Float32List {
  JSFloat32ArrayImpl._(WasmExternRef? _dataViewRef, [WasmExternRef? _arrayRef])
    : super(_dataViewRef, 2, _arrayRef);

  factory JSFloat32ArrayImpl(int length) =>
      JSFloat32ArrayImpl._(_newDataView(length * 4));

  static int _getRefType(WasmExternRef? ref) => js.JS<WasmI32>('''o => {
          if (o === null || o === undefined) return 0;
          if (o instanceof Float32Array) return 1;
          return 2;
        }''', ref).toIntUnsigned();

  factory JSFloat32ArrayImpl.fromRefUnchecked(WasmExternRef? ref) {
    assert(_getRefType(ref) == 1);
    return JSFloat32ArrayImpl._(_dataViewFromJSArray(ref), ref);
  }

  factory JSFloat32ArrayImpl.fromRef(WasmExternRef? ref) {
    if (_getRefType(ref) != 1) {
      return _throwConversionFailureError("Float32List");
    }
    return JSFloat32ArrayImpl.fromRefUnchecked(ref);
  }

  static JSFloat32ArrayImpl? fromRefNullable(WasmExternRef? ref) {
    final refType = _getRefType(ref);
    if (refType == 0) return null;
    if (refType == 1) return JSFloat32ArrayImpl.fromRefUnchecked(ref);
    return _throwConversionFailureError("Float32List");
  }

  factory JSFloat32ArrayImpl.view(
    JSArrayBufferImpl buffer,
    int offsetInBytes,
    int? length,
  ) {
    RangeErrorUtils.checkAlignment(offsetInBytes, Float32List.bytesPerElement);
    final lengthInBytes = (length == null
        ? ((buffer.lengthInBytes - offsetInBytes) & -4)
        : length * 4);
    return JSFloat32ArrayImpl._(
      buffer._jsDataView(offsetInBytes, lengthInBytes),
    );
  }

  @override
  @pragma("wasm:prefer-inline")
  int get elementSizeInBytes => 4;

  @override
  WasmExternRef? toJSArrayExternRef([int start = 0, int? length]) {
    if (!_arrayRef.isNull &&
        start == 0 &&
        (length == null || length == this.length)) {
      return _arrayRef;
    }
    return js.JS<WasmExternRef?>(
      '(o, start, length) => new Float32Array(o.buffer, o.byteOffset + start, length)',
      wrappedExternRef,
      WasmI32.fromInt(start * 4),
      WasmI32.fromInt(length ?? (this.length - start)),
    );
  }

  @pragma("wasm:prefer-inline")
  double _getUnchecked(int index) =>
      _getFloat32(wrappedExternRef, index * 4, true);

  @pragma("wasm:prefer-inline")
  void _setUnchecked(int index, double value) =>
      _setFloat32(wrappedExternRef, index * 4, value, true);

  @override
  @pragma("wasm:prefer-inline")
  double operator [](int index) {
    IndexErrorUtils.checkIndexBCE(index, length);
    return _getFloat32(wrappedExternRef, index * 4, true);
  }

  @override
  @pragma("wasm:prefer-inline")
  void operator []=(int index, double value) {
    IndexErrorUtils.checkIndexBCE(index, length);
    _setFloat32(wrappedExternRef, index * 4, value, true);
  }

  @override
  UnmodifiableJSFloat32Array asUnmodifiableView() =>
      UnmodifiableJSFloat32Array._(wrappedExternRef);

  @override
  JSFloat32ArrayImpl sublist(int start, [int? end]) {
    final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
    final newOffsetInBytes = offsetInBytes + 4 * start;
    final newLengthInBytes = 4 * (newEnd - start);
    return JSFloat32ArrayImpl._(
      buffer._cloneAsDataView(newOffsetInBytes, newLengthInBytes),
    );
  }

  @override
  _JSFloat32ArrayIterator get iterator => _JSFloat32ArrayIterator(this);
}

final class _JSFloat32ArrayIterator extends _DoubleArrayIteratorBase {
  _JSFloat32ArrayIterator(JSFloat32ArrayImpl array)
    : super(array.wrappedExternRef, array.length);

  @pragma("wasm:prefer-inline")
  bool moveNext() {
    _position += 1;
    if (_position < _length) {
      _current = _getFloat32(wrappedExternRef, _position * 4, true);
      return true;
    }
    return false;
  }
}

final class UnmodifiableJSFloat32Array extends JSFloat32ArrayImpl
    with _UnmodifiableDoubleListMixin {
  UnmodifiableJSFloat32Array._(WasmExternRef? ref) : super._(ref);
}

final class JSFloat64ArrayImpl extends JSFloatArrayBase
    with _DoubleListMixin
    implements Float64List {
  JSFloat64ArrayImpl._(WasmExternRef? _dataViewRef, [WasmExternRef? _arrayRef])
    : super(_dataViewRef, 3, _arrayRef);

  factory JSFloat64ArrayImpl(int length) =>
      JSFloat64ArrayImpl._(_newDataView(length * 8));

  static int _getRefType(WasmExternRef? ref) => js.JS<WasmI32>('''o => {
          if (o === null || o === undefined) return 0;
          if (o instanceof Float64Array) return 1;
          return 2;
        }''', ref).toIntUnsigned();

  factory JSFloat64ArrayImpl.fromRefUnchecked(WasmExternRef? ref) {
    assert(_getRefType(ref) == 1);
    return JSFloat64ArrayImpl._(_dataViewFromJSArray(ref), ref);
  }

  factory JSFloat64ArrayImpl.fromRef(WasmExternRef? ref) {
    if (_getRefType(ref) != 1) {
      return _throwConversionFailureError("Float64List");
    }
    return JSFloat64ArrayImpl.fromRefUnchecked(ref);
  }

  static JSFloat64ArrayImpl? fromRefNullable(WasmExternRef? ref) {
    final refType = _getRefType(ref);
    if (refType == 0) return null;
    if (refType == 1) return JSFloat64ArrayImpl.fromRefUnchecked(ref);
    return _throwConversionFailureError("Float64List");
  }

  factory JSFloat64ArrayImpl.view(
    JSArrayBufferImpl buffer,
    int offsetInBytes,
    int? length,
  ) {
    RangeErrorUtils.checkAlignment(offsetInBytes, Float64List.bytesPerElement);
    final lengthInBytes = (length == null
        ? ((buffer.lengthInBytes - offsetInBytes) & -8)
        : length * 8);
    return JSFloat64ArrayImpl._(
      buffer._jsDataView(offsetInBytes, lengthInBytes),
    );
  }

  @override
  @pragma("wasm:prefer-inline")
  int get elementSizeInBytes => 8;

  @override
  WasmExternRef? toJSArrayExternRef([int start = 0, int? length]) {
    if (!_arrayRef.isNull &&
        start == 0 &&
        (length == null || length == this.length)) {
      return _arrayRef;
    }
    return js.JS<WasmExternRef?>(
      '(o, start, length) => new Float64Array(o.buffer, o.byteOffset + start, length)',
      wrappedExternRef,
      WasmI32.fromInt(start * 8),
      WasmI32.fromInt(length ?? (this.length - start)),
    );
  }

  @pragma("wasm:prefer-inline")
  double _getUnchecked(int index) =>
      _getFloat64(wrappedExternRef, index * 8, true);

  @pragma("wasm:prefer-inline")
  void _setUnchecked(int index, double value) =>
      _setFloat64(wrappedExternRef, index * 8, value, true);

  @override
  @pragma("wasm:prefer-inline")
  double operator [](int index) {
    IndexErrorUtils.checkIndexBCE(index, length);
    return _getFloat64(wrappedExternRef, index * 8, true);
  }

  @override
  @pragma("wasm:prefer-inline")
  void operator []=(int index, double value) {
    IndexErrorUtils.checkIndexBCE(index, length);
    _setFloat64(wrappedExternRef, index * 8, value, true);
  }

  @override
  UnmodifiableJSFloat64Array asUnmodifiableView() =>
      UnmodifiableJSFloat64Array._(wrappedExternRef);

  @override
  JSFloat64ArrayImpl sublist(int start, [int? end]) {
    final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
    final newOffsetInBytes = offsetInBytes + 8 * start;
    final newLengthInBytes = 8 * (newEnd - start);
    return JSFloat64ArrayImpl._(
      buffer._cloneAsDataView(newOffsetInBytes, newLengthInBytes),
    );
  }

  @override
  _JSFloat64ArrayIterator get iterator => _JSFloat64ArrayIterator(this);
}

final class _JSFloat64ArrayIterator extends _DoubleArrayIteratorBase {
  _JSFloat64ArrayIterator(JSFloat64ArrayImpl array)
    : super(array.wrappedExternRef, array.length);

  @pragma("wasm:prefer-inline")
  bool moveNext() {
    _position += 1;
    if (_position < _length) {
      _current = _getFloat64(wrappedExternRef, _position * 8, true);
      return true;
    }
    return false;
  }
}

final class UnmodifiableJSFloat64Array extends JSFloat64ArrayImpl
    with _UnmodifiableDoubleListMixin {
  UnmodifiableJSFloat64Array._(WasmExternRef? ref) : super._(ref);
}

final class JSFloat32x4ArrayImpl
    with ListMixin<Float32x4>, FixedLengthListMixin<Float32x4>
    implements Float32x4List {
  final JSFloat32ArrayImpl _storage;

  JSFloat32x4ArrayImpl.externalStorage(JSFloat32ArrayImpl storage)
    : _storage = storage;

  @override
  ByteBuffer get buffer => _storage.buffer;

  @override
  @pragma("wasm:prefer-inline")
  int get lengthInBytes => _storage.lengthInBytes;

  @override
  int get offsetInBytes => _storage.offsetInBytes;

  @override
  int get elementSizeInBytes => Float32x4List.bytesPerElement;

  @override
  @pragma("wasm:prefer-inline")
  int get length => _storage.length ~/ 4;

  @override
  @pragma("wasm:prefer-inline")
  Float32x4 operator [](int index) {
    IndexErrorUtils.checkIndexBCE(index, length);
    double _x = _storage[(index * 4) + 0];
    double _y = _storage[(index * 4) + 1];
    double _z = _storage[(index * 4) + 2];
    double _w = _storage[(index * 4) + 3];
    return Float32x4(_x, _y, _z, _w);
  }

  @override
  @pragma("wasm:prefer-inline")
  void operator []=(int index, Float32x4 value) {
    IndexErrorUtils.checkIndexBCE(index, length);
    _storage[(index * 4) + 0] = value.x;
    _storage[(index * 4) + 1] = value.y;
    _storage[(index * 4) + 2] = value.z;
    _storage[(index * 4) + 3] = value.w;
  }

  @override
  Float32x4List asUnmodifiableView() =>
      NaiveUnmodifiableFloat32x4List.externalStorage(_storage);

  @override
  JSFloat32x4ArrayImpl sublist(int start, [int? end]) {
    final stop = RangeErrorUtils.checkValidRange(start, end, length);
    return JSFloat32x4ArrayImpl.externalStorage(
      _storage.sublist(start * 4, stop * 4),
    );
  }

  @override
  void setAll(int index, Iterable<Float32x4> iterable) {
    final end = iterable.length + index;
    setRange(index, end, iterable);
  }

  @override
  void setRange(
    int start,
    int end,
    Iterable<Float32x4> iterable, [
    int skipCount = 0,
  ]) {
    RangeErrorUtils.checkValidRange(start, end, length);

    if (skipCount < 0) {
      throw ArgumentError(skipCount);
    }

    List<Float32x4> otherList = iterable
        .skip(skipCount)
        .toList(growable: false);

    final count = end - start;
    if (otherList.length < count) {
      throw IterableElementError.tooFew();
    }

    // TODO(omersa): Use unchecked operations here.
    for (int i = 0, j = start; i < count; i++, j++) {
      this[j] = otherList[i];
    }
  }
}

final class JSFloat64x2ArrayImpl
    with ListMixin<Float64x2>, FixedLengthListMixin<Float64x2>
    implements Float64x2List {
  final JSFloat64ArrayImpl _storage;

  JSFloat64x2ArrayImpl.externalStorage(JSFloat64ArrayImpl storage)
    : _storage = storage;

  @override
  ByteBuffer get buffer => _storage.buffer;

  @override
  @pragma("wasm:prefer-inline")
  int get lengthInBytes => _storage.lengthInBytes;

  @override
  int get offsetInBytes => _storage.offsetInBytes;

  @override
  int get elementSizeInBytes => Float64x2List.bytesPerElement;

  @override
  @pragma("wasm:prefer-inline")
  int get length => _storage.length ~/ 2;

  @override
  @pragma("wasm:prefer-inline")
  Float64x2 operator [](int index) {
    IndexErrorUtils.checkIndexBCE(index, length);
    double _x = _storage[(index * 2) + 0];
    double _y = _storage[(index * 2) + 1];
    return Float64x2(_x, _y);
  }

  @override
  @pragma("wasm:prefer-inline")
  void operator []=(int index, Float64x2 value) {
    IndexErrorUtils.checkIndexBCE(index, length);
    _storage[(index * 2) + 0] = value.x;
    _storage[(index * 2) + 1] = value.y;
  }

  @override
  Float64x2List asUnmodifiableView() =>
      NaiveUnmodifiableFloat64x2List.externalStorage(_storage);

  @override
  JSFloat64x2ArrayImpl sublist(int start, [int? end]) {
    final stop = RangeErrorUtils.checkValidRange(start, end, length);
    return JSFloat64x2ArrayImpl.externalStorage(
      _storage.sublist(start * 2, stop * 2),
    );
  }

  @override
  void setAll(int index, Iterable<Float64x2> iterable) {
    final end = iterable.length + index;
    setRange(index, end, iterable);
  }

  @override
  void setRange(
    int start,
    int end,
    Iterable<Float64x2> iterable, [
    int skipCount = 0,
  ]) {
    RangeErrorUtils.checkValidRange(start, end, length);

    if (skipCount < 0) {
      throw ArgumentError(skipCount);
    }

    List<Float64x2> otherList = iterable
        .skip(skipCount)
        .toList(growable: false);

    final count = end - start;
    if (otherList.length < count) {
      throw IterableElementError.tooFew();
    }

    // TODO(omersa): Use unchecked operations here.
    for (int i = 0, j = start; i < count; i++, j++) {
      this[j] = otherList[i];
    }
  }
}

void _setRangeFast(WasmExternRef? targetArray, WasmExternRef? sourceArray) =>
    js.JS<void>('(t, s) => t.set(s)', targetArray, sourceArray);

@pragma("wasm:prefer-inline")
WasmExternRef? _newDataView(int length) => js.JS<WasmExternRef?>(
  'l => new DataView(new ArrayBuffer(l))',
  WasmI32.fromInt(length),
);

WasmExternRef? _dataViewFromJSArray(WasmExternRef? jsArrayRef) =>
    js.JS<WasmExternRef?>(
      '(o) => new DataView(o.buffer, o.byteOffset, o.byteLength)',
      jsArrayRef,
    );

@pragma("wasm:prefer-inline")
int _arrayBufferByteLength(WasmExternRef? ref) =>
    js.JS<WasmI32>('o => o.byteLength', ref).toIntSigned();

WasmExternRef? _dataViewBuffer(WasmExternRef? dataViewRef) =>
    js.JS<WasmExternRef?>('o => o.buffer', dataViewRef);

@pragma("wasm:prefer-inline")
int _dataViewByteOffset(WasmExternRef? dataViewRef) =>
    js.JS<WasmI32>('o => o.byteOffset', dataViewRef).toIntSigned();

@pragma("wasm:prefer-inline")
int _dataViewByteLength(WasmExternRef? ref) => js
    .JS<WasmF64>(
      "Function.prototype.call.bind(Object.getOwnPropertyDescriptor(DataView.prototype, 'byteLength').get)",
      ref,
    )
    .truncSatS()
    .toInt();

@pragma("wasm:prefer-inline")
WasmExternRef? _newDataViewFromArrayBufferOrSharedArrayBuffer(
  WasmExternRef? bufferRef,
  int offsetInBytes,
  int? length,
) => length == null
    ? js.JS<WasmExternRef?>(
        '(b, o) => new DataView(b, o)',
        bufferRef,
        WasmI32.fromInt(offsetInBytes),
      )
    : js.JS<WasmExternRef?>(
        '(b, o, l) => new DataView(b, o, l)',
        bufferRef,
        WasmI32.fromInt(offsetInBytes),
        WasmI32.fromInt(length),
      );

@pragma("wasm:prefer-inline")
int _getUint8(WasmExternRef? ref, int byteOffset) => js
    .JS<WasmI32>(
      'Function.prototype.call.bind(DataView.prototype.getUint8)',
      ref,
      WasmI32.fromInt(byteOffset),
    )
    .toIntUnsigned();

@pragma("wasm:prefer-inline")
void _setUint8(WasmExternRef? ref, int byteOffset, int value) => js.JS<void>(
  'Function.prototype.call.bind(DataView.prototype.setUint8)',
  ref,
  WasmI32.fromInt(byteOffset),
  WasmI32.fromInt(value),
);

@pragma("wasm:prefer-inline")
int _getInt8(WasmExternRef? ref, int byteOffset) => js
    .JS<WasmI32>(
      'Function.prototype.call.bind(DataView.prototype.getInt8)',
      ref,
      WasmI32.fromInt(byteOffset),
    )
    .toIntSigned();

@pragma("wasm:prefer-inline")
void _setInt8(WasmExternRef? ref, int byteOffset, int value) => js.JS<void>(
  'Function.prototype.call.bind(DataView.prototype.setInt8)',
  ref,
  WasmI32.fromInt(byteOffset),
  WasmI32.fromInt(value),
);

@pragma("wasm:prefer-inline")
int _getUint16(WasmExternRef? ref, int byteOffset, bool littleEndian) => js
    .JS<WasmI32>(
      'Function.prototype.call.bind(DataView.prototype.getUint16)',
      ref,
      WasmI32.fromInt(byteOffset),
      WasmI32.fromBool(littleEndian),
    )
    .toIntUnsigned();

@pragma("wasm:prefer-inline")
void _setUint16(
  WasmExternRef? ref,
  int byteOffset,
  int value,
  bool littleEndian,
) => js.JS<void>(
  'Function.prototype.call.bind(DataView.prototype.setUint16)',
  ref,
  WasmI32.fromInt(byteOffset),
  WasmI32.fromInt(value),
  WasmI32.fromBool(littleEndian),
);

@pragma("wasm:prefer-inline")
int _getInt16(WasmExternRef? ref, int byteOffset, bool littleEndian) => js
    .JS<WasmI32>(
      'Function.prototype.call.bind(DataView.prototype.getInt16)',
      ref,
      WasmI32.fromInt(byteOffset),
      WasmI32.fromBool(littleEndian),
    )
    .toIntSigned();

@pragma("wasm:prefer-inline")
void _setInt16(
  WasmExternRef? ref,
  int byteOffset,
  int value,
  bool littleEndian,
) => js.JS<void>(
  'Function.prototype.call.bind(DataView.prototype.setInt16)',
  ref,
  WasmI32.fromInt(byteOffset),
  WasmI32.fromInt(value),
  WasmI32.fromBool(littleEndian),
);

@pragma("wasm:prefer-inline")
int _getUint32(WasmExternRef? ref, int byteOffset, bool littleEndian) => js
    .JS<WasmI32>(
      'Function.prototype.call.bind(DataView.prototype.getUint32)',
      ref,
      WasmI32.fromInt(byteOffset),
      WasmI32.fromBool(littleEndian),
    )
    .toIntUnsigned();

@pragma("wasm:prefer-inline")
void _setUint32(
  WasmExternRef? ref,
  int byteOffset,
  int value,
  bool littleEndian,
) => js.JS<void>(
  'Function.prototype.call.bind(DataView.prototype.setUint32)',
  ref,
  WasmI32.fromInt(byteOffset),
  WasmI32.fromInt(value),
  WasmI32.fromBool(littleEndian),
);

@pragma("wasm:prefer-inline")
int _getInt32(WasmExternRef? ref, int byteOffset, bool littleEndian) => js
    .JS<WasmI32>(
      'Function.prototype.call.bind(DataView.prototype.getInt32)',
      ref,
      WasmI32.fromInt(byteOffset),
      WasmI32.fromBool(littleEndian),
    )
    .toIntSigned();

@pragma("wasm:prefer-inline")
void _setInt32(
  WasmExternRef? ref,
  int byteOffset,
  int value,
  bool littleEndian,
) => js.JS<void>(
  'Function.prototype.call.bind(DataView.prototype.setInt32)',
  ref,
  WasmI32.fromInt(byteOffset),
  WasmI32.fromInt(value),
  WasmI32.fromBool(littleEndian),
);

@pragma("wasm:prefer-inline")
int _getBigUint64(WasmExternRef? ref, int byteOffset, bool littleEndian) => js
    .JS<WasmI64>(
      'Function.prototype.call.bind(DataView.prototype.getBigUint64)',
      ref,
      WasmI32.fromInt(byteOffset),
      WasmI32.fromBool(littleEndian),
    )
    .toInt();

@pragma("wasm:prefer-inline")
void _setBigUint64(
  WasmExternRef? ref,
  int byteOffset,
  int value,
  bool littleEndian,
) => js.JS<void>(
  'Function.prototype.call.bind(DataView.prototype.setBigUint64)',
  ref,
  WasmI32.fromInt(byteOffset),
  WasmI64.fromInt(value),
  WasmI32.fromBool(littleEndian),
);

@pragma("wasm:prefer-inline")
int _getBigInt64(WasmExternRef? ref, int byteOffset, bool littleEndian) => js
    .JS<WasmI64>(
      'Function.prototype.call.bind(DataView.prototype.getBigInt64)',
      ref,
      WasmI32.fromInt(byteOffset),
      WasmI32.fromBool(littleEndian),
    )
    .toInt();

@pragma("wasm:prefer-inline")
void _setBigInt64(
  WasmExternRef? ref,
  int byteOffset,
  int value,
  bool littleEndian,
) => js.JS<void>(
  'Function.prototype.call.bind(DataView.prototype.setBigInt64)',
  ref,
  WasmI32.fromInt(byteOffset),
  WasmI64.fromInt(value),
  WasmI32.fromBool(littleEndian),
);

@pragma("wasm:prefer-inline")
double _getFloat32(WasmExternRef? ref, int byteOffset, bool littleEndian) => js
    .JS<WasmF32>(
      'Function.prototype.call.bind(DataView.prototype.getFloat32)',
      ref,
      WasmI32.fromInt(byteOffset),
      WasmI32.fromBool(littleEndian),
    )
    .toDouble();

@pragma("wasm:prefer-inline")
void _setFloat32(
  WasmExternRef? ref,
  int byteOffset,
  num value,
  bool littleEndian,
) => js.JS<void>(
  'Function.prototype.call.bind(DataView.prototype.setFloat32)',
  ref,
  WasmI32.fromInt(byteOffset),
  WasmF32.fromDouble(value.toDouble()),
  WasmI32.fromBool(littleEndian),
);

@pragma("wasm:prefer-inline")
double _getFloat64(WasmExternRef? ref, int byteOffset, bool littleEndian) => js
    .JS<WasmF64>(
      'Function.prototype.call.bind(DataView.prototype.getFloat64)',
      ref,
      WasmI32.fromInt(byteOffset),
      WasmI32.fromBool(littleEndian),
    )
    .toDouble();

@pragma("wasm:prefer-inline")
void _setFloat64(
  WasmExternRef? ref,
  int byteOffset,
  num value,
  bool littleEndian,
) => js.JS<void>(
  'Function.prototype.call.bind(DataView.prototype.setFloat64)',
  ref,
  WasmI32.fromInt(byteOffset),
  WasmF64.fromDouble(value.toDouble()),
  WasmI32.fromBool(littleEndian),
);

Never _throwConversionFailureError(String expectedType) => minify
    ? throw ArgumentError()
    : throw ArgumentError("JS reference cannot be converted to $expectedType");
