content: import 'dart:convert';
  
  import 'package:desktop_multi_window/desktop_multi_window.dart';
  import 'package:flutter/foundation.dart';
  import 'package:flutter/material.dart';
  import 'package:flutter/services.dart';
  import 'package:flutter_hbb/consts.dart';
  import 'package:flutter_hbb/common.dart';
  import 'package:flutter_hbb/main.dart';
  import 'package:flutter_hbb/models/input_model.dart';
  
  /// must keep the order
  // ignore: constant_identifier_names
  enum WindowType {
    Main,
    RemoteDesktop,
    FileTransfer,
    ViewCamera,
    PortForward,
    Terminal,
    Unknown
  }
  
  extension Index on int {
    WindowType get windowType {
      switch (this) {
        case 0:
          return WindowType.Main;
        case 1:
          return WindowType.RemoteDesktop;
        case 2:
          return WindowType.FileTransfer;
        case 3:
          return WindowType.ViewCamera;
        case 4:
          return WindowType.PortForward;
        case 5:
          return WindowType.Terminal;
        default:
          return WindowType.Unknown;
      }
    }
  }
  
  class MultiWindowCallResult {
    int windowId;
    dynamic result;
  
    MultiWindowCallResult(this.windowId, this.result);
  }
  
  /// Window Manager
  /// mainly use it in `Main Window`
  /// use it in sub window is not recommended
  class RustDeskMultiWindowManager {
    RustDeskMultiWindowManager._();
  
    static final instance = RustDeskMultiWindowManager._();
  
    final Set<int> _inactiveWindows = {};
    final Set<int> _activeWindows = {};
    final List<AsyncCallback> _windowActiveCallbacks = List.empty(growable: true);
    final List<int> _remoteDesktopWindows = List.empty(growable: true);
    final List<int> _fileTransferWindows = List.empty(growable: true);
    final List<int> _viewCameraWindows = List.empty(growable: true);
    final List<int> _portForwardWindows = List.empty(growable: true);
    final List<int> _terminalWindows = List.empty(growable: true);
  
    moveTabToNewWindow(int windowId, String peerId, String sessionId,
        WindowType windowType) async {
      var params = {
        'type': windowType.index,
        'id': peerId,
        'tab_window_id': windowId,
        'session_id': sessionId,
      };
      if (windowType == WindowType.RemoteDesktop) {
        await _newSession(
          false,
          WindowType.RemoteDesktop,
          kWindowEventNewRemoteDesktop,
          peerId,
          _remoteDesktopWindows,
          jsonEncode(params),
        );
      } else if (windowType == WindowType.ViewCamera) {
        await _newSession(
          false,
          WindowType.ViewCamera,
          kWindowEventNewViewCamera,
          peerId,
          _viewCameraWindows,
          jsonEncode(params),
        );
      }
    }
  
    // This function must be called in the main window thread.
    // Because the _remoteDesktopWindows is managed in that thread.
    openMonitorSession(int windowId, String peerId, int display, int displayCount,
        Rect? screenRect, int windowType) async {
      final isCamera = windowType == WindowType.ViewCamera.index;
      final windowIDs = isCamera ? _viewCameraWindows : _remoteDesktopWindows;
      if (windowIDs.length > 1) {
        for (final windowId in windowIDs) {
          if (await DesktopMultiWindow.invokeMethod(
              windowId,
              kWindowEventActiveDisplaySession,
              jsonEncode({
                'id': peerId,
                'display': display,
              }))) {
            return;
          }
        }
      }
  
      final displays = display == kAllDisplayValue
          ? List.generate(displayCount, (index) => index)
          : [display];
      var params = {
        'type': windowType,
        'id': peerId,
        'tab_window_id': windowId,
        'display': display,
        'displays': displays,
      };
      if (screenRect != null) {
        params['screen_rect'] = {
          'l': screenRect.left,
          't': screenRect.top,
          'r': screenRect.right,
          'b': screenRect.bottom,
        };
      }
      await _newSession(
        false,
        windowType.windowType,
        isCamera ? kWindowEventNewViewCamera : kWindowEventNewRemoteDesktop,
        peerId,
        windowIDs,
        jsonEncode(params),
        screenRect: screenRect,
      );
    }
  
    Future<int> newSessionWindow(
      WindowType type,
      String remoteId,
      String msg,
      List<int> windows,
      bool withScreenRect,
    ) async {
      final windowController = await DesktopMultiWindow.createWindow(msg);
      if (isWindows) {
        windowController.setInitBackgroundColor(Colors.black);
      }
      final windowId = windowController.windowId;
      if (!withScreenRect) {
        windowController
          ..setFrame(const Offset(0, 0) &
              Size(1280 + windowId * 20, 720 + windowId * 20))
          ..center()
          ..setTitle(getWindowNameWithId(
            remoteId,
            overrideType: type,
          ));
      } else {
        windowController.setTitle(getWindowNameWithId(
          remoteId,
          overrideType: type,
        ));
      }
      if (isMacOS) {
        Future.microtask(() => windowController.show());
      }
      registerActiveWindow(windowId);
      windows.add(windowId);
      return windowId;
    }
  
    Future<MultiWindowCallResult> _newSession(
      bool openInTabs,
      WindowType type,
      String methodName,
      String remoteId,
      List<int> windows,
      String msg, {
      Rect? screenRect,
    }) async {
      if (openInTabs) {
        if (windows.isEmpty) {
          final windowId = await newSessionWindow(
              type, remoteId, msg, windows, screenRect != null);
          return MultiWindowCallResult(windowId, null);
        } else {
          return call(type, methodName, msg);
        }
      } else {
        if (_inactiveWindows.isNotEmpty) {
          for (final windowId in windows) {
            if (_inactiveWindows.contains(windowId)) {
              if (screenRect == null) {
                await restoreWindowPosition(type,
                    windowId: windowId, peerId: remoteId);
              }
              await DesktopMultiWindow.invokeMethod(windowId, methodName, msg);
              if (methodName != kWindowEventNewRemoteDesktop) {
                WindowController.fromWindowId(windowId).show();
              }
              registerActiveWindow(windowId);
              return MultiWindowCallResult(windowId, null);
            }
          }
        }
        final windowId = await newSessionWindow(
            type, remoteId, msg, windows, screenRect != null);
        return MultiWindowCallResult(windowId, null);
      }
    }
  
    Future<MultiWindowCallResult> newSession(
      WindowType type,
      String methodName,
      String remoteId,
      List<int> windows, {
      String? password,
      bool? forceRelay,
      String? switchUuid,
      bool? isRDP,
      bool? isSharedPassword,
      String? connToken,
    }) async {
      var params = {
        "type": type.index,
        "id": remoteId,
        "password": password,
        "forceRelay": forceRelay
      };
      if (switchUuid != null) {
        params['switch_uuid'] = switchUuid;
      }
      if (isRDP != null) {
        params['isRDP'] = isRDP;
      }
      if (isSharedPassword != null) {
        params['isSharedPassword'] = isSharedPassword;
      }
      if (connToken != null) {
        params['connToken'] = connToken;
      }
      final msg = jsonEncode(params);
  
      // separate window for file transfer is not supported
      bool openInTabs = type != WindowType.RemoteDesktop ||
          mainGetLocalBoolOptionSync(kOptionOpenNewConnInTabs);
  
      if (windows.length > 1 || !openInTabs) {
        for (final windowId in windows) {
          if (await DesktopMultiWindow.invokeMethod(
              windowId, kWindowEventActiveSession, remoteId)) {
            return MultiWindowCallResult(windowId, null);
          }
        }
      }
  
      return _newSession(openInTabs, type, methodName, remoteId, windows, msg);
    }
  
    Future<MultiWindowCallResult> newRemoteDesktop(
      String remoteId, {
      String? password,
      bool? isSharedPassword,
      String? switchUuid,
      bool? forceRelay,
    }) async {
      return await newSession(
        WindowType.RemoteDesktop,
        kWindowEventNewRemoteDesktop,
        remoteId,
        _remoteDesktopWindows,
        password: password,
        forceRelay: forceRelay,
        switchUuid: switchUuid,
        isSharedPassword: isSharedPassword,
      );
    }
  
    Future<MultiWindowCallResult> newFileTransfer(
      String remoteId, {
      String? password,
      bool? isSharedPassword,
      bool? forceRelay,
      String? connToken,
    }) async {
      return await newSession(
        WindowType.FileTransfer,
        kWindowEventNewFileTransfer,
        remoteId,
        _fileTransferWindows,
        password: password,
        forceRelay: forceRelay,
        isSharedPassword: isSharedPassword,
        connToken: connToken,
      );
    }
  
    Future<MultiWindowCallResult> newViewCamera(
      String remoteId, {
      String? password,
      bool? isSharedPassword,
      String? switchUuid,
      bool? forceRelay,
      String? connToken,
    }) async {
      return await newSession(
        WindowType.ViewCamera,
        kWindowEventNewViewCamera,
        remoteId,
        _viewCameraWindows,
        password: password,
        forceRelay: forceRelay,
        switchUuid: switchUuid,
        isSharedPassword: isSharedPassword,
        connToken: connToken,
      );
    }
  
    Future<MultiWindowCallResult> newPortForward(
      String remoteId,
      bool isRDP, {
      String? password,
      bool? isSharedPassword,
      bool? forceRelay,
      String? connToken,
    }) async {
      return await newSession(
        WindowType.PortForward,
        kWindowEventNewPortForward,
        remoteId,
        _portForwardWindows,
        password: password,
        forceRelay: forceRelay,
        isRDP: isRDP,
        isSharedPassword: isSharedPassword,
        connToken: connToken,
      );
    }
  
    Future<MultiWindowCallResult> newTerminal(
      String remoteId, {
      String? password,
      bool? isSharedPassword,
      bool? forceRelay,
      String? connToken,
    }) async {
      // Iterate through terminal windows in reverse order to prioritize
      // the most recently added or used windows, as they are more likely
      // to have an active session.
      for (final windowId in _terminalWindows.reversed) {
        if (await DesktopMultiWindow.invokeMethod(
            windowId, kWindowEventActiveSession, remoteId)) {
          return MultiWindowCallResult(windowId, null);
        }
      }
  
      // Terminal windows should always create new windows, not reuse
      // This avoids the MissingPluginException when trying to invoke
      // new_terminal on an inactive window
      var params = {
        "type": WindowType.Terminal.index,
        "id": remoteId,
        "password": password,
        "forceRelay": forceRelay,
        "isSharedPassword": isSharedPassword,
        "connToken": connToken,
      };
      final msg = jsonEncode(params);
  
      // Always create a new window for terminal
      final windowId = await newSessionWindow(
          WindowType.Terminal, remoteId, msg, _terminalWindows, false);
      return MultiWindowCallResult(windowId, null);
    }
  
    Future<MultiWindowCallResult> call(
        WindowType type, String methodName, dynamic args) async {
      final wnds = _findWindowsByType(type);
      if (wnds.isEmpty) {
        return MultiWindowCallResult(kInvalidWindowId, null);
      }
      for (final windowId in wnds) {
        if (_activeWindows.contains(windowId)) {
          final res =
              await DesktopMultiWindow.invokeMethod(windowId, methodName, args);
          return MultiWindowCallResult(windowId, res);
        }
      }
      final res =
          await DesktopMultiWindow.invokeMethod(wnds[0], methodName, args);
      return MultiWindowCallResult(wnds[0], res);
    }
  
    List<int> _findWindowsByType(WindowType type) {
      switch (type) {
        case WindowType.Main:
          return [kMainWindowId];
        case WindowType.RemoteDesktop:
          return _remoteDesktopWindows;
        case WindowType.FileTransfer:
          return _fileTransferWindows;
        case WindowType.ViewCamera:
          return _viewCameraWindows;
        case WindowType.PortForward:
          return _portForwardWindows;
        case WindowType.Terminal:
          return _terminalWindows;
        case WindowType.Unknown:
          break;
      }
      return [];
    }
  
    void clearWindowType(WindowType type) {
      switch (type) {
        case WindowType.Main:
          return;
        case WindowType.RemoteDesktop:
          _remoteDesktopWindows.clear();
          break;
        case WindowType.FileTransfer:
          _fileTransferWindows.clear();
          break;
        case WindowType.ViewCamera:
          _viewCameraWindows.clear();
          break;
        case WindowType.PortForward:
          _portForwardWindows.clear();
          break;
        case WindowType.Terminal:
          _terminalWindows.clear();
        case WindowType.Unknown:
          break;
      }
    }
  
    void setMethodHandler(
        Future<dynamic> Function(MethodCall call, int fromWindowId)? handler) {
      DesktopMultiWindow.setMethodHandler(handler);
    }
  
    Future<void> closeAllSubWindows() async {
      await Future.wait(WindowType.values.map((e) => _closeWindows(e)));
    }
  
    Future<void> _closeWindows(WindowType type) async {
      if (type == WindowType.Main) {
        // skip main window, use window manager instead
        return;
      }
  
      List<int> windows = [];
      try {
        windows = _findWindowsByType(type);
      } catch (e) {
        debugPrint('Failed to getAllSubWindowIds of $type, $e');
        return;
      }
  
      if (windows.isEmpty) {
        return;
      }
      for (int i = 0; i < windows.length; i++) {
        final wId = windows[i];
        final shouldSavePos = type != WindowType.Terminal || i == windows.length - 1;
        if (shouldSavePos) {
          debugPrint("closing multi window, type: ${type.toString()} id: $wId");
          await saveWindowPosition(type, windowId: wId);
        }
        try {
          await WindowController.fromWindowId(wId).setPreventClose(false);
          await WindowController.fromWindowId(wId).close();
          _activeWindows.remove(wId);
        } catch (e) {
          debugPrint("$e");
          return;
        }
      }
      clearWindowType(type);
    }
  
    Future<List<int>> getAllSubWindowIds() async {
      try {
        final windows = await DesktopMultiWindow.getAllSubWindowIds();
        return windows;
      } catch (err) {
        if (err is AssertionError) {
          return [];
        } else {
          rethrow;
        }
      }
    }
  
    Set<int> getActiveWindows() {
      return _activeWindows;
    }
  
    Future<void> _notifyActiveWindow() async {
      for (final callback in _windowActiveCallbacks) {
        await callback.call();
      }
    }
  
    Future<void> registerActiveWindow(int windowId) async {
      _activeWindows.add(windowId);
      _inactiveWindows.remove(windowId);
      await _notifyActiveWindow();
    }
  
    /// Remove active window which has [`windowId`]
    ///
    /// [Availability]
    /// This function should only be called from main window.
    /// For other windows, please post a unregister(hide) event to main window handler:
    /// `rustDeskWinManager.call(WindowType.Main, kWindowEventHide, {"id": windowId!});`
    Future<void> unregisterActiveWindow(int windowId) async {
      _activeWindows.remove(windowId);
      if (windowId != kMainWindowId) {
        _inactiveWindows.add(windowId);
      }
      await _notifyActiveWindow();
    }
  
    void registerActiveWindowListener(AsyncCallback callback) {
      _windowActiveCallbacks.add(callback);
    }
  
    void unregisterActiveWindowListener(AsyncCallback callback) {
      _windowActiveCallbacks.remove(callback);
    }
  
    // This function is called from the main window.
    // It will query the active remote windows to get their coords.
    Future<List<String>> getOtherRemoteWindowCoords(int wId) async {
      List<String> coords = [];
      for (final windowId in _remoteDesktopWindows) {
        if (windowId != wId) {
          if (_activeWindows.contains(windowId)) {
            final res = await DesktopMultiWindow.invokeMethod(
                windowId, kWindowEventRemoteWindowCoords, '');
            if (res != null) {
              coords.add(res);
            }
          }
        }
      }
      return coords;
    }
  
    // This function is called from one remote window.
    // Only the main window knows `_remoteDesktopWindows` and `_activeWindows`.
    // So we need to call the main window to get the other remote windows' coords.
    Future<List<RemoteWindowCoords>> getOtherRemoteWindowCoordsFromMain() async {
      List<RemoteWindowCoords> coords = [];
      // Call the main window to get the coords of other remote windows.
      String res = await DesktopMultiWindow.invokeMethod(
          kMainWindowId, kWindowEventRemoteWindowCoords, kWindowId.toString());
      List<dynamic> list = jsonDecode(res);
      for (var item in list) {
        coords.add(RemoteWindowCoords.fromJson(jsonDecode(item)));
      }
      return coords;
    }
  }
  
  final rustDeskWinManager = RustDeskMultiWindowManager.instance;
  