// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:convert' show utf8;
import 'dart:ffi' hide Size;
import 'dart:io';
import 'dart:ui' show Display, FlutterView;

import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';

import '../foundation/_features.dart';
import '_window.dart';
import '_window_positioner.dart';
import 'binding.dart';

// Do not import this file in production applications or packages published
// to pub.dev. Flutter will make breaking changes to this file, even in patch
// versions.
//
// All APIs in this file must be private or must:
//
// 1. Have the `@internal` attribute.
// 2. Throw an `UnsupportedError` if `isWindowingEnabled`
//    is `false`.
//
// See: https://github.com/flutter/flutter/issues/30701.

const String _kWindowingDisabledErrorMessage = '''
Windowing APIs are not enabled.

Windowing APIs are currently experimental. Do not use windowing APIs in
production applications or plugins published to pub.dev.

To try experimental windowing APIs:
1. Switch to Flutter's main release channel.
2. Turn on the windowing feature flag.

See: https://github.com/flutter/flutter/issues/30701.
''';

/// [WindowingOwner] implementation for macOS.
///
/// If [Platform.isMacOS] is false, then the constructor will throw an
/// [UnsupportedError].
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
///  * [WindowingOwner], the abstract class that manages native windows.
class WindowingOwnerMacOS extends WindowingOwner {
  /// Creates a new [WindowingOwnerMacOS] instance.
  ///
  /// {@macro flutter.widgets.windowing.experimental}
  ///
  /// See also:
  ///
  ///  * [WindowingOwner], the abstract class that manages native windows.
  @internal
  WindowingOwnerMacOS() {
    if (!isWindowingEnabled) {
      throw UnsupportedError(_kWindowingDisabledErrorMessage);
    }

    if (!Platform.isMacOS) {
      throw UnsupportedError('Only available on the macOS platform');
    }

    assert(
      WidgetsBinding.instance.platformDispatcher.engineId != null,
      'WindowingOwnerMacOS must be created after the engine has been initialized.',
    );
  }

  @override
  RegularWindowController createRegularWindowController({
    required RegularWindowControllerDelegate delegate,
    Size? preferredSize,
    BoxConstraints? preferredConstraints,
    String? title,
  }) {
    final res = RegularWindowControllerMacOS(
      owner: this,
      delegate: delegate,
      preferredSize: preferredSize,
      title: title,
    );
    _activeControllers.add(res);
    return res;
  }

  @override
  DialogWindowController createDialogWindowController({
    required DialogWindowControllerDelegate delegate,
    Size? preferredSize,
    BoxConstraints? preferredConstraints,
    BaseWindowController? parent,
    String? title,
  }) {
    final res = DialogWindowControllerMacOS(
      owner: this,
      delegate: delegate,
      preferredSize: preferredSize,
      parent: parent,
      title: title,
    );
    _activeControllers.add(res);
    return res;
  }

  @internal
  @override
  TooltipWindowController createTooltipWindowController({
    required TooltipWindowControllerDelegate delegate,
    required BoxConstraints preferredConstraints,
    required Rect anchorRect,
    required WindowPositioner positioner,
    required BaseWindowController parent,
  }) {
    throw UnimplementedError('Tooltip windows are not yet implemented on MacOS.');
  }

  @internal
  @override
  PopupWindowController createPopupWindowController({
    required PopupWindowControllerDelegate delegate,
    required BoxConstraints preferredConstraints,
    required Rect anchorRect,
    required WindowPositioner positioner,
    required BaseWindowController parent,
  }) {
    throw UnimplementedError('Popup windows are not yet implemented on MacOS.');
  }

  final List<BaseWindowController> _activeControllers = <BaseWindowController>[];

  /// Returns the window handle for the given [view], or null is the window
  /// handle is not available.
  ///
  /// The window handle is a pointer to the NSWindow instance.
  static Pointer<Void> getWindowHandle(FlutterView view) {
    return _MacOSPlatformInterface.getWindowHandle(
      WidgetsBinding.instance.platformDispatcher.engineId!,
      view.viewId,
    );
  }
}

/// Implementation of [RegularWindowController] for the macOS platform.
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
///  * [RegularWindowController], the base class for regular windows.
class RegularWindowControllerMacOS extends RegularWindowController {
  /// Creates a new regular window controller for macOS. When this constructor
  /// completes the FlutterView is created and framework is aware of it.
  RegularWindowControllerMacOS({
    required WindowingOwnerMacOS owner,
    required RegularWindowControllerDelegate delegate,
    required Size? preferredSize,
    BoxConstraints? preferredConstraints,
    String? title,
  }) : _owner = owner,
       _delegate = delegate,
       super.empty() {
    if (!isWindowingEnabled) {
      throw UnsupportedError(_kWindowingDisabledErrorMessage);
    }

    _onShouldClose = NativeCallable<Void Function()>.isolateLocal(_handleOnShouldClose);
    _onWillClose = NativeCallable<Void Function()>.isolateLocal(_handleOnWillClose);
    _onResize = NativeCallable<Void Function()>.isolateLocal(_handleOnResize);
    final int viewId = _MacOSPlatformInterface.createRegularWindow(
      preferredSize: preferredSize,
      preferredConstraints: preferredConstraints,
      onShouldClose: _onShouldClose.nativeFunction,
      onWillClose: _onWillClose.nativeFunction,
      onNotifyListeners: _onResize.nativeFunction,
    );
    final FlutterView flutterView = WidgetsBinding.instance.platformDispatcher.views.firstWhere(
      (FlutterView view) => view.viewId == viewId,
    );
    rootView = flutterView;
    if (title != null) {
      setTitle(title);
    }
  }

  /// Returns window handle for the current window.
  ///
  /// The handle is a pointer to an NSWindow instance.
  Pointer<Void> getWindowHandle() {
    _ensureNotDestroyed();
    return WindowingOwnerMacOS.getWindowHandle(rootView);
  }

  bool _destroyed = false;

  @override
  void destroy() {
    if (_destroyed) {
      return;
    }
    final Pointer<Void> handle = getWindowHandle();
    _MacOSPlatformInterface.destroyWindow(handle);
  }

  void _handleOnShouldClose() {
    _delegate.onWindowCloseRequested(this);
  }

  void _handleOnWillClose() {
    _destroyed = true;
    _owner._activeControllers.remove(this);
    _delegate.onWindowDestroyed();
    _onShouldClose.close();
    _onWillClose.close();
    _onResize.close();
  }

  void _handleOnResize() {
    notifyListeners();
  }

  @override
  @internal
  void setSize(Size size) {
    _ensureNotDestroyed();
    _MacOSPlatformInterface.setWindowContentSize(getWindowHandle(), size);
  }

  @override
  @internal
  void setConstraints(BoxConstraints constraints) {
    _ensureNotDestroyed();
    _MacOSPlatformInterface.setWindowConstraints(getWindowHandle(), constraints);
  }

  @override
  void setTitle(String title) {
    _ensureNotDestroyed();
    _MacOSPlatformInterface.setWindowTitle(getWindowHandle(), title);
    notifyListeners();
  }

  final WindowingOwnerMacOS _owner;
  final RegularWindowControllerDelegate _delegate;
  late final NativeCallable<Void Function()> _onShouldClose;
  late final NativeCallable<Void Function()> _onWillClose;
  late final NativeCallable<Void Function()> _onResize;

  @override
  Size get contentSize {
    _ensureNotDestroyed();
    return _MacOSPlatformInterface.getWindowContentSize(getWindowHandle());
  }

  @override
  void activate() {
    _ensureNotDestroyed();
    _MacOSPlatformInterface.activate(getWindowHandle());
  }

  @override
  void setMaximized(bool maximized) {
    _ensureNotDestroyed();
    _MacOSPlatformInterface.setMaximized(getWindowHandle(), maximized);
  }

  @override
  bool get isMaximized {
    _ensureNotDestroyed();
    return _MacOSPlatformInterface.isMaximized(getWindowHandle());
  }

  @override
  void setMinimized(bool minimized) {
    _ensureNotDestroyed();
    if (minimized) {
      _MacOSPlatformInterface.minimize(getWindowHandle());
    } else {
      _MacOSPlatformInterface.unminimize(getWindowHandle());
    }
  }

  @override
  bool get isMinimized {
    _ensureNotDestroyed();
    return _MacOSPlatformInterface.isMinimized(getWindowHandle());
  }

  @override
  void setFullscreen(bool fullscreen, {Display? display}) {
    _ensureNotDestroyed();
    _MacOSPlatformInterface.setFullscreen(getWindowHandle(), fullscreen);
  }

  @override
  bool get isFullscreen {
    _ensureNotDestroyed();
    return _MacOSPlatformInterface.isFullscreen(getWindowHandle());
  }

  void _ensureNotDestroyed() {
    if (_destroyed) {
      throw StateError('Window has been destroyed.');
    }
  }

  @override
  bool get isActivated => _MacOSPlatformInterface.isActivated(getWindowHandle());

  @override
  String get title => _MacOSPlatformInterface.getTitle(getWindowHandle());
}

/// Implementation of [DialogWindowController] for the macOS platform.
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
///  * [DialogWindowController], the base class for dialog windows.
class DialogWindowControllerMacOS extends DialogWindowController {
  /// Creates a new regular window controller for macOS. When this constructor
  /// completes the FlutterView is created and framework is aware of it.
  DialogWindowControllerMacOS({
    required WindowingOwnerMacOS owner,
    required DialogWindowControllerDelegate delegate,
    required Size? preferredSize,
    this.parent,
    BoxConstraints? preferredConstraints,
    String? title,
  }) : _owner = owner,
       _delegate = delegate,
       super.empty() {
    if (!isWindowingEnabled) {
      throw UnsupportedError(_kWindowingDisabledErrorMessage);
    }

    _onShouldClose = NativeCallable<Void Function()>.isolateLocal(_handleOnShouldClose);
    _onWillClose = NativeCallable<Void Function()>.isolateLocal(_handleOnWillClose);
    _onResize = NativeCallable<Void Function()>.isolateLocal(_handleOnResize);
    final int viewId = _MacOSPlatformInterface.createDialogWindow(
      preferredSize: preferredSize,
      preferredConstraints: preferredConstraints,
      onShouldClose: _onShouldClose.nativeFunction,
      onWillClose: _onWillClose.nativeFunction,
      onNotifyListeners: _onResize.nativeFunction,
      parentViewId: parent?.rootView.viewId,
    );
    final FlutterView flutterView = WidgetsBinding.instance.platformDispatcher.views.firstWhere(
      (FlutterView view) => view.viewId == viewId,
    );
    rootView = flutterView;
    if (title != null) {
      setTitle(title);
    }
  }

  /// Returns the window handle for this window.
  ///
  /// The handle is a pointer to an `NSWindow` instance.
  Pointer<Void> getWindowHandle() {
    _ensureNotDestroyed();
    return WindowingOwnerMacOS.getWindowHandle(rootView);
  }

  bool _destroyed = false;

  @override
  void destroy() {
    if (_destroyed) {
      return;
    }
    final Pointer<Void> handle = getWindowHandle();
    _MacOSPlatformInterface.destroyWindow(handle);
  }

  void _handleOnShouldClose() {
    _delegate.onWindowCloseRequested(this);
  }

  void _handleOnWillClose() {
    _destroyed = true;
    _owner._activeControllers.remove(this);
    _delegate.onWindowDestroyed();
    _onShouldClose.close();
    _onWillClose.close();
    _onResize.close();
  }

  void _handleOnResize() {
    notifyListeners();
  }

  @override
  @internal
  void setSize(Size size) {
    _ensureNotDestroyed();
    _MacOSPlatformInterface.setWindowContentSize(getWindowHandle(), size);
  }

  @override
  @internal
  void setConstraints(BoxConstraints constraints) {
    _ensureNotDestroyed();
    _MacOSPlatformInterface.setWindowConstraints(getWindowHandle(), constraints);
  }

  @override
  void setTitle(String title) {
    _ensureNotDestroyed();
    _MacOSPlatformInterface.setWindowTitle(getWindowHandle(), title);
    notifyListeners();
  }

  final WindowingOwnerMacOS _owner;
  final DialogWindowControllerDelegate _delegate;
  late final NativeCallable<Void Function()> _onShouldClose;
  late final NativeCallable<Void Function()> _onWillClose;
  late final NativeCallable<Void Function()> _onResize;

  @override
  Size get contentSize {
    _ensureNotDestroyed();
    return _MacOSPlatformInterface.getWindowContentSize(getWindowHandle());
  }

  @override
  void activate() {
    _ensureNotDestroyed();
    _MacOSPlatformInterface.activate(getWindowHandle());
  }

  @override
  void setMinimized(bool minimized) {
    _ensureNotDestroyed();
    if (minimized) {
      _MacOSPlatformInterface.minimize(getWindowHandle());
    } else {
      _MacOSPlatformInterface.unminimize(getWindowHandle());
    }
  }

  @override
  bool get isMinimized {
    _ensureNotDestroyed();
    return _MacOSPlatformInterface.isMinimized(getWindowHandle());
  }

  void _ensureNotDestroyed() {
    if (_destroyed) {
      throw StateError('Window has been destroyed.');
    }
  }

  @override
  bool get isActivated => _MacOSPlatformInterface.isActivated(getWindowHandle());

  @override
  String get title => _MacOSPlatformInterface.getTitle(getWindowHandle());

  @override
  final BaseWindowController? parent;
}

final class _WindowCreationRequest extends Struct {
  @Bool()
  external bool hasSize;
  external _Size contentSize;

  @Bool()
  external bool hasConstraints;
  external _Constraints constraints;

  @Int64()
  external int parentViewId;

  external Pointer<NativeFunction<Void Function()>> onShouldClose;
  external Pointer<NativeFunction<Void Function()>> onWillClose;
  external Pointer<NativeFunction<Void Function()>> onNotifyListeners;
}

final class _Size extends Struct {
  @Double()
  external double width;

  @Double()
  external double height;
}

final class _Constraints extends Struct {
  @Double()
  external double minWidth;

  @Double()
  external double minHeight;

  @Double()
  external double maxWidth;

  @Double()
  external double maxHeight;
}

class _MacOSPlatformInterface {
  @Native<Pointer<Void> Function(Int64, Int64)>(symbol: 'InternalFlutter_Window_GetHandle')
  external static Pointer<Void> getWindowHandle(int engineId, int viewId);

  @Native<Void Function(Pointer<Void>, Pointer<_Size>)>(
    symbol: 'InternalFlutter_Window_SetContentSize',
  )
  external static void _setWindowContentSize(Pointer<Void> windowHandle, Pointer<_Size> size);

  static void setWindowContentSize(Pointer<Void> windowHandle, Size size) {
    final Pointer<_Size> ffiSize = _allocator<_Size>();
    ffiSize.ref
      ..width = size.width
      ..height = size.height;
    _setWindowContentSize(windowHandle, ffiSize);
    _allocator.free(ffiSize);
  }

  @Native<Void Function(Pointer<Void>, Pointer<_Constraints>)>(
    symbol: 'InternalFlutter_Window_SetConstraints',
  )
  external static void _setWindowConstraints(
    Pointer<Void> windowHandle,
    Pointer<_Constraints> size,
  );

  static void setWindowConstraints(Pointer<Void> windowHandle, BoxConstraints constraints) {
    final Pointer<_Constraints> ffiConstraints = _allocator<_Constraints>();
    ffiConstraints.ref
      ..minWidth = constraints.minWidth
      ..minHeight = constraints.minHeight
      ..maxWidth = constraints.maxWidth
      ..maxHeight = constraints.maxHeight;

    _setWindowConstraints(windowHandle, ffiConstraints);
    _allocator.free(ffiConstraints);
  }

  @Native<Int64 Function(Int64, Pointer<_WindowCreationRequest>)>(
    symbol: 'InternalFlutter_WindowController_CreateRegularWindow',
  )
  external static int _createRegularWindow(int engineId, Pointer<_WindowCreationRequest> request);

  /// Creates a new window and returns the viewId of the created FlutterView.
  static int createRegularWindow({
    required Size? preferredSize,
    BoxConstraints? preferredConstraints,
    required Pointer<NativeFunction<Void Function()>> onShouldClose,
    required Pointer<NativeFunction<Void Function()>> onWillClose,
    required Pointer<NativeFunction<Void Function()>> onNotifyListeners,
  }) {
    final Pointer<_WindowCreationRequest> request = _allocator<_WindowCreationRequest>()
      ..ref.onShouldClose = onShouldClose
      ..ref.onWillClose = onWillClose
      ..ref.onNotifyListeners = onNotifyListeners;

    if (preferredSize != null) {
      request.ref
        ..hasSize = true
        ..contentSize.width = preferredSize.width
        ..contentSize.height = preferredSize.height;
    }

    if (preferredConstraints != null) {
      request.ref
        ..hasConstraints = true
        ..constraints.minWidth = preferredConstraints.minWidth
        ..constraints.minHeight = preferredConstraints.minHeight
        ..constraints.maxWidth = preferredConstraints.maxWidth
        ..constraints.maxHeight = preferredConstraints.maxHeight;
    }
    final int viewId = _createRegularWindow(
      WidgetsBinding.instance.platformDispatcher.engineId!,
      request,
    );
    _allocator.free(request);
    return viewId;
  }

  @Native<Int64 Function(Int64, Pointer<_WindowCreationRequest>)>(
    symbol: 'InternalFlutter_WindowController_CreateDialogWindow',
  )
  external static int _createDialogWindow(int engineId, Pointer<_WindowCreationRequest> request);

  /// Creates a new window and returns the viewId of the created FlutterView.
  static int createDialogWindow({
    required Size? preferredSize,
    BoxConstraints? preferredConstraints,
    int? parentViewId,
    required Pointer<NativeFunction<Void Function()>> onShouldClose,
    required Pointer<NativeFunction<Void Function()>> onWillClose,
    required Pointer<NativeFunction<Void Function()>> onNotifyListeners,
  }) {
    final Pointer<_WindowCreationRequest> request = _allocator<_WindowCreationRequest>()
      ..ref.onShouldClose = onShouldClose
      ..ref.onWillClose = onWillClose
      ..ref.onNotifyListeners = onNotifyListeners
      ..ref.parentViewId = parentViewId ?? 0;

    if (preferredSize != null) {
      request.ref
        ..hasSize = true
        ..contentSize.width = preferredSize.width
        ..contentSize.height = preferredSize.height;
    }

    if (preferredConstraints != null) {
      request.ref
        ..hasConstraints = true
        ..constraints.minWidth = preferredConstraints.minWidth
        ..constraints.minHeight = preferredConstraints.minHeight
        ..constraints.maxWidth = preferredConstraints.maxWidth
        ..constraints.maxHeight = preferredConstraints.maxHeight;
    }
    try {
      final int viewId = _createDialogWindow(
        WidgetsBinding.instance.platformDispatcher.engineId!,
        request,
      );
      return viewId;
    } finally {
      _allocator.free(request);
    }
  }

  @Native<Void Function(Int64, Pointer<Void>)>(symbol: 'InternalFlutter_Window_Destroy')
  external static void _destroyWindow(int engineId, Pointer<Void> handle);

  static void destroyWindow(Pointer<Void> windowHandle) {
    _destroyWindow(WidgetsBinding.instance.platformDispatcher.engineId!, windowHandle);
  }

  @Native<_Size Function(Pointer<Void>)>(symbol: 'InternalFlutter_Window_GetContentSize')
  external static _Size _getWindowContentSize(Pointer<Void> windowHandle);

  static Size getWindowContentSize(Pointer<Void> windowHandle) {
    final _Size size = _getWindowContentSize(windowHandle);
    return Size(size.width, size.height);
  }

  @Native<Void Function(Pointer<Void>, Pointer<_Utf8>)>(symbol: 'InternalFlutter_Window_SetTitle')
  external static void _setWindowTitle(Pointer<Void> windowHandle, Pointer<_Utf8> title);

  static void setWindowTitle(Pointer<Void> windowHandle, String title) {
    final Pointer<_Utf8> titlePointer = title.toNativeUtf8();
    _setWindowTitle(windowHandle, titlePointer);
    _allocator.free(titlePointer);
  }

  @Native<Void Function(Pointer<Void>, Bool)>(symbol: 'InternalFlutter_Window_SetMaximized')
  external static void setMaximized(Pointer<Void> windowHandle, bool maximized);

  @Native<Bool Function(Pointer<Void>)>(symbol: 'InternalFlutter_Window_IsMaximized')
  external static bool isMaximized(Pointer<Void> windowHandle);

  @Native<Void Function(Pointer<Void>)>(symbol: 'InternalFlutter_Window_Minimize')
  external static void minimize(Pointer<Void> windowHandle);

  @Native<Void Function(Pointer<Void>)>(symbol: 'InternalFlutter_Window_Unminimize')
  external static void unminimize(Pointer<Void> windowHandle);

  @Native<Bool Function(Pointer<Void>)>(symbol: 'InternalFlutter_Window_IsMinimized')
  external static bool isMinimized(Pointer<Void> windowHandle);

  @Native<Void Function(Pointer<Void>, Bool)>(symbol: 'InternalFlutter_Window_SetFullScreen')
  external static void setFullscreen(Pointer<Void> windowHandle, bool fullscreen);

  @Native<Bool Function(Pointer<Void>)>(symbol: 'InternalFlutter_Window_IsFullScreen')
  external static bool isFullscreen(Pointer<Void> windowHandle);

  @Native<Void Function(Pointer<Void>)>(symbol: 'InternalFlutter_Window_Activate')
  external static void activate(Pointer<Void> windowHandle);

  @Native<Pointer<_Utf8> Function(Pointer<Void>)>(symbol: 'InternalFlutter_Window_GetTitle')
  external static Pointer<_Utf8> _getTitle(Pointer<Void> windowHandle);

  static String getTitle(Pointer<Void> windowHandle) {
    final Pointer<_Utf8> title = _getTitle(windowHandle);
    final String result = title.toDartString();
    _allocator.free(title);
    return result;
  }

  @Native<Bool Function(Pointer<Void>)>(symbol: 'InternalFlutter_Window_IsActivated')
  external static bool isActivated(Pointer<Void> windowHandle);
}

// FFI utilities.

typedef _PosixCallocNative = Pointer<Void> Function(IntPtr num, IntPtr size);

@Native<_PosixCallocNative>(symbol: 'calloc')
external Pointer<Void> _posixCalloc(int num, int size);

typedef _PosixFreeNative = Void Function(Pointer<NativeType>);

@Native<Void Function(Pointer<NativeType>)>(symbol: 'free')
external void _posixFree(Pointer<NativeType> ptr);

final Pointer<NativeFunction<_PosixFreeNative>> _posixFreePointer =
    Native.addressOf<NativeFunction<_PosixFreeNative>>(_posixFree);

const _CallocAllocator _allocator = _CallocAllocator._();

final class _CallocAllocator implements Allocator {
  const _CallocAllocator._();

  /// Allocates [byteCount] bytes of zero-initialized of memory on the native
  /// heap.
  @override
  Pointer<T> allocate<T extends NativeType>(int byteCount, {int? alignment}) {
    final Pointer<T> result = _posixCalloc(byteCount, 1).cast();

    if (result.address == 0) {
      throw ArgumentError('Could not allocate $byteCount bytes.');
    }
    return result;
  }

  /// Releases memory allocated on the native heap.
  @override
  void free(Pointer<NativeType> pointer) {
    _posixFree(pointer);
  }

  /// Returns a pointer to a native free function.
  Pointer<NativeFinalizerFunction> get nativeFree => _posixFreePointer;
}

/// The contents of a native zero-terminated array of UTF-8 code units.
///
/// The Utf8 type itself has no functionality, it's only intended to be used
/// through a `Pointer<Utf8>` representing the entire array. This pointer is
/// the equivalent of a char pointer (`const char*`) in C code.
final class _Utf8 extends Opaque {}

/// Extension method for converting a`Pointer<Utf8>` to a [String].
extension _Utf8Pointer on Pointer<_Utf8> {
  /// Converts this UTF-8 encoded string to a Dart string.
  ///
  /// Decodes the UTF-8 code units of this zero-terminated byte array as
  /// Unicode code points and creates a Dart string containing those code
  /// points.
  ///
  /// If [length] is provided, zero-termination is ignored and the result can
  /// contain NUL characters.
  ///
  /// If [length] is not provided, the returned string is the string up til
  /// but not including  the first NUL character.
  String toDartString({int? length}) {
    _ensureNotNullptr('toDartString');
    final Pointer<Uint8> codeUnits = cast<Uint8>();
    if (length != null) {
      RangeError.checkNotNegative(length, 'length');
    } else {
      length = _length(codeUnits);
    }
    return utf8.decode(codeUnits.asTypedList(length));
  }

  static int _length(Pointer<Uint8> codeUnits) {
    var length = 0;
    while (codeUnits[length] != 0) {
      length++;
    }
    return length;
  }

  void _ensureNotNullptr(String operation) {
    if (this == nullptr) {
      throw UnsupportedError("Operation '$operation' not allowed on a 'nullptr'.");
    }
  }
}

/// Extension method for converting a [String] to a `Pointer<Utf8>`.
extension _StringUtf8Pointer on String {
  /// Creates a zero-terminated [_Utf8] code-unit array from this String.
  ///
  /// If this [String] contains NUL characters, converting it back to a string
  /// using [_Utf8Pointer.toDartString] will truncate the result if a length is
  /// not passed.
  ///
  /// Unpaired surrogate code points in this [String] will be encoded as
  /// replacement characters (U+FFFD, encoded as the bytes 0xEF 0xBF 0xBD) in
  /// the UTF-8 encoded result. See [Utf8Encoder] for details on encoding.
  ///
  /// Returns an [allocator]-allocated pointer to the result.
  Pointer<_Utf8> toNativeUtf8({Allocator allocator = _allocator}) {
    final Uint8List units = utf8.encode(this);
    final Pointer<Uint8> result = allocator<Uint8>(units.length + 1);
    final Uint8List nativeString = result.asTypedList(units.length + 1);
    nativeString.setAll(0, units);
    nativeString[units.length] = 0;
    return result.cast();
  }
}
