// 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.

// 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.

import 'dart:ffi' as ffi;
import 'dart:io';
import 'dart:typed_data';
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';

/// A Win32 window handle.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
typedef HWND = ffi.Pointer<ffi.Void>;

const int _WM_DESTROY = 0x0002;
const int _WM_SIZE = 0x0005;
const int _WM_ACTIVATE = 0x0006;
const int _WM_CLOSE = 0x0010;

const int _SW_RESTORE = 9;
const int _SW_MAXIMIZE = 3;
const int _SW_MINIMIZE = 6;

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.
''';

/// Abstract handler class for Windows messages.
///
/// Implementations of this class should register with
/// [WindowingOwnerWin32.addMessageHandler] to begin receiving messages.
/// When finished handling messages, implementations should deregister
/// themselves with [WindowingOwnerWin32.removeMessageHandler].
abstract class _WindowsMessageHandler {
  /// Handles a window message.
  ///
  /// Returned value, if not null will be returned to the system as LRESULT
  /// and will stop all other handlers from being called. See
  /// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nc-winuser-wndproc
  /// for more information.
  ///
  /// {@macro flutter.widgets.windowing.experimental}
  @internal
  int? handleWindowsMessage(
    FlutterView view,
    HWND windowHandle,
    int message,
    int wParam,
    int lParam,
  );
}

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

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

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

    _Win32PlatformInterface.initializeWindowing(
      allocator,
      WidgetsBinding.instance.platformDispatcher.engineId!,
      _onMessage,
    );
  }

  final List<_WindowsMessageHandler> _messageHandlers = <_WindowsMessageHandler>[];

  /// The [Allocator] used for allocating native memory in this owner.
  ///
  /// This can be overridden via the [WindowingOwnerWin32.test] constructor.
  ///
  /// {@macro flutter.widgets.windowing.experimental}
  @internal
  final ffi.Allocator allocator;

  @internal
  @override
  RegularWindowController createRegularWindowController({
    Size? preferredSize,
    BoxConstraints? preferredConstraints,
    String? title,
    required RegularWindowControllerDelegate delegate,
  }) {
    return RegularWindowControllerWin32(
      owner: this,
      delegate: delegate,
      preferredSize: preferredSize,
      preferredConstraints: preferredConstraints,
      title: title,
    );
  }

  @internal
  @override
  DialogWindowController createDialogWindowController({
    required DialogWindowControllerDelegate delegate,
    Size? preferredSize,
    BoxConstraints? preferredConstraints,
    BaseWindowController? parent,
    String? title,
  }) {
    return DialogWindowControllerWin32(
      owner: this,
      delegate: delegate,
      preferredSize: preferredSize,
      preferredConstraints: preferredConstraints,
      title: title,
      parent: parent,
    );
  }

  @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 Windows.');
  }

  @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 Windows.');
  }

  /// Register a new [WindowsMessageHandler].
  ///
  /// The handler will be triggered for unhandled messages for all top level
  /// windows.
  ///
  /// Adding a handler multiple times has no effect.
  ///
  /// Handlers are called in the order that they are added.
  ///
  /// Callers must remove their message handlers using
  /// [WindowingOwnerWin32._removeMessageHandler].
  void _addMessageHandler(_WindowsMessageHandler handler) {
    if (_messageHandlers.contains(handler)) {
      return;
    }

    _messageHandlers.add(handler);
  }

  /// Unregister a [WindowsMessageHandler].
  ///
  /// If the handler has not been registered, this method has no effect.
  void _removeMessageHandler(_WindowsMessageHandler handler) {
    _messageHandlers.remove(handler);
  }

  void _onMessage(ffi.Pointer<_WindowsMessage> message) {
    final FlutterView flutterView = WidgetsBinding.instance.platformDispatcher.views.firstWhere(
      (FlutterView view) => view.viewId == message.ref.viewId,
    );

    final int handlesLength = _messageHandlers.length;
    for (final _WindowsMessageHandler handler in _messageHandlers) {
      assert(
        _messageHandlers.length == handlesLength,
        'Message handler list changed while processing message: $message',
      );
      final int? result = handler.handleWindowsMessage(
        flutterView,
        message.ref.windowHandle,
        message.ref.message,
        message.ref.wParam,
        message.ref.lParam,
      );
      if (result != null) {
        message.ref.handled = true;
        message.ref.lResult = result;
        return;
      }
    }
  }
}

class _RegularWindowMesageHandler implements _WindowsMessageHandler {
  _RegularWindowMesageHandler({required this.controller});

  final RegularWindowControllerWin32 controller;

  @override
  int? handleWindowsMessage(
    FlutterView view,
    HWND windowHandle,
    int message,
    int wParam,
    int lParam,
  ) {
    return controller._handleWindowsMessage(view, windowHandle, message, wParam, lParam);
  }
}

/// Implementation of [RegularWindowController] for the Windows platform.
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
///  * [RegularWindowController], the base class for regular windows.
class RegularWindowControllerWin32 extends RegularWindowController {
  /// Creates a new regular window controller for Win32.
  ///
  /// When this constructor completes the native window has been created and
  /// has a view associated with it.
  ///
  /// {@macro flutter.widgets.windowing.experimental}
  ///
  /// See also:
  ///
  ///  * [RegularWindowController], the base class for regular windows.
  @internal
  RegularWindowControllerWin32({
    required WindowingOwnerWin32 owner,
    required RegularWindowControllerDelegate delegate,
    Size? preferredSize,
    BoxConstraints? preferredConstraints,
    String? title,
  }) : _owner = owner,
       _delegate = delegate,
       super.empty() {
    if (!isWindowingEnabled) {
      throw UnsupportedError(_kWindowingDisabledErrorMessage);
    }

    _handler = _RegularWindowMesageHandler(controller: this);
    owner._addMessageHandler(_handler);
    final int viewId = _Win32PlatformInterface.createRegularWindow(
      _owner.allocator,
      WidgetsBinding.instance.platformDispatcher.engineId!,
      preferredSize,
      preferredConstraints,
      title,
    );
    if (viewId < 0) {
      throw Exception('Windows failed to create a regular window with a valid view id.');
    }

    final FlutterView flutterView = WidgetsBinding.instance.platformDispatcher.views.firstWhere(
      (FlutterView view) => view.viewId == viewId,
    );
    rootView = flutterView;
  }

  final WindowingOwnerWin32 _owner;
  final RegularWindowControllerDelegate _delegate;
  late final _RegularWindowMesageHandler _handler;
  bool _destroyed = false;

  @override
  @internal
  Size get contentSize {
    _ensureNotDestroyed();
    final _ActualContentSize size = _Win32PlatformInterface.getWindowContentSize(getWindowHandle());
    final result = Size(size.width, size.height);
    return result;
  }

  @override
  @internal
  String get title {
    _ensureNotDestroyed();
    return _Win32PlatformInterface.getWindowTitle(_owner.allocator, getWindowHandle());
  }

  @override
  @internal
  bool get isActivated {
    _ensureNotDestroyed();
    return _Win32PlatformInterface.getForegroundWindow() == getWindowHandle();
  }

  @override
  @internal
  bool get isMaximized {
    _ensureNotDestroyed();
    return _Win32PlatformInterface.isZoomed(getWindowHandle()) != 0;
  }

  @override
  @internal
  bool get isMinimized {
    _ensureNotDestroyed();
    return _Win32PlatformInterface.isIconic(getWindowHandle()) != 0;
  }

  @override
  @internal
  bool get isFullscreen {
    _ensureNotDestroyed();
    return _Win32PlatformInterface.getFullscreen(getWindowHandle());
  }

  @override
  @internal
  void setSize(Size? size) {
    _ensureNotDestroyed();
    _Win32PlatformInterface.setWindowContentSize(_owner.allocator, getWindowHandle(), size);
  }

  @override
  @internal
  void setConstraints(BoxConstraints constraints) {
    _ensureNotDestroyed();
    _Win32PlatformInterface.setWindowConstraints(_owner.allocator, getWindowHandle(), constraints);
    notifyListeners();
  }

  @override
  @internal
  void setTitle(String title) {
    _ensureNotDestroyed();
    _Win32PlatformInterface.setWindowTitle(_owner.allocator, getWindowHandle(), title);
    notifyListeners();
  }

  @override
  @internal
  void activate() {
    _ensureNotDestroyed();
    _Win32PlatformInterface.showWindow(getWindowHandle(), _SW_RESTORE);
  }

  @override
  @internal
  void setMaximized(bool maximized) {
    _ensureNotDestroyed();
    if (maximized) {
      _Win32PlatformInterface.showWindow(getWindowHandle(), _SW_MAXIMIZE);
    } else {
      _Win32PlatformInterface.showWindow(getWindowHandle(), _SW_RESTORE);
    }
  }

  @override
  @internal
  void setMinimized(bool minimized) {
    _ensureNotDestroyed();
    if (minimized) {
      _Win32PlatformInterface.showWindow(getWindowHandle(), _SW_MINIMIZE);
    } else {
      _Win32PlatformInterface.showWindow(getWindowHandle(), _SW_RESTORE);
    }
  }

  @override
  @internal
  void setFullscreen(bool fullscreen, {Display? display}) {
    _Win32PlatformInterface.setFullscreen(
      _owner.allocator,
      getWindowHandle(),
      fullscreen,
      display: display,
    );
  }

  /// Returns HWND pointer to the top level window.
  @internal
  HWND getWindowHandle() {
    _ensureNotDestroyed();
    return _Win32PlatformInterface.getWindowHandle(
      WidgetsBinding.instance.platformDispatcher.engineId!,
      rootView.viewId,
    );
  }

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

  @override
  void destroy() {
    if (_destroyed) {
      return;
    }
    _Win32PlatformInterface.destroyWindow(getWindowHandle());
    _destroyed = true;
  }

  int? _handleWindowsMessage(
    FlutterView view,
    HWND windowHandle,
    int message,
    int wParam,
    int lParam,
  ) {
    if (view.viewId != rootView.viewId) {
      return null;
    }

    if (message == _WM_CLOSE) {
      _delegate.onWindowCloseRequested(this);
      return 0;
    } else if (message == _WM_DESTROY) {
      _destroyed = true;
      _owner._removeMessageHandler(_handler);
      _delegate.onWindowDestroyed();
      return 0;
    } else if (message == _WM_SIZE || message == _WM_ACTIVATE) {
      notifyListeners();
    }
    return null;
  }
}

class _DialogWindowMesageHandler implements _WindowsMessageHandler {
  _DialogWindowMesageHandler({required this.controller});

  final DialogWindowControllerWin32 controller;

  @override
  int? handleWindowsMessage(
    FlutterView view,
    HWND windowHandle,
    int message,
    int wParam,
    int lParam,
  ) {
    return controller._handleWindowsMessage(view, windowHandle, message, wParam, lParam);
  }
}

/// Implementation of [DialogWindowController] for the Windows platform.
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
///  * [DialogWindowController], the base class for dialog windows.
class DialogWindowControllerWin32 extends DialogWindowController {
  /// Creates a new dialog window controller for Win32.
  ///
  /// When this constructor completes the native window has been created and
  /// has a view associated with it.
  ///
  /// {@macro flutter.widgets.windowing.experimental}
  ///
  /// See also:
  ///
  ///  * [DialogWindowController], the base class for dialog windows.
  @internal
  DialogWindowControllerWin32({
    required WindowingOwnerWin32 owner,
    required DialogWindowControllerDelegate delegate,
    Size? preferredSize,
    BoxConstraints? preferredConstraints,
    String? title,
    BaseWindowController? parent,
  }) : _owner = owner,
       _delegate = delegate,
       _parent = parent,
       super.empty() {
    if (!isWindowingEnabled) {
      throw UnsupportedError(_kWindowingDisabledErrorMessage);
    }

    _handler = _DialogWindowMesageHandler(controller: this);
    owner._addMessageHandler(_handler);
    final int viewId = _Win32PlatformInterface.createDialogWindow(
      _owner.allocator,
      WidgetsBinding.instance.platformDispatcher.engineId!,
      preferredSize,
      preferredConstraints,
      title,
      parent != null
          ? _Win32PlatformInterface.getWindowHandle(
              WidgetsBinding.instance.platformDispatcher.engineId!,
              parent.rootView.viewId,
            )
          : null,
    );
    if (viewId < 0) {
      throw Exception('Windows failed to create a dialog window with a valid view id.');
    }

    final FlutterView flutterView = WidgetsBinding.instance.platformDispatcher.views.firstWhere(
      (FlutterView view) => view.viewId == viewId,
    );
    rootView = flutterView;
  }

  final WindowingOwnerWin32 _owner;
  final DialogWindowControllerDelegate _delegate;
  final BaseWindowController? _parent;
  late final _DialogWindowMesageHandler _handler;
  bool _destroyed = false;

  @override
  @internal
  Size get contentSize {
    _ensureNotDestroyed();
    final _ActualContentSize size = _Win32PlatformInterface.getWindowContentSize(getWindowHandle());
    final result = Size(size.width, size.height);
    return result;
  }

  @override
  @internal
  String get title {
    _ensureNotDestroyed();
    return _Win32PlatformInterface.getWindowTitle(_owner.allocator, getWindowHandle());
  }

  @override
  @internal
  bool get isActivated {
    _ensureNotDestroyed();
    return _Win32PlatformInterface.getForegroundWindow() == getWindowHandle();
  }

  @override
  @internal
  bool get isMinimized {
    _ensureNotDestroyed();
    return _Win32PlatformInterface.isIconic(getWindowHandle()) != 0;
  }

  @override
  @internal
  void setSize(Size? size) {
    _ensureNotDestroyed();
    _Win32PlatformInterface.setWindowContentSize(_owner.allocator, getWindowHandle(), size);
    // Note that we do not notify the listener when setting the size,
    // as that will happen when the WM_SIZE message is received in
    // _handleWindowsMessage.
  }

  @override
  @internal
  void setConstraints(BoxConstraints constraints) {
    _ensureNotDestroyed();
    _Win32PlatformInterface.setWindowConstraints(_owner.allocator, getWindowHandle(), constraints);
    notifyListeners();
  }

  @override
  @internal
  void setTitle(String title) {
    _ensureNotDestroyed();
    _Win32PlatformInterface.setWindowTitle(_owner.allocator, getWindowHandle(), title);
    notifyListeners();
  }

  @override
  @internal
  void activate() {
    _ensureNotDestroyed();
    _Win32PlatformInterface.showWindow(getWindowHandle(), _SW_RESTORE);
  }

  @override
  @internal
  void setMinimized(bool minimized) {
    if (parent != null) {
      return;
    }

    _ensureNotDestroyed();
    if (minimized) {
      _Win32PlatformInterface.showWindow(getWindowHandle(), _SW_MINIMIZE);
    } else {
      _Win32PlatformInterface.showWindow(getWindowHandle(), _SW_RESTORE);
    }
  }

  @override
  @internal
  BaseWindowController? get parent => _parent;

  /// Returns HWND pointer to the top level window.
  @internal
  HWND getWindowHandle() {
    _ensureNotDestroyed();
    return _Win32PlatformInterface.getWindowHandle(
      WidgetsBinding.instance.platformDispatcher.engineId!,
      rootView.viewId,
    );
  }

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

  @override
  void destroy() {
    if (_destroyed) {
      return;
    }
    _Win32PlatformInterface.destroyWindow(getWindowHandle());
  }

  int? _handleWindowsMessage(
    FlutterView view,
    HWND windowHandle,
    int message,
    int wParam,
    int lParam,
  ) {
    if (view.viewId != rootView.viewId) {
      return null;
    }

    if (message == _WM_CLOSE) {
      _delegate.onWindowCloseRequested(this);
      return 0;
    } else if (message == _WM_DESTROY) {
      _destroyed = true;
      _owner._removeMessageHandler(_handler);
      _delegate.onWindowDestroyed();
      return 0;
    } else if (message == _WM_SIZE || message == _WM_ACTIVATE) {
      notifyListeners();
    }
    return null;
  }
}

class _Win32PlatformInterface {
  static void initializeWindowing(
    ffi.Allocator allocator,
    int engineId,
    void Function(ffi.Pointer<_WindowsMessage>) onMessage,
  ) {
    final ffi.Pointer<_WindowingInitRequest> request = allocator<_WindowingInitRequest>();
    try {
      request.ref.onMessage =
          ffi.NativeCallable<ffi.Void Function(ffi.Pointer<_WindowsMessage>)>.isolateLocal(
            onMessage,
          ).nativeFunction;
      _initializeWindowing(engineId, request);
    } finally {
      allocator.free(request);
    }
  }

  @ffi.Native<ffi.Void Function(ffi.Int64, ffi.Pointer<_WindowingInitRequest>)>(
    symbol: 'InternalFlutterWindows_WindowManager_Initialize',
  )
  external static void _initializeWindowing(
    int engineId,
    ffi.Pointer<_WindowingInitRequest> request,
  );

  static int createRegularWindow(
    ffi.Allocator allocator,
    int engineId,
    Size? preferredSize,
    BoxConstraints? preferredConstraints,
    String? title,
  ) {
    final ffi.Pointer<_RegularWindowCreationRequest> request =
        allocator<_RegularWindowCreationRequest>();
    try {
      request.ref.preferredSize.from(preferredSize);
      request.ref.preferredConstraints.from(preferredConstraints);
      request.ref.title = (title ?? 'Regular window').toNativeUtf16(allocator: allocator);
      return _createRegularWindow(engineId, request);
    } finally {
      allocator.free(request);
    }
  }

  @ffi.Native<ffi.Int64 Function(ffi.Int64, ffi.Pointer<_RegularWindowCreationRequest>)>(
    symbol: 'InternalFlutterWindows_WindowManager_CreateRegularWindow',
  )
  external static int _createRegularWindow(
    int engineId,
    ffi.Pointer<_RegularWindowCreationRequest> request,
  );

  static int createDialogWindow(
    ffi.Allocator allocator,
    int engineId,
    Size? preferredSize,
    BoxConstraints? preferredConstraints,
    String? title,
    HWND? parent,
  ) {
    final ffi.Pointer<_DialogWindowCreationRequest> request =
        allocator<_DialogWindowCreationRequest>();
    try {
      request.ref.preferredSize.from(preferredSize);
      request.ref.preferredConstraints.from(preferredConstraints);
      request.ref.title = (title ?? 'Dialog window').toNativeUtf16(allocator: allocator);
      request.ref.parentOrNull = parent ?? ffi.Pointer<ffi.Void>.fromAddress(0);
      return _createDialogWindow(engineId, request);
    } finally {
      allocator.free(request);
    }
  }

  @ffi.Native<ffi.Int64 Function(ffi.Int64, ffi.Pointer<_DialogWindowCreationRequest>)>(
    symbol: 'InternalFlutterWindows_WindowManager_CreateDialogWindow',
  )
  external static int _createDialogWindow(
    int engineId,
    ffi.Pointer<_DialogWindowCreationRequest> request,
  );

  @ffi.Native<HWND Function(ffi.Int64, ffi.Int64)>(
    symbol: 'InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle',
  )
  external static HWND getWindowHandle(int engineId, int viewId);

  @ffi.Native<ffi.Void Function(HWND)>(symbol: 'DestroyWindow')
  external static void destroyWindow(HWND windowHandle);

  @ffi.Native<_ActualContentSize Function(HWND)>(
    symbol: 'InternalFlutterWindows_WindowManager_GetWindowContentSize',
  )
  external static _ActualContentSize getWindowContentSize(HWND windowHandle);

  static void setWindowTitle(ffi.Allocator allocator, HWND windowHandle, String title) {
    final ffi.Pointer<_Utf16> titlePointer = title.toNativeUtf16(allocator: allocator);
    try {
      _setWindowTitle(windowHandle, titlePointer);
    } finally {
      allocator.free(titlePointer);
    }
  }

  @ffi.Native<ffi.Void Function(HWND, ffi.Pointer<_Utf16>)>(symbol: 'SetWindowTextW')
  external static void _setWindowTitle(HWND windowHandle, ffi.Pointer<_Utf16> title);

  static void setWindowContentSize(ffi.Allocator allocator, HWND windowHandle, Size? size) {
    final ffi.Pointer<_WindowSizeRequest> request = allocator<_WindowSizeRequest>();
    try {
      request.ref.from(size);
      _setWindowContentSize(windowHandle, request);
    } finally {
      allocator.free(request);
    }
  }

  @ffi.Native<ffi.Void Function(HWND, ffi.Pointer<_WindowSizeRequest>)>(
    symbol: 'InternalFlutterWindows_WindowManager_SetWindowSize',
  )
  external static void _setWindowContentSize(
    HWND windowHandle,
    ffi.Pointer<_WindowSizeRequest> size,
  );

  static void setWindowConstraints(
    ffi.Allocator allocator,
    HWND windowHandle,
    BoxConstraints? constraints,
  ) {
    final ffi.Pointer<_WindowConstraintsRequest> request = allocator<_WindowConstraintsRequest>();
    try {
      request.ref.from(constraints);
      _setWindowConstraints(windowHandle, request);
    } finally {
      allocator.free(request);
    }
  }

  @ffi.Native<ffi.Void Function(HWND, ffi.Pointer<_WindowConstraintsRequest>)>(
    symbol: 'InternalFlutterWindows_WindowManager_SetWindowConstraints',
  )
  external static void _setWindowConstraints(
    HWND windowHandle,
    ffi.Pointer<_WindowConstraintsRequest> constraints,
  );

  @ffi.Native<ffi.Void Function(HWND, ffi.Int32)>(symbol: 'ShowWindow')
  external static void showWindow(HWND windowHandle, int command);

  @ffi.Native<ffi.Int32 Function(HWND)>(symbol: 'IsIconic')
  external static int isIconic(HWND windowHandle);

  @ffi.Native<ffi.Int32 Function(HWND)>(symbol: 'IsZoomed')
  external static int isZoomed(HWND windowHandle);

  static void setFullscreen(
    ffi.Allocator allocator,
    HWND windowHandle,
    bool fullscreen, {
    Display? display,
  }) {
    final ffi.Pointer<_WindowFullscreenRequest> request = allocator<_WindowFullscreenRequest>();
    try {
      request.ref.fullscreen = fullscreen;
      request.ref.hasDisplayId = display != null;
      request.ref.displayId = display?.id ?? 0;
      _setFullscreen(windowHandle, request);
    } finally {
      allocator.free(request);
    }
  }

  @ffi.Native<ffi.Void Function(HWND, ffi.Pointer<_WindowFullscreenRequest>)>(
    symbol: 'InternalFlutterWindows_WindowManager_SetFullscreen',
  )
  external static void _setFullscreen(
    HWND windowHandle,
    ffi.Pointer<_WindowFullscreenRequest> request,
  );

  @ffi.Native<ffi.Bool Function(HWND)>(symbol: 'InternalFlutterWindows_WindowManager_GetFullscreen')
  external static bool getFullscreen(HWND windowHandle);

  @ffi.Native<ffi.Int32 Function(HWND)>(symbol: 'GetWindowTextLengthW')
  external static int _getWindowTextLength(HWND windowHandle);

  @ffi.Native<ffi.Int32 Function(HWND, ffi.Pointer<_Utf16>, ffi.Int32)>(symbol: 'GetWindowTextW')
  external static int _getWindowText(
    HWND windowHandle,
    ffi.Pointer<_Utf16> lpString,
    int maxLength,
  );

  static String getWindowTitle(ffi.Allocator allocator, HWND windowHandle) {
    final int length = _getWindowTextLength(windowHandle);
    if (length == 0) {
      return '';
    }

    final ffi.Pointer<ffi.Uint16> data = allocator<ffi.Uint16>(length + 1);
    try {
      final ffi.Pointer<_Utf16> buffer = data.cast<_Utf16>();
      _getWindowText(windowHandle, buffer, length + 1);
      return buffer.toDartString();
    } finally {
      allocator.free(data);
    }
  }

  @ffi.Native<HWND Function()>(symbol: 'GetForegroundWindow')
  external static HWND getForegroundWindow();
}

/// Payload for the creation method used by [_Win32PlatformInterface.createRegularWindow].
final class _RegularWindowCreationRequest extends ffi.Struct {
  external _WindowSizeRequest preferredSize;
  external _WindowConstraintsRequest preferredConstraints;
  external ffi.Pointer<_Utf16> title;
}

/// Payload for the creation method used by [_Win32PlatformInterface.createDialogWindow].
final class _DialogWindowCreationRequest extends ffi.Struct {
  external _WindowSizeRequest preferredSize;
  external _WindowConstraintsRequest preferredConstraints;
  external ffi.Pointer<_Utf16> title;
  external HWND parentOrNull;
}

/// Payload for the initialization request for the windowing subsystem used
/// by the constructor for [WindowingOwnerWin32].
final class _WindowingInitRequest extends ffi.Struct {
  external ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<_WindowsMessage>)>>
  onMessage;
}

/// Payload for the size of a window used by [_RegularWindowCreationRequest] and
/// [_Win32PlatformInterface.setWindowContentSize].
final class _WindowSizeRequest extends ffi.Struct {
  @ffi.Bool()
  external bool hasSize;

  @ffi.Double()
  external double width;

  @ffi.Double()
  external double height;

  void from(Size? size) {
    hasSize = size != null;
    width = size?.width ?? 0;
    height = size?.height ?? 0;
  }
}

/// Payload for the constraints of a window used by [_RegularWindowCreationRequest] and
/// [_Win32PlatformInterface.setWindowConstraints].
final class _WindowConstraintsRequest extends ffi.Struct {
  @ffi.Bool()
  external bool hasConstraints;

  @ffi.Double()
  external double minWidth;

  @ffi.Double()
  external double minHeight;

  @ffi.Double()
  external double maxWidth;

  @ffi.Double()
  external double maxHeight;

  void from(BoxConstraints? constraints) {
    hasConstraints = constraints != null;
    minWidth = constraints?.minWidth ?? 0;
    minHeight = constraints?.minHeight ?? 0;
    maxWidth = constraints?.maxWidth ?? double.maxFinite;
    maxHeight = constraints?.maxHeight ?? double.maxFinite;
  }
}

/// A message received for all toplevel windows, used by [_WindowingInitRequest].
final class _WindowsMessage extends ffi.Struct {
  @ffi.Int64()
  external int viewId;

  external HWND windowHandle;

  @ffi.Int32()
  external int message;

  @ffi.Int64()
  external int wParam;

  @ffi.Int64()
  external int lParam;

  @ffi.Int64()
  external int lResult;

  @ffi.Bool()
  external bool handled;
}

/// Holds the real size of a window as retrieved from
/// [_Win32PlatformInterface.getWindowContentSize].
final class _ActualContentSize extends ffi.Struct {
  @ffi.Double()
  external double width;

  @ffi.Double()
  external double height;
}

/// Payload for the [_Win32PlatformInterface.setFullscreen] request.
final class _WindowFullscreenRequest extends ffi.Struct {
  @ffi.Bool()
  external bool fullscreen;

  @ffi.Bool()
  external bool hasDisplayId;

  @ffi.Uint64()
  external int displayId;
}

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

/// Extension method for converting a`Pointer<Utf16>` to a [String].
extension _Utf16Pointer on ffi.Pointer<_Utf16> {
  /// Converts this UTF-16 encoded string to a Dart string.
  ///
  /// Decodes the UTF-16 code units of this zero-terminated code unit 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 ffi.Pointer<ffi.Uint16> codeUnits = cast<ffi.Uint16>();
    if (length == null) {
      return _toUnknownLengthString(codeUnits);
    } else {
      RangeError.checkNotNegative(length, 'length');
      return _toKnownLengthString(codeUnits, length);
    }
  }

  static String _toKnownLengthString(ffi.Pointer<ffi.Uint16> codeUnits, int length) =>
      String.fromCharCodes(codeUnits.asTypedList(length));

  static String _toUnknownLengthString(ffi.Pointer<ffi.Uint16> codeUnits) {
    final buffer = StringBuffer();
    var i = 0;
    while (true) {
      final int char = (codeUnits + i).value;
      if (char == 0) {
        return buffer.toString();
      }
      buffer.writeCharCode(char);
      i++;
    }
  }

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

/// Extension method for converting a [String] to a `Pointer<Utf16>`.
extension _StringUtf16Pointer on String {
  /// Creates a zero-terminated [Utf16] code-unit array from this String.
  ///
  /// If this [String] contains NUL characters, converting it back to a string
  /// using [Utf16Pointer.toDartString] will truncate the result if a length is
  /// not passed.
  ///
  /// Returns an [allocator]-allocated pointer to the result.
  ffi.Pointer<_Utf16> toNativeUtf16({required ffi.Allocator allocator}) {
    final List<int> units = codeUnits;
    final ffi.Pointer<ffi.Uint16> result = allocator<ffi.Uint16>(units.length + 1);
    final Uint16List nativeString = result.asTypedList(units.length + 1);
    nativeString.setRange(0, units.length, units);
    nativeString[units.length] = 0;
    return result.cast();
  }
}

typedef _WinCoTaskMemAllocNative = ffi.Pointer<ffi.NativeType> Function(ffi.Size);
typedef _WinCoTaskMemAlloc = ffi.Pointer<ffi.NativeType> Function(int);
typedef _WinCoTaskMemFreeNative = ffi.Void Function(ffi.Pointer<ffi.NativeType>);
typedef _WinCoTaskMemFree = void Function(ffi.Pointer<ffi.NativeType>);

final class _CallocAllocator implements ffi.Allocator {
  _CallocAllocator() {
    _ole32lib = ffi.DynamicLibrary.open('ole32.dll');
    _winCoTaskMemAlloc = _ole32lib.lookupFunction<_WinCoTaskMemAllocNative, _WinCoTaskMemAlloc>(
      'CoTaskMemAlloc',
    );
    _winCoTaskMemFreePointer = _ole32lib.lookup('CoTaskMemFree');
    _winCoTaskMemFree = _winCoTaskMemFreePointer.asFunction();
  }

  late final ffi.DynamicLibrary _ole32lib;
  late final _WinCoTaskMemAlloc _winCoTaskMemAlloc;
  late final ffi.Pointer<ffi.NativeFunction<_WinCoTaskMemFreeNative>> _winCoTaskMemFreePointer;
  late final _WinCoTaskMemFree _winCoTaskMemFree;

  /// Fills a block of memory with a specified value.
  // ignore: always_specify_types
  void _fillMemory(ffi.Pointer destination, int length, int fill) {
    final ffi.Pointer<ffi.Uint8> ptr = destination.cast<ffi.Uint8>();
    for (var i = 0; i < length; i++) {
      ptr[i] = fill;
    }
  }

  /// Fills a block of memory with zeros.
  // ignore: always_specify_types
  void _zeroMemory(ffi.Pointer destination, int length) => _fillMemory(destination, length, 0);

  /// Allocates [byteCount] bytes of zero-initialized of memory on the native
  /// heap.
  @override
  ffi.Pointer<T> allocate<T extends ffi.NativeType>(int byteCount, {int? alignment}) {
    ffi.Pointer<T> result;
    result = _winCoTaskMemAlloc(byteCount).cast();
    if (result.address == 0) {
      throw ArgumentError('Could not allocate $byteCount bytes.');
    }
    if (Platform.isWindows) {
      _zeroMemory(result, byteCount);
    }
    return result;
  }

  /// Releases memory allocated on the native heap.
  @override
  // ignore: always_specify_types
  void free(ffi.Pointer pointer) {
    _winCoTaskMemFree(pointer);
  }

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