content: import 'dart:convert';
  import 'dart:async';
  import 'dart:ui' as ui;
  
  import 'package:desktop_multi_window/desktop_multi_window.dart';
  import 'package:flutter/material.dart';
  import 'package:flutter_hbb/common.dart';
  import 'package:flutter_hbb/common/shared_state.dart';
  import 'package:flutter_hbb/consts.dart';
  import 'package:flutter_hbb/models/input_model.dart';
  import 'package:flutter_hbb/models/state_model.dart';
  import 'package:flutter_hbb/desktop/pages/view_camera_page.dart';
  import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart';
  import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
  import 'package:flutter_hbb/desktop/widgets/material_mod_popup_menu.dart'
      as mod_menu;
  import 'package:flutter_hbb/desktop/widgets/popup_menu.dart';
  import 'package:flutter_hbb/utils/multi_window_manager.dart';
  import 'package:flutter_svg/flutter_svg.dart';
  import 'package:get/get.dart';
  import 'package:bot_toast/bot_toast.dart';
  
  import '../../models/platform_model.dart';
  
  class _MenuTheme {
    static const Color blueColor = MyTheme.button;
    // kMinInteractiveDimension
    static const double height = 20.0;
    static const double dividerHeight = 12.0;
  }
  
  class ViewCameraTabPage extends StatefulWidget {
    final Map<String, dynamic> params;
  
    const ViewCameraTabPage({Key? key, required this.params}) : super(key: key);
  
    @override
    State<ViewCameraTabPage> createState() => _ViewCameraTabPageState(params);
  }
  
  class _ViewCameraTabPageState extends State<ViewCameraTabPage> {
    final tabController =
        Get.put(DesktopTabController(tabType: DesktopTabType.viewCamera));
    final contentKey = UniqueKey();
    static const IconData selectedIcon = Icons.desktop_windows_sharp;
    static const IconData unselectedIcon = Icons.desktop_windows_outlined;
  
    String? peerId;
    bool _isScreenRectSet = false;
    int? _display;
  
    var connectionMap = RxList<Widget>.empty(growable: true);
  
    _ViewCameraTabPageState(Map<String, dynamic> params) {
      RemoteCountState.init();
      peerId = params['id'];
      final sessionId = params['session_id'];
      final tabWindowId = params['tab_window_id'];
      final display = params['display'];
      final displays = params['displays'];
      final screenRect = parseParamScreenRect(params);
      _isScreenRectSet = screenRect != null;
      _display = display as int?;
      tryMoveToScreenAndSetFullscreen(screenRect);
      if (peerId != null) {
        ConnectionTypeState.init(peerId!);
        tabController.onSelected = (id) {
          final viewCameraPage = tabController.widget(id);
          if (viewCameraPage is ViewCameraPage) {
            final ffi = viewCameraPage.ffi;
            bind.setCurSessionId(sessionId: ffi.sessionId);
          }
          WindowController.fromWindowId(params['windowId'])
              .setTitle(getWindowNameWithId(id));
          UnreadChatCountState.find(id).value = 0;
        };
        tabController.add(TabInfo(
          key: peerId!,
          label: peerId!,
          selectedIcon: selectedIcon,
          unselectedIcon: unselectedIcon,
          onTabCloseButton: () => tabController.closeBy(peerId),
          page: ViewCameraPage(
            key: ValueKey(peerId),
            id: peerId!,
            sessionId: sessionId == null ? null : SessionID(sessionId),
            tabWindowId: tabWindowId,
            display: display,
            displays: displays?.cast<int>(),
            password: params['password'],
            toolbarState: ToolbarState(),
            tabController: tabController,
            connToken: params['connToken'],
            forceRelay: params['forceRelay'],
            isSharedPassword: params['isSharedPassword'],
          ),
        ));
        _update_remote_count();
      }
      tabController.onRemoved = (_, id) => onRemoveId(id);
      rustDeskWinManager.setMethodHandler(_remoteMethodHandler);
    }
  
    @override
    void initState() {
      super.initState();
  
      if (!_isScreenRectSet) {
        Future.delayed(Duration.zero, () {
          restoreWindowPosition(
            WindowType.ViewCamera,
            windowId: windowId(),
            peerId: tabController.state.value.tabs.isEmpty
                ? null
                : tabController.state.value.tabs[0].key,
            display: _display,
          );
        });
      }
    }
  
    @override
    Widget build(BuildContext context) {
      final child = Scaffold(
        backgroundColor: Theme.of(context).colorScheme.background,
        body: DesktopTab(
          controller: tabController,
          onWindowCloseButton: handleWindowCloseButton,
          tail: const AddButton(),
          selectedBorderColor: MyTheme.accent,
          pageViewBuilder: (pageView) => pageView,
          labelGetter: DesktopTab.tablabelGetter,
          tabBuilder: (key, icon, label, themeConf) => Obx(() {
            final connectionType = ConnectionTypeState.find(key);
            if (!connectionType.isValid()) {
              return Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  icon,
                  label,
                ],
              );
            } else {
              bool secure =
                  connectionType.secure.value == ConnectionType.strSecure;
              bool direct =
                  connectionType.direct.value == ConnectionType.strDirect;
              String msgConn = getConnectionText(
                  secure, direct, connectionType.stream_type.value);
              var msgFingerprint = '${translate('Fingerprint')}:\n';
              var fingerprint = FingerprintState.find(key).value;
              if (fingerprint.isEmpty) {
                fingerprint = 'N/A';
              }
              if (fingerprint.length > 5 * 8) {
                var first = fingerprint.substring(0, 39);
                var second = fingerprint.substring(40);
                msgFingerprint += '$first\n$second';
              } else {
                msgFingerprint += fingerprint;
              }
  
              final tab = Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  icon,
                  Tooltip(
                    message: '$msgConn\n$msgFingerprint',
                    child: SvgPicture.asset(
                      'assets/${connectionType.secure.value}${connectionType.direct.value}.svg',
                      width: themeConf.iconSize,
                      height: themeConf.iconSize,
                    ).paddingOnly(right: 5),
                  ),
                  label,
                  unreadMessageCountBuilder(UnreadChatCountState.find(key))
                      .marginOnly(left: 4),
                ],
              );
  
              return Listener(
                onPointerDown: (e) {
                  if (e.kind != ui.PointerDeviceKind.mouse) {
                    return;
                  }
                  final viewCameraPage = tabController.state.value.tabs
                      .firstWhere((tab) => tab.key == key)
                      .page as ViewCameraPage;
                  if (viewCameraPage.ffi.ffiModel.pi.isSet.isTrue &&
                      e.buttons == 2) {
                    showRightMenu(
                      (CancelFunc cancelFunc) {
                        return _tabMenuBuilder(key, cancelFunc);
                      },
                      target: e.position,
                    );
                  }
                },
                child: tab,
              );
            }
          }),
        ),
      );
      final tabWidget = isLinux
          ? buildVirtualWindowFrame(context, child)
          : workaroundWindowBorder(
              context,
              Obx(() => Container(
                    decoration: BoxDecoration(
                      border: Border.all(
                          color: MyTheme.color(context).border!,
                          width: stateGlobal.windowBorderWidth.value),
                    ),
                    child: child,
                  )));
      return isMacOS || kUseCompatibleUiMode
          ? tabWidget
          : Obx(() => SubWindowDragToResizeArea(
                key: contentKey,
                child: tabWidget,
                // Specially configured for a better resize area and remote control.
                childPadding: kDragToResizeAreaPadding,
                resizeEdgeSize: stateGlobal.resizeEdgeSize.value,
                enableResizeEdges: subWindowManagerEnableResizeEdges,
                windowId: stateGlobal.windowId,
              ));
    }
  
    // Note: Some dup code to ../widgets/remote_toolbar
    Widget _tabMenuBuilder(String key, CancelFunc cancelFunc) {
      final List<MenuEntryBase<String>> menu = [];
      const EdgeInsets padding = EdgeInsets.only(left: 8.0, right: 5.0);
      final viewCameraPage = tabController.state.value.tabs
          .firstWhere((tab) => tab.key == key)
          .page as ViewCameraPage;
      final ffi = viewCameraPage.ffi;
      final sessionId = ffi.sessionId;
      final toolbarState = viewCameraPage.toolbarState;
      menu.addAll([
        MenuEntryButton<String>(
          childBuilder: (TextStyle? style) => Obx(() => Text(
                translate(
                    toolbarState.show.isTrue ? 'Hide Toolbar' : 'Show Toolbar'),
                style: style,
              )),
          proc: () {
            toolbarState.switchShow(sessionId);
            cancelFunc();
          },
          padding: padding,
        ),
      ]);
  
      if (tabController.state.value.tabs.length > 1) {
        final splitAction = MenuEntryButton<String>(
          childBuilder: (TextStyle? style) => Text(
            translate('Move tab to new window'),
            style: style,
          ),
          proc: () async {
            await DesktopMultiWindow.invokeMethod(
                kMainWindowId,
                kWindowEventMoveTabToNewWindow,
                '${windowId()},$key,$sessionId,ViewCamera');
            cancelFunc();
          },
          padding: padding,
        );
        menu.insert(1, splitAction);
      }
  
      menu.addAll([
        MenuEntryDivider<String>(),
        MenuEntryButton<String>(
          childBuilder: (TextStyle? style) => Text(
            translate('Copy Fingerprint'),
            style: style,
          ),
          proc: () => onCopyFingerprint(FingerprintState.find(key).value),
          padding: padding,
          dismissOnClicked: true,
          dismissCallback: cancelFunc,
        ),
        MenuEntryButton<String>(
          childBuilder: (TextStyle? style) => Text(
            translate('Close'),
            style: style,
          ),
          proc: () {
            tabController.closeBy(key);
            cancelFunc();
          },
          padding: padding,
        )
      ]);
  
      return mod_menu.PopupMenu<String>(
        items: menu
            .map((entry) => entry.build(
                context,
                const MenuConfig(
                  commonColor: _MenuTheme.blueColor,
                  height: _MenuTheme.height,
                  dividerHeight: _MenuTheme.dividerHeight,
                )))
            .expand((i) => i)
            .toList(),
      );
    }
  
    void onRemoveId(String id) async {
      if (tabController.state.value.tabs.isEmpty) {
        // Keep calling until the window status is hidden.
        //
        // Workaround for Windows:
        // If you click other buttons and close in msgbox within a very short period of time, the close may fail.
        // `await WindowController.fromWindowId(windowId()).close();`.
        Future<void> loopCloseWindow() async {
          int c = 0;
          final windowController = WindowController.fromWindowId(windowId());
          while (c < 20 &&
              tabController.state.value.tabs.isEmpty &&
              (!await windowController.isHidden())) {
            await windowController.close();
            await Future.delayed(Duration(milliseconds: 100));
            c++;
          }
        }
  
        loopCloseWindow();
      }
      ConnectionTypeState.delete(id);
      _update_remote_count();
    }
  
    int windowId() {
      return widget.params["windowId"];
    }
  
    Future<bool> handleWindowCloseButton() async {
      final connLength = tabController.length;
      if (connLength <= 1) {
        tabController.clear();
        return true;
      } else {
        final bool res;
        if (!option2bool(kOptionEnableConfirmClosingTabs,
            bind.mainGetLocalOption(key: kOptionEnableConfirmClosingTabs))) {
          res = true;
        } else {
          res = await closeConfirmDialog();
        }
        if (res) {
          tabController.clear();
        }
        return res;
      }
    }
  
    _update_remote_count() =>
        RemoteCountState.find().value = tabController.length;
  
    Future<dynamic> _remoteMethodHandler(call, fromWindowId) async {
      debugPrint(
          "[View Camera Page] call ${call.method} with args ${call.arguments} from window $fromWindowId");
  
      dynamic returnValue;
      // for simplify, just replace connectionId
      if (call.method == kWindowEventNewViewCamera) {
        final args = jsonDecode(call.arguments);
        final id = args['id'];
        final sessionId = args['session_id'];
        final tabWindowId = args['tab_window_id'];
        final display = args['display'];
        final displays = args['displays'];
        final screenRect = parseParamScreenRect(args);
        final prePeerCount = tabController.length;
        Future.delayed(Duration.zero, () async {
          if (stateGlobal.fullscreen.isTrue) {
            await WindowController.fromWindowId(windowId()).setFullscreen(false);
            stateGlobal.setFullscreen(false, procWnd: false);
          }
          await setNewConnectWindowFrame(windowId(), id!, prePeerCount,
              WindowType.ViewCamera, display, screenRect);
          Future.delayed(Duration(milliseconds: isWindows ? 100 : 0), () async {
            await windowOnTop(windowId());
          });
        });
        ConnectionTypeState.init(id);
        tabController.add(TabInfo(
          key: id,
          label: id,
          selectedIcon: selectedIcon,
          unselectedIcon: unselectedIcon,
          onTabCloseButton: () => tabController.closeBy(id),
          page: ViewCameraPage(
            key: ValueKey(id),
            id: id,
            sessionId: sessionId == null ? null : SessionID(sessionId),
            tabWindowId: tabWindowId,
            display: display,
            displays: displays?.cast<int>(),
            password: args['password'],
            toolbarState: ToolbarState(),
            tabController: tabController,
            connToken: args['connToken'],
            forceRelay: args['forceRelay'],
            isSharedPassword: args['isSharedPassword'],
          ),
        ));
      } else if (call.method == kWindowDisableGrabKeyboard) {
        // ???
      } else if (call.method == "onDestroy") {
        tabController.clear();
      } else if (call.method == kWindowActionRebuild) {
        reloadCurrentWindow();
      } else if (call.method == kWindowEventActiveSession) {
        final jumpOk = tabController.jumpToByKey(call.arguments);
        if (jumpOk) {
          windowOnTop(windowId());
        }
        return jumpOk;
      } else if (call.method == kWindowEventActiveDisplaySession) {
        final args = jsonDecode(call.arguments);
        final id = args['id'];
        final display = args['display'];
        final jumpOk =
            tabController.jumpToByKeyAndDisplay(id, display, isCamera: true);
        if (jumpOk) {
          windowOnTop(windowId());
        }
        return jumpOk;
      } else if (call.method == kWindowEventGetRemoteList) {
        return tabController.state.value.tabs
            .map((e) => e.key)
            .toList()
            .join(',');
      } else if (call.method == kWindowEventGetSessionIdList) {
        return tabController.state.value.tabs
            .map((e) => '${e.key},${(e.page as ViewCameraPage).ffi.sessionId}')
            .toList()
            .join(';');
      } else if (call.method == kWindowEventGetCachedSessionData) {
        // Ready to show new window and close old tab.
        final args = jsonDecode(call.arguments);
        final id = args['id'];
        final close = args['close'];
        try {
          final viewCameraPage = tabController.state.value.tabs
              .firstWhere((tab) => tab.key == id)
              .page as ViewCameraPage;
          returnValue = viewCameraPage.ffi.ffiModel.cachedPeerData.toString();
        } catch (e) {
          debugPrint('Failed to get cached session data: $e');
        }
        if (close && returnValue != null) {
          closeSessionOnDispose[id] = false;
          tabController.closeBy(id);
        }
      } else if (call.method == kWindowEventRemoteWindowCoords) {
        final viewCameraPage =
            tabController.state.value.selectedTabInfo.page as ViewCameraPage;
        final ffi = viewCameraPage.ffi;
        final displayRect = ffi.ffiModel.displaysRect();
        if (displayRect != null) {
          final wc = WindowController.fromWindowId(windowId());
          Rect? frame;
          try {
            frame = await wc.getFrame();
          } catch (e) {
            debugPrint(
                "Failed to get frame of window $windowId, it may be hidden");
          }
          if (frame != null) {
            ffi.cursorModel.moveLocal(0, 0);
            final coords = RemoteWindowCoords(
                frame,
                CanvasCoords.fromCanvasModel(ffi.canvasModel),
                CursorCoords.fromCursorModel(ffi.cursorModel),
                displayRect);
            returnValue = jsonEncode(coords.toJson());
          }
        }
      } else if (call.method == kWindowEventSetFullscreen) {
        stateGlobal.setFullscreen(call.arguments == 'true');
      }
      _update_remote_count();
      return returnValue;
    }
  }
  