# -*- coding: utf-8 -*-
"""Top-level application window."""

from util.runtime import *
from util.recent_projects import load_recent_projects, save_recent_project
from models.config import Config, _empty_config_data, apply_terminal_launch_config
from controllers.workers import NodeStatusCheckWorker, UpdateCheckWorker, UpdateDownloadWorker
from views.common import CopyableIpLabel, apply_button_style, make_line_icon, set_button_icon
from views.console import OutputConsole
from views.tabs import CommandTab, ConfigTab, CreateServerTab, ServerManageTab, DefaultKvTab
from views.welcome import WelcomePage

class MainWindow(QMainWindow):
    """主窗口。支持“无项目”启动：先显示欢迎页，打开项目后加载并显示功能页。"""

    CONSOLE_PANEL_HEIGHT = 345
    AUTO_UPDATE_CHECK_INTERVAL_MS = 5 * 60 * 1000
    
    def __init__(self, config: Optional[Config] = None):
        super().__init__()
        self.config = config if config is not None else Config(project_root=None)
        self._project_content_widget = None  # 项目内容容器，首次打开项目时创建
        self._cache_init_worker = None
        self._startup_update_check_started = False
        self._update_check_worker = None
        self._update_prompt_active = False
        self._last_prompted_update_version = ""
        self.init_ui()
    
    def init_ui(self):
        self.setWindowTitle("Server Manager v{}".format(get_current_version()))
        self.setMinimumSize(1000, 750)
        
        # 设置浅色控制台主题
        self.setStyleSheet("""
            QMainWindow {
                background-color: #181b22;
            }
            QWidget {
                background-color: #181b22;
                color: #e8eef7;
                font-size: 13px;
            }
            QGroupBox {
                background-color: #242932;
                border: 1px solid #3a4352;
                border-radius: 8px;
                margin-top: 18px;
                padding: 16px 12px 12px 12px;
                font-weight: bold;
                font-size: 13px;
            }
            QGroupBox::title {
                subcontrol-origin: margin;
                subcontrol-position: top left;
                left: 14px;
                padding: 0 10px;
                color: #f4f7fb;
                background-color: #242932;
            }
            QLabel {
                font-size: 13px;
                padding: 2px;
            }
            QLineEdit, QTextEdit, QSpinBox, QComboBox, QDateTimeEdit {
                background-color: #242932;
                border: 1px solid #3a4352;
                border-radius: 7px;
                padding: 7px 9px;
                color: #e8eef7;
                font-size: 13px;
                min-height: 20px;
                selection-background-color: #4f8cff;
            }
            QLineEdit:focus, QTextEdit:focus, QSpinBox:focus, QComboBox:focus, QDateTimeEdit:focus {
                border: 1px solid #4f8cff;
            }
            QLineEdit:disabled, QSpinBox:disabled, QComboBox:disabled {
                background-color: #20242c;
                color: #778398;
            }
            QPushButton {
                background-color: #2b313c;
                color: #c6d7ef;
                border: 1px solid #4f6b92;
                border-radius: 6px;
                padding: 8px 16px;
                font-size: 13px;
                font-weight: 600;
            }
            QPushButton:hover {
                background-color: #263a55;
            }
            QPushButton:pressed {
                background-color: #2b4d75;
            }
            QPushButton:disabled {
                background-color: #20242c;
                color: #778398;
                border-color: #3a4352;
            }
            QTabWidget::pane {
                border: none;
                top: 0px;
            }
            QTabBar::tab {
                background-color: transparent;
                color: #aab4c3;
                padding: 12px 26px;
                border: none;
                border-bottom: 2px solid transparent;
                font-weight: 600;
                font-size: 13px;
                min-width: 86px;
                margin: 0px 10px 0px 0px;
            }
            QTabBar::tab:selected {
                color: #4f8cff;
                border-bottom: 2px solid #4f8cff;
            }
            QTabBar::tab:hover:!selected {
                color: #4f8cff;
                background-color: #243247;
            }
            QTabBar::tab:pressed {
                background-color: #263a55;
            }
            QListWidget {
                background-color: #242932;
                border: 1px solid #3a4352;
                border-radius: 8px;
                font-size: 13px;
            }
            QListWidget::item {
                padding: 8px 10px;
                border-bottom: 1px solid #303846;
            }
            QListWidget::item:selected {
                background-color: #4f8cff;
                color: #e8eef7;
            }
            QListWidget::item:hover:!selected {
                background-color: #243247;
            }
            QTableWidget {
                background-color: #242932;
                alternate-background-color: #2b313c;
                gridline-color: #303846;
                border: 1px solid #3a4352;
                border-radius: 8px;
                color: #e8eef7;
            }
            QTableWidget::item {
                padding: 6px;
            }
            QTableWidget::item:selected {
                background-color: #4f8cff;
                color: #e8eef7;
            }
            QHeaderView::section {
                background-color: #20242c;
                color: #aab4c3;
                border: none;
                border-right: 1px solid #3a4352;
                border-bottom: 1px solid #3a4352;
                padding: 8px;
                font-weight: 600;
            }
            QScrollArea {
                border: none;
                background-color: transparent;
            }
            QScrollBar:vertical {
                background-color: #181b22;
                width: 11px;
                margin: 0;
            }
            QScrollBar::handle:vertical {
                background-color: #3a4352;
                border-radius: 5px;
                min-height: 30px;
            }
            QScrollBar::handle:vertical:hover {
                background-color: #aab4c3;
            }
            QScrollBar:horizontal {
                background-color: #181b22;
                height: 11px;
                margin: 0;
            }
            QScrollBar::handle:horizontal {
                background-color: #3a4352;
                border-radius: 5px;
                min-width: 30px;
            }
            QScrollBar::handle:horizontal:hover {
                background-color: #aab4c3;
            }
            QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical,
            QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal {
                height: 0px;
                width: 0px;
            }
            QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical,
            QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal {
                background: none;
            }
            QRadioButton {
                padding: 4px 8px;
                font-size: 13px;
                spacing: 8px;
            }
            QRadioButton::indicator {
                width: 18px;
                height: 18px;
            }
            QCheckBox {
                padding: 4px 8px;
                font-size: 13px;
                spacing: 8px;
            }
            QCheckBox::indicator {
                width: 18px;
                height: 18px;
            }
            QFormLayout {
                margin: 10px;
            }
        """)
        
        # 主布局：堆叠“欢迎页”与“项目内容”
        central = QWidget()
        self.setCentralWidget(central)
        main_layout = QVBoxLayout(central)
        main_layout.setContentsMargins(0, 0, 0, 0)
        
        self.stacked = QStackedWidget()
        self.welcome_page = WelcomePage()
        self.welcome_page.open_project_requested.connect(self._on_open_project)
        self.project_content_container = QWidget()
        self.project_content_layout = QVBoxLayout(self.project_content_container)
        self.project_content_layout.setContentsMargins(0, 0, 0, 0)
        
        self.stacked.addWidget(self.welcome_page)
        self.stacked.addWidget(self.project_content_container)
        main_layout.addWidget(self.stacked)
        
        # 状态栏：左侧运行状态，中间版本，右侧系统时间
        sb = self.statusBar()
        sb.showMessage("●  系统运行正常")
        self._status_version_label = QLabel(f"Server Manager v{get_current_version()}")
        self._status_version_label.setStyleSheet("color: #aab4c3; font-size: 12px; padding: 0 18px;")
        sb.addPermanentWidget(self._status_version_label, 1)
        self._status_time_label = QLabel()
        self._status_time_label.setStyleSheet("color: #aab4c3; font-size: 12px; padding: 0 12px;")
        sb.addPermanentWidget(self._status_time_label)
        sb.setStyleSheet("background-color: #242932; color: #25c889; font-size: 12px; padding: 6px; border-top: 1px solid #3a4352;")
        self._status_timer = QTimer(self)
        self._status_timer.timeout.connect(self._update_status_time)
        self._status_timer.start(1000)
        self._update_status_time()
        self._auto_update_timer = QTimer(self)
        self._auto_update_timer.setInterval(self.AUTO_UPDATE_CHECK_INTERVAL_MS)
        self._auto_update_timer.timeout.connect(self._start_startup_update_check)
        
        # 菜单栏：保证在窗口内可见（非系统原生菜单），便于看到“文件”“帮助”
        menubar = self.menuBar()
        menubar.setNativeMenuBar(False)
        menubar.setStyleSheet("""
            QMenuBar { background-color: #242932; color: #e8eef7; padding: 5px 12px; font-size: 13px; border-bottom: 1px solid #3a4352; }
            QMenuBar::item { padding: 5px 10px; border-radius: 5px; }
            QMenuBar::item:selected { background-color: #243247; color: #4f8cff; }
            QMenuBar::item:pressed { background-color: #2b4d75; color: #4f8cff; }
            QMenu { background-color: #242932; color: #e8eef7; border: 1px solid #3a4352; padding: 5px; }
            QMenu::item { padding: 7px 28px 7px 18px; border-radius: 5px; }
            QMenu::item:selected { background-color: #4f8cff; color: #e8eef7; }
        """)
        file_menu = menubar.addMenu("文件(&F)")
        open_act = file_menu.addAction("打开项目(&O)...")
        open_act.triggered.connect(self._on_open_project_menu)
        # 最近的项目：打开菜单时动态刷新，点击即可切换项目
        self.recent_projects_menu = file_menu.addMenu("最近的项目(&R)")
        file_menu.aboutToShow.connect(self._refresh_recent_projects_menu)
        self.close_project_act = file_menu.addAction("关闭项目(&C)")
        self.close_project_act.triggered.connect(self._on_close_project)
        file_menu.addSeparator()
        exit_act = file_menu.addAction("退出(&X)")
        exit_act.triggered.connect(self.close)
        
        # 帮助菜单：检查更新、关于（欢迎页“帮助”按钮会弹出此菜单）
        self.help_menu = menubar.addMenu("帮助(&H)")
        check_update_act = self.help_menu.addAction("检查更新(&U)")
        check_update_act.triggered.connect(self._on_check_update)
        about_act = self.help_menu.addAction("关于(&A)")
        about_act.triggered.connect(self._on_about)
        self.welcome_page.help_requested.connect(self._on_welcome_help_requested)
        
        # 无项目时先显示欢迎页；有项目时才显示项目内容
        if self.config.has_project():
            self._build_project_content()
            self.stacked.setCurrentIndex(1)
            self.setWindowTitle("Server Manager v{} - {}".format(get_current_version(), self.config.tool_dir))
            self.close_project_act.setEnabled(True)
            self._run_project_migrations()
        else:
            self.stacked.setCurrentIndex(0)
            self.close_project_act.setEnabled(False)

    def _update_status_time(self):
        if hasattr(self, "_status_time_label"):
            now = datetime.now().strftime("%Y-%m-%d  %H:%M:%S")
            self._status_time_label.setText(f"◷  系统时间： {now}")
    
    def showEvent(self, event):
        super().showEvent(event)
        # 首次打开后延迟检查一次，随后每 5 分钟在后台检查远端版本清单。
        if not self._startup_update_check_started:
            self._startup_update_check_started = True
            self._auto_update_timer.start()
            QTimer.singleShot(1500, self._start_startup_update_check)
    
    def _start_startup_update_check(self):
        """在后台检查远端版本清单（仅当配置了 update_url 时）。"""
        info = get_version_info()
        update_url = (info.get("update_url") or "").strip()
        if not update_url:
            return
        if self._update_check_worker and self._update_check_worker.isRunning():
            return
        worker = UpdateCheckWorker(update_url, self)
        self._update_check_worker = worker
        worker.finished_signal.connect(self._on_startup_update_checked)
        worker.finished.connect(lambda w=worker: self._clear_update_check_worker(w))
        worker.finished.connect(worker.deleteLater)
        worker.start()

    def _clear_update_check_worker(self, worker):
        if self._update_check_worker is worker:
            self._update_check_worker = None
    
    def _on_startup_update_checked(self, manifest):
        """后台检查结果：若远端版本更高则弹窗询问是否更新。"""
        if not manifest:
            return
        current = get_current_version()
        remote_version = (manifest.get("version") or "").strip()
        if not remote_version or not version_less(current, remote_version):
            if remote_version == current:
                self._last_prompted_update_version = ""
            return
        if remote_version == self._last_prompted_update_version:
            return
        if self._update_prompt_active:
            return
        if hasattr(self, "_update_worker") and self._update_worker and self._update_worker.isRunning():
            return
        self._last_prompted_update_version = remote_version
        self._update_prompt_active = True
        release_notes = (manifest.get("release_notes") or "").strip()
        msg = f"发现新版本 {remote_version}（当前 {current}）"
        if release_notes:
            msg += f"\n\n{release_notes}"
        msg += "\n\n是否立即更新？"
        try:
            ret = QMessageBox.question(
                self,
                "检查更新",
                msg,
                QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
                QMessageBox.StandardButton.Yes,
            )
        finally:
            self._update_prompt_active = False
        if ret != QMessageBox.StandardButton.Yes:
            return
        delta = get_delta_for_current(manifest, current)
        has_delta = bool(delta)
        full_url = (manifest.get("full_installer_url") or manifest.get("download_url") or "").strip()
        self._update_manifest = manifest  # 保存 manifest，增量失败时可用来发起全量更新
        if has_delta:
            self._start_update_download(
                get_delta_download_url(delta),
                use_delta=True,
                expected_sha256=get_delta_expected_sha256(delta),
                delta_kind=get_delta_kind(delta),
            )
        elif full_url:
            self._start_update_download(full_url, use_delta=False)
        else:
            QMessageBox.warning(self, "检查更新", "未配置下载地址，请稍后在「帮助」中手动检查更新。")
    
    def _on_welcome_help_requested(self):
        """欢迎页点击“帮助”时在光标位置弹出帮助菜单"""
        self.help_menu.popup(QCursor.pos())
    
    def _refresh_recent_projects_menu(self):
        """刷新“文件 -> 最近的项目”子菜单，显示最近打开的项目列表，点击即可切换"""
        self.recent_projects_menu.clear()
        recent = load_recent_projects()
        if not recent:
            no_item = self.recent_projects_menu.addAction("（无最近项目）")
            no_item.setEnabled(False)
            return
        for path in recent:
            # 显示路径末尾一段（便于区分），完整路径放 tooltip
            display = path if len(path) <= 50 else "..." + path[-47:]
            act = self.recent_projects_menu.addAction(display)
            act.setToolTip(path)
            act.setData(path)
            act.triggered.connect(lambda checked, p=path: self._on_open_project(Path(p)))
    
    def _on_open_project_menu(self):
        from PyQt6.QtWidgets import QFileDialog
        path = QFileDialog.getExistingDirectory(self, "选择服务器项目目录", str(Path.cwd()))
        if path:
            self._on_open_project(Path(path))
    
    def _on_open_project(self, path):
        path = Path(path) if not isinstance(path, Path) else path
        if not self.config.open_project(path):
            from PyQt6.QtWidgets import QMessageBox
            QMessageBox.warning(self, "打开项目", self.config.config_error or "无效项目目录")
            return
        apply_terminal_launch_config(self.config)
        save_recent_project(str(path))
        self._build_project_content()
        self.stacked.setCurrentIndex(1)
        self.setWindowTitle("Server Manager v{} - {}".format(get_current_version(), path))
        self.close_project_act.setEnabled(True)
        self._run_project_migrations()
    
    def _run_project_migrations(self):
        """打开项目后执行一次性版本迁移（幂等，已完成的迁移会自动跳过）。"""
        server_root = self.config.get('workspace', 'server_root', default='') or (
            str(self.config.tool_dir) if self.config.tool_dir else ''
        )
        if not server_root:
            return
        try:
            executed = run_startup_migrations(server_root, logger_func=self.console.append_output)
        except Exception as e:
            logger.error("执行启动迁移失败: %s", e, exc_info=True)
            return
        if executed:
            self.console.append_output("-" * 50 + "\n")
    
    def _on_close_project(self):
        self.config.tool_dir = None
        self.config.user_config_path = None
        self.config.tool_config_path = None
        self.config.config_error = "未打开项目"
        self.config.data = _empty_config_data()
        apply_terminal_launch_config(self.config)
        # 清空项目内容区域，下次打开项目会重建
        while self.project_content_layout.count():
            child = self.project_content_layout.takeAt(0)
            if child.widget():
                child.widget().deleteLater()
        self.stacked.setCurrentIndex(0)
        self.setWindowTitle("Server Manager v{}".format(get_current_version()))
        self.close_project_act.setEnabled(False)
    
    def _on_about(self):
        """关于对话框"""
        info = get_version_info()
        version = info.get("version", "?")
        notes = info.get("release_notes", "").strip()
        msg = f"Server Manager\n\n版本: {version}"
        if notes:
            msg += f"\n\n{notes}"
        QMessageBox.about(self, "关于", msg)
    
    def _on_check_update(self):
        """检查更新：拉取 manifest（version, full_installer_url, delta_updates），可选全量或增量"""
        info = get_version_info()
        update_url = (info.get("update_url") or "").strip()
        if not update_url:
            QMessageBox.information(
                self,
                "检查更新",
                "未配置更新地址。\n请在 version.json 中设置 update_url（指向版本清单 JSON 的地址）。"
            )
            return
        current = get_current_version()
        manifest = fetch_update_manifest(update_url)
        if not manifest:
            msg = (
                "无法获取更新信息。\n\n"
                "请检查：\n"
                "1. 本机网络是否正常\n"
                "2. 更新服务器地址是否可访问（若为内网地址，请确保当前网络能访问）\n"
                "3. 稍后重试\n\n"
                "当前更新地址：\n{}"
            ).format(update_url[:80] + "..." if len(update_url) > 80 else update_url)
            QMessageBox.information(self, "检查更新", msg)
            return
        remote_version = manifest.get("version", "")
        if not version_less(current, remote_version):
            QMessageBox.information(self, "检查更新", f"当前已是最新版本（{current}）。")
            return
        release_notes = manifest.get("release_notes", "")
        full_url = (manifest.get("full_installer_url") or manifest.get("download_url") or "").strip()
        delta = get_delta_for_current(manifest, current)
        has_delta = bool(delta)
        if not full_url and not has_delta:
            QMessageBox.warning(
                self,
                "检查更新",
                f"发现新版本 {remote_version}，但未配置 full_installer_url 或 delta_updates。\n\n{release_notes}"
            )
            return
        btn_full = "立即更新（全量）" if has_delta else "立即更新"
        btn_delta = "立即更新（增量）" if has_delta else None
        msg = f"发现新版本 {remote_version}（当前 {current}）\n\n{release_notes}"
        box = QMessageBox(self)
        box.setWindowTitle("检查更新")
        box.setText(msg)
        box.setIcon(QMessageBox.Icon.Information)
        box.addButton("取消", QMessageBox.ButtonRole.RejectRole)
        if btn_delta:
            box.addButton(btn_delta, QMessageBox.ButtonRole.AcceptRole)
        if full_url:
            box.addButton(btn_full, QMessageBox.ButtonRole.AcceptRole)
        box.exec()
        clicked = box.clickedButton().text()
        if clicked == "取消":
            return
        self._update_manifest = manifest
        if clicked == btn_delta and has_delta:
            self._start_update_download(
                get_delta_download_url(delta),
                use_delta=True,
                expected_sha256=get_delta_expected_sha256(delta),
                delta_kind=get_delta_kind(delta),
            )
        else:
            self._start_update_download(full_url, use_delta=False)
    
    def _start_update_download(
        self,
        download_url: str,
        use_delta: bool = False,
        expected_sha256: Optional[str] = None,
        delta_kind: str = "",
    ):
        """下载更新包（全量 exe 或增量 patch），完成后全量则启动安装，增量则 bsdiff 打补丁并替换"""
        dest = Path(tempfile.gettempdir()) / "ServerManager_Update"
        dest.mkdir(parents=True, exist_ok=True)
        from urllib.parse import urlparse
        suffix = Path(urlparse(download_url).path).suffix
        if not suffix:
            suffix = ".smdelta" if use_delta and delta_kind == "file_zip" else (".patch" if use_delta else ".exe")
        dest_file = dest / ("ServerManager_patch" + suffix if use_delta else f"ServerManager_Setup{suffix}")
        self._update_delta_kind = delta_kind
        self._update_download_path = dest_file
        self._update_progress = QProgressDialog("正在下载更新...", "取消", 0, 100, self)
        self._update_progress.setWindowTitle("检查更新")
        self._update_progress.setMinimumDuration(0)
        self._update_progress.setValue(0)
        self._update_worker = UpdateDownloadWorker(
            download_url,
            dest_file,
            is_delta=use_delta,
            expected_sha256=expected_sha256,
        )
        self._update_worker.progress_signal.connect(self._on_update_progress)
        self._update_worker.finished_signal.connect(self._on_update_download_finished)
        self._update_progress.canceled.connect(self._on_update_canceled)
        self._update_worker.start()
    
    def _on_update_progress(self, percent: int):
        if hasattr(self, "_update_progress") and self._update_progress:
            self._update_progress.setValue(percent)
    
    def _on_update_canceled(self):
        if hasattr(self, "_update_worker") and self._update_worker and self._update_worker.isRunning():
            self._update_worker.terminate()
        if hasattr(self, "_update_progress") and self._update_progress:
            self._update_progress.close()
    
    def _on_update_download_finished(self, path, is_delta: bool, expected_sha256: Optional[str]):
        if hasattr(self, "_update_progress") and self._update_progress:
            self._update_progress.close()
        if not path or not Path(path).exists():
            QMessageBox.warning(self, "检查更新", "下载失败，请稍后重试或手动下载。")
            return
        if is_delta:
            QMessageBox.information(
                self,
                "检查更新",
                "增量包已下载，即将退出并重启以应用更新。"
            )
            delta_kind = getattr(self, "_update_delta_kind", "") or "bsdiff_exe"
            if delta_kind == "file_zip":
                ok, reason = apply_file_delta_package(Path(path), expected_sha256 or "")
            else:
                ok, reason = apply_delta_patch(Path(path), expected_sha256 or "")
            if ok:
                pass  # 成功时进程会退出
            else:
                box = QMessageBox(self)
                box.setWindowTitle("检查更新")
                box.setIcon(QMessageBox.Icon.Warning)
                box.setText(f"增量更新应用失败：{reason}")
                btn_full = box.addButton("全量更新", QMessageBox.ButtonRole.AcceptRole)
                box.addButton("确定", QMessageBox.ButtonRole.RejectRole)
                box.exec()
                if box.clickedButton() == btn_full and getattr(self, "_update_manifest", None):
                    manifest = self._update_manifest
                    full_url = (manifest.get("full_installer_url") or manifest.get("download_url") or "").strip()
                    if full_url:
                        self._start_update_download(full_url, use_delta=False)
        else:
            QMessageBox.information(
                self,
                "检查更新",
                "更新包已下载，即将退出并重启以完成安装。"
            )
            run_installer_and_exit(Path(path), silent=True)
    
    def _build_project_content(self):
        """创建或重建项目内容区（标签页 + 控制台）"""
        apply_terminal_launch_config(self.config)
        while self.project_content_layout.count():
            child = self.project_content_layout.takeAt(0)
            if child.widget():
                child.widget().deleteLater()
        
        self.project_splitter = QSplitter(Qt.Orientation.Vertical)
        self.project_splitter.setStyleSheet("""
            QSplitter::handle {
                background-color: #3a4352;
                height: 6px;
            }
            QSplitter::handle:hover {
                background-color: #4f8cff;
            }
        """)
        self.tabs = QTabWidget()
        self.console = OutputConsole()
        
        self.command_tab = CommandTab(self.config, self.console)
        self.create_server_tab = CreateServerTab(self.config, self.console)
        self.server_manage_tab = ServerManageTab(self.config, self.console)
        self.log_viewer_tab = LogViewerTab(self.config, self.console)
        self.config_tab = ConfigTab(self.config, self.console)
        self.config_tab.config_saved.connect(self._on_config_saved)
        
        self._main_tab_icon_kinds = ["calendar", "plus", "server", "log", "settings"]
        self.tabs.addTab(self.command_tab, make_line_icon("calendar", "#aab4c3", 16), "控制台")
        self.tabs.addTab(self.create_server_tab, make_line_icon("plus", "#aab4c3", 16), "创建服务器")
        self.tabs.addTab(self.server_manage_tab, make_line_icon("server", "#aab4c3", 16), "服务器管理")
        self.tabs.addTab(self.log_viewer_tab, make_line_icon("log", "#aab4c3", 16), "日志查看")
        self.tabs.addTab(self.config_tab, make_line_icon("settings", "#aab4c3", 16), "工具设置")
        self._update_main_tab_icons(0)
        self.tabs.currentChanged.connect(self._on_tab_changed)
        self.tabs.setMinimumHeight(300)
        top_ip_label = CopyableIpLabel(get_local_ip())
        top_ip_label.setStyleSheet("""
            QLabel {
                color: #aab4c3;
                background-color: #2b313c;
                border: 1px solid #3a4352;
                border-radius: 6px;
                padding: 5px 12px;
                margin-right: 10px;
            }
            QLabel:hover {
                color: #4f8cff;
                background-color: #243247;
                border-color: #5f94db;
            }
        """)
        self.tabs.setCornerWidget(top_ip_label, Qt.Corner.TopRightCorner)
        self.project_splitter.addWidget(self.tabs)
        
        self.console_widget = QWidget()
        self.console_widget.setFixedHeight(self.CONSOLE_PANEL_HEIGHT)
        self.console_widget.setObjectName("consolePanel")
        self.console_widget.setStyleSheet("""
            QWidget#consolePanel {
                background-color: #242932;
                border: 1px solid #3a4352;
                border-radius: 8px;
            }
        """)
        console_layout = QVBoxLayout(self.console_widget)
        console_layout.setContentsMargins(16, 10, 16, 12)
        console_layout.setSpacing(8)
        console_header = QHBoxLayout()
        console_icon = QLabel()
        console_icon.setPixmap(make_line_icon("log", "#c6d7ef", 18).pixmap(18, 18))
        console_header.addWidget(console_icon)
        console_label = QLabel("输出日志")
        console_label.setStyleSheet("font-weight: bold; font-size: 13px; color: #e8eef7;")
        console_header.addWidget(console_label)
        console_header.addStretch()
        popup_btn = QPushButton("独立窗口")
        popup_btn.setMaximumWidth(108)
        popup_btn.clicked.connect(self.console.open_window)
        apply_button_style(popup_btn, "outline", compact=True)
        set_button_icon(popup_btn, "external", "#4f8cff", 16)
        console_header.addWidget(popup_btn)
        clear_btn = QPushButton("清空")
        clear_btn.setMaximumWidth(66)
        clear_btn.clicked.connect(self.console.clear_output)
        apply_button_style(clear_btn, "danger", compact=True)
        set_button_icon(clear_btn, "trash", "#ff5c6a", 16)
        console_header.addWidget(clear_btn)
        console_layout.addLayout(console_header)
        self.console.setMinimumHeight(260)
        console_layout.addWidget(self.console)
        self.project_splitter.addWidget(self.console_widget)
        self.project_splitter.setCollapsible(0, False)
        self.project_splitter.setCollapsible(1, False)
        self.project_splitter.setStretchFactor(0, 1)
        self.project_splitter.setStretchFactor(1, 0)
        self.project_splitter.setSizes([360, self.CONSOLE_PANEL_HEIGHT])
        
        self.project_content_layout.addWidget(self.project_splitter)
        self._sync_console_panel_visibility(self.tabs.currentIndex())
        QTimer.singleShot(0, lambda: self._sync_console_panel_visibility(self.tabs.currentIndex()))
        
        self.console.append_output("=" * 50 + "\n")
        self.console.append_output("    Server Manager v{}\n".format(get_current_version()))
        self.console.append_output("=" * 50 + "\n")
        self.console.append_output(f"本机IP: {get_local_ip()}\n")
        server_root = self.config.get('workspace', 'server_root', default='')
        run_dir = self.config.get('workspace', 'run_dir', default='')
        if server_root:
            self.console.append_output(f"服务器目录: {server_root}\n")
        else:
            self.console.append_output("[提示] 请在「工具设置」中配置服务器根目录\n")
        if run_dir:
            self.console.append_output(f"运行目录: {run_dir}\n")
        self.console.append_output("-" * 50 + "\n")
        QTimer.singleShot(500, self._init_node_status_cache)
    
    def _init_node_status_cache(self):
        """初始化本地节点状态缓存
        
        首次启动时，批量查询所有本地节点状态并加载到缓存
        """
        cache = get_node_status_cache()
        
        # 如果缓存已初始化，跳过
        if cache.is_initialized():
            self.console.append_output("[缓存] 节点状态缓存已初始化，跳过\n")
            return
        
        # 获取本地服务器列表
        server_root = self.config.get('workspace', 'server_root', default='')
        run_dir = self.config.get('workspace', 'run_dir', default='')
        
        if not run_dir and server_root:
            run_dir = str(Path(server_root) / 'run')
        
        if not run_dir:
            self.console.append_output("[缓存] 运行目录未配置，跳过缓存初始化\n")
            cache.set_initialized(True)
            return
        
        if not Path(run_dir).exists():
            self.console.append_output(f"[缓存] 运行目录不存在: {run_dir}，跳过缓存初始化\n")
            cache.set_initialized(True)
            return
        
        servers = get_server_list(run_dir)
        all_servers = []
        for category in ['game', 'cross', 'login', 'client', 'center', 'other']:
            all_servers.extend(servers.get(category, []))
        
        if not all_servers:
            self.console.append_output("[缓存] 未找到任何服务器，跳过缓存初始化\n")
            cache.set_initialized(True)
            return
        
        # 批量查询所有本地节点状态
        self.console.append_output(f"[缓存] 正在批量检查 {len(all_servers)} 个节点状态...\n")
        if self._cache_init_worker and self._cache_init_worker.isRunning():
            return

        cookie = self.config.get('server', 'cookie', default='ddxq2-node')
        worker = NodeStatusCheckWorker(
            all_servers,
            cookie,
            erl_path=self.config.get('erlang', 'r25_path', default=''),
        )
        self._cache_init_worker = worker
        worker.finished_signal.connect(
            lambda results, total=len(all_servers): self._on_init_node_status_cache_finished(results, total)
        )
        worker.finished.connect(
            lambda w=worker: setattr(self, '_cache_init_worker', None) if self._cache_init_worker is w else None
        )
        worker.finished.connect(worker.deleteLater)
        worker.start()

    def _on_init_node_status_cache_finished(self, results, total: int):
        """本地节点状态缓存初始化完成。"""
        cache = get_node_status_cache()
        cache.batch_set(results)
        cache.set_initialized(True)

        online_count = sum(1 for v in results.values() if v)
        self.console.append_output(f"[缓存] 节点状态已加载，在线: {online_count}/{total}\n")
        if hasattr(self, 'server_manage_tab'):
            self.server_manage_tab.refresh_status_from_cache()
    
    def _on_tab_changed(self, index: int):
        """标签页切换时刷新状态显示"""
        self._update_main_tab_icons(index)
        self._sync_console_panel_visibility(index)
        # 切换到"服务器管理"标签页时（索引为2），从缓存刷新状态
        if index == 2 and hasattr(self, 'server_manage_tab'):
            self.server_manage_tab.refresh_status_from_cache()
        # 切换到"日志查看"标签页时（索引为3），自动刷新服务器列表
        if index == 3 and hasattr(self, "log_viewer_tab"):
            self.log_viewer_tab.refresh_server_list()

    def _update_main_tab_icons(self, current_index: int):
        """同步主导航图标颜色：选中蓝色，未选中灰色。"""
        if not hasattr(self, "_main_tab_icon_kinds") or not hasattr(self, "tabs"):
            return
        for i, kind in enumerate(self._main_tab_icon_kinds):
            color = "#4f8cff" if i == current_index else "#aab4c3"
            self.tabs.setTabIcon(i, make_line_icon(kind, color, 16))

    def _sync_console_panel_visibility(self, index: int):
        """底部输出日志只在主「控制台」页签显示。"""
        if not hasattr(self, "console_widget"):
            return
        show_console = index == 0
        self.console_widget.setVisible(show_console)
        if hasattr(self, "project_splitter"):
            if show_console:
                self.console_widget.setFixedHeight(self.CONSOLE_PANEL_HEIGHT)
                self.project_splitter.setSizes([360, self.CONSOLE_PANEL_HEIGHT])
            else:
                self.project_splitter.setSizes([1, 0])
    
    def _on_config_saved(self):
        """配置保存后刷新其他页签"""
        # 刷新创建服务器页签的数据库配置
        if hasattr(self, 'create_server_tab'):
            self.create_server_tab.refresh_config()
        
        # 刷新 Server management 页签
        if hasattr(self, 'server_manage_tab'):
            self.server_manage_tab.refresh_server_list()
        if hasattr(self, "log_viewer_tab"):
            self.log_viewer_tab.refresh_server_list()
    
    def closeEvent(self, event):
        """关闭窗口时的处理
        
        抑制 PyInstaller 打包后的临时目录删除警告
        当有 Erlang 节点运行时，临时目录可能无法删除，但这不影响使用
        """
        import sys
        import os
        
        # 如果是 PyInstaller 打包的程序
        if getattr(sys, 'frozen', False) and sys.platform == 'win32':
            try:
                import ctypes
                
                # 设置错误模式，抑制所有系统错误对话框
                SEM_FAILCRITICALERRORS = 0x0001
                SEM_NOGPFAULTERRORBOX = 0x0002
                SEM_NOALIGNMENTFAULTEXCEPT = 0x0004
                SEM_NOOPENFILEERRORBOX = 0x8000
                error_mode = (SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | 
                             SEM_NOALIGNMENTFAULTEXCEPT | SEM_NOOPENFILEERRORBOX)
                ctypes.windll.kernel32.SetErrorMode(error_mode)
                
                # 禁用 Windows 错误报告
                try:
                    ctypes.windll.kernel32.SetUnhandledExceptionFilter(None)
                except Exception:
                    pass
                    
            except Exception:
                pass
        
        event.accept()
        
        # 强制退出，跳过 PyInstaller 的清理步骤
        if getattr(sys, 'frozen', False):
            # 使用 QTimer 延迟调用 os._exit，确保 Qt 事件循环正常结束
            from PyQt6.QtCore import QTimer
            QTimer.singleShot(100, lambda: os._exit(0))


def check_config_and_show_dialog(config: Config) -> bool:
    """检查配置并显示配置对话框
    
    Returns:
        True 如果配置有效，False 如果用户取消
    """
    if config.is_configured():
        return True
    
    # 显示配置提示对话框
    msg = QMessageBox()
    msg.setWindowTitle("首次配置")
    msg.setIcon(QMessageBox.Icon.Information)
    msg.setText("欢迎使用 Server Manager！\n\n检测到工具尚未配置，请先完成以下设置：")
    
    missing = config.get_missing_config()
    detail = "\n".join(f"• {item}" for item in missing) if missing else "• 服务器根目录"
    msg.setInformativeText(f"缺失配置项：\n{detail}\n\n点击\"确定\"进入工具设置页面进行配置。")
    
    msg.setStandardButtons(QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel)
    msg.setDefaultButton(QMessageBox.StandardButton.Ok)
    
    result = msg.exec()
    return result == QMessageBox.StandardButton.Ok
