import math
import sys
from functools import partial

import click
from PyQt5 import QtCore, QtGui, QtWidgets

MAX_ROWS = 21


try:
    import qdarkstyle

    _has_qdarkstyle = True
except ModuleNotFoundError:
    _has_qdarkstyle = False


_GTypeRole = QtCore.Qt.UserRole
_missing = object()


class GStyle(object):
    _base_style = """
        ._OptionLabel {
            font-size: 16px;
            font: bold;
            font-family: arial;
            }
        ._HelpLabel {
            font-family: arial;
            font-size: 14px;
            }
        ._InputComboBox{
            font-size: 16px;
            }
        ._InputLineEdit{
            font-size: 16px;
            }
        ._InputCheckBox{
            font-size: 16px;
            }
        ._InputSpinBox{
            font-size: 16px;
            }
        ._InputTabWidget{
            font: bold;
            font-size: 16px;
            }
        .GListView{
            font-size: 16px;
            }
        QToolTip{
            font-family: arial;
            }
        """

    def __init__(self, style=""):
        if not GStyle.check_style(style):
            self.text_color = "black"
            self.placehoder_color = "#898b8d"
            self.stylesheet = (
                GStyle._base_style
                + """
                    ._Spliter{
                        border: 1px inset gray;
                        }
                    """
            )
        elif style == "qdarkstyle":
            self.text_color = "#eff0f1"
            self.placehoder_color = "#898b8d"
            self.stylesheet = (
                qdarkstyle.load_stylesheet_pyqt5()
                + GStyle._base_style
                + """
                    .GListView{
                        padding: 5px;
                        }
                    ._Spliter{
                        border: 5px solid gray;
                        }
                    """
            )

    @staticmethod
    def check_style(style):
        if style == "qdarkstyle":
            return _has_qdarkstyle
        return False


_gstyle = GStyle()


class GListView(QtWidgets.QListView):
    def __init__(self, opt):
        super(GListView, self).__init__()
        self.nargs = opt.nargs
        self.model = GItemModel(
            opt.nargs, parent=self, opt_type=opt.type, default=opt.default
        )
        self.setModel(self.model)
        self.delegate = GEditDelegate(self)
        self.setItemDelegate(self.delegate)
        self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
        if self.nargs == -1:
            self.keyPressEvent = self.key_press
            self.setToolTip(
                "'a': add a new item blow the selected one\n"
                "'d': delete the selected item"
            )

    def key_press(self, e):
        if self.nargs == -1:
            if e.key() == QtCore.Qt.Key_A:
                if len(self.selectedIndexes()) == 0:
                    self.model.insertRow(0)
                else:
                    for i in self.selectedIndexes():
                        self.model.insertRow(i.row() + 1)
            if e.key() == QtCore.Qt.Key_D:
                si = self.selectedIndexes()
                for i in si:
                    self.model.removeRow(i.row())
        super(GListView, self).keyPressEvent(e)


class GItemModel(QtGui.QStandardItemModel):
    def __init__(self, n, parent=None, opt_type=click.STRING, default=None):
        super(QtGui.QStandardItemModel, self).__init__(0, 1, parent)
        self.type = opt_type
        for row in range(n):
            if hasattr(default, "__len__"):
                self.insertRow(row, default[row])
            else:
                self.insertRow(row, default)

    def insertRow(self, idx, val=""):
        super(GItemModel, self).insertRow(idx)

        index = self.index(idx, 0, QtCore.QModelIndex())
        if val is None or val == "":
            self.setData(
                index,
                QtGui.QBrush(QtGui.QColor(_gstyle.placehoder_color)),
                role=QtCore.Qt.ForegroundRole,
            )
        else:
            self.setData(index, val)

    def data(self, index, role=QtCore.Qt.DisplayRole):

        if role == QtCore.Qt.DisplayRole:
            dstr = QtGui.QStandardItemModel.data(self, index, role)
            if dstr == "" or dstr is None:
                if isinstance(self.type, click.types.Tuple):
                    row = index.row()
                    if 0 <= row < len(self.type.types):
                        tp = self.type.types[row]
                        dstr = tp.name
                else:
                    dstr = self.type.name
                return dstr

        if role == _GTypeRole:
            tp = click.STRING
            if isinstance(self.type, click.types.Tuple):
                row = index.row()
                if 0 <= row < len(self.type.types):
                    tp = self.type.types[row]
            elif isinstance(self.type, click.types.ParamType):
                tp = self.type
            return tp

        return QtGui.QStandardItemModel.data(self, index, role)


class GEditDelegate(QtWidgets.QStyledItemDelegate):
    def createEditor(self, parent, option, index):
        tp = index.data(role=_GTypeRole)
        if isinstance(tp, click.Path):
            led = GLineEdit_path.from_option(tp, parent)
        else:
            led = QtWidgets.QLineEdit(parent)
        led.setPlaceholderText(tp.name)
        led.setValidator(select_type_validator(tp))
        return led

    def setEditorData(self, editor, index):
        item_var = index.data(role=QtCore.Qt.EditRole)
        if item_var is not None:
            editor.setText(str(item_var))

    def setModelData(self, editor, model, index):
        data_str = editor.text()
        if data_str == "" or data_str is None:
            model.setData(
                index,
                QtGui.QBrush(QtGui.QColor(_gstyle.placehoder_color)),
                role=QtCore.Qt.ForegroundRole,
            )
        else:
            model.setData(
                index,
                QtGui.QBrush(QtGui.QColor(_gstyle.text_color)),
                role=QtCore.Qt.ForegroundRole,
            )
        QtWidgets.QStyledItemDelegate.setModelData(self, editor, model, index)


def generate_label(opt):
    show_name = getattr(opt, "show_name", _missing)
    show_name = opt.name if show_name is _missing else show_name
    param = _OptionLabel(show_name)
    param.setToolTip(getattr(opt, "help", None))
    return param


class GStringLineEditor(click.types.StringParamType):
    def to_widget(self, opt, validator=None):
        value = _InputLineEdit()
        value.setPlaceholderText(self.name)
        if opt.default:
            value.setText(str(opt.default))
        if getattr(opt, "hide_input", False):
            value.setEchoMode(QtWidgets.QLineEdit.Password)
        value.setValidator(validator)

        def to_command():
            return [opt.opts[0], value.text()]

        return [generate_label(opt), value], to_command


class GIntLineEditor(GStringLineEditor):
    def to_widget(self, opt):
        return GStringLineEditor.to_widget(self, opt, validator=QtGui.QIntValidator())


class GFloatLineEditor(GStringLineEditor):
    def to_widget(self, opt):
        return GStringLineEditor.to_widget(
            self, opt, validator=QtGui.QDoubleValidator()
        )


class GFileDialog(QtWidgets.QFileDialog):
    def __init__(self, *args, exists=False, file_okay=True, dir_okay=True, **kwargs):
        super(GFileDialog, self).__init__(*args, **kwargs)
        self.setOption(QtWidgets.QFileDialog.DontUseNativeDialog, True)
        self.setLabelText(QtWidgets.QFileDialog.Accept, "Select")
        if (exists, file_okay, dir_okay) == (True, True, False):
            self.setFileMode(QtWidgets.QFileDialog.ExistingFile)
        elif (exists, file_okay, dir_okay) == (False, True, False):
            self.setFileMode(QtWidgets.QFileDialog.AnyFile)
        elif (exists, file_okay, dir_okay) == (True, False, True):
            self.setFileMode(QtWidgets.QFileDialog.Directory)
        elif (exists, file_okay, dir_okay) == (False, False, True):
            self.setFileMode(QtWidgets.QFileDialog.Directory)
        elif exists == True:
            self.setFileMode(QtWidgets.QFileDialog.ExistingFile)
            self.accept = self.accept_all
        elif exists == False:
            self.setFileMode(QtWidgets.QFileDialog.AnyFile)
            self.accept = self.accept_all

    def accept_all(self):
        super(GFileDialog, self).done(QtWidgets.QFileDialog.Accepted)


class GLineEdit_path(QtWidgets.QLineEdit):
    def __init__(self, parent=None, exists=False, file_okay=True, dir_okay=True):
        super(GLineEdit_path, self).__init__(parent)
        self.action = self.addAction(
            self.style().standardIcon(QtWidgets.QStyle.SP_DirIcon),
            QtWidgets.QLineEdit.TrailingPosition,
        )
        self.fdlg = GFileDialog(
            self,
            "Select File Dialog",
            "./",
            "*",
            exists=exists,
            file_okay=file_okay,
            dir_okay=dir_okay,
        )
        self.action.triggered.connect(self.run_dialog)

    def run_dialog(self):
        if self.fdlg.exec() == QtWidgets.QFileDialog.Accepted:
            self.setText(self.fdlg.selectedFiles()[0])

    @staticmethod
    def from_option(opt, parent=None):
        return GLineEdit_path(
            parent=parent,
            exists=opt.exists,
            file_okay=opt.file_okay,
            dir_okay=opt.dir_okay,
        )


class GPathGLindEidt_path(click.types.Path):
    def to_widget(self, opt):
        value = GLineEdit_path(
            exists=self.exists, file_okay=self.file_okay, dir_okay=self.dir_okay
        )
        value.setPlaceholderText(self.name)
        if opt.default:
            value.setText(str(opt.default))

        def to_command():
            return [opt.opts[0], value.text()]

        return [generate_label(opt), value], to_command


class _GLabeledSlider(QtWidgets.QSlider):
    def __init__(self, min, max, val):
        super(_GLabeledSlider, self).__init__(QtCore.Qt.Horizontal)
        self.min, self.max = min, max

        self.setMinimum(min)
        self.setMaximum(max)
        self.setValue(val)

        self.label = self.__init_label()

    def __init_label(self):
        l = max(
            [
                math.ceil(math.log10(abs(x))) if x != 0 else 1
                for x in [self.min, self.max]
            ]
        )
        l += 1
        return QtWidgets.QLabel("0" * l)


def argument_command(to_command):
    def tc():
        a = to_command()
        return a[1:]

    return tc


class GSlider(QtWidgets.QHBoxLayout):
    def __init__(self, min=0, max=10, default=None, *args, **kwargs):
        super(QtWidgets.QHBoxLayout, self).__init__()

        self.min, self.max, self.default = min, max, default
        self.label = self.__init_label()
        self.slider = self.__init_slider()

        self.label.setText(str(self.default))

        self.addWidget(self.slider)
        self.addWidget(self.label)

    def value(self):
        return self.slider.value()

    def __init_slider(self):
        slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
        slider.setMinimum(self.min)
        slider.setMaximum(self.max)
        default_val = (self.min + self.max) // 2
        if isinstance(self.default, int):
            if self.min <= self.default <= self.max:
                default_val = self.default
        self.default = default_val
        slider.setValue(default_val)
        slider.valueChanged.connect(lambda x: self.label.setText(str(x)))
        return slider

    def __init_label(self):
        l = max(
            [
                math.ceil(math.log10(abs(x))) if x != 0 else 1
                for x in [self.min, self.max]
            ]
        )
        l += 1
        return QtWidgets.QLabel("0" * l)


class GIntRangeGSlider(click.types.IntRange):
    def to_widget(self, opt):
        value = GSlider(min=self.min, max=self.max, default=opt.default)

        def to_command():
            return [opt.opts[0], str(value.value())]

        return [generate_label(opt), value], to_command


class GIntRangeSlider(click.types.IntRange):
    def to_widget(self, opt):
        value = QtWidgets.QSlider(QtCore.Qt.Horizontal)
        value.setMinimum(self.min)
        value.setMaximum(self.max)

        default_val = (self.min + self.max) // 2
        if isinstance(opt.default, int):
            if self.min <= opt.default <= self.max:
                default_val = opt.default
        value.setValue(default_val)

        def to_command():
            return [opt.opts[0], str(value.value())]

        return [generate_label(opt), value], to_command


class GIntRangeLineEditor(click.types.IntRange):
    def to_widget(self, opt):
        value = QtWidgets.QLineEdit()
        # TODO: set validator

        def to_command():
            return [opt.opts[0], value.text()]

        return [generate_label(opt), value], to_command


def bool_flag_option(opt):
    checkbox = _InputCheckBox(opt.name)
    if opt.default:
        checkbox.setCheckState(2)
    # set tip
    checkbox.setToolTip(opt.help)

    def to_command():
        if checkbox.checkState():
            return [opt.opts[0]]
        else:
            return opt.secondary_opts

    return [checkbox], to_command


class GChoiceComboBox(click.types.Choice):
    def to_widget(self, opt):
        cb = _InputComboBox()
        cb.addItems(self.choices)

        def to_command():
            return [opt.opts[0], cb.currentText()]

        return [generate_label(opt), cb], to_command


def count_option(opt):
    sb = _InputSpinBox()

    def to_command():
        return [opt.opts[0]] * int(sb.text())

    return [generate_label(opt), sb], to_command


class GTupleGListView(click.Tuple):
    def to_widget(self, opt):
        view = GListView(opt)

        def to_command():
            _ = [opt.opts[0]]
            for idx in range(view.model.rowCount()):
                _.append(view.model.item(idx).text())
            return _

        return [generate_label(opt), view], to_command


def multi_text_arguement(opt):
    value = GListView(opt)

    def to_command():
        _ = []
        for idx in range(value.model.rowCount()):
            _.append(value.model.item(idx).text())
        # if opt.required and value.model.rowCount() == 0:
        # raise click.exceptions.BadParameter("Required")
        # print(opt.__dict__)
        return _

    # return [QtWidgets.QLabel(opt.name), value], to_command
    return [_OptionLabel(opt.name), value], to_command


def select_type_validator(tp: click.types.ParamType) -> QtGui.QValidator:
    """ select the right validator for `tp`"""

    if isinstance(tp, click.types.IntParamType):
        return QtGui.QIntValidator()
    elif isinstance(tp, click.types.FloatParamType):
        return QtGui.QDoubleValidator()

    return None


def select_opt_validator(opt):
    """ select the right validator for `opt`"""

    return select_type_validator(opt.type)


def opt_to_widget(opt):
    if opt.nargs > 1:
        return GTupleGListView.to_widget(opt.type, opt)
    elif getattr(opt, "is_bool_flag", False):
        return bool_flag_option(opt)
    elif getattr(opt, "count", False):
        return count_option(opt)
    elif isinstance(opt.type, click.types.Choice):
        return GChoiceComboBox.to_widget(opt.type, opt)
    elif isinstance(opt.type, click.types.Path):
        return GPathGLindEidt_path.to_widget(opt.type, opt)
    elif isinstance(opt.type, click.types.IntRange):
        return GIntRangeGSlider.to_widget(opt.type, opt)
    elif isinstance(opt.type, click.types.IntParamType):
        return GIntLineEditor.to_widget(opt.type, opt)
    elif isinstance(opt.type, click.types.FloatParamType):
        return GFloatLineEditor.to_widget(opt.type, opt)
    else:
        return GStringLineEditor.to_widget(opt.type, opt)


def _to_widget(opt):
    # customed widget

    if isinstance(opt.type, click.types.FuncParamType):
        if hasattr(opt.type.func, "to_widget"):
            return opt.type.func.to_widget()
    elif hasattr(opt.type, "to_widget"):
        return opt.type.to_widget()

    if isinstance(opt, click.core.Argument):
        if opt.nargs == 1:
            w, tc = opt_to_widget(opt)

            return w, argument_command(tc)
        elif opt.nargs > 1 or opt.nargs == -1:
            return multi_text_arguement(opt)
    else:
        return opt_to_widget(opt)


def layout_append_opts(layout, opts):
    params_func = []
    widgets = []
    i = 0

    for i, para in enumerate(opts):
        widget, value_func = _to_widget(para)
        widgets.append(widget)
        params_func.append(value_func)

        for idx, w in enumerate(widget):

            if isinstance(w, QtWidgets.QLayout):
                layout.addLayout(w, i, idx)
            else:
                layout.addWidget(w, i, idx)

    return layout, params_func, widgets


def generate_sysargv(cmd_list):
    argv_list = []

    for name, func_list in cmd_list:
        argv_list.append(name)

        for value_func in func_list:
            argv_list += value_func()

    return argv_list


class _Spliter(QtWidgets.QFrame):
    def __init__(self, parent=None):
        super(_Spliter, self).__init__(parent=parent)
        self.setFrameShape(QtWidgets.QFrame.HLine)


class _InputComboBox(QtWidgets.QComboBox):
    pass


class _InputTabWidget(QtWidgets.QTabWidget):
    pass


class _HelpLabel(QtWidgets.QLabel):
    pass


class _OptionLabel(QtWidgets.QLabel):
    pass


class _InputLineEdit(QtWidgets.QLineEdit):
    pass


class _InputCheckBox(QtWidgets.QCheckBox):
    pass


class _InputSpinBox(QtWidgets.QSpinBox):
    pass


class CommandLayout(QtWidgets.QGridLayout):
    def __init__(self, func, run_exit, parent_layout=None):
        super(CommandLayout, self).__init__()
        self.parent_layout = parent_layout
        self.func = func
        self.run_exit = run_exit

        if func.help:
            label = _HelpLabel(func.help)
            label.setWordWrap(True)
            self.addWidget(label, 0, 0, 1, 2)
            frame = _Spliter()
            self.addWidget(frame, 1, 0, 1, 4)
        self.params_func, self.widgets = self.append_opts(self.func.params)

    def add_sysargv(self):
        if hasattr(self.parent_layout, "add_sysargv"):
            self.parent_layout.add_sysargv()
        sys.argv += generate_sysargv([(self.func.name, self.params_func)])

    def append_opts(self, opts):
        params_func = []
        widgets = []

        def _nwidgets(x):
            return -len(_to_widget(x)[0])

        opts = sorted(opts, key=_nwidgets)
        row, col = 0, 0
        for para in opts:
            widget, value_func = _to_widget(para)
            widgets.append(widget)
            params_func.append(value_func)

            for idx, w in enumerate(widget):
                if isinstance(w, QtWidgets.QLayout):
                    self.addLayout(w, row + 2, 2 * col + idx)
                else:
                    self.addWidget(w, row + 2, 2 * col + idx)
            self.setRowStretch(row, 5)
            row += 1
            if row >= MAX_ROWS - 1:
                row = 0
                col += 1

        return params_func, widgets

    def generate_cmd_button(self, label, cmd_slot, tooltip=""):
        button = QtWidgets.QPushButton(label)
        button.setToolTip(tooltip)
        button.clicked.connect(self.clean_sysargv)
        button.clicked.connect(self.add_sysargv)
        button.clicked.connect(cmd_slot)

        return button

    def add_cmd_button(self, label, cmd_slot, pos=None):
        run_button = self.generate_cmd_button(label, cmd_slot)

        if pos is None:
            # pos = self.rowCount() + 1, 0
            pos = 0, 2
        self.addWidget(run_button, pos[0], pos[1])

    def add_cmd_buttons(self, args):
        # row = self.rowCount() + 1
        row = 0
        cmd_layout = QtWidgets.QGridLayout()
        cmd_layout.setHorizontalSpacing(20)

        for col, arg in enumerate(args):
            button = self.generate_cmd_button(**arg)
            cmd_layout.addWidget(button, col, 0)
        self.addLayout(cmd_layout, row, 2, 1, 2)

    @QtCore.pyqtSlot()
    def clean_sysargv(self):
        sys.argv = []


class RunCommand(QtCore.QRunnable):
    def __init__(self, func, run_exit):
        super(RunCommand, self).__init__()
        self.func = func
        self.run_exit = run_exit

    @QtCore.pyqtSlot()
    def run(self):
        print(sys.argv)
        try:
            self.func(standalone_mode=self.run_exit)
        except click.exceptions.BadParameter as bpe:
            # warning message
            msg = QtWidgets.QMessageBox()
            msg.setIcon(QtWidgets.QMessageBox.Warning)
            msg.setText(bpe.format_message())
            msg.exec_()
        except Exception as bpe:
            msg = QtWidgets.QMessageBox()
            msg.setIcon(QtWidgets.QMessageBox.Warning)
            msg.setText(repr(bpe))
            msg.exec_()
        # if self.outputEdit is not None:
        # self.outputEdit.show()


class GCommand(click.Command):
    def __init__(self, new_thread=True, *arg, **args):
        super(GCommand, self).__init__(*arg, **args)
        self.new_thread = new_thread


class GOption(click.Option):
    def __init__(self, *arg, show_name=_missing, **args):
        super(GOption, self).__init__(*arg, **args)
        self.show_name = show_name


# def normalOutputWritten(t):
# """Append text to the QTextEdit."""
# Maybe QTextEdit.append() works as well, but this is how I do it:
# cursor = text.textCursor()
# cursor.movePosition(QtGui.QTextCursor.End)
# cursor.insertText(t)
# text.setTextCursor(cursor)
# text.ensureCursorVisible()


class GuiStream(QtCore.QObject):
    textWritten = QtCore.pyqtSignal(str)

    def flush(self):
        pass

    def write(self, text):
        self.textWritten.emit(str(text))


class OutputEdit(QtWidgets.QTextEdit):
    def print(self, text):
        cursor = self.textCursor()
        cursor.movePosition(QtGui.QTextCursor.End)
        cursor.insertText(text)
        self.setTextCursor(cursor)
        self.ensureCursorVisible()


class App(QtWidgets.QWidget):
    def __init__(
        self,
        func,
        run_exit,
        new_thread,
        output="gui",
        left=10,
        top=10,
        width=800,
        height=280,
    ):
        """
        Parameters
        ----------
        output : str
            'gui': [default] redirect screen output to the gui
            'term': do nothing
        """
        super().__init__()
        self.new_thread = new_thread
        self.title = func.name
        self.func = func
        self.initUI(run_exit, QtCore.QRect(left, top, width, height))
        self.threadpool = QtCore.QThreadPool()
        self.outputEdit = self.initOutput(output)

    def initOutput(self, output):
        if output == "gui":
            sys.stdout = GuiStream()
            sys.stderr = sys.stdout
            text = OutputEdit()
            text.setReadOnly(True)
            sys.stdout.textWritten.connect(text.print)
            sys.stdout.textWritten.connect(text.show)
            # sys.stdout.textWritten.connect(text.append)
            # text.show()

            return text
        else:
            return None

    def initCommandUI(self, func, run_exit, parent_layout=None):
        opt_set = CommandLayout(func, run_exit, parent_layout=parent_layout)

        if isinstance(func, click.MultiCommand):
            tabs = _InputTabWidget()

            for cmd, f in func.commands.items():
                sub_opt_set = self.initCommandUI(f, run_exit, parent_layout=opt_set)
                tab = QtWidgets.QWidget()
                tab.setLayout(sub_opt_set)
                tabs.addTab(tab, cmd)
            opt_set.addWidget(tabs, opt_set.rowCount(), 0, 1, 4)
            # return opt_set
        elif isinstance(func, click.Command):
            new_thread = getattr(func, "new_thread", self.new_thread)
            opt_set.add_cmd_buttons(
                args=[
                    {
                        "label": "&Run",
                        "cmd_slot": partial(self.run_cmd, new_thread=new_thread),
                        "tooltip": "run command",
                    },
                    {
                        "label": "&Copy",
                        "cmd_slot": self.copy_cmd,
                        "tooltip": "copy command to clipboard",
                    },
                ]
            )

        return opt_set

    def initUI(self, run_exit, geometry):
        self.run_exit = run_exit
        self.setWindowTitle(self.title)
        # self.setGeometry(self.left, self.top, self.width, self.height)
        self.setGeometry(geometry)
        self.opt_set = self.initCommandUI(self.func, run_exit,)
        self.setLayout(self.opt_set)
        self.show()

    @QtCore.pyqtSlot()
    def copy_cmd(self):
        cb = QtWidgets.QApplication.clipboard()
        cb.clear(mode=cb.Clipboard)
        cmd_text = " ".join(sys.argv)
        cb.setText(cmd_text, mode=cb.Clipboard)

        msg = QtWidgets.QMessageBox()
        msg.setIcon(QtWidgets.QMessageBox.Information)
        msg.setText(f"copy '{cmd_text}' to clipboard")
        msg.exec_()

    def run_cmd(self, new_thread):
        runcmd = RunCommand(self.func, self.run_exit)

        if new_thread:
            self.threadpool.start(runcmd)
        else:
            runcmd.run()


def gui_it(click_func, style="qdarkstyle", **argvs) -> None:
    """
    Parameters
    ----------
    click_func
    `new_thread` is used for qt-based func, like matplotlib
    """
    global _gstyle
    _gstyle = GStyle(style)
    app = QtWidgets.QApplication(sys.argv)
    app.setStyleSheet(_gstyle.stylesheet)

    # set the default value for argvs
    argvs["run_exit"] = argvs.get("run_exit", False)
    argvs["new_thread"] = argvs.get("new_thread", False)

    _ = App(click_func, **argvs)
    sys.exit(app.exec_())


def gui_option(f: click.core.BaseCommand) -> click.core.BaseCommand:
    """decorator for adding '--gui' option to command"""
    # TODO: add run_exit, new_thread
    def run_gui_it(ctx, param, value):
        if not value or ctx.resilient_parsing:
            return
        f.params = [p for p in f.params if not p.name == "gui"]
        gui_it(f)
        ctx.exit()

    return click.option(
        "--gui",
        is_flag=True,
        callback=run_gui_it,
        help="run with gui",
        expose_value=False,
        is_eager=False,
    )(f)
