#include "desktop_integration.h"
#include "popup_menu_window.h"
#include "tray_bridge_model.h"
#include "tray_ui_logic.h"
#include "tray_visuals.h"

#include <QAction>
#include <QActionGroup>
#include <QApplication>
#include <QClipboard>
#include <QGuiApplication>
#include <QDir>
#include <QInputDialog>
#include <QJsonDocument>
#include <QJsonObject>
#include <QKeyEvent>
#include <QLockFile>
#include <QMenu>
#include <QMessageBox>
#include <QMouseEvent>
#include <QProcess>
#include <QScreen>
#include <QStandardPaths>
#include <QSystemTrayIcon>
#include <QTimer>
#include <QUrl>
#include <QWindow>

namespace {
constexpr int kBridgeTimeoutMs = 60000;
constexpr int kHealthyPollMs = 8000;
constexpr int kUnhealthyPollMs = 3000;
constexpr int kWatchRestartMs = 4000;
constexpr int kAuthURLRetryMs = 500;
constexpr int kAuthURLRetryAttempts = 12;

QString trayTitle()
{
    return QStringLiteral("Mirage");
}

QString bridgeProgram()
{
    QString path = qEnvironmentVariable("MIRAGE_BRIDGE_PROGRAM");
    if (!path.isEmpty()) {
        return path;
    }
    return QStringLiteral("mirage-qt");
}

QString defaultControlInput()
{
    QString value = qEnvironmentVariable("MIRAGE_CONTROL_DEFAULT");
    if (!value.isEmpty()) {
        return value;
    }
    return QStringLiteral("https://sdp.mm.md");
}

QString lockFilePath()
{
    QString runtimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
    if (runtimeDir.isEmpty()) {
        runtimeDir = QDir::tempPath();
    }
    return QDir(runtimeDir).filePath(QStringLiteral("mirage-qt.lock"));
}

QString currentDesktopUser()
{
    for (const char *key : {"USER", "LOGNAME"}) {
        const QString value = qEnvironmentVariable(key).trimmed();
        if (!value.isEmpty()) {
            return value;
        }
    }
    return QStringLiteral("$USER");
}

QString bridgeActionName(const QStringList &args)
{
    for (int i = 0; i + 1 < args.size(); ++i) {
        if (args.at(i) == QStringLiteral("--action")) {
            return args.at(i + 1).trimmed();
        }
    }
    return {};
}

QString operatorGrantPromptText()
{
    const QString user = currentDesktopUser();
    return QStringLiteral("当前用户没有 Mirage 管理权限。\n\n是否现在授权当前桌面用户 `%1` 为 operator？\n这将调用 pkexec，并可能要求输入管理员密码。\n\n如需手工执行：\nsudo mirage set --operator=%1")
        .arg(user);
}

QByteArray runBridge(const QStringList &args, QString *error)
{
    QProcess proc;
    proc.start(bridgeProgram(), args);
    if (!proc.waitForStarted()) {
        if (error) {
            *error = QStringLiteral("failed to start bridge program: %1").arg(proc.errorString());
        }
        return {};
    }

    proc.closeWriteChannel();
    if (!proc.waitForFinished(kBridgeTimeoutMs)) {
        proc.kill();
        proc.waitForFinished();
        if (error) {
            *error = QStringLiteral("bridge command timed out");
        }
        return {};
    }

    const QByteArray stdErr = proc.readAllStandardError().trimmed();
    if (proc.exitStatus() != QProcess::NormalExit || proc.exitCode() != 0) {
        if (error) {
            *error = stdErr.isEmpty() ? QStringLiteral("bridge command failed") : QString::fromUtf8(stdErr);
        }
        return {};
    }

    if (error) {
        error->clear();
    }
    return proc.readAllStandardOutput();
}

QJsonObject runBridgeObject(const QStringList &args, QString *error)
{
    const QByteArray out = runBridge(args, error);
    if (out.isEmpty()) {
        if (error && error->isEmpty()) {
            *error = QStringLiteral("empty bridge response");
        }
        return {};
    }

    QJsonParseError parseError;
    const QJsonDocument doc = QJsonDocument::fromJson(out, &parseError);
    if (parseError.error != QJsonParseError::NoError || !doc.isObject()) {
        if (error) {
            *error = QStringLiteral("invalid bridge JSON: %1").arg(parseError.errorString());
        }
        return {};
    }
    return doc.object();
}

QStringList bridgeActionArgs(const QString &action)
{
    return {QStringLiteral("--bridge"), QStringLiteral("--action"), action};
}

QStringList bridgeSnapshotArgs()
{
    return {QStringLiteral("--bridge"), QStringLiteral("--action"), QStringLiteral("snapshot"), QStringLiteral("--json")};
}

QStringList bridgeWatchArgs()
{
    return {QStringLiteral("--bridge"), QStringLiteral("--watch")};
}

QStringList bridgeServiceStatusArgs()
{
    return {QStringLiteral("--bridge"), QStringLiteral("--action"), QStringLiteral("service-status"), QStringLiteral("--json")};
}

QStringList bridgeControllerArgs(const QString &action, const QString &controllerInput)
{
    return {QStringLiteral("--bridge"),
            QStringLiteral("--action"),
            action,
            QStringLiteral("--controller"),
            controllerInput};
}

QStringList bridgeStableIDArgs(const QString &stableID)
{
    return {QStringLiteral("--bridge"),
            QStringLiteral("--action"),
            QStringLiteral("select-exit-node"),
            QStringLiteral("--stable-id"),
            stableID};
}

QStringList bridgeToggleArgs(const QString &action, bool enabled)
{
    return {QStringLiteral("--bridge"),
            QStringLiteral("--action"),
            action,
            QStringLiteral("--enabled=") +
                (enabled ? QStringLiteral("true") : QStringLiteral("false"))};
}

QString stateLabel(const QJsonObject &snapshot)
{
    return snapshot.value(QStringLiteral("stateLabel")).toString(QStringLiteral("未初始化"));
}

QString backendState(const QJsonObject &snapshot)
{
    return snapshot.value(QStringLiteral("backendState")).toString();
}

QString controllerInput(const QJsonObject &snapshot)
{
    const QString value = snapshot.value(QStringLiteral("controllerInput")).toString().trimmed();
    return value.isEmpty() ? defaultControlInput() : value;
}

QString controlURL(const QJsonObject &snapshot)
{
    return snapshot.value(QStringLiteral("controlURL")).toString();
}

QString adminURL(const QJsonObject &snapshot)
{
    return snapshot.value(QStringLiteral("adminURL")).toString();
}

bool bridgeServiceSocketReady(const QJsonObject &status)
{
    return status.value(QStringLiteral("socketReady")).toBool();
}

bool bridgeServiceActive(const QJsonObject &status)
{
    return status.value(QStringLiteral("active")).toBool();
}

QString bridgeServiceMessage(const QJsonObject &status)
{
    const QString message = status.value(QStringLiteral("message")).toString().trimmed();
    if (!message.isEmpty()) {
        return message;
    }
    if (bridgeServiceSocketReady(status)) {
        return QStringLiteral("后台服务已就绪");
    }
    if (bridgeServiceActive(status)) {
        return QStringLiteral("后台服务正在启动");
    }
    return QStringLiteral("后台服务未启动");
}

QString socketPath(const QJsonObject &snapshot)
{
    return snapshot.value(QStringLiteral("socketPath")).toString();
}

QString accountLabel(const QJsonObject &snapshot)
{
    return snapshot.value(QStringLiteral("accountLabel")).toString(QStringLiteral("未登录"));
}

QString versionString(const QJsonObject &snapshot)
{
    return snapshot.value(QStringLiteral("version")).toString(QStringLiteral("-"));
}

QJsonObject selfObject(const QJsonObject &snapshot)
{
    return snapshot.value(QStringLiteral("self")).toObject();
}

QString selfName(const QJsonObject &snapshot)
{
    return selfObject(snapshot).value(QStringLiteral("name")).toString(QStringLiteral("-"));
}

QString selfDNSName(const QJsonObject &snapshot)
{
    return selfObject(snapshot).value(QStringLiteral("dnsName")).toString();
}

QStringList stringListFromArray(const QJsonArray &array)
{
    QStringList values;
    values.reserve(array.size());
    for (const QJsonValue &value : array) {
        const QString item = value.toString().trimmed();
        if (!item.isEmpty()) {
            values << item;
        }
    }
    return values;
}

QStringList selfIPs(const QJsonObject &snapshot)
{
    return stringListFromArray(selfObject(snapshot).value(QStringLiteral("ips")).toArray());
}

QString selfIP(const QJsonObject &snapshot)
{
    const QStringList ips = selfIPs(snapshot);
    return ips.isEmpty() ? QString() : ips.first();
}

QString nodeSummary(const QJsonObject &snapshot)
{
    const QString name = selfName(snapshot);
    const QString ip = selfIP(snapshot);
    if (ip.isEmpty()) {
        return name;
    }
    return QStringLiteral("%1 (%2)").arg(name, ip);
}

QString currentExitNodeStableID(const QJsonObject &snapshot)
{
    const QJsonArray exitNodes = snapshot.value(QStringLiteral("exitNodes")).toArray();
    for (const QJsonValue &value : exitNodes) {
        const QJsonObject item = value.toObject();
        if (item.value(QStringLiteral("selected")).toBool()) {
            return item.value(QStringLiteral("stableID")).toString();
        }
    }
    return {};
}

QString currentExitNodeLabel(const QJsonObject &snapshot)
{
    const QJsonArray exitNodes = snapshot.value(QStringLiteral("exitNodes")).toArray();
    for (const QJsonValue &value : exitNodes) {
        const QJsonObject item = value.toObject();
        if (item.value(QStringLiteral("selected")).toBool()) {
            return exitNodeMenuLabel(item);
        }
    }
    return QStringLiteral("不使用");
}

bool hasRealExitNodeChoices(const QJsonObject &snapshot)
{
    const QJsonArray exitNodes = snapshot.value(QStringLiteral("exitNodes")).toArray();
    if (exitNodes.size() > 1) {
        return true;
    }
    return !currentExitNodeStableID(snapshot).isEmpty();
}

bool canToggleExitNodeLAN(const QJsonObject &snapshot)
{
    return !currentExitNodeStableID(snapshot).isEmpty();
}

bool canToggleAdvertiseExit(const QJsonObject &snapshot)
{
    const bool advertisesExit = snapshot.value(QStringLiteral("prefs"))
                                    .toObject()
                                    .value(QStringLiteral("advertisesExitNode"))
                                    .toBool();
    return advertisesExit || currentExitNodeStableID(snapshot).isEmpty();
}

bool isLoggedInState(const QJsonObject &snapshot)
{
    const QString state = backendState(snapshot);
    return !state.isEmpty() && state != QStringLiteral("NoState") && state != QStringLiteral("NeedsLogin");
}

QString firstLineSummary(const QString &error)
{
    if (error.isEmpty()) {
        return QStringLiteral("后端不可用");
    }
    const int lineEnd = error.indexOf('\n');
    const QString summary = (lineEnd >= 0 ? error.left(lineEnd) : error).trimmed();
    return summary.isEmpty() ? QStringLiteral("后端不可用") : summary;
}

QString tooltipForSnapshot(const QJsonObject &snapshot)
{
    const QString controller = controlURL(snapshot).isEmpty() ? defaultControlInput() : controlURL(snapshot);
    return QStringLiteral("Mirage\n%1\n%2\n%3").arg(stateLabel(snapshot), controller, nodeSummary(snapshot));
}

QString effectiveControllerInput(const QString &value)
{
    const QString trimmed = value.trimmed();
    return trimmed.isEmpty() ? defaultControlInput() : trimmed;
}

void showWarningDialog(QWidget *parent, const QString &message)
{
    QMessageBox::warning(parent, trayTitle(), message);
}

void showInfoDialog(QWidget *parent, const QString &message)
{
    QMessageBox::information(parent, trayTitle(), message);
}

void showTrayMessage(QSystemTrayIcon &tray, const QString &message, QSystemTrayIcon::MessageIcon icon)
{
    if (!QSystemTrayIcon::supportsMessages() || message.isEmpty()) {
        return;
    }
    tray.showMessage(trayTitle(), message, icon, 3000);
}

void appendProcessText(QString *buffer, const QByteArray &chunk)
{
    if (!buffer || chunk.isEmpty()) {
        return;
    }
    *buffer += QString::fromUtf8(chunk);
    if (buffer->size() > 4096) {
        *buffer = buffer->right(4096);
    }
}

QPoint globalMouseEventPos(QEvent *event)
{
    if (!event) {
        return {};
    }
    if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease ||
        event->type() == QEvent::MouseMove) {
        auto *mouseEvent = static_cast<QMouseEvent *>(event);
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
        return mouseEvent->globalPosition().toPoint();
#else
        return mouseEvent->globalPos();
#endif
    }
    return {};
}
}

class TrayApp : public QObject {
    Q_OBJECT
public:
    TrayApp()
        : lockFile_(lockFilePath())
    {
        lockFile_.setStaleLockTime(0);
        if (!lockFile_.tryLock(0)) {
            showInfoDialog(nullptr, QStringLiteral("Mirage Linux 托盘已经在运行。"));
            QTimer::singleShot(0, qApp, &QCoreApplication::quit);
            return;
        }
        primaryInstance_ = true;

        setupMenu();
        connectActions();
        connectWatchProcess();
        rootPopup_.setPreferredMaxWidth(360);
        sidePopup_.setPreferredMaxWidth(420);

        tray_.setToolTip(trayTitle());
        tray_.setIcon(iconForTrayState(TrayIconState::Logo));
        tray_.show();

        connect(&poller_, &QTimer::timeout, this, [this] { refreshSnapshot(false); });
        connect(&watchRestartTimer_, &QTimer::timeout, this, &TrayApp::ensureWatchProcess);
        connect(&connectingIconTimer_, &QTimer::timeout, this, &TrayApp::advanceConnectingIcon);
        connect(&authURLRetryTimer_, &QTimer::timeout, this, &TrayApp::retryPendingAuthURLOpen);
        connect(&rootPopup_, &PopupMenuWindow::actionHovered, this, [this](QAction *action) {
            if (!isLauncherAction(action)) {
                closeActiveSidePopup();
                return;
            }
            openSidePopup(action);
        });
        connect(&rootPopup_, &PopupMenuWindow::actionTriggered, this, [this](QAction *action) {
            if (isLauncherAction(action)) {
                openSidePopup(action);
                return;
            }
            if (action) {
                closePopupStack();
                action->trigger();
            }
        });
        connect(&sidePopup_, &PopupMenuWindow::actionTriggered, this, [this](QAction *action) {
            if (!action) {
                return;
            }
            closePopupStack();
            action->trigger();
        });
        connect(&tray_, &QSystemTrayIcon::activated, this, [this](QSystemTrayIcon::ActivationReason reason) {
            if (reason == QSystemTrayIcon::Trigger || reason == QSystemTrayIcon::DoubleClick ||
                reason == QSystemTrayIcon::Context) {
                toggleRootPopup();
            }
        });
        qApp->installEventFilter(this);

        connectingIconTimer_.setInterval(300);
        authURLRetryTimer_.setInterval(kAuthURLRetryMs);
        authURLRetryTimer_.setSingleShot(true);
        refreshSnapshot(false);
    }

    ~TrayApp() override
    {
        shuttingDown_ = true;
        watchRestartTimer_.stop();
        if (watchProcess_.state() != QProcess::NotRunning) {
            watchProcess_.kill();
            watchProcess_.waitForFinished(1000);
        }
        if (lockFile_.isLocked()) {
            lockFile_.unlock();
        }
    }

    bool isPrimaryInstance() const
    {
        return primaryInstance_;
    }

private:
    bool eventFilter(QObject *watched, QEvent *event) override
    {
        Q_UNUSED(watched);
        if (!popupStackVisible()) {
            return QObject::eventFilter(watched, event);
        }

        switch (event->type()) {
        case QEvent::MouseButtonPress: {
            const QPoint globalPos = globalMouseEventPos(event);
            if (!rootPopup_.containsGlobal(globalPos) && !sidePopup_.containsGlobal(globalPos)) {
                closePopupStack();
            }
            break;
        }
        case QEvent::KeyPress: {
            auto *keyEvent = static_cast<QKeyEvent *>(event);
            if (keyEvent->key() == Qt::Key_Escape) {
                closePopupStack();
                return true;
            }
            break;
        }
        case QEvent::ApplicationDeactivate:
            closePopupStack();
            break;
        default:
            break;
        }

        return QObject::eventFilter(watched, event);
    }

    bool popupStackVisible() const
    {
        return rootPopup_.isVisible() || sidePopup_.isVisible();
    }

    bool anySubmenuVisible() const
    {
        return sidePopup_.isVisible();
    }

    bool isLauncherAction(QAction *action) const
    {
        return action == &accountLauncherAction_ || action == &peersLauncherAction_ ||
               action == &exitLauncherAction_ || action == &prefsLauncherAction_;
    }

    QMenu *popupMenuForLauncher(QAction *action)
    {
        if (action == &accountLauncherAction_) {
            return &accountMenu_;
        }
        if (action == &peersLauncherAction_) {
            return &peersMenu_;
        }
        if (action == &exitLauncherAction_) {
            return &exitMenu_;
        }
        if (action == &prefsLauncherAction_) {
            return &prefsMenu_;
        }
        return nullptr;
    }

    void rebuildMenuForLauncher(QAction *action)
    {
        if (action == &accountLauncherAction_) {
            rebuildAccountMenu();
            return;
        }
        if (action == &peersLauncherAction_) {
            rebuildPeersMenu();
            return;
        }
        if (action == &exitLauncherAction_) {
            rebuildExitNodeMenu();
            return;
        }
        if (action == &prefsLauncherAction_) {
            rebuildPrefsMenu();
        }
    }

    QRect launcherAnchorGlobalRect(QAction *action) const
    {
        if (!action) {
            return {};
        }
        return rootPopup_.actionGlobalRect(action);
    }

    QRect availableScreenRectForPoint(const QPoint &globalPoint) const
    {
        QScreen *screen = QGuiApplication::screenAt(globalPoint);
        if (!screen) {
            screen = QGuiApplication::primaryScreen();
        }
        return screen ? screen->availableGeometry() : QRect();
    }

    void closeActiveSidePopup()
    {
        sidePopup_.hide();
        activeSideSourceMenu_ = nullptr;
        activeLauncherAction_ = nullptr;
        activeSidePopupEdge_.reset();
    }

    void closePopupStack()
    {
        closeActiveSidePopup();
        rootPopup_.hide();
    }

    QSet<QAction *> launcherActions() const
    {
        return QSet<QAction *>{const_cast<QAction *>(&accountLauncherAction_),
                               const_cast<QAction *>(&peersLauncherAction_),
                               const_cast<QAction *>(&exitLauncherAction_),
                               const_cast<QAction *>(&prefsLauncherAction_)};
    }

    void refreshRootPopupActions()
    {
        rootPopup_.setActions(menu_.actions(), launcherActions());
    }

    void showRootPopup()
    {
        refreshMenuPresentation(true);
        refreshRootPopupActions();
        rootPopup_.adjustSize();
        const QPoint anchor = QCursor::pos();
        const QPoint pos =
            rootPopupPosition(anchor, rootPopup_.sizeHint(), availableScreenRectForPoint(anchor));
        rootPopup_.move(pos);
        rootPopup_.show();
    }

    void openSidePopup(QAction *launcher)
    {
        if (!launcher || !rootPopup_.isVisible() || !launcher->isVisible() || !launcher->isEnabled()) {
            return;
        }

        QMenu *sourceMenu = popupMenuForLauncher(launcher);
        if (!sourceMenu) {
            return;
        }
        if (activeLauncherAction_ == launcher && sidePopup_.isVisible()) {
            return;
        }

        rebuildMenuForLauncher(launcher);
        const QRect anchorGlobal = launcherAnchorGlobalRect(launcher);
        if (!anchorGlobal.isValid()) {
            return;
        }

        sidePopup_.setActions(sourceMenu->actions());
        sidePopup_.adjustSize();
        const QSize popupSize = sidePopup_.sizeHint().expandedTo(sidePopup_.size());
        const SidePopupPlacement placement =
            sidePopupPlacement(anchorGlobal,
                               popupSize,
                               availableScreenRectForPoint(anchorGlobal.center()),
                               activeLauncherAction_ == launcher ? activeSidePopupEdge_ : std::nullopt);

        sidePopup_.move(placement.position);
        sidePopup_.show();
        sidePopup_.raise();
        activeSideSourceMenu_ = sourceMenu;
        activeLauncherAction_ = launcher;
        activeSidePopupEdge_ = placement.edge;
    }

    void toggleRootPopup()
    {
        if (popupStackVisible()) {
            closePopupStack();
            return;
        }
        showRootPopup();
    }

    void setupMenu()
    {
        statusAction_.setEnabled(false);
        controllerAction_.setEnabled(false);
        accountOverviewAction_.setEnabled(false);
        startDaemonAction_.setEnabled(false);
        showErrorAction_.setEnabled(false);
        accountIdentityAction_.setEnabled(false);
        selfDnsAction_.setEnabled(false);
        selfIPsAction_.setEnabled(false);
        exitNodeListTitleAction_.setEnabled(false);
        exitPrefTitleAction_.setEnabled(false);

        allowLanAction_.setCheckable(true);
        advertiseExitAction_.setCheckable(true);
        allowIncomingAction_.setCheckable(true);
        corpDnsAction_.setCheckable(true);
        routeAllAction_.setCheckable(true);
        forceDaemonAction_.setCheckable(true);
        autoStartAction_.setCheckable(true);

        menu_.addAction(&statusAction_);
        menu_.addAction(&controllerAction_);
        menu_.addSeparator();
        menu_.addAction(&loginAction_);
        menu_.addAction(&reauthAction_);
        menu_.addAction(&connectAction_);
        menu_.addAction(&disconnectAction_);
        menu_.addAction(&refreshAction_);
        menu_.addAction(&switchControllerAction_);
        menu_.addAction(&startDaemonAction_);
        menu_.addAction(&showErrorAction_);
        menu_.addSeparator();
        menu_.addAction(&accountLauncherAction_);
        menu_.addAction(&selfLauncherAction_);
        menu_.addAction(&peersLauncherAction_);
        menu_.addSeparator();
        menu_.addAction(&exitLauncherAction_);
        menu_.addSeparator();
        menu_.addAction(&prefsLauncherAction_);
        menu_.addAction(&aboutAction_);
        menu_.addSeparator();
        menu_.addAction(&quitAction_);
        accountLauncherAction_.setText(submenuLauncherText(QStringLiteral("账号")));
        selfLauncherAction_.setText(QStringLiteral("本设备"));
        peersLauncherAction_.setText(submenuLauncherText(QStringLiteral("网内设备")));
        exitLauncherAction_.setText(submenuLauncherText(QStringLiteral("出口节点")));
        prefsLauncherAction_.setText(submenuLauncherText(QStringLiteral("配置项")));
        exitNodeListTitleAction_.setText(QStringLiteral("无可用出口节点"));
        exitPrefTitleAction_.setText(QStringLiteral("配置项"));

        setBusy(false);
        setBackendUnavailable(QStringLiteral("正在等待后端响应"), false);
    }

    void connectActions()
    {
        connect(&loginAction_, &QAction::triggered, this, [this] { doLogin(false); });
        connect(&reauthAction_, &QAction::triggered, this, [this] { doLogin(true); });
        connect(&connectAction_, &QAction::triggered, this, [this] {
            runSimpleAction(QStringLiteral("connect"), QStringLiteral("正在连接"));
        });
        connect(&disconnectAction_, &QAction::triggered, this, [this] {
            runSimpleAction(QStringLiteral("disconnect"), QStringLiteral("已断开"));
        });
        connect(&refreshAction_, &QAction::triggered, this, [this] { refreshSnapshot(true); });
        connect(&switchControllerAction_, &QAction::triggered, this, &TrayApp::switchController);
        connect(&startDaemonAction_, &QAction::triggered, this, &TrayApp::startDaemon);
        connect(&showErrorAction_, &QAction::triggered, this, &TrayApp::showLastError);
        connect(&accountOpenConsoleAction_, &QAction::triggered, this, &TrayApp::openConsole);
        connect(&accountLogoutAction_, &QAction::triggered, this, [this] {
            runSimpleAction(QStringLiteral("logout"), QStringLiteral("当前设备已登出"));
        });
        connect(&selfLauncherAction_, &QAction::triggered, this, [this] { copySelfPrimaryIP(); });
        connect(&copySelfIPAction_, &QAction::triggered, this, [this] { copySelfPrimaryIP(); });
        connect(&copySelfIPsAction_, &QAction::triggered, this, [this] { copySelfAllIPs(); });
        connect(&allowLanAction_, &QAction::triggered, this, [this](bool checked) {
            runBoolAction(QStringLiteral("set-exit-node-lan"), checked);
        });
        connect(&advertiseExitAction_, &QAction::triggered, this, [this](bool checked) {
            runBoolAction(QStringLiteral("set-advertise-exit"), checked);
        });
        connect(&allowIncomingAction_, &QAction::triggered, this, [this](bool checked) {
            runBoolAction(QStringLiteral("set-allows-incoming"), checked);
        });
        connect(&corpDnsAction_, &QAction::triggered, this, [this](bool checked) {
            runBoolAction(QStringLiteral("set-corp-dns"), checked);
        });
        connect(&routeAllAction_, &QAction::triggered, this, [this](bool checked) {
            runBoolAction(QStringLiteral("set-route-all"), checked);
        });
        connect(&forceDaemonAction_, &QAction::triggered, this, [this](bool checked) {
            runBoolAction(QStringLiteral("set-force-daemon"), checked);
        });
        connect(&autoStartAction_, &QAction::triggered, this, [this](bool checked) {
            runBoolAction(QStringLiteral("set-autostart"), checked);
        });
        connect(&restoreDefaultsAction_, &QAction::triggered, this, [this] {
            runSimpleAction(QStringLiteral("restore-defaults"), QStringLiteral("默认设置已恢复"));
        });
        connect(&aboutAction_, &QAction::triggered, this, &TrayApp::showAbout);
        connect(&quitAction_, &QAction::triggered, qApp, &QCoreApplication::quit);
    }

    void connectWatchProcess()
    {
        connect(&watchProcess_, &QProcess::readyReadStandardOutput, this, &TrayApp::consumeWatchOutput);
        connect(&watchProcess_, &QProcess::readyReadStandardError, this, [this] {
            appendProcessText(&watchStderr_, watchProcess_.readAllStandardError());
        });
        connect(&watchProcess_,
                QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
                this,
                &TrayApp::handleWatchFinished);
        connect(&watchProcess_, &QProcess::errorOccurred, this, &TrayApp::handleWatchProcessError);
    }

    void setBusy(bool busy)
    {
        actionInFlight_ = busy;
        const bool loggedIn = isLoggedInState(snapshot_);
        const bool enableRunningOnlyLaunchers = shouldEnableRunningOnlyLauncher(backendState(snapshot_));

        refreshAction_.setEnabled(!busy);
        startDaemonAction_.setEnabled(!busy && !backendAvailable_);
        showErrorAction_.setEnabled(!lastError_.isEmpty());
        loginAction_.setEnabled(!busy && backendAvailable_);
        reauthAction_.setEnabled(!busy && backendAvailable_ && loggedIn);
        connectAction_.setEnabled(!busy && backendAvailable_ && canConnect_);
        disconnectAction_.setEnabled(!busy && backendAvailable_ && canDisconnect_);
        switchControllerAction_.setEnabled(!busy);
        accountLauncherAction_.setEnabled(!busy && backendAvailable_ && loggedIn);
        selfLauncherAction_.setEnabled(!busy && backendAvailable_ && enableRunningOnlyLaunchers);
        peersLauncherAction_.setEnabled(!busy && backendAvailable_ && enableRunningOnlyLaunchers &&
                                        hasPeerSections_);
        exitLauncherAction_.setEnabled(!busy && backendAvailable_ && enableRunningOnlyLaunchers);
        prefsLauncherAction_.setEnabled(!busy);
        accountOpenConsoleAction_.setEnabled(!busy && backendAvailable_ && canOpenConsole_);
        accountLogoutAction_.setEnabled(!busy && backendAvailable_ && canLogout_);
        copySelfIPAction_.setEnabled(!busy && backendAvailable_ && !selfIP(snapshot_).isEmpty());
        copySelfIPsAction_.setEnabled(!busy && backendAvailable_ && !selfIPs(snapshot_).isEmpty());
        allowLanAction_.setEnabled(!busy && backendAvailable_ && canEditExitNodeLAN_);
        advertiseExitAction_.setEnabled(!busy && backendAvailable_ && canAdvertiseExit_);
        allowIncomingAction_.setEnabled(!busy && backendAvailable_);
        corpDnsAction_.setEnabled(!busy && backendAvailable_);
        routeAllAction_.setEnabled(!busy && backendAvailable_);
        forceDaemonAction_.setEnabled(!busy && backendAvailable_);
        autoStartAction_.setEnabled(!busy);
        restoreDefaultsAction_.setEnabled(!busy && backendAvailable_);
    }

    void enablePollingFallback(int intervalMs)
    {
        poller_.setInterval(intervalMs);
        if (!poller_.isActive()) {
            poller_.start();
        }
    }

    void disablePollingFallback()
    {
        poller_.stop();
    }

    void ensureWatchProcess()
    {
        if (!primaryInstance_ || shuttingDown_ || watchProcess_.state() != QProcess::NotRunning || !serviceSocketReady_) {
            return;
        }

        watchStdoutBuffer_.clear();
        watchStderr_.clear();
        watchDeliveredSnapshot_ = false;
        watchProcess_.start(bridgeProgram(), bridgeWatchArgs());
    }

    bool refreshServiceStatus(QString *error = nullptr)
    {
        QString bridgeError;
        const QJsonObject status = runBridgeObject(bridgeServiceStatusArgs(), &bridgeError);
        if (!bridgeError.isEmpty()) {
            if (error) {
                *error = bridgeError;
            }
            serviceSocketReady_ = false;
            serviceActive_ = false;
            serviceStatusMessage_.clear();
            return false;
        }

        serviceSocketReady_ = bridgeServiceSocketReady(status);
        serviceActive_ = bridgeServiceActive(status);
        serviceStatusMessage_ = bridgeServiceMessage(status);
        if (error) {
            error->clear();
        }
        return serviceSocketReady_;
    }

    void handleWatchProcessError(QProcess::ProcessError error)
    {
        if (shuttingDown_ || !primaryInstance_) {
            return;
        }
        if (error == QProcess::FailedToStart) {
            lastError_ = QStringLiteral("failed to start watch bridge: %1").arg(watchProcess_.errorString());
            showErrorAction_.setEnabled(true);
            enablePollingFallback(backendAvailable_ ? kHealthyPollMs : kUnhealthyPollMs);
            watchRestartTimer_.start(kWatchRestartMs);
        }
    }

    void consumeWatchOutput()
    {
        watchStdoutBuffer_ += watchProcess_.readAllStandardOutput();

        while (true) {
            const int newlineIndex = watchStdoutBuffer_.indexOf('\n');
            if (newlineIndex < 0) {
                break;
            }

            const QByteArray line = watchStdoutBuffer_.left(newlineIndex);
            watchStdoutBuffer_.remove(0, newlineIndex + 1);

            const BridgeStreamEvent event = parseBridgeStreamLine(line);
            switch (event.kind) {
            case BridgeStreamEvent::Snapshot:
                watchDeliveredSnapshot_ = true;
                applySnapshot(event.snapshot);
                disablePollingFallback();
                break;
            case BridgeStreamEvent::State:
                if (!snapshot_.isEmpty()) {
                    snapshot_[QStringLiteral("backendState")] = event.stringPayload;
                    snapshot_[QStringLiteral("stateLabel")] = event.objectPayload.value(QStringLiteral("stateLabel"));
                    applySnapshot(snapshot_);
                }
                break;
            case BridgeStreamEvent::Prefs:
                if (!snapshot_.isEmpty()) {
                    snapshot_[QStringLiteral("prefs")] = event.objectPayload;
                    applySnapshot(snapshot_);
                }
                break;
            case BridgeStreamEvent::Netmap:
                if (!snapshot_.isEmpty()) {
                    if (event.objectPayload.contains(QStringLiteral("accountLabel"))) {
                        snapshot_[QStringLiteral("accountLabel")] = event.objectPayload.value(QStringLiteral("accountLabel"));
                    }
                    if (event.objectPayload.contains(QStringLiteral("adminURL"))) {
                        snapshot_[QStringLiteral("adminURL")] = event.objectPayload.value(QStringLiteral("adminURL"));
                    }
                    if (event.objectPayload.contains(QStringLiteral("self"))) {
                        snapshot_[QStringLiteral("self")] = event.objectPayload.value(QStringLiteral("self"));
                    }
                    if (event.objectPayload.contains(QStringLiteral("peers"))) {
                        snapshot_[QStringLiteral("peers")] = event.objectPayload.value(QStringLiteral("peers"));
                    }
                    if (event.objectPayload.contains(QStringLiteral("exitNodes"))) {
                        snapshot_[QStringLiteral("exitNodes")] = event.objectPayload.value(QStringLiteral("exitNodes"));
                    }
                    applySnapshot(snapshot_);
                }
                break;
            case BridgeStreamEvent::Version:
                if (!snapshot_.isEmpty()) {
                    snapshot_[QStringLiteral("version")] = event.stringPayload;
                    applySnapshot(snapshot_);
                }
                break;
            case BridgeStreamEvent::BrowseURL:
                if (!event.stringPayload.isEmpty()) {
                    openLoginAuthURL(event.stringPayload, true);
                }
                break;
            case BridgeStreamEvent::Error:
                setBackendUnavailable(event.errorMessage, false);
                break;
            case BridgeStreamEvent::KeepAlive:
            case BridgeStreamEvent::Invalid:
                break;
            }
        }
    }

    void handleWatchFinished(int, QProcess::ExitStatus)
    {
        if (shuttingDown_ || !primaryInstance_) {
            return;
        }

        const QString error = watchStderr_.trimmed();
        if (!error.isEmpty()) {
            lastError_ = error;
            showErrorAction_.setEnabled(true);
        }
        enablePollingFallback(backendAvailable_ ? kHealthyPollMs : kUnhealthyPollMs);
        watchRestartTimer_.start(kWatchRestartMs);
    }

    void advanceConnectingIcon()
    {
        if (!backendAvailable_ || backendState(snapshot_) != QStringLiteral("Starting")) {
            connectingIconTimer_.stop();
            return;
        }
        connectingSpinnerPhase_ = !connectingSpinnerPhase_;
        applyTrayIcon();
    }

    void rebuildAccountMenu()
    {
        const QString account = backendAvailable_ ? accountLabel(snapshot_) : QStringLiteral("后端不可用");
        accountLauncherAction_.setText(accountLauncherText(account));
        accountMenu_.clear();
        accountMenu_.addAction(&accountOpenConsoleAction_);
        accountMenu_.addAction(&accountLogoutAction_);
        accountOpenConsoleAction_.setVisible(canOpenConsole_);
        accountLogoutAction_.setVisible(canLogout_);
        if (accountOpenConsoleAction_.isVisible() && accountLogoutAction_.isVisible()) {
            accountMenu_.insertSeparator(&accountLogoutAction_);
        }
    }

    void rebuildSelfMenu()
    {
        selfLauncherAction_.setText(selfRootActionText(backendAvailable_ ? selfName(snapshot_) : QString(),
                                                      backendAvailable_ ? selfIP(snapshot_) : QString()));
        selfMenu_.clear();
        selfMenu_.addAction(&selfDnsAction_);
        selfMenu_.addAction(&selfIPsAction_);
        selfMenu_.addSeparator();
        selfMenu_.addAction(&copySelfIPAction_);
        selfMenu_.addAction(&copySelfIPsAction_);
        selfDnsAction_.setText(QStringLiteral("MagicDNS: %1")
                                   .arg(backendAvailable_ ? selfDNSName(snapshot_).isEmpty() ? QStringLiteral("-")
                                                                                              : selfDNSName(snapshot_)
                                                         : QStringLiteral("后端不可用")));
        selfIPsAction_.setText(QStringLiteral("地址: %1")
                                   .arg(backendAvailable_ ? selfIPs(snapshot_).join(QStringLiteral(", "))
                                                         : QStringLiteral("后端不可用")));
    }

    void rebuildPeersMenu()
    {
        peersMenu_.clear();
        hasPeerSections_ = false;

        const QJsonArray peers = snapshot_.value(QStringLiteral("peers")).toArray();
        Q_UNUSED(peers);
        peersLauncherAction_.setText(peersLauncherText());

        if (!backendAvailable_ || !isLoggedInState(snapshot_)) {
            QAction *placeholder = peersMenu_.addAction(QStringLiteral("当前没有可显示的设备"));
            placeholder->setEnabled(false);
            return;
        }

        const QList<PeerMenuSection> sections = buildPeerMenuSections(snapshot_);
        if (sections.isEmpty()) {
            QAction *placeholder = peersMenu_.addAction(QStringLiteral("当前没有可显示的设备"));
            placeholder->setEnabled(false);
            return;
        }
        hasPeerSections_ = true;

        bool firstSection = true;
        for (const PeerMenuSection &section : sections) {
            if (!firstSection) {
                peersMenu_.addSeparator();
            }
            firstSection = false;
            QAction *header = peersMenu_.addAction(section.title);
            header->setEnabled(false);
            for (const QJsonValue &value : section.peers) {
                if (!value.isObject()) {
                    continue;
                }
                const QJsonObject peer = value.toObject();
                QAction *action = peersMenu_.addAction(peerMenuLabel(peer));
                connect(action, &QAction::triggered, this, [this, peer] { copyPeerIP(peer); });
            }
        }
    }

    void rebuildExitNodeMenu()
    {
        exitMenu_.clear();
        delete exitNodeGroup_;
        exitNodeGroup_ = nullptr;

        const QString current = backendAvailable_ ? currentExitNodeLabel(snapshot_) : QStringLiteral("后端不可用");
        const bool hasChoices = backendAvailable_ && isLoggedInState(snapshot_) &&
                                !snapshot_.value(QStringLiteral("exitNodes")).toArray().isEmpty();
        exitLauncherAction_.setText(submenuLauncherText(exitMenuTitleText(current)));
        exitNodeListTitleAction_.setText(exitListTitleText(hasChoices));
        exitMenu_.addAction(&exitNodeListTitleAction_);
        exitMenu_.addSeparator();

        const QJsonArray exitNodes = snapshot_.value(QStringLiteral("exitNodes")).toArray();
        bool addedExitNodeActions = false;
        if (!backendAvailable_ || exitNodes.isEmpty()) {
        } else {
            exitNodeGroup_ = new QActionGroup(this);
            exitNodeGroup_->setExclusive(true);

            QAction *noneAction = exitMenu_.addAction(QStringLiteral("不使用"));
            noneAction->setCheckable(true);
            noneAction->setChecked(currentExitNodeStableID(snapshot_).isEmpty());
            noneAction->setEnabled(!actionInFlight_);
            exitNodeGroup_->addAction(noneAction);
            addedExitNodeActions = true;
            connect(noneAction, &QAction::triggered, this, [this] {
                if (!runAction(bridgeStableIDArgs(QString()))) {
                    return;
                }
                refreshSnapshot(false);
            });

            for (const QJsonValue &value : exitNodes) {
                if (!value.isObject()) {
                    continue;
                }
                const QJsonObject item = value.toObject();
                const QString stableID = item.value(QStringLiteral("stableID")).toString();
                const QString name = item.value(QStringLiteral("name")).toString();
                if (isSyntheticNoExitNodeChoice(stableID, name)) {
                    continue;
                }
                QAction *action = exitMenu_.addAction(exitNodeMenuLabel(item));
                action->setCheckable(true);
                action->setChecked(item.value(QStringLiteral("selected")).toBool());
                action->setEnabled(!actionInFlight_);
                exitNodeGroup_->addAction(action);
                addedExitNodeActions = true;
                connect(action, &QAction::triggered, this, [this, stableID] {
                    if (!runAction(bridgeStableIDArgs(stableID))) {
                        return;
                    }
                    refreshSnapshot(false);
                });
            }
        }

        if (addedExitNodeActions) {
            exitMenu_.addSeparator();
        }
        exitMenu_.addAction(&exitPrefTitleAction_);
        exitMenu_.addAction(&allowLanAction_);
        exitMenu_.addAction(&advertiseExitAction_);
    }

    void rebuildPrefsMenu()
    {
        const QJsonObject prefs = snapshot_.value(QStringLiteral("prefs")).toObject();
        prefsLauncherAction_.setText(submenuLauncherText(QStringLiteral("配置项")));
        prefsMenu_.clear();
        prefsMenu_.addAction(&allowIncomingAction_);
        prefsMenu_.addAction(&corpDnsAction_);
        prefsMenu_.addAction(&routeAllAction_);
        prefsMenu_.addAction(&forceDaemonAction_);
        prefsMenu_.addSeparator();
        prefsMenu_.addAction(&restoreDefaultsAction_);
        prefsMenu_.addAction(&autoStartAction_);

        allowLanAction_.blockSignals(true);
        advertiseExitAction_.blockSignals(true);
        allowIncomingAction_.blockSignals(true);
        corpDnsAction_.blockSignals(true);
        routeAllAction_.blockSignals(true);
        forceDaemonAction_.blockSignals(true);
        autoStartAction_.blockSignals(true);

        allowLanAction_.setChecked(prefs.value(QStringLiteral("exitNodeAllowLANAccess")).toBool());
        advertiseExitAction_.setChecked(prefs.value(QStringLiteral("advertisesExitNode")).toBool());
        advertiseExitAction_.setText(advertiseExitActionText(advertiseExitAction_.isChecked()));
        allowIncomingAction_.setChecked(prefs.value(QStringLiteral("allowsIncoming")).toBool());
        corpDnsAction_.setChecked(prefs.value(QStringLiteral("corpDNS")).toBool());
        routeAllAction_.setChecked(prefs.value(QStringLiteral("routeAll")).toBool());
        forceDaemonAction_.setChecked(prefs.value(QStringLiteral("forceDaemon")).toBool());
        autoStartAction_.setChecked(prefs.value(QStringLiteral("autoStartUp")).toBool());

        allowLanAction_.blockSignals(false);
        advertiseExitAction_.blockSignals(false);
        allowIncomingAction_.blockSignals(false);
        corpDnsAction_.blockSignals(false);
        routeAllAction_.blockSignals(false);
        forceDaemonAction_.blockSignals(false);
        autoStartAction_.blockSignals(false);
    }

    void setBackendUnavailable(const QString &error, bool manual)
    {
        const bool wasAvailable = backendAvailable_;
        QString probeError;
        refreshServiceStatus(&probeError);
        const QString effectiveError = serviceSocketReady_
                                           ? error
                                           : (serviceStatusMessage_.isEmpty() ? error : serviceStatusMessage_);
        clearPendingAuthURLOpen();
        backendAvailable_ = false;
        ++refreshFailures_;
        lastError_ = serviceSocketReady_ ? error : QString();
        snapshot_ = {};
        canConnect_ = false;
        canDisconnect_ = false;
        canLogout_ = false;
        canOpenConsole_ = false;
        canEditExitNodeLAN_ = false;
        canAdvertiseExit_ = false;
        hasPeerSections_ = false;

        connectingIconTimer_.stop();
        connectingSpinnerPhase_ = true;
        tray_.setIcon(iconForTrayState(TrayIconState::Issue));
        tray_.setToolTip(QStringLiteral("Mirage\n%1").arg(firstLineSummary(effectiveError)));
        const QString serviceStateLabel = serviceSocketReady_
                                              ? QStringLiteral("后端不可用")
                                              : (serviceActive_ ? QStringLiteral("后台启动中")
                                                                : QStringLiteral("后台未启动"));
        statusAction_.setText(QStringLiteral("状态: %1").arg(serviceStateLabel));
        controllerAction_.setText(QStringLiteral("控制器: %1").arg(defaultControlInput()));
        accountOverviewAction_.setText(QStringLiteral("账号: 后端不可用"));
        loginAction_.setVisible(true);
        reauthAction_.setVisible(false);
        connectAction_.setVisible(false);
        disconnectAction_.setVisible(false);
        accountLauncherAction_.setVisible(false);
        selfLauncherAction_.setVisible(false);
        peersLauncherAction_.setVisible(false);
        exitLauncherAction_.setVisible(false);
        prefsLauncherAction_.setVisible(true);
        startDaemonAction_.setVisible(true);
        showErrorAction_.setVisible(!lastError_.isEmpty());
        closeActiveSidePopup();

        rebuildAccountMenu();
        rebuildSelfMenu();
        rebuildPeersMenu();
        rebuildExitNodeMenu();
        rebuildPrefsMenu();
        setBusy(false);
        startDaemonAction_.setEnabled(!actionInFlight_);
        showErrorAction_.setEnabled(true);
        enablePollingFallback(kUnhealthyPollMs);

        if ((wasAvailable || refreshFailures_ == 1) && serviceSocketReady_) {
            showTrayMessage(tray_, QStringLiteral("后端暂时不可用，正在重试"), QSystemTrayIcon::Warning);
        }
        if (manual) {
            showWarningDialog(nullptr, effectiveError);
        }
    }

    void applySnapshot(const QJsonObject &snapshot)
    {
        const bool recovered = !backendAvailable_ && refreshFailures_ > 0;
        snapshot_ = snapshot;
        backendAvailable_ = true;
        refreshFailures_ = 0;
        if (watchProcess_.state() != QProcess::NotRunning) {
            disablePollingFallback();
        } else {
            enablePollingFallback(kHealthyPollMs);
        }

        const QString state = backendState(snapshot_);
        const QJsonObject prefs = snapshot_.value(QStringLiteral("prefs")).toObject();
        const QString controller = controlURL(snapshot_).isEmpty() ? defaultControlInput() : controlURL(snapshot_);

        canConnect_ = state != QStringLiteral("Running") && state != QStringLiteral("Starting");
        canDisconnect_ = state == QStringLiteral("Running") || state == QStringLiteral("Starting");
        canLogout_ = !prefs.value(QStringLiteral("loggedOut")).toBool() && isLoggedInState(snapshot_);
        canOpenConsole_ = !adminURL(snapshot_).isEmpty();
        canEditExitNodeLAN_ = canToggleExitNodeLAN(snapshot_);
        canAdvertiseExit_ = canToggleAdvertiseExit(snapshot_);

        if (state == QStringLiteral("Starting")) {
            if (!connectingIconTimer_.isActive()) {
                connectingSpinnerPhase_ = true;
                connectingIconTimer_.start();
            }
        } else {
            connectingIconTimer_.stop();
            connectingSpinnerPhase_ = true;
        }

        applyTrayIcon();
        tray_.setToolTip(tooltipForSnapshot(snapshot_));

        refreshMenuPresentation(false);

        if (pendingAuthURLOpen_) {
            tryOpenPendingAuthURLFromSnapshot(false);
        }

        if (recovered) {
            showTrayMessage(tray_, QStringLiteral("后端连接已恢复"), QSystemTrayIcon::Information);
        }
    }

    void refreshMenuPresentation(bool force)
    {
        if (!force && shouldDeferTrayMenuRefresh(rootPopup_.isVisible(), anySubmenuVisible())) {
            return;
        }

        statusAction_.setText(QStringLiteral("状态: %1").arg(stateLabel(snapshot_)));
        const QString controller = controlURL(snapshot_).isEmpty() ? defaultControlInput() : controlURL(snapshot_);
        controllerAction_.setText(QStringLiteral("控制器: %1").arg(controller));
        accountOverviewAction_.setText(QStringLiteral("账号: %1").arg(accountLabel(snapshot_)));

        const QString state = backendState(snapshot_);
        const bool showLogin = shouldShowLoginAction(state);
        const bool showAuthenticatedMenus = shouldShowAuthenticatedMenus(state);
        const bool showConnect = !showLogin && shouldShowConnectAction(state);
        const bool showDisconnect = !showLogin && shouldShowDisconnectAction(state);
        loginAction_.setVisible(showLogin);
        reauthAction_.setVisible(shouldShowReauthAction(state));
        connectAction_.setVisible(showConnect);
        disconnectAction_.setVisible(showDisconnect);
        accountLauncherAction_.setVisible(showAuthenticatedMenus);
        selfLauncherAction_.setVisible(showAuthenticatedMenus);
        exitLauncherAction_.setVisible(showAuthenticatedMenus);
        prefsLauncherAction_.setVisible(true);
        startDaemonAction_.setVisible(!backendAvailable_);
        showErrorAction_.setVisible(!lastError_.isEmpty());
        connectAction_.setText(state == QStringLiteral("Running")
                                   ? QStringLiteral("已连接")
                                   : state == QStringLiteral("Starting") ? QStringLiteral("正在连接…")
                                                                          : QStringLiteral("连接"));
        if (!showAuthenticatedMenus) {
            closeActiveSidePopup();
        }

        lastError_.clear();
        rebuildAccountMenu();
        rebuildSelfMenu();
        rebuildPeersMenu();
        rebuildExitNodeMenu();
        rebuildPrefsMenu();
        peersLauncherAction_.setVisible(shouldShowPeersLauncher(showAuthenticatedMenus, hasPeerSections_));
        setBusy(false);
        showErrorAction_.setEnabled(false);
        startDaemonAction_.setEnabled(false);
    }

    void clearPendingAuthURLOpen()
    {
        pendingAuthURLOpen_ = false;
        pendingAuthURLAttemptsRemaining_ = 0;
        authURLRetryTimer_.stop();
    }

    bool openLoginAuthURL(const QString &url, bool showWarnings)
    {
        const QString trimmed = url.trimmed();
        if (trimmed.isEmpty()) {
            return false;
        }
        if (lastOpenedAuthURL_ == trimmed) {
            clearPendingAuthURLOpen();
            return true;
        }

        QString error;
        if (!openUrlWithDesktopIntegration(trimmed, &error)) {
            if (showWarnings) {
                showWarningDialog(nullptr, error);
            }
            return false;
        }

        lastOpenedAuthURL_ = trimmed;
        clearPendingAuthURLOpen();
        showTrayMessage(tray_, QStringLiteral("已在默认浏览器打开登录链接"), QSystemTrayIcon::Information);
        return true;
    }

    bool tryOpenPendingAuthURLFromSnapshot(bool showWarnings)
    {
        if (!pendingAuthURLOpen_) {
            return false;
        }
        return openLoginAuthURL(loginAuthURLFromSnapshot(snapshot_), showWarnings);
    }

    void beginPendingAuthURLOpen()
    {
        pendingAuthURLOpen_ = true;
        pendingAuthURLAttemptsRemaining_ = kAuthURLRetryAttempts;
        lastOpenedAuthURL_.clear();
        authURLRetryTimer_.stop();
    }

    void retryPendingAuthURLOpen()
    {
        if (!pendingAuthURLOpen_) {
            return;
        }
        if (tryOpenPendingAuthURLFromSnapshot(true)) {
            return;
        }
        if (pendingAuthURLAttemptsRemaining_ <= 0) {
            clearPendingAuthURLOpen();
            showTrayMessage(tray_, QStringLiteral("登录流程已启动，请留意浏览器或后续系统提示"), QSystemTrayIcon::Information);
            return;
        }

        --pendingAuthURLAttemptsRemaining_;
        refreshSnapshot(false);
        if (pendingAuthURLOpen_) {
            authURLRetryTimer_.start();
        }
    }

    void applyTrayIcon()
    {
        tray_.setIcon(iconForTrayState(trayIconStateForSnapshot(snapshot_, backendAvailable_, connectingSpinnerPhase_)));
    }

    void refreshSnapshot(bool manual)
    {
        if (!primaryInstance_ || actionInFlight_) {
            return;
        }
        QString serviceError;
        if (!refreshServiceStatus(&serviceError)) {
            setBackendUnavailable(serviceError.isEmpty() ? serviceStatusMessage_ : serviceError, manual);
            return;
        }

        ensureWatchProcess();

        QString error;
        const QJsonObject snapshot = runBridgeObject(bridgeSnapshotArgs(), &error);
        if (!error.isEmpty()) {
            setBackendUnavailable(error, manual);
            return;
        }
        applySnapshot(snapshot);
    }

    bool runAction(const QStringList &args, QString *output = nullptr)
    {
        if (!primaryInstance_ || actionInFlight_) {
            return false;
        }
        setBusy(true);
        QString error;
        QByteArray out = runBridge(args, &error);
        setBusy(false);
        const QString action = bridgeActionName(args);
        if (!error.isEmpty() && action != QStringLiteral("grant-operator") && errorSuggestsOperatorGrant(error)) {
            if (QMessageBox::question(nullptr,
                                      trayTitle(),
                                      operatorGrantPromptText(),
                                      QMessageBox::Yes | QMessageBox::No,
                                      QMessageBox::Yes) != QMessageBox::Yes) {
                lastError_ = error;
                showErrorAction_.setEnabled(true);
                showWarningDialog(nullptr, operatorGrantPromptText());
                refreshSnapshot(false);
                return false;
            }

            QString grantError;
            setBusy(true);
            runBridge(bridgeActionArgs(QStringLiteral("grant-operator")), &grantError);
            setBusy(false);
            if (!grantError.isEmpty()) {
                lastError_ = grantError;
                showErrorAction_.setEnabled(true);
                showWarningDialog(nullptr, grantError);
                refreshSnapshot(false);
                return false;
            }

            showTrayMessage(tray_, QStringLiteral("当前用户已获得 Mirage 管理权限"), QSystemTrayIcon::Information);
            setBusy(true);
            out = runBridge(args, &error);
            setBusy(false);
        }
        if (!error.isEmpty()) {
            lastError_ = error;
            showErrorAction_.setEnabled(true);
            showWarningDialog(nullptr, error);
            refreshSnapshot(false);
            return false;
        }
        if (output) {
            *output = QString::fromUtf8(out).trimmed();
        }
        return true;
    }

    QString promptControllerInput(const QString &title, const QString &label, const QString &initial, bool *ok) const
    {
        return QInputDialog::getText(nullptr, title, label, QLineEdit::Normal, initial, ok).trimmed();
    }

    void doLogin(bool)
    {
        if (!backendAvailable_) {
            refreshSnapshot(true);
            return;
        }

        QString input = controllerInput(snapshot_);
        if (!snapshot_.value(QStringLiteral("hasControllerStore")).toBool()) {
            bool ok = false;
            const QString entered = promptControllerInput(trayTitle(),
                                                          QStringLiteral("请输入控制器代码或完整 URL："),
                                                          input,
                                                          &ok);
            if (!ok) {
                return;
            }
            input = effectiveControllerInput(entered);
        }

        if (!runAction(bridgeControllerArgs(QStringLiteral("login"), input))) {
            return;
        }
        beginPendingAuthURLOpen();
        refreshSnapshot(false);
        if (pendingAuthURLOpen_) {
            showTrayMessage(tray_, QStringLiteral("登录流程已启动，正在获取登录链接…"), QSystemTrayIcon::Information);
            authURLRetryTimer_.start();
        }
    }

    void switchController()
    {
        const QString currentInput = backendAvailable_ ? controllerInput(snapshot_) : defaultControlInput();
        const QString currentBackendState = backendState(snapshot_);
        bool ok = false;
        const QString entered = promptControllerInput(QStringLiteral("切换控制器"),
                                                      QStringLiteral("请输入控制器代码或完整 URL（留空恢复默认）："),
                                                      currentInput,
                                                      &ok);
        if (!ok) {
            return;
        }

        if (!runAction(bridgeControllerArgs(QStringLiteral("switch-controller"), entered.trimmed()))) {
            return;
        }
        refreshSnapshot(false);
        if (controllerSwitchNeedsLogoutPrompt(currentInput,
                                              entered,
                                              defaultControlInput(),
                                              currentBackendState) &&
            QMessageBox::question(nullptr,
                                  QStringLiteral("切换控制器"),
                                  QStringLiteral("新控制器已保存。是否现在登出，以便重新登录？"),
                                  QMessageBox::Yes | QMessageBox::No,
                                  QMessageBox::No) == QMessageBox::Yes) {
            runSimpleAction(QStringLiteral("logout"), QStringLiteral("当前设备已登出"));
            return;
        }
        showTrayMessage(tray_, QStringLiteral("控制器已更新"), QSystemTrayIcon::Information);
    }

    void openConsole()
    {
        const QString url = adminURL(snapshot_).trimmed();
        if (url.isEmpty()) {
            showWarningDialog(nullptr, QStringLiteral("当前控制器没有提供控制台地址。"));
            return;
        }
        QString error;
        if (!openUrlWithDesktopIntegration(url, &error)) {
            showWarningDialog(nullptr, error);
            return;
        }
        showTrayMessage(tray_, QStringLiteral("已打开控制台"), QSystemTrayIcon::Information);
    }

    void startDaemon()
    {
        showTrayMessage(tray_, QStringLiteral("正在启动后台服务"), QSystemTrayIcon::Information);
        if (!runAction(bridgeActionArgs(QStringLiteral("start-daemon")))) {
            return;
        }
        showTrayMessage(tray_, QStringLiteral("后台服务已启动"), QSystemTrayIcon::Information);
        refreshSnapshot(false);
    }

    void showLastError()
    {
        if (lastError_.isEmpty()) {
            return;
        }
        showWarningDialog(nullptr, lastError_);
    }

    void runSimpleAction(const QString &action, const QString &successMessage = {})
    {
        if (!runAction(bridgeActionArgs(action))) {
            return;
        }
        if (!successMessage.isEmpty()) {
            showTrayMessage(tray_, successMessage, QSystemTrayIcon::Information);
        }
        refreshSnapshot(false);
    }

    void runBoolAction(const QString &action, bool enabled)
    {
        if (action == QStringLiteral("set-exit-node-lan") && !canToggleExitNodeLAN(snapshot_)) {
            refreshSnapshot(false);
            return;
        }
        if (action == QStringLiteral("set-advertise-exit") && !canToggleAdvertiseExit(snapshot_)) {
            showWarningDialog(nullptr, QStringLiteral("当前正在使用其他出口节点，不能同时把本机设为出口节点。"));
            refreshSnapshot(false);
            return;
        }
        if (action == QStringLiteral("set-advertise-exit") && advertiseExitEnableNeedsConfirmation(enabled) &&
            QMessageBox::question(nullptr,
                                  QStringLiteral("用作出口节点？"),
                                  QStringLiteral("你确定要将此设备用作出口节点吗？\n\nLinux 设备通常更适合作为稳定出口节点。"),
                                  QMessageBox::Yes | QMessageBox::No,
                                  QMessageBox::No) != QMessageBox::Yes) {
            refreshSnapshot(false);
            return;
        }

        if (!runAction(bridgeToggleArgs(action, enabled))) {
            return;
        }
        refreshSnapshot(false);
    }

    void copyToClipboard(const QString &value, const QString &success, const QString &emptyWarning)
    {
        if (value.isEmpty()) {
            showInfoDialog(nullptr, emptyWarning);
            return;
        }
        QApplication::clipboard()->setText(value);
        showTrayMessage(tray_, success, QSystemTrayIcon::Information);
    }

    void copySelfPrimaryIP()
    {
        const QString value = selfIP(snapshot_);
        copyToClipboard(value, QStringLiteral("已复制本机地址 %1").arg(value), QStringLiteral("当前设备没有可复制的地址。"));
    }

    void copySelfAllIPs()
    {
        const QStringList ips = selfIPs(snapshot_);
        copyToClipboard(ips.join(QStringLiteral("\n")),
                        QStringLiteral("已复制本机全部地址"),
                        QStringLiteral("当前设备没有可复制的地址。"));
    }

    void copyPeerIP(const QJsonObject &peer)
    {
        const QString ip = peer.value(QStringLiteral("ip")).toString().trimmed();
        const QString name = peer.value(QStringLiteral("name")).toString(QStringLiteral("该节点"));
        if (ip.isEmpty()) {
            showInfoDialog(nullptr, QStringLiteral("%1 没有可复制的 Mirage 地址。").arg(name));
            return;
        }
        QApplication::clipboard()->setText(ip);
        showTrayMessage(tray_, QStringLiteral("已复制 %1 的地址 %2").arg(name, ip), QSystemTrayIcon::Information);
    }

    void showAbout()
    {
        QStringList lines{
            QStringLiteral("状态: %1").arg(backendAvailable_ ? stateLabel(snapshot_) : QStringLiteral("后端不可用")),
            QStringLiteral("控制器: %1")
                .arg(backendAvailable_ ? controlURL(snapshot_).isEmpty() ? defaultControlInput() : controlURL(snapshot_)
                                       : QStringLiteral("-")),
            QStringLiteral("账号: %1").arg(backendAvailable_ ? accountLabel(snapshot_) : QStringLiteral("-")),
            QStringLiteral("本设备: %1").arg(backendAvailable_ ? nodeSummary(snapshot_) : QStringLiteral("-")),
            QStringLiteral("版本: %1").arg(backendAvailable_ ? versionString(snapshot_) : QStringLiteral("-")),
            QStringLiteral("Socket: %1").arg(backendAvailable_ ? socketPath(snapshot_) : QStringLiteral("-")),
            QStringLiteral("桥接更新: %1")
                .arg(watchProcess_.state() == QProcess::NotRunning ? QStringLiteral("轮询回退") : QStringLiteral("事件流")),
        };
        if (!lastError_.isEmpty()) {
            lines << QStringLiteral("最近错误: %1").arg(firstLineSummary(lastError_));
        }
        showInfoDialog(nullptr, lines.join(QStringLiteral("\n")));
    }

    bool primaryInstance_ = false;
    bool backendAvailable_ = false;
    bool actionInFlight_ = false;
    bool shuttingDown_ = false;
    int refreshFailures_ = 0;
    bool connectingSpinnerPhase_ = true;
    bool canConnect_ = false;
    bool canDisconnect_ = false;
    bool canLogout_ = false;
    bool canOpenConsole_ = false;
    bool canEditExitNodeLAN_ = false;
    bool canAdvertiseExit_ = false;
    bool hasPeerSections_ = false;
    bool watchDeliveredSnapshot_ = false;
    bool serviceSocketReady_ = false;
    bool serviceActive_ = false;

    QLockFile lockFile_;
    QSystemTrayIcon tray_;
    QMenu menu_;
    QMenu accountMenu_;
    QMenu selfMenu_;
    QMenu peersMenu_;
    QMenu exitMenu_;
    QMenu prefsMenu_;
    PopupMenuWindow rootPopup_;
    PopupMenuWindow sidePopup_;
    QAction statusAction_{QStringLiteral("状态: 初始化中")};
    QAction controllerAction_{QStringLiteral("控制器: 初始化中")};
    QAction accountOverviewAction_{QStringLiteral("账号: 初始化中")};
    QAction loginAction_{QStringLiteral("登录…")};
    QAction reauthAction_{QStringLiteral("重新认证…")};
    QAction connectAction_{QStringLiteral("连接")};
    QAction disconnectAction_{QStringLiteral("断开")};
    QAction refreshAction_{QStringLiteral("刷新状态")};
    QAction switchControllerAction_{QStringLiteral("切换控制器…")};
    QAction startDaemonAction_{QStringLiteral("启动后台服务")};
    QAction showErrorAction_{QStringLiteral("查看后端错误…")};
    QAction accountLauncherAction_{submenuLauncherText(QStringLiteral("账号: -"))};
    QAction selfLauncherAction_{QStringLiteral("本设备")};
    QAction peersLauncherAction_{submenuLauncherText(QStringLiteral("网内设备"))};
    QAction exitLauncherAction_{submenuLauncherText(QStringLiteral("出口节点"))};
    QAction prefsLauncherAction_{submenuLauncherText(QStringLiteral("配置项"))};
    QAction accountIdentityAction_{QStringLiteral("当前账号: -")};
    QAction accountOpenConsoleAction_{QStringLiteral("控制台")};
    QAction accountLogoutAction_{QStringLiteral("登出")};
    QAction selfDnsAction_{QStringLiteral("MagicDNS: -")};
    QAction selfIPsAction_{QStringLiteral("地址: -")};
    QAction copySelfIPAction_{QStringLiteral("复制首选地址")};
    QAction copySelfIPsAction_{QStringLiteral("复制全部地址")};
    QAction exitNodeListTitleAction_{QStringLiteral("无可用出口节点")};
    QAction exitPrefTitleAction_{QStringLiteral("配置项")};
    QAction allowLanAction_{QStringLiteral("本地网络不走出口")};
    QAction advertiseExitAction_{QStringLiteral("用作出口节点…")};
    QAction allowIncomingAction_{QStringLiteral("允许入流量连接")};
    QAction corpDnsAction_{QStringLiteral("使用 DNS 设置")};
    QAction routeAllAction_{QStringLiteral("使用子网转发")};
    QAction forceDaemonAction_{QStringLiteral("无人值守运行")};
    QAction autoStartAction_{QStringLiteral("开机自启动")};
    QAction restoreDefaultsAction_{QStringLiteral("恢复默认设置")};
    QAction aboutAction_{QStringLiteral("关于…")};
    QAction quitAction_{QStringLiteral("退出")};
    QActionGroup *exitNodeGroup_ = nullptr;
    QTimer poller_;
    QTimer watchRestartTimer_;
    QTimer connectingIconTimer_;
    QTimer authURLRetryTimer_;
    QProcess watchProcess_;
    QByteArray watchStdoutBuffer_;
    QString watchStderr_;
    QString lastError_;
    QString serviceStatusMessage_;
    QString lastOpenedAuthURL_;
    bool pendingAuthURLOpen_ = false;
    int pendingAuthURLAttemptsRemaining_ = 0;
    QMenu *activeSideSourceMenu_ = nullptr;
    QAction *activeLauncherAction_ = nullptr;
    std::optional<SidePopupEdge> activeSidePopupEdge_;
    QJsonObject snapshot_;
};

int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
    QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
    QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
#ifdef Q_OS_LINUX
    const QString currentPlatform = QString::fromUtf8(qgetenv("QT_QPA_PLATFORM"));
    const QString currentDesktop = QString::fromUtf8(qgetenv("XDG_CURRENT_DESKTOP"));
    const bool hasWaylandDisplay = !qEnvironmentVariable("WAYLAND_DISPLAY").trimmed().isEmpty();
    const QString normalizedPlatform =
        normalizedQtPlatformPlugin(currentPlatform, currentDesktop, hasWaylandDisplay);
    if (!normalizedPlatform.trimmed().isEmpty() && normalizedPlatform != currentPlatform) {
        qputenv("QT_QPA_PLATFORM", normalizedPlatform.toUtf8());
    }
#endif
    QApplication app(argc, argv);
    QApplication::setQuitOnLastWindowClosed(false);
    QApplication::setWindowIcon(iconForTrayState(TrayIconState::Logo));
    qputenv("MIRAGE_QT_FRONTEND", QCoreApplication::applicationFilePath().toUtf8());
    if (!QSystemTrayIcon::isSystemTrayAvailable()) {
        QMessageBox::critical(nullptr, trayTitle(), QStringLiteral("当前桌面环境不支持系统托盘。"));
        return 1;
    }

    TrayApp tray;
    if (!tray.isPrimaryInstance()) {
        return 0;
    }
    return app.exec();
}

#include "main.moc"
