import 'dart:convert';
import 'dart:io';
import 'dart:math' as math;
import 'dart:ui' as ui;

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hbb/models/chat_model.dart';
import 'package:flutter_hbb/models/state_model.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/utils/multi_window_manager.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
import 'package:debounce_throttle/debounce_throttle.dart';
import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:window_size/window_size.dart' as window_size;

import '../../common.dart';
import '../../mobile/widgets/dialog.dart';
import '../../models/model.dart';
import '../../models/platform_model.dart';
import '../../common/shared_state.dart';
import './popup_menu.dart';
import './material_mod_popup_menu.dart' as mod_menu;
import './kb_layout_type_chooser.dart';

class MenubarState {
  final kStoreKey = 'remoteMenubarState';
  late RxBool show;
  late RxBool _pin;
  RxString viewStyle = RxString(kRemoteViewStyleOriginal);

  MenubarState() {
    final s = bind.getLocalFlutterConfig(k: kStoreKey);
    if (s.isEmpty) {
      _initSet(false, false);
      return;
    }

    try {
      final m = jsonDecode(s);
      if (m == null) {
        _initSet(false, false);
      } else {
        _initSet(m['pin'] ?? false, m['pin'] ?? false);
      }
    } catch (e) {
      debugPrint('Failed to decode menubar state ${e.toString()}');
      _initSet(false, false);
    }
  }

  _initSet(bool s, bool p) {
    // Show remubar when connection is established.
    show = RxBool(true);
    _pin = RxBool(p);
  }

  bool get pin => _pin.value;

  switchShow() async {
    show.value = !show.value;
  }

  setShow(bool v) async {
    if (show.value != v) {
      show.value = v;
    }
  }

  switchPin() async {
    _pin.value = !_pin.value;
    // Save everytime changed, as this func will not be called frequently
    await _savePin();
  }

  setPin(bool v) async {
    if (_pin.value != v) {
      _pin.value = v;
      // Save everytime changed, as this func will not be called frequently
      await _savePin();
    }
  }

  _savePin() async {
    bind.setLocalFlutterConfig(
        k: kStoreKey, v: jsonEncode({'pin': _pin.value}));
  }

  save() async {
    await _savePin();
  }
}

class _MenubarTheme {
  static const Color commonColor = MyTheme.accent;
  // kMinInteractiveDimension
  static const double height = 20.0;
  static const double dividerHeight = 12.0;
}

class RemoteMenubar extends StatefulWidget {
  final String id;
  final FFI ffi;
  final MenubarState state;
  final Function(Function(bool)) onEnterOrLeaveImageSetter;
  final Function() onEnterOrLeaveImageCleaner;

  const RemoteMenubar({
    Key? key,
    required this.id,
    required this.ffi,
    required this.state,
    required this.onEnterOrLeaveImageSetter,
    required this.onEnterOrLeaveImageCleaner,
  }) : super(key: key);

  @override
  State<RemoteMenubar> createState() => _RemoteMenubarState();
}

class _RemoteMenubarState extends State<RemoteMenubar> {
  late Debouncer<int> _debouncerHide;
  bool _isCursorOverImage = false;
  window_size.Screen? _screen;
  final _fractionX = 0.5.obs;
  final _dragging = false.obs;

  int get windowId => stateGlobal.windowId;

  bool get isFullscreen => stateGlobal.fullscreen;
  void _setFullscreen(bool v) {
    stateGlobal.setFullscreen(v);
    setState(() {});
  }

  RxBool get show => widget.state.show;
  bool get pin => widget.state.pin;

  @override
  initState() {
    super.initState();

    _debouncerHide = Debouncer<int>(
      Duration(milliseconds: 5000),
      onChanged: _debouncerHideProc,
      initialValue: 0,
    );

    widget.onEnterOrLeaveImageSetter((enter) {
      if (enter) {
        _debouncerHide.value = 0;
        _isCursorOverImage = true;
      } else {
        _isCursorOverImage = false;
      }
    });
  }

  _debouncerHideProc(int v) {
    if (!pin && show.isTrue && _isCursorOverImage && _dragging.isFalse) {
      show.value = false;
    }
  }

  @override
  dispose() {
    super.dispose();

    widget.onEnterOrLeaveImageCleaner();
  }

  @override
  Widget build(BuildContext context) {
    // No need to use future builder here.
    _updateScreen();
    return Align(
      alignment: Alignment.topCenter,
      child: Obx(() => show.value
          ? _buildMenubar(context)
          : _buildDraggableShowHide(context)),
    );
  }

  Widget _buildDraggableShowHide(BuildContext context) {
    return Obx(() {
      if (show.isTrue && _dragging.isFalse) {
        _debouncerHide.value = 1;
      }
      return Align(
        alignment: FractionalOffset(_fractionX.value, 0),
        child: Offstage(
          offstage: _dragging.isTrue,
          child: _DraggableShowHide(
            dragging: _dragging,
            fractionX: _fractionX,
            show: show,
          ),
        ),
      );
    });
  }

  _updateScreen() async {
    final v = await rustDeskWinManager.call(
        WindowType.Main, kWindowGetWindowInfo, '');
    final String valueStr = v;
    if (valueStr.isEmpty) {
      _screen = null;
    } else {
      final screenMap = jsonDecode(valueStr);
      _screen = window_size.Screen(
          Rect.fromLTRB(screenMap['frame']['l'], screenMap['frame']['t'],
              screenMap['frame']['r'], screenMap['frame']['b']),
          Rect.fromLTRB(
              screenMap['visibleFrame']['l'],
              screenMap['visibleFrame']['t'],
              screenMap['visibleFrame']['r'],
              screenMap['visibleFrame']['b']),
          screenMap['scaleFactor']);
    }
  }

  Widget _buildPointerTrackWidget(Widget child) {
    return Listener(
      onPointerHover: (PointerHoverEvent e) =>
          widget.ffi.inputModel.lastMousePos = e.position,
      child: MouseRegion(
        child: child,
      ),
    );
  }

  Widget _buildMenubar(BuildContext context) {
    final List<Widget> menubarItems = [];
    if (!isWebDesktop) {
      menubarItems.add(_buildPinMenubar(context));
      menubarItems.add(_buildFullscreen(context));
      if (widget.ffi.ffiModel.isPeerAndroid) {
        menubarItems.add(IconButton(
          tooltip: translate('Mobile Actions'),
          color: _MenubarTheme.commonColor,
          icon: const Icon(Icons.build),
          onPressed: () {
            widget.ffi.dialogManager
                .toggleMobileActionsOverlay(ffi: widget.ffi);
          },
        ));
      }
    }
    menubarItems.add(_buildMonitor(context));
    menubarItems.add(_buildControl(context));
    menubarItems.add(_buildDisplay(context));
    menubarItems.add(_buildKeyboard(context));
    if (!isWeb) {
      menubarItems.add(_buildChat(context));
    }
    menubarItems.add(_buildRecording(context));
    menubarItems.add(_buildClose(context));
    return PopupMenuTheme(
        data: const PopupMenuThemeData(
            textStyle: TextStyle(color: _MenubarTheme.commonColor)),
        child: Column(mainAxisSize: MainAxisSize.min, children: [
          Container(
              decoration: BoxDecoration(
                color: Colors.white,
                border: Border.all(color: MyTheme.border),
              ),
              child: Row(
                mainAxisSize: MainAxisSize.min,
                children: menubarItems,
              )),
          _buildDraggableShowHide(context),
        ]));
  }

  Widget _buildPinMenubar(BuildContext context) {
    return Obx(() => IconButton(
          tooltip: translate(pin ? 'Unpin menubar' : 'Pin menubar'),
          onPressed: () {
            widget.state.switchPin();
          },
          icon: Obx(() => Transform.rotate(
              angle: pin ? math.pi / 4 : 0,
              child: Icon(
                Icons.push_pin,
                color: pin ? _MenubarTheme.commonColor : Colors.grey,
              ))),
        ));
  }

  Widget _buildFullscreen(BuildContext context) {
    return IconButton(
      tooltip: translate(isFullscreen ? 'Exit Fullscreen' : 'Fullscreen'),
      onPressed: () {
        _setFullscreen(!isFullscreen);
      },
      icon: isFullscreen
          ? const Icon(
              Icons.fullscreen_exit,
              color: _MenubarTheme.commonColor,
            )
          : const Icon(
              Icons.fullscreen,
              color: _MenubarTheme.commonColor,
            ),
    );
  }

  Widget _buildChat(BuildContext context) {
    return IconButton(
      tooltip: translate('Chat'),
      onPressed: () {
        widget.ffi.chatModel.changeCurrentID(ChatModel.clientModeID);
        widget.ffi.chatModel.toggleChatOverlay();
      },
      icon: const Icon(
        Icons.message,
        color: _MenubarTheme.commonColor,
      ),
    );
  }

  Widget _buildMonitor(BuildContext context) {
    final pi = widget.ffi.ffiModel.pi;
    return mod_menu.PopupMenuButton(
      tooltip: translate('Select Monitor'),
      padding: EdgeInsets.zero,
      position: mod_menu.PopupMenuPosition.under,
      icon: Stack(
        alignment: Alignment.center,
        children: [
          const Icon(
            Icons.personal_video,
            color: _MenubarTheme.commonColor,
          ),
          Padding(
            padding: const EdgeInsets.only(bottom: 3.9),
            child: Obx(() {
              RxInt display = CurrentDisplayState.find(widget.id);
              return Text(
                '${display.value + 1}/${pi.displays.length}',
                style: const TextStyle(
                    color: _MenubarTheme.commonColor, fontSize: 8),
              );
            }),
          )
        ],
      ),
      itemBuilder: (BuildContext context) {
        final List<Widget> rowChildren = [];
        for (int i = 0; i < pi.displays.length; i++) {
          rowChildren.add(
            Stack(
              alignment: Alignment.center,
              children: [
                const Icon(
                  Icons.personal_video,
                  color: _MenubarTheme.commonColor,
                ),
                TextButton(
                  child: Container(
                      alignment: AlignmentDirectional.center,
                      constraints:
                          const BoxConstraints(minHeight: _MenubarTheme.height),
                      child: Padding(
                        padding: const EdgeInsets.only(bottom: 2.5),
                        child: Text(
                          (i + 1).toString(),
                          style:
                              const TextStyle(color: _MenubarTheme.commonColor),
                        ),
                      )),
                  onPressed: () {
                    if (Navigator.canPop(context)) {
                      Navigator.pop(context);
                    }
                    RxInt display = CurrentDisplayState.find(widget.id);
                    if (display.value != i) {
                      bind.sessionSwitchDisplay(id: widget.id, value: i);
                    }
                  },
                )
              ],
            ),
          );
        }
        return <mod_menu.PopupMenuEntry<String>>[
          mod_menu.PopupMenuItem<String>(
            height: _MenubarTheme.height,
            padding: EdgeInsets.zero,
            child: _buildPointerTrackWidget(
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: rowChildren,
              ),
            ),
          )
        ];
      },
    );
  }

  Widget _buildControl(BuildContext context) {
    return mod_menu.PopupMenuButton(
      padding: EdgeInsets.zero,
      icon: const Icon(
        Icons.bolt,
        color: _MenubarTheme.commonColor,
      ),
      tooltip: translate('Control Actions'),
      position: mod_menu.PopupMenuPosition.under,
      itemBuilder: (BuildContext context) => _getControlMenu(context)
          .map((entry) => entry.build(
              context,
              const MenuConfig(
                commonColor: _MenubarTheme.commonColor,
                height: _MenubarTheme.height,
                dividerHeight: _MenubarTheme.dividerHeight,
              )))
          .expand((i) => i)
          .toList(),
    );
  }

  Widget _buildDisplay(BuildContext context) {
    return FutureBuilder(future: () async {
      widget.state.viewStyle.value =
          await bind.sessionGetViewStyle(id: widget.id) ?? '';
      final supportedHwcodec =
          await bind.sessionSupportedHwcodec(id: widget.id);
      return {'supportedHwcodec': supportedHwcodec};
    }(), builder: (context, snapshot) {
      if (snapshot.hasData) {
        return Obx(() {
          final remoteCount = RemoteCountState.find().value;
          return mod_menu.PopupMenuButton(
            padding: EdgeInsets.zero,
            icon: const Icon(
              Icons.tv,
              color: _MenubarTheme.commonColor,
            ),
            tooltip: translate('Display Settings'),
            position: mod_menu.PopupMenuPosition.under,
            menuWrapper: _buildPointerTrackWidget,
            itemBuilder: (BuildContext context) =>
                _getDisplayMenu(snapshot.data!, remoteCount)
                    .map((entry) => entry.build(
                        context,
                        const MenuConfig(
                          commonColor: _MenubarTheme.commonColor,
                          height: _MenubarTheme.height,
                          dividerHeight: _MenubarTheme.dividerHeight,
                        )))
                    .expand((i) => i)
                    .toList(),
          );
        });
      } else {
        return const Offstage();
      }
    });
  }

  Widget _buildKeyboard(BuildContext context) {
    FfiModel ffiModel = Provider.of<FfiModel>(context);
    if (ffiModel.permissions['keyboard'] == false) {
      return Offstage();
    }
    return mod_menu.PopupMenuButton(
      padding: EdgeInsets.zero,
      icon: const Icon(
        Icons.keyboard,
        color: _MenubarTheme.commonColor,
      ),
      tooltip: translate('Keyboard Settings'),
      position: mod_menu.PopupMenuPosition.under,
      itemBuilder: (BuildContext context) => _getKeyboardMenu()
          .map((entry) => entry.build(
              context,
              const MenuConfig(
                commonColor: _MenubarTheme.commonColor,
                height: _MenubarTheme.height,
                dividerHeight: _MenubarTheme.dividerHeight,
              )))
          .expand((i) => i)
          .toList(),
    );
  }

  Widget _buildRecording(BuildContext context) {
    return Consumer<FfiModel>(builder: ((context, value, child) {
      if (value.permissions['recording'] != false) {
        return Consumer<RecordingModel>(
            builder: (context, value, child) => IconButton(
                  tooltip: value.start
                      ? translate('Stop session recording')
                      : translate('Start session recording'),
                  onPressed: () => value.toggle(),
                  icon: Icon(
                    value.start
                        ? Icons.pause_circle_filled
                        : Icons.videocam_outlined,
                    color: _MenubarTheme.commonColor,
                  ),
                ));
      } else {
        return Offstage();
      }
    }));
  }

  Widget _buildClose(BuildContext context) {
    return IconButton(
      tooltip: translate('Close'),
      onPressed: () {
        clientClose(widget.id, widget.ffi.dialogManager);
      },
      icon: const Icon(
        Icons.close,
        color: _MenubarTheme.commonColor,
      ),
    );
  }

  List<MenuEntryBase<String>> _getControlMenu(BuildContext context) {
    final pi = widget.ffi.ffiModel.pi;
    final perms = widget.ffi.ffiModel.permissions;
    final peer_version = widget.ffi.ffiModel.pi.version;
    const EdgeInsets padding = EdgeInsets.only(left: 14.0, right: 5.0);
    final List<MenuEntryBase<String>> displayMenu = [];
    displayMenu.addAll([
      MenuEntryButton<String>(
        childBuilder: (TextStyle? style) => Container(
            alignment: AlignmentDirectional.center,
            height: _MenubarTheme.height,
            child: Row(
              children: [
                Text(
                  translate('OS Password'),
                  style: style,
                ),
                Expanded(
                    child: Align(
                  alignment: Alignment.centerRight,
                  child: Transform.scale(
                      scale: 0.8,
                      child: IconButton(
                          padding: EdgeInsets.zero,
                          icon: const Icon(Icons.edit),
                          onPressed: () {
                            if (Navigator.canPop(context)) {
                              Navigator.pop(context);
                            }
                            showSetOSPassword(
                                widget.id, false, widget.ffi.dialogManager);
                          })),
                ))
              ],
            )),
        proc: () {
          showSetOSPassword(widget.id, false, widget.ffi.dialogManager);
        },
        padding: padding,
        dismissOnClicked: true,
      ),
      MenuEntryButton<String>(
        childBuilder: (TextStyle? style) => Text(
          translate('Transfer File'),
          style: style,
        ),
        proc: () {
          connect(context, widget.id, isFileTransfer: true);
        },
        padding: padding,
        dismissOnClicked: true,
      ),
      MenuEntryButton<String>(
        childBuilder: (TextStyle? style) => Text(
          translate('TCP Tunneling'),
          style: style,
        ),
        padding: padding,
        proc: () {
          connect(context, widget.id, isTcpTunneling: true);
        },
        dismissOnClicked: true,
      ),
    ]);
    // {handler.get_audit_server() && <li #note>{translate('Note')}</li>}
    final auditServer =
        bind.sessionGetAuditServerSync(id: widget.id, typ: "conn");
    if (auditServer.isNotEmpty) {
      displayMenu.add(
        MenuEntryButton<String>(
          childBuilder: (TextStyle? style) => Text(
            translate('Note'),
            style: style,
          ),
          proc: () {
            showAuditDialog(widget.id, widget.ffi.dialogManager);
          },
          padding: padding,
          dismissOnClicked: true,
        ),
      );
    }
    displayMenu.add(MenuEntryDivider());
    if (perms['keyboard'] != false) {
      if (pi.platform == kPeerPlatformLinux || pi.sasEnabled) {
        displayMenu.add(MenuEntryButton<String>(
          childBuilder: (TextStyle? style) => Text(
            '${translate("Insert")} Ctrl + Alt + Del',
            style: style,
          ),
          proc: () {
            bind.sessionCtrlAltDel(id: widget.id);
          },
          padding: padding,
          dismissOnClicked: true,
        ));
      }
    }
    if (perms['restart'] != false &&
        (pi.platform == kPeerPlatformLinux ||
            pi.platform == kPeerPlatformWindows ||
            pi.platform == kPeerPlatformMacOS)) {
      displayMenu.add(MenuEntryButton<String>(
        childBuilder: (TextStyle? style) => Text(
          translate('Restart Remote Device'),
          style: style,
        ),
        proc: () {
          showRestartRemoteDevice(pi, widget.id, gFFI.dialogManager);
        },
        padding: padding,
        dismissOnClicked: true,
      ));
    }

    if (perms['keyboard'] != false) {
      displayMenu.add(MenuEntryButton<String>(
        childBuilder: (TextStyle? style) => Text(
          translate('Insert Lock'),
          style: style,
        ),
        proc: () {
          bind.sessionLockScreen(id: widget.id);
        },
        padding: padding,
        dismissOnClicked: true,
      ));

      if (pi.platform == kPeerPlatformWindows) {
        displayMenu.add(MenuEntryButton<String>(
          childBuilder: (TextStyle? style) => Obx(() => Text(
                translate(
                    '${BlockInputState.find(widget.id).value ? 'Unb' : 'B'}lock user input'),
                style: style,
              )),
          proc: () {
            RxBool blockInput = BlockInputState.find(widget.id);
            bind.sessionToggleOption(
                id: widget.id,
                value: '${blockInput.value ? 'un' : ''}block-input');
            blockInput.value = !blockInput.value;
          },
          padding: padding,
          dismissOnClicked: true,
        ));
      }
      if (pi.platform != kPeerPlatformAndroid &&
          pi.platform != kPeerPlatformMacOS && // unsupport yet
          version_cmp(peer_version, '1.2.0') >= 0) {
        displayMenu.add(MenuEntryButton<String>(
          childBuilder: (TextStyle? style) => Text(
            translate('Switch Sides'),
            style: style,
          ),
          proc: () =>
              showConfirmSwitchSidesDialog(widget.id, widget.ffi.dialogManager),
          padding: padding,
          dismissOnClicked: true,
        ));
      }
    }

    if (pi.version.isNotEmpty) {
      displayMenu.add(MenuEntryButton<String>(
        childBuilder: (TextStyle? style) => Text(
          translate('Refresh'),
          style: style,
        ),
        proc: () {
          bind.sessionRefresh(id: widget.id);
        },
        padding: padding,
        dismissOnClicked: true,
      ));
    }

    if (!isWebDesktop) {
      //   if (perms['keyboard'] != false && perms['clipboard'] != false) {
      //     displayMenu.add(MenuEntryButton<String>(
      //       childBuilder: (TextStyle? style) => Text(
      //         translate('Paste'),
      //         style: style,
      //       ),
      //       proc: () {
      //         () async {
      //           ClipboardData? data =
      //               await Clipboard.getData(Clipboard.kTextPlain);
      //           if (data != null && data.text != null) {
      //             bind.sessionInputString(id: widget.id, value: data.text ?? '');
      //           }
      //         }();
      //       },
      //       padding: padding,
      //       dismissOnClicked: true,
      //     ));
      //   }
    }

    return displayMenu;
  }

  bool _isWindowCanBeAdjusted(int remoteCount) {
    if (remoteCount != 1) {
      return false;
    }
    if (_screen == null) {
      return false;
    }
    final scale = kIgnoreDpi ? 1.0 : _screen!.scaleFactor;
    double selfWidth = _screen!.visibleFrame.width;
    double selfHeight = _screen!.visibleFrame.height;
    if (isFullscreen) {
      selfWidth = _screen!.frame.width;
      selfHeight = _screen!.frame.height;
    }

    final canvasModel = widget.ffi.canvasModel;
    final displayWidth = canvasModel.getDisplayWidth();
    final displayHeight = canvasModel.getDisplayHeight();
    final requiredWidth = displayWidth +
        (canvasModel.tabBarHeight + canvasModel.windowBorderWidth * 2);
    final requiredHeight = displayHeight +
        (canvasModel.tabBarHeight + canvasModel.windowBorderWidth * 2);
    return selfWidth > (requiredWidth * scale) &&
        selfHeight > (requiredHeight * scale);
  }

  List<MenuEntryBase<String>> _getDisplayMenu(
      dynamic futureData, int remoteCount) {
    const EdgeInsets padding = EdgeInsets.only(left: 18.0, right: 8.0);
    final peer_version = widget.ffi.ffiModel.pi.version;
    final displayMenu = [
      MenuEntryRadios<String>(
        text: translate('Ratio'),
        optionsGetter: () => [
          MenuEntryRadioOption(
            text: translate('Scale original'),
            value: kRemoteViewStyleOriginal,
            dismissOnClicked: true,
          ),
          MenuEntryRadioOption(
            text: translate('Scale adaptive'),
            value: kRemoteViewStyleAdaptive,
            dismissOnClicked: true,
          ),
        ],
        curOptionGetter: () async {
          // null means peer id is not found, which there's no need to care about
          final viewStyle = await bind.sessionGetViewStyle(id: widget.id) ?? '';
          widget.state.viewStyle.value = viewStyle;
          return viewStyle;
        },
        optionSetter: (String oldValue, String newValue) async {
          await bind.sessionSetViewStyle(id: widget.id, value: newValue);
          widget.state.viewStyle.value = newValue;
          widget.ffi.canvasModel.updateViewStyle();
        },
        padding: padding,
        dismissOnClicked: true,
      ),
      MenuEntryDivider<String>(),
      MenuEntryRadios<String>(
        text: translate('Image Quality'),
        optionsGetter: () => [
          MenuEntryRadioOption(
            text: translate('Good image quality'),
            value: kRemoteImageQualityBest,
            dismissOnClicked: true,
          ),
          MenuEntryRadioOption(
            text: translate('Balanced'),
            value: kRemoteImageQualityBalanced,
            dismissOnClicked: true,
          ),
          MenuEntryRadioOption(
            text: translate('Optimize reaction time'),
            value: kRemoteImageQualityLow,
            dismissOnClicked: true,
          ),
          MenuEntryRadioOption(
              text: translate('Custom'),
              value: kRemoteImageQualityCustom,
              dismissOnClicked: true),
        ],
        curOptionGetter: () async =>
            // null means peer id is not found, which there's no need to care about
            await bind.sessionGetImageQuality(id: widget.id) ?? '',
        optionSetter: (String oldValue, String newValue) async {
          if (oldValue != newValue) {
            await bind.sessionSetImageQuality(id: widget.id, value: newValue);
          }

          double qualityInitValue = 50;
          double fpsInitValue = 30;
          bool qualitySet = false;
          bool fpsSet = false;
          setCustomValues({double? quality, double? fps}) async {
            if (quality != null) {
              qualitySet = true;
              await bind.sessionSetCustomImageQuality(
                  id: widget.id, value: quality.toInt());
            }
            if (fps != null) {
              fpsSet = true;
              await bind.sessionSetCustomFps(id: widget.id, fps: fps.toInt());
            }
            if (!qualitySet) {
              qualitySet = true;
              await bind.sessionSetCustomImageQuality(
                  id: widget.id, value: qualityInitValue.toInt());
            }
            if (!fpsSet) {
              fpsSet = true;
              await bind.sessionSetCustomFps(
                  id: widget.id, fps: fpsInitValue.toInt());
            }
          }

          if (newValue == kRemoteImageQualityCustom) {
            final btnClose = dialogButton('Close', onPressed: () async {
              await setCustomValues();
              widget.ffi.dialogManager.dismissAll();
            });

            // quality
            final quality =
                await bind.sessionGetCustomImageQuality(id: widget.id);
            qualityInitValue = quality != null && quality.isNotEmpty
                ? quality[0].toDouble()
                : 50.0;
            const qualityMinValue = 10.0;
            const qualityMaxValue = 100.0;
            if (qualityInitValue < qualityMinValue) {
              qualityInitValue = qualityMinValue;
            }
            if (qualityInitValue > qualityMaxValue) {
              qualityInitValue = qualityMaxValue;
            }
            final RxDouble qualitySliderValue = RxDouble(qualityInitValue);
            final debouncerQuality = Debouncer<double>(
              Duration(milliseconds: 1000),
              onChanged: (double v) {
                setCustomValues(quality: v);
              },
              initialValue: qualityInitValue,
            );
            final qualitySlider = Obx(() => Row(
                  children: [
                    Slider(
                      value: qualitySliderValue.value,
                      min: qualityMinValue,
                      max: qualityMaxValue,
                      divisions: 18,
                      onChanged: (double value) {
                        qualitySliderValue.value = value;
                        debouncerQuality.value = value;
                      },
                    ),
                    SizedBox(
                        width: 40,
                        child: Text(
                          '${qualitySliderValue.value.round()}%',
                          style: const TextStyle(fontSize: 15),
                        )),
                    SizedBox(
                        width: 50,
                        child: Text(
                          translate('Bitrate'),
                          style: const TextStyle(fontSize: 15),
                        ))
                  ],
                ));
            // fps
            final fpsOption =
                await bind.sessionGetOption(id: widget.id, arg: 'custom-fps');
            fpsInitValue =
                fpsOption == null ? 30 : double.tryParse(fpsOption) ?? 30;
            if (fpsInitValue < 10 || fpsInitValue > 120) {
              fpsInitValue = 30;
            }
            final RxDouble fpsSliderValue = RxDouble(fpsInitValue);
            final debouncerFps = Debouncer<double>(
              Duration(milliseconds: 1000),
              onChanged: (double v) {
                setCustomValues(fps: v);
              },
              initialValue: qualityInitValue,
            );
            bool? direct;
            try {
              direct = ConnectionTypeState.find(widget.id).direct.value ==
                  ConnectionType.strDirect;
            } catch (_) {}
            final fpsSlider = Offstage(
              offstage:
                  (await bind.mainIsUsingPublicServer() && direct != true) ||
                      version_cmp(peer_version, '1.2.0') < 0,
              child: Row(
                children: [
                  Obx((() => Slider(
                        value: fpsSliderValue.value,
                        min: 10,
                        max: 120,
                        divisions: 22,
                        onChanged: (double value) {
                          fpsSliderValue.value = value;
                          debouncerFps.value = value;
                        },
                      ))),
                  SizedBox(
                      width: 40,
                      child: Obx(() => Text(
                            '${fpsSliderValue.value.round()}',
                            style: const TextStyle(fontSize: 15),
                          ))),
                  SizedBox(
                      width: 50,
                      child: Text(
                        translate('FPS'),
                        style: const TextStyle(fontSize: 15),
                      ))
                ],
              ),
            );

            final content = Column(
              children: [qualitySlider, fpsSlider],
            );
            msgBoxCommon(widget.ffi.dialogManager, 'Custom Image Quality',
                content, [btnClose]);
          }
        },
        padding: padding,
      ),
      MenuEntryDivider<String>(),
    ];

    if (widget.state.viewStyle.value == kRemoteViewStyleOriginal) {
      displayMenu.insert(
          2,
          MenuEntryRadios<String>(
            text: translate('Scroll Style'),
            optionsGetter: () => [
              MenuEntryRadioOption(
                text: translate('ScrollAuto'),
                value: kRemoteScrollStyleAuto,
                dismissOnClicked: true,
                enabled: widget.ffi.canvasModel.imageOverflow,
              ),
              MenuEntryRadioOption(
                text: translate('Scrollbar'),
                value: kRemoteScrollStyleBar,
                dismissOnClicked: true,
                enabled: widget.ffi.canvasModel.imageOverflow,
              ),
            ],
            curOptionGetter: () async =>
                // null means peer id is not found, which there's no need to care about
                await bind.sessionGetScrollStyle(id: widget.id) ?? '',
            optionSetter: (String oldValue, String newValue) async {
              await bind.sessionSetScrollStyle(id: widget.id, value: newValue);
              widget.ffi.canvasModel.updateScrollStyle();
            },
            padding: padding,
            dismissOnClicked: true,
          ));
      displayMenu.insert(3, MenuEntryDivider<String>());

      if (_isWindowCanBeAdjusted(remoteCount)) {
        displayMenu.insert(
          0,
          MenuEntryDivider<String>(),
        );
        displayMenu.insert(
          0,
          MenuEntryButton<String>(
            childBuilder: (TextStyle? style) => Container(
                child: Text(
              translate('Adjust Window'),
              style: style,
            )),
            proc: () {
              () async {
                await _updateScreen();
                if (_screen != null) {
                  _setFullscreen(false);
                  double scale = _screen!.scaleFactor;
                  final wndRect =
                      await WindowController.fromWindowId(windowId).getFrame();
                  final mediaSize = MediaQueryData.fromWindow(ui.window).size;
                  // On windows, wndRect is equal to GetWindowRect and mediaSize is equal to GetClientRect.
                  // https://stackoverflow.com/a/7561083
                  double magicWidth =
                      wndRect.right - wndRect.left - mediaSize.width * scale;
                  double magicHeight =
                      wndRect.bottom - wndRect.top - mediaSize.height * scale;

                  final canvasModel = widget.ffi.canvasModel;
                  final width =
                      (canvasModel.getDisplayWidth() * canvasModel.scale +
                                  canvasModel.windowBorderWidth * 2) *
                              scale +
                          magicWidth;
                  final height =
                      (canvasModel.getDisplayHeight() * canvasModel.scale +
                                  canvasModel.tabBarHeight +
                                  canvasModel.windowBorderWidth * 2) *
                              scale +
                          magicHeight;
                  double left = wndRect.left + (wndRect.width - width) / 2;
                  double top = wndRect.top + (wndRect.height - height) / 2;

                  Rect frameRect = _screen!.frame;
                  if (!isFullscreen) {
                    frameRect = _screen!.visibleFrame;
                  }
                  if (left < frameRect.left) {
                    left = frameRect.left;
                  }
                  if (top < frameRect.top) {
                    top = frameRect.top;
                  }
                  if ((left + width) > frameRect.right) {
                    left = frameRect.right - width;
                  }
                  if ((top + height) > frameRect.bottom) {
                    top = frameRect.bottom - height;
                  }
                  await WindowController.fromWindowId(windowId)
                      .setFrame(Rect.fromLTWH(left, top, width, height));
                }
              }();
            },
            padding: padding,
            dismissOnClicked: true,
          ),
        );
      }
    }

    /// Show Codec Preference
    if (bind.mainHasHwcodec()) {
      final List<bool> codecs = [];
      try {
        final Map codecsJson = jsonDecode(futureData['supportedHwcodec']);
        final h264 = codecsJson['h264'] ?? false;
        final h265 = codecsJson['h265'] ?? false;
        codecs.add(h264);
        codecs.add(h265);
      } catch (e) {
        debugPrint("Show Codec Preference err=$e");
      }
      if (codecs.length == 2 && (codecs[0] || codecs[1])) {
        displayMenu.add(MenuEntryRadios<String>(
          text: translate('Codec Preference'),
          optionsGetter: () {
            final list = [
              MenuEntryRadioOption(
                text: translate('Auto'),
                value: 'auto',
                dismissOnClicked: true,
              ),
              MenuEntryRadioOption(
                text: 'VP9',
                value: 'vp9',
                dismissOnClicked: true,
              ),
            ];
            if (codecs[0]) {
              list.add(MenuEntryRadioOption(
                text: 'H264',
                value: 'h264',
                dismissOnClicked: true,
              ));
            }
            if (codecs[1]) {
              list.add(MenuEntryRadioOption(
                text: 'H265',
                value: 'h265',
                dismissOnClicked: true,
              ));
            }
            return list;
          },
          curOptionGetter: () async =>
              // null means peer id is not found, which there's no need to care about
              await bind.sessionGetOption(
                  id: widget.id, arg: 'codec-preference') ??
              '',
          optionSetter: (String oldValue, String newValue) async {
            await bind.sessionPeerOption(
                id: widget.id, name: 'codec-preference', value: newValue);
            bind.sessionChangePreferCodec(id: widget.id);
          },
          padding: padding,
          dismissOnClicked: true,
        ));
      }
    }
    displayMenu.add(MenuEntryDivider());

    /// Show remote cursor
    if (!widget.ffi.canvasModel.cursorEmbedded) {
      displayMenu.add(() {
        final state = ShowRemoteCursorState.find(widget.id);
        return MenuEntrySwitch2<String>(
          switchType: SwitchType.scheckbox,
          text: translate('Show remote cursor'),
          getter: () {
            return state;
          },
          setter: (bool v) async {
            state.value = v;
            await bind.sessionToggleOption(
                id: widget.id, value: 'show-remote-cursor');
          },
          padding: padding,
          dismissOnClicked: true,
        );
      }());
    }

    /// Show remote cursor scaling with image
    if (widget.state.viewStyle.value != kRemoteViewStyleOriginal) {
      displayMenu.add(() {
        final opt = 'zoom-cursor';
        final state = PeerBoolOption.find(widget.id, opt);
        return MenuEntrySwitch2<String>(
          switchType: SwitchType.scheckbox,
          text: translate('Zoom cursor'),
          getter: () {
            return state;
          },
          setter: (bool v) async {
            await bind.sessionToggleOption(id: widget.id, value: opt);
            state.value =
                bind.sessionGetToggleOptionSync(id: widget.id, arg: opt);
          },
          padding: padding,
          dismissOnClicked: true,
        );
      }());
    }

    /// Show quality monitor
    displayMenu.add(MenuEntrySwitch<String>(
      switchType: SwitchType.scheckbox,
      text: translate('Show quality monitor'),
      getter: () async {
        return bind.sessionGetToggleOptionSync(
            id: widget.id, arg: 'show-quality-monitor');
      },
      setter: (bool v) async {
        await bind.sessionToggleOption(
            id: widget.id, value: 'show-quality-monitor');
        widget.ffi.qualityMonitorModel.checkShowQualityMonitor(widget.id);
      },
      padding: padding,
      dismissOnClicked: true,
    ));

    final perms = widget.ffi.ffiModel.permissions;
    final pi = widget.ffi.ffiModel.pi;

    if (perms['audio'] != false) {
      displayMenu
          .add(_createSwitchMenuEntry('Mute', 'disable-audio', padding, true));
    }

    if (Platform.isWindows &&
        pi.platform == kPeerPlatformWindows &&
        perms['file'] != false) {
      displayMenu.add(_createSwitchMenuEntry(
          'Allow file copy and paste', 'enable-file-transfer', padding, true));
    }

    if (perms['keyboard'] != false) {
      if (perms['clipboard'] != false) {
        displayMenu.add(_createSwitchMenuEntry(
            'Disable clipboard', 'disable-clipboard', padding, true));
      }
      displayMenu.add(_createSwitchMenuEntry(
          'Lock after session end', 'lock-after-session-end', padding, true));
      if (pi.features.privacyMode) {
        displayMenu.add(MenuEntrySwitch2<String>(
          switchType: SwitchType.scheckbox,
          text: translate('Privacy mode'),
          getter: () {
            return PrivacyModeState.find(widget.id);
          },
          setter: (bool v) async {
            await bind.sessionToggleOption(
                id: widget.id, value: 'privacy-mode');
          },
          padding: padding,
          dismissOnClicked: true,
        ));
      }
    }
    return displayMenu;
  }

  List<MenuEntryBase<String>> _getKeyboardMenu() {
    final List<MenuEntryBase<String>> keyboardMenu = [
      MenuEntryRadios<String>(
        text: translate('Ratio'),
        optionsGetter: () {
          List<MenuEntryRadioOption> list = [];
          List<String> modes = ["legacy"];

          if (bind.sessionIsKeyboardModeSupported(id: widget.id, mode: "map")) {
            modes.add("map");
          }

          for (String mode in modes) {
            if (mode == "legacy") {
              list.add(MenuEntryRadioOption(
                  text: translate('Legacy mode'), value: 'legacy'));
            } else if (mode == "map") {
              list.add(MenuEntryRadioOption(
                  text: translate('Map mode'), value: 'map'));
            }
          }
          return list;
        },
        curOptionGetter: () async {
          return await bind.sessionGetKeyboardMode(id: widget.id) ?? "legacy";
        },
        optionSetter: (String oldValue, String newValue) async {
          await bind.sessionSetKeyboardMode(id: widget.id, value: newValue);
        },
      )
    ];
    final localPlatform =
        getLocalPlatformForKBLayoutType(widget.ffi.ffiModel.pi.platform);
    if (localPlatform != '') {
      keyboardMenu.add(MenuEntryDivider());
      keyboardMenu.add(
        MenuEntryButton<String>(
          childBuilder: (TextStyle? style) => Container(
              alignment: AlignmentDirectional.center,
              height: _MenubarTheme.height,
              child: Row(
                children: [
                  Obx(() => RichText(
                        text: TextSpan(
                          text: '${translate('Local keyboard type')}: ',
                          style: DefaultTextStyle.of(context).style,
                          children: <TextSpan>[
                            TextSpan(
                              text: KBLayoutType.value,
                              style: TextStyle(fontWeight: FontWeight.bold),
                            ),
                          ],
                        ),
                      )),
                  Expanded(
                      child: Align(
                    alignment: Alignment.centerRight,
                    child: Transform.scale(
                      scale: 0.8,
                      child: IconButton(
                        padding: EdgeInsets.zero,
                        icon: const Icon(Icons.settings),
                        onPressed: () {
                          if (Navigator.canPop(context)) {
                            Navigator.pop(context);
                          }
                          showKBLayoutTypeChooser(
                              localPlatform, widget.ffi.dialogManager);
                        },
                      ),
                    ),
                  ))
                ],
              )),
          proc: () {},
          padding: EdgeInsets.zero,
          dismissOnClicked: false,
        ),
      );
    }
    return keyboardMenu;
  }

  MenuEntrySwitch<String> _createSwitchMenuEntry(
      String text, String option, EdgeInsets? padding, bool dismissOnClicked) {
    return MenuEntrySwitch<String>(
      switchType: SwitchType.scheckbox,
      text: translate(text),
      getter: () async {
        return bind.sessionGetToggleOptionSync(id: widget.id, arg: option);
      },
      setter: (bool v) async {
        await bind.sessionToggleOption(id: widget.id, value: option);
      },
      padding: padding,
      dismissOnClicked: dismissOnClicked,
    );
  }
}

void showSetOSPassword(
    String id, bool login, OverlayDialogManager dialogManager) async {
  final controller = TextEditingController();
  var password = await bind.sessionGetOption(id: id, arg: 'os-password') ?? '';
  var autoLogin = await bind.sessionGetOption(id: id, arg: 'auto-login') != '';
  controller.text = password;
  dialogManager.show((setState, close) {
    submit() {
      var text = controller.text.trim();
      bind.sessionPeerOption(id: id, name: 'os-password', value: text);
      bind.sessionPeerOption(
          id: id, name: 'auto-login', value: autoLogin ? 'Y' : '');
      if (text != '' && login) {
        bind.sessionInputOsPassword(id: id, value: text);
      }
      close();
    }

    return CustomAlertDialog(
      title: Text(translate('OS Password')),
      content: Column(mainAxisSize: MainAxisSize.min, children: [
        PasswordWidget(controller: controller),
        CheckboxListTile(
          contentPadding: const EdgeInsets.all(0),
          dense: true,
          controlAffinity: ListTileControlAffinity.leading,
          title: Text(
            translate('Auto Login'),
          ),
          value: autoLogin,
          onChanged: (v) {
            if (v == null) return;
            setState(() => autoLogin = v);
          },
        ),
      ]),
      actions: [
        dialogButton('Cancel', onPressed: close, isOutline: true),
        dialogButton('OK', onPressed: submit),
      ],
      onSubmit: submit,
      onCancel: close,
    );
  });
}

void showAuditDialog(String id, dialogManager) async {
  final controller = TextEditingController();
  dialogManager.show((setState, close) {
    submit() {
      var text = controller.text.trim();
      if (text != '') {
        bind.sessionSendNote(id: id, note: text);
      }
      close();
    }

    late final focusNode = FocusNode(
      onKey: (FocusNode node, RawKeyEvent evt) {
        if (evt.logicalKey.keyLabel == 'Enter') {
          if (evt is RawKeyDownEvent) {
            int pos = controller.selection.base.offset;
            controller.text =
                '${controller.text.substring(0, pos)}\n${controller.text.substring(pos)}';
            controller.selection =
                TextSelection.fromPosition(TextPosition(offset: pos + 1));
          }
          return KeyEventResult.handled;
        }
        if (evt.logicalKey.keyLabel == 'Esc') {
          if (evt is RawKeyDownEvent) {
            close();
          }
          return KeyEventResult.handled;
        } else {
          return KeyEventResult.ignored;
        }
      },
    );

    return CustomAlertDialog(
      title: Text(translate('Note')),
      content: SizedBox(
          width: 250,
          height: 120,
          child: TextField(
            autofocus: true,
            keyboardType: TextInputType.multiline,
            textInputAction: TextInputAction.newline,
            decoration: const InputDecoration.collapsed(
              hintText: 'input note here',
            ),
            // inputFormatters: [
            //   LengthLimitingTextInputFormatter(16),
            //   // FilteringTextInputFormatter(RegExp(r'[a-zA-z][a-zA-z0-9\_]*'), allow: true)
            // ],
            maxLines: null,
            maxLength: 256,
            controller: controller,
            focusNode: focusNode,
          )),
      actions: [
        dialogButton('Cancel', onPressed: close, isOutline: true),
        dialogButton('OK', onPressed: submit)
      ],
      onSubmit: submit,
      onCancel: close,
    );
  });
}

void showConfirmSwitchSidesDialog(
    String id, OverlayDialogManager dialogManager) async {
  dialogManager.show((setState, close) {
    submit() async {
      await bind.sessionSwitchSides(id: id);
      closeConnection(id: id);
    }

    return CustomAlertDialog(
      content: msgboxContent('info', 'Switch Sides',
          'Please confirm if you want to share your desktop?'),
      actions: [
        dialogButton('Cancel', onPressed: close, isOutline: true),
        dialogButton('OK', onPressed: submit),
      ],
      onSubmit: submit,
      onCancel: close,
    );
  });
}

class _DraggableShowHide extends StatefulWidget {
  final RxDouble fractionX;
  final RxBool dragging;
  final RxBool show;
  const _DraggableShowHide({
    Key? key,
    required this.fractionX,
    required this.dragging,
    required this.show,
  }) : super(key: key);

  @override
  State<_DraggableShowHide> createState() => _DraggableShowHideState();
}

class _DraggableShowHideState extends State<_DraggableShowHide> {
  Offset position = Offset.zero;
  Size size = Size.zero;

  Widget _buildDraggable(BuildContext context) {
    return Draggable(
      axis: Axis.horizontal,
      child: Icon(
        Icons.drag_indicator,
        size: 20,
        color: Colors.grey,
      ),
      feedback: widget,
      onDragStarted: (() {
        final RenderObject? renderObj = context.findRenderObject();
        if (renderObj != null) {
          final RenderBox renderBox = renderObj as RenderBox;
          size = renderBox.size;
          position = renderBox.localToGlobal(Offset.zero);
        }
        widget.dragging.value = true;
      }),
      onDragEnd: (details) {
        final mediaSize = MediaQueryData.fromWindow(ui.window).size;
        widget.fractionX.value +=
            (details.offset.dx - position.dx) / (mediaSize.width - size.width);
        if (widget.fractionX.value < 0.35) {
          widget.fractionX.value = 0.35;
        }
        if (widget.fractionX.value > 0.65) {
          widget.fractionX.value = 0.65;
        }
        widget.dragging.value = false;
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    final ButtonStyle buttonStyle = ButtonStyle(
      minimumSize: MaterialStateProperty.all(const Size(0, 0)),
      padding: MaterialStateProperty.all(EdgeInsets.zero),
    );
    final child = Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        _buildDraggable(context),
        TextButton(
          onPressed: () => setState(() {
            widget.show.value = !widget.show.value;
          }),
          child: Obx((() => Icon(
                widget.show.isTrue ? Icons.expand_less : Icons.expand_more,
                size: 20,
              ))),
        ),
      ],
    );
    return TextButtonTheme(
      data: TextButtonThemeData(style: buttonStyle),
      child: Container(
        decoration: BoxDecoration(
          color: Colors.white,
          border: Border.all(color: MyTheme.border),
        ),
        child: SizedBox(
          height: 20,
          child: child,
        ),
      ),
    );
  }
}