"""Windows settings dialog — PyQt6 QDialog with tabs.

Tabs: AI Provider | Hotkeys | Updates | About / License | Language | Appearance
"""

from __future__ import annotations

import logging
import threading
from collections.abc import Callable

from PyQt6.QtCore import Qt, QTimer
from PyQt6.QtWidgets import (
    QCheckBox,
    QComboBox,
    QDialog,
    QDialogButtonBox,
    QFormLayout,
    QGroupBox,
    QHBoxLayout,
    QKeySequenceEdit,
    QLabel,
    QLineEdit,
    QMessageBox,
    QPushButton,
    QRadioButton,
    QSpinBox,
    QTabWidget,
    QVBoxLayout,
    QWidget,
)

from ..ai.china_adapter import PROVIDER_NAMES
from ..config import Config
from ..i18n import SUPPORTED_LANGUAGES, _, get_language, setup_i18n
from ..security import privacy_manager

log = logging.getLogger(__name__)

_MODELS: dict[str, list[str]] = {
    "deepseek": ["deepseek-chat", "deepseek-coder", "deepseek-reasoner"],
    "aliyun": ["qwen-max", "qwen-plus", "qwen-turbo", "qwen-long"],
    "88api": ["gpt-4o", "gpt-4-turbo", "gpt-3.5-turbo", "claude-3-5-sonnet-20241022"],
    "ollama": ["llama3.2", "llama3.1", "mistral", "codellama", "phi3"],
}

_API_LINKS: dict[str, str] = {
    "deepseek": "https://platform.deepseek.com/api_keys",
    "aliyun": "https://dashscope.console.aliyun.com/apiKey",
    "88api": "https://www.88api.dev/",
}

_DEFAULT_HOTKEY = "ctrl+shift+space"


class SettingsDialog(QDialog):
    """Main settings dialog."""

    def __init__(
        self,
        config: Config,
        parent: QWidget | None = None,
        on_hotkey_change: Callable[[str], bool] | None = None,
        on_check_update: Callable[[], None] | None = None,
        on_clear_cache: Callable[[], int] | None = None,
        on_language_change: Callable[[str], None] | None = None,
        on_theme_change: Callable[[str], None] | None = None,
        on_prompts_hotkey_change: Callable[[str], bool] | None = None,
        on_vision_hotkey_change: Callable[[str], bool] | None = None,
    ) -> None:
        super().__init__(parent)
        self._config = config
        self._on_hotkey_change = on_hotkey_change
        self._on_check_update = on_check_update
        self._on_clear_cache = on_clear_cache
        self._on_language_change = on_language_change
        self._on_theme_change = on_theme_change
        self._on_prompts_hotkey_change = on_prompts_hotkey_change
        self._on_vision_hotkey_change = on_vision_hotkey_change

        self.setWindowTitle(_("settings.title"))
        self.setMinimumWidth(520)
        self.setWindowFlags(self.windowFlags() & ~Qt.WindowType.WindowContextHelpButtonHint)

        self._setup_ui()

    def _setup_ui(self) -> None:
        layout = QVBoxLayout(self)

        self._tabs = QTabWidget()
        self._tabs.addTab(self._build_ai_tab(), _("settings.tab_ai"))
        self._tabs.addTab(self._build_hotkey_tab(), _("settings.tab_hotkey"))
        self._tabs.addTab(self._build_update_tab(), _("settings.tab_update"))
        self._tabs.addTab(self._build_about_tab(), _("settings.tab_about"))
        self._tabs.addTab(self._build_language_tab(), _("settings.tab_language"))
        self._tabs.addTab(self._build_appearance_tab(), _("settings.tab_appearance"))
        self._tabs.addTab(self._build_vision_tab(), _("settings.tab_vision"))
        layout.addWidget(self._tabs)

        self._buttons = QDialogButtonBox(
            QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
        )
        self._buttons.accepted.connect(self._on_accept)
        self._buttons.rejected.connect(self.reject)
        layout.addWidget(self._buttons)

    def retranslate_ui(self) -> None:
        """Update all widget texts to the current locale (called after language switch)."""
        self.setWindowTitle(_("settings.title"))

        # Tab labels
        self._tabs.setTabText(0, _("settings.tab_ai"))
        self._tabs.setTabText(1, _("settings.tab_hotkey"))
        self._tabs.setTabText(2, _("settings.tab_update"))
        self._tabs.setTabText(3, _("settings.tab_about"))
        self._tabs.setTabText(4, _("settings.tab_language"))
        self._tabs.setTabText(5, _("settings.tab_appearance"))
        self._tabs.setTabText(6, _("settings.tab_vision"))

        # ── AI tab ────────────────────────────────────────────────────────
        self._lbl_provider.setText(_("settings.ai.provider"))
        self._lbl_model.setText(_("settings.ai.model"))
        self._ollama_group.setTitle(_("settings.ai.ollama_group"))
        self._lbl_server_url.setText(_("settings.ai.server_url"))
        self._btn_fetch_models.setText(_("settings.ai.fetch_models"))
        self._install_hint.setText(_("settings.ai.install_hint"))
        self._lbl_api_key.setText(_("settings.ai.api_key"))
        self._key_edit.setPlaceholderText(_("settings.ai.api_key_placeholder"))
        self._btn_save_key.setText(_("settings.ai.save_key"))
        self._lbl_get_key.setText(_("settings.ai.get_key"))
        self._stream_cb.setText(_("settings.ai.streaming"))
        self._cache_cb.setText(_("settings.ai.cache"))
        self._lbl_cache_ttl.setText(_("settings.ai.cache_ttl_label"))
        self._cache_ttl_spin.setSuffix(_("settings.ai.cache_ttl_suffix"))
        self._btn_clear_cache.setText(_("settings.ai.clear_cache"))
        self._btn_clear_cache.setToolTip(_("settings.ai.clear_cache_tooltip"))

        # ── Hotkey tab ────────────────────────────────────────────────────
        self._hotkey_info.setText(_("settings.hotkey.info"))
        self._actions_group.setTitle(_("settings.hotkey.actions_group"))
        self._lbl_process_sel.setText(_("settings.hotkey.process_selection"))
        self._hotkey_edit.setToolTip(_("settings.hotkey.field_tooltip"))
        self._current_hotkey_lbl.setText(
            _("settings.hotkey.current_active", hotkey=self._config.hotkey.upper())
        )
        self._lbl_prompts_hk.setText(_("settings.hotkey.prompts_label"))
        self._prompts_hotkey_edit.setToolTip(_("settings.hotkey.field_tooltip"))
        self._lbl_vision_hk.setText(_("settings.hotkey.vision_label"))
        self._vision_hotkey_edit.setToolTip(_("settings.hotkey.field_tooltip"))
        self._btn_apply_hotkey.setText(_("settings.hotkey.btn_apply"))
        self._btn_reset_hotkey.setText(_("settings.hotkey.btn_reset"))
        self._btn_reset_hotkey.setToolTip(
            _("settings.hotkey.btn_reset_tooltip", hotkey=_DEFAULT_HOTKEY)
        )

        # ── Update tab ────────────────────────────────────────────────────
        self._lbl_repo.setText(_("settings.update.repo_label"))
        self._repo_edit.setPlaceholderText(_("settings.update.repo_placeholder"))
        self._btn_check_update.setText(_("settings.update.btn_check"))
        self._update_status.setText(_("settings.update.status_initial"))

        # ── About tab ─────────────────────────────────────────────────────
        from src import __version__

        self._about_info.setText(_("settings.about.description", version=__version__))
        self._lic_group.setTitle(_("settings.about.license_group"))
        self._lbl_server.setText(_("settings.about.server_label"))
        self._server_url_edit.setPlaceholderText(_("settings.about.server_placeholder"))
        self._lbl_lic_key.setText(_("settings.about.key_label"))
        self._license_edit.setPlaceholderText(_("settings.about.key_placeholder"))
        self._btn_activate.setText(_("settings.about.btn_activate"))
        self._btn_revoke.setText(_("settings.about.btn_revoke"))
        self._btn_revoke.setToolTip(_("settings.about.btn_revoke_tooltip"))

        # ── Language tab ──────────────────────────────────────────────────
        self._lbl_lang.setText(_("settings.language.label"))
        self._lang_note.setText(_("settings.language.note"))
        self._btn_apply_lang.setText(_("settings.language.btn_apply"))

        # ── Appearance tab ────────────────────────────────────────────────
        self._lbl_theme.setText(_("settings.appearance.theme_label"))
        self._radio_light.setText(_("settings.appearance.light"))
        self._radio_dark.setText(_("settings.appearance.dark"))
        self._radio_system.setText(_("settings.appearance.system"))
        self._btn_apply_theme.setText(_("settings.appearance.btn_apply"))
        self._lbl_theme_note.setText(_("settings.appearance.note"))

        # ── Vision tab ────────────────────────────────────────────────────
        self._vision_enabled_cb.setText(_("settings.vision.enabled"))
        self._lbl_vision_provider.setText(_("settings.vision.provider"))
        self._lbl_vision_model.setText(_("settings.vision.model"))
        self._vision_key_note.setText(_("settings.vision.key_note"))

        # ── Prompts group (in Hotkey tab) ─────────────────────────────────
        self._prompts_settings_group.setTitle(_("settings.prompts.group"))
        self._slash_cb.setText(_("settings.prompts.slash_commands"))

    # ------------------------------------------------------------------ #
    # AI tab
    # ------------------------------------------------------------------ #

    def _build_ai_tab(self) -> QWidget:
        widget = QWidget()
        form = QFormLayout(widget)
        form.setRowWrapPolicy(QFormLayout.RowWrapPolicy.WrapLongRows)

        # Provider selector
        self._provider_combo = QComboBox()
        for name in PROVIDER_NAMES:
            self._provider_combo.addItem(name)
        idx = (
            list(PROVIDER_NAMES).index(self._config.provider)
            if self._config.provider in PROVIDER_NAMES
            else 0
        )
        self._provider_combo.setCurrentIndex(idx)
        self._provider_combo.currentTextChanged.connect(self._on_provider_changed)
        self._lbl_provider = QLabel(_("settings.ai.provider"))
        form.addRow(self._lbl_provider, self._provider_combo)

        # Model selector
        self._model_combo = QComboBox()
        self._refresh_models(self._config.provider)
        self._lbl_model = QLabel(_("settings.ai.model"))
        form.addRow(self._lbl_model, self._model_combo)

        # ── Ollama-specific group (shown only for ollama provider) ──────
        self._ollama_group = QGroupBox(_("settings.ai.ollama_group"))
        ollama_form = QFormLayout(self._ollama_group)

        self._ollama_url_edit = QLineEdit()
        self._ollama_url_edit.setPlaceholderText("http://localhost:11434/v1")
        self._ollama_url_edit.setText(self._config.ollama_base_url)
        self._lbl_server_url = QLabel(_("settings.ai.server_url"))
        ollama_form.addRow(self._lbl_server_url, self._ollama_url_edit)

        fetch_row = QHBoxLayout()
        self._btn_fetch_models = QPushButton(_("settings.ai.fetch_models"))
        self._btn_fetch_models.clicked.connect(self._fetch_ollama_models)
        self._ollama_status = QLabel("")
        self._ollama_status.setStyleSheet("color: #888; font-size: 11px;")
        fetch_row.addWidget(self._btn_fetch_models)
        fetch_row.addWidget(self._ollama_status, stretch=1)
        fetch_widget = QWidget()
        fetch_widget.setLayout(fetch_row)
        ollama_form.addRow("", fetch_widget)

        self._install_hint = QLabel(_("settings.ai.install_hint"))
        self._install_hint.setOpenExternalLinks(True)
        self._install_hint.setWordWrap(True)
        self._install_hint.setStyleSheet("color: #888; font-size: 11px;")
        ollama_form.addRow("", self._install_hint)

        form.addRow(self._ollama_group)

        # ── API key field (hidden for Ollama) ────────────────────────────
        self._lbl_api_key = QLabel(_("settings.ai.api_key"))
        key_layout = QHBoxLayout()
        self._key_edit = QLineEdit()
        self._key_edit.setEchoMode(QLineEdit.EchoMode.Password)
        self._key_edit.setPlaceholderText(_("settings.ai.api_key_placeholder"))

        existing = privacy_manager.load_api_key(self._config.provider)
        if existing:
            self._key_edit.setText(existing)

        self._btn_save_key = QPushButton(_("settings.ai.save_key"))
        self._btn_save_key.clicked.connect(self._save_api_key)
        key_layout.addWidget(self._key_edit)
        key_layout.addWidget(self._btn_save_key)

        self._key_widget = QWidget()
        self._key_widget.setLayout(key_layout)
        form.addRow(_("settings.ai.api_key"), self._key_widget)

        # Link to get API key
        self._key_link = QLabel()
        self._key_link.setOpenExternalLinks(True)
        self._update_key_link(self._config.provider)
        self._lbl_get_key = QLabel(_("settings.ai.get_key"))
        form.addRow(self._lbl_get_key, self._key_link)

        # Streaming toggle
        self._stream_cb = QCheckBox(_("settings.ai.streaming"))
        self._stream_cb.setChecked(self._config.stream_enabled)
        form.addRow("", self._stream_cb)

        # Cache toggle
        self._cache_cb = QCheckBox(_("settings.ai.cache"))
        self._cache_cb.setChecked(self._config.cache_enabled)
        form.addRow("", self._cache_cb)

        # TTL spinner (days)
        ttl_layout = QHBoxLayout()
        self._cache_ttl_spin = QSpinBox()
        self._cache_ttl_spin.setRange(1, 365)
        self._cache_ttl_spin.setValue(max(1, self._config.cache_ttl_seconds // 86400))
        self._cache_ttl_spin.setSuffix(_("settings.ai.cache_ttl_suffix"))
        ttl_layout.addWidget(self._cache_ttl_spin)
        ttl_layout.addStretch()
        ttl_widget = QWidget()
        ttl_widget.setLayout(ttl_layout)
        self._lbl_cache_ttl = QLabel(_("settings.ai.cache_ttl_label"))
        form.addRow(self._lbl_cache_ttl, ttl_widget)

        # Clear cache button
        self._btn_clear_cache = QPushButton(_("settings.ai.clear_cache"))
        self._btn_clear_cache.setToolTip(_("settings.ai.clear_cache_tooltip"))
        self._btn_clear_cache.clicked.connect(self._clear_cache)
        form.addRow("", self._btn_clear_cache)

        # Apply initial Ollama visibility
        self._update_provider_ui(self._config.provider)

        return widget

    def _on_provider_changed(self, name: str) -> None:
        self._refresh_models(name)
        self._update_key_link(name)
        self._update_provider_ui(name)
        existing = privacy_manager.load_api_key(name) if name != "ollama" else ""
        self._key_edit.setText(existing or "")

    def _update_provider_ui(self, provider: str) -> None:
        """Show/hide Ollama-specific controls and API key field."""
        is_ollama = provider == "ollama"
        self._ollama_group.setVisible(is_ollama)
        self._key_widget.setVisible(not is_ollama)

    def _refresh_models(self, provider: str) -> None:
        self._model_combo.clear()
        models = _MODELS.get(provider, [])
        for m in models:
            self._model_combo.addItem(m)
        if self._config.model in models:
            self._model_combo.setCurrentText(self._config.model)
        elif models:
            self._model_combo.setCurrentIndex(0)

    def _update_key_link(self, provider: str) -> None:
        url = _API_LINKS.get(provider, "")
        if url:
            self._key_link.setText(f'<a href="{url}">{url}</a>')
        else:
            self._key_link.setText("")

    def _fetch_ollama_models(self) -> None:
        """Fetch installed Ollama models in a background thread."""
        self._btn_fetch_models.setEnabled(False)
        self._ollama_status.setText(_("settings.ai.ollama_connecting"))
        base_url = self._ollama_url_edit.text().strip() or "http://localhost:11434/v1"
        threading.Thread(target=self._do_fetch_ollama_models, args=(base_url,), daemon=True).start()

    def _do_fetch_ollama_models(self, base_url: str) -> None:
        from ..ai.provider_ollama import list_models

        models = list_models(base_url, timeout=5.0)
        QTimer.singleShot(0, lambda: self._on_ollama_models_fetched(models))

    def _on_ollama_models_fetched(self, models: list[str]) -> None:
        self._btn_fetch_models.setEnabled(True)
        if not models:
            self._ollama_status.setText(_("settings.ai.ollama_unavailable"))
            return
        current = self._model_combo.currentText()
        _MODELS["ollama"] = models
        self._model_combo.clear()
        for m in models:
            self._model_combo.addItem(m)
        if current in models:
            self._model_combo.setCurrentText(current)
        self._ollama_status.setText(_("settings.ai.ollama_models_count", count=len(models)))

    def _clear_cache(self) -> None:
        reply = QMessageBox.question(
            self,
            _("settings.cache.clear_title"),
            _("settings.cache.clear_question"),
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
            QMessageBox.StandardButton.No,
        )
        if reply != QMessageBox.StandardButton.Yes:
            return
        if self._on_clear_cache:
            count = self._on_clear_cache()
            QMessageBox.information(
                self,
                _("settings.cache.cleared_title"),
                _("settings.cache.cleared_msg", count=count),
            )
        else:
            QMessageBox.information(
                self,
                _("settings.cache.inactive_title"),
                _("settings.cache.inactive_msg"),
            )

    def _save_api_key(self) -> None:
        provider = self._provider_combo.currentText()
        key = self._key_edit.text().strip()
        if not key:
            return
        ok = privacy_manager.save_api_key(provider, key)
        if ok:
            log.info("API key saved for %s", provider)
        else:
            log.error("Failed to save API key for %s", provider)

    # ------------------------------------------------------------------ #
    # Hotkey tab
    # ------------------------------------------------------------------ #

    def _build_hotkey_tab(self) -> QWidget:
        widget = QWidget()
        layout = QVBoxLayout(widget)
        layout.setSpacing(10)

        # Explanation
        self._hotkey_info = QLabel(_("settings.hotkey.info"))
        self._hotkey_info.setWordWrap(True)
        self._hotkey_info.setStyleSheet("color: #888; font-size: 11px;")
        layout.addWidget(self._hotkey_info)

        # Actions table / single-entry form
        self._actions_group = QGroupBox(_("settings.hotkey.actions_group"))
        actions_form = QFormLayout(self._actions_group)

        # Hotkey capture field
        self._hotkey_edit = QKeySequenceEdit()
        self._hotkey_edit.setKeySequence(self._hotkey_to_qt(self._config.hotkey))
        self._hotkey_edit.setToolTip(_("settings.hotkey.field_tooltip"))
        self._lbl_process_sel = QLabel(_("settings.hotkey.process_selection"))
        actions_form.addRow(self._lbl_process_sel, self._hotkey_edit)

        # Current active hotkey label
        self._current_hotkey_lbl = QLabel(
            _("settings.hotkey.current_active", hotkey=self._config.hotkey.upper())
        )
        self._current_hotkey_lbl.setStyleSheet("font-size: 11px;")
        actions_form.addRow("", self._current_hotkey_lbl)

        # Prompts hotkey field
        self._prompts_hotkey_edit = QKeySequenceEdit()
        self._prompts_hotkey_edit.setKeySequence(self._hotkey_to_qt(self._config.prompts_hotkey))
        self._prompts_hotkey_edit.setToolTip(_("settings.hotkey.field_tooltip"))
        self._lbl_prompts_hk = QLabel(_("settings.hotkey.prompts_label"))
        actions_form.addRow(self._lbl_prompts_hk, self._prompts_hotkey_edit)

        # Vision hotkey field
        self._vision_hotkey_edit = QKeySequenceEdit()
        self._vision_hotkey_edit.setKeySequence(self._hotkey_to_qt(self._config.vision_hotkey))
        self._vision_hotkey_edit.setToolTip(_("settings.hotkey.field_tooltip"))
        self._lbl_vision_hk = QLabel(_("settings.hotkey.vision_label"))
        actions_form.addRow(self._lbl_vision_hk, self._vision_hotkey_edit)

        layout.addWidget(self._actions_group)

        # Prompts settings group
        self._prompts_settings_group = QGroupBox(_("settings.prompts.group"))
        prompts_form = QFormLayout(self._prompts_settings_group)
        self._slash_cb = QCheckBox(_("settings.prompts.slash_commands"))
        self._slash_cb.setChecked(self._config.prompts_slash_commands_enabled)
        prompts_form.addRow("", self._slash_cb)
        layout.addWidget(self._prompts_settings_group)

        # Apply / Reset buttons + status
        btn_row = QHBoxLayout()
        self._btn_apply_hotkey = QPushButton(_("settings.hotkey.btn_apply"))
        self._btn_apply_hotkey.setDefault(False)
        self._btn_apply_hotkey.clicked.connect(self._apply_hotkey)

        self._btn_reset_hotkey = QPushButton(_("settings.hotkey.btn_reset"))
        self._btn_reset_hotkey.setToolTip(
            _("settings.hotkey.btn_reset_tooltip", hotkey=_DEFAULT_HOTKEY)
        )
        self._btn_reset_hotkey.clicked.connect(self._reset_hotkey)

        self._hotkey_status = QLabel("")
        self._hotkey_status.setStyleSheet("font-size: 11px;")

        btn_row.addWidget(self._btn_apply_hotkey)
        btn_row.addWidget(self._btn_reset_hotkey)
        btn_row.addWidget(self._hotkey_status, stretch=1)
        layout.addLayout(btn_row)

        layout.addStretch()
        return widget

    @staticmethod
    def _hotkey_to_qt(hotkey: str) -> str:
        """Convert 'ctrl+shift+space' to Qt key sequence string."""
        return hotkey.replace("ctrl", "Ctrl").replace("shift", "Shift").replace("alt", "Alt")

    @staticmethod
    def _normalize_keyseq(ks: str) -> str:
        """Convert Qt key sequence 'Ctrl+Shift+Space' → 'ctrl+shift+space'."""
        return ks.lower()

    def _apply_hotkey(self) -> None:
        """Register the new hotkey immediately without closing the dialog."""
        ks = self._hotkey_edit.keySequence().toString()
        if not ks:
            self._hotkey_status.setText(_("settings.hotkey.status_empty"))
            return

        new_hotkey = self._normalize_keyseq(ks)
        if new_hotkey == self._config.hotkey:
            self._hotkey_status.setText(_("settings.hotkey.status_already_active"))
            return

        self._btn_apply_hotkey.setEnabled(False)
        self._hotkey_status.setText(_("settings.hotkey.status_registering"))

        success = False
        if self._on_hotkey_change:
            success = bool(self._on_hotkey_change(new_hotkey))
        else:
            success = True  # no callback (test mode)

        if success:
            self._config.hotkey = new_hotkey
            self._config.save()
            self._current_hotkey_lbl.setText(
                _("settings.hotkey.current_active", hotkey=new_hotkey.upper())
            )
            self._hotkey_status.setText(
                _("settings.hotkey.status_active", hotkey=new_hotkey.upper())
            )
        else:
            self._hotkey_status.setText(_("settings.hotkey.status_failed"))

        self._btn_apply_hotkey.setEnabled(True)

    def _reset_hotkey(self) -> None:
        """Reset hotkey editor to the default value."""
        self._hotkey_edit.setKeySequence(self._hotkey_to_qt(_DEFAULT_HOTKEY))
        self._hotkey_status.setText(_("settings.hotkey.status_hint", hotkey=_DEFAULT_HOTKEY))

    # ------------------------------------------------------------------ #
    # Updates tab
    # ------------------------------------------------------------------ #

    def _build_update_tab(self) -> QWidget:
        widget = QWidget()
        form = QFormLayout(widget)

        self._repo_edit = QLineEdit()
        self._repo_edit.setPlaceholderText(_("settings.update.repo_placeholder"))
        self._repo_edit.setText(self._config.github_repo)
        self._lbl_repo = QLabel(_("settings.update.repo_label"))
        form.addRow(self._lbl_repo, self._repo_edit)

        self._btn_check_update = QPushButton(_("settings.update.btn_check"))
        self._btn_check_update.clicked.connect(self._check_update)
        self._update_status = QLabel(_("settings.update.status_initial"))
        form.addRow("", self._btn_check_update)
        form.addRow("", self._update_status)

        return widget

    def _check_update(self) -> None:
        if self._on_check_update:
            self._update_status.setText(_("settings.update.checking"))
            self._on_check_update()

    # ------------------------------------------------------------------ #
    # About tab
    # ------------------------------------------------------------------ #

    def _build_about_tab(self) -> QWidget:
        from src import __version__

        widget = QWidget()
        layout = QVBoxLayout(widget)

        self._about_info = QLabel(_("settings.about.description", version=__version__))
        self._about_info.setOpenExternalLinks(True)
        layout.addWidget(self._about_info)

        # License section
        self._lic_group = QGroupBox(_("settings.about.license_group"))
        lic_form = QFormLayout(self._lic_group)
        lic_form.setRowWrapPolicy(QFormLayout.RowWrapPolicy.WrapLongRows)

        # Server URL
        self._server_url_edit = QLineEdit()
        self._server_url_edit.setPlaceholderText(_("settings.about.server_placeholder"))
        self._server_url_edit.setText(self._config.license_server_url)
        self._lbl_server = QLabel(_("settings.about.server_label"))
        lic_form.addRow(self._lbl_server, self._server_url_edit)

        # Token input + action buttons
        self._license_edit = QLineEdit()
        self._license_edit.setPlaceholderText("Paste activation token…")

        self._btn_activate = QPushButton(_("settings.about.btn_activate"))
        self._btn_activate.clicked.connect(self._activate_license)

        self._btn_revoke = QPushButton(_("settings.about.btn_revoke"))
        self._btn_revoke.setToolTip(_("settings.about.btn_revoke_tooltip"))
        self._btn_revoke.clicked.connect(self._revoke_license)

        key_row = QHBoxLayout()
        key_row.addWidget(self._license_edit, stretch=1)
        key_row.addWidget(self._btn_activate)
        key_row.addWidget(self._btn_revoke)
        key_widget = QWidget()
        key_widget.setLayout(key_row)
        self._lbl_lic_key = QLabel(_("settings.about.key_label"))
        lic_form.addRow(self._lbl_lic_key, key_widget)

        # Status label
        self._lic_status = QLabel("")
        self._lic_status.setWordWrap(True)
        lic_form.addRow("", self._lic_status)

        # Show current status immediately
        self._refresh_license_status()

        layout.addWidget(self._lic_group)
        layout.addStretch()

        return widget

    # ------------------------------------------------------------------ #
    # Language tab
    # ------------------------------------------------------------------ #

    def _build_language_tab(self) -> QWidget:
        widget = QWidget()
        layout = QVBoxLayout(widget)
        layout.setSpacing(12)

        form = QFormLayout()

        self._lang_combo = QComboBox()
        self._lang_codes = list(SUPPORTED_LANGUAGES.keys())
        for code, display in SUPPORTED_LANGUAGES.items():
            self._lang_combo.addItem(display, userData=code)

        # Select current language
        current_lang = get_language()
        try:
            idx = self._lang_codes.index(current_lang)
        except ValueError:
            idx = 0
        self._lang_combo.setCurrentIndex(idx)

        self._lbl_lang = QLabel(_("settings.language.label"))
        form.addRow(self._lbl_lang, self._lang_combo)
        layout.addLayout(form)

        self._lang_note = QLabel(_("settings.language.note"))
        self._lang_note.setStyleSheet("color: #888; font-size: 11px;")
        layout.addWidget(self._lang_note)

        btn_row = QHBoxLayout()
        self._btn_apply_lang = QPushButton(_("settings.language.btn_apply"))
        self._btn_apply_lang.clicked.connect(self._apply_language)
        btn_row.addWidget(self._btn_apply_lang)
        btn_row.addStretch()
        layout.addLayout(btn_row)

        layout.addStretch()
        return widget

    # ------------------------------------------------------------------ #
    # Appearance tab
    # ------------------------------------------------------------------ #

    def _build_appearance_tab(self) -> QWidget:
        widget = QWidget()
        layout = QVBoxLayout(widget)
        layout.setSpacing(12)

        self._lbl_theme = QLabel(_("settings.appearance.theme_label"))
        layout.addWidget(self._lbl_theme)

        self._radio_light = QRadioButton(_("settings.appearance.light"))
        self._radio_dark = QRadioButton(_("settings.appearance.dark"))
        self._radio_system = QRadioButton(_("settings.appearance.system"))

        current_theme = self._config.theme
        if current_theme == "light":
            self._radio_light.setChecked(True)
        elif current_theme == "dark":
            self._radio_dark.setChecked(True)
        else:
            self._radio_system.setChecked(True)

        layout.addWidget(self._radio_light)
        layout.addWidget(self._radio_dark)
        layout.addWidget(self._radio_system)

        btn_row = QHBoxLayout()
        self._btn_apply_theme = QPushButton(_("settings.appearance.btn_apply"))
        self._btn_apply_theme.clicked.connect(self._apply_theme_setting)
        btn_row.addWidget(self._btn_apply_theme)
        btn_row.addStretch()
        layout.addLayout(btn_row)

        self._lbl_theme_note = QLabel(_("settings.appearance.note"))
        self._lbl_theme_note.setWordWrap(True)
        layout.addWidget(self._lbl_theme_note)

        layout.addStretch()
        return widget

    # ------------------------------------------------------------------ #
    # Vision tab
    # ------------------------------------------------------------------ #

    def _build_vision_tab(self) -> QWidget:
        from ..ai.vision_adapter import VISION_PROVIDERS

        widget = QWidget()
        layout = QVBoxLayout(widget)
        layout.setSpacing(10)

        # Enable toggle
        self._vision_enabled_cb = QCheckBox(_("settings.vision.enabled"))
        self._vision_enabled_cb.setChecked(self._config.vision_enabled)
        layout.addWidget(self._vision_enabled_cb)

        form = QFormLayout()

        # Vision provider selector (only vision-capable providers)
        self._vision_provider_combo = QComboBox()
        for name in VISION_PROVIDERS:
            self._vision_provider_combo.addItem(name)
        if self._config.vision_provider in VISION_PROVIDERS:
            self._vision_provider_combo.setCurrentText(self._config.vision_provider)
        self._vision_provider_combo.currentTextChanged.connect(self._on_vision_provider_changed)
        self._lbl_vision_provider = QLabel(_("settings.vision.provider"))
        form.addRow(self._lbl_vision_provider, self._vision_provider_combo)

        # Vision model selector
        self._vision_model_combo = QComboBox()
        self._refresh_vision_models(self._config.vision_provider)
        self._lbl_vision_model = QLabel(_("settings.vision.model"))
        form.addRow(self._lbl_vision_model, self._vision_model_combo)

        # Key reuse note
        self._vision_key_note = QLabel(_("settings.vision.key_note"))
        self._vision_key_note.setWordWrap(True)
        self._vision_key_note.setStyleSheet("color: #888; font-size: 11px;")
        form.addRow("", self._vision_key_note)

        layout.addLayout(form)
        layout.addStretch()
        return widget

    def _on_vision_provider_changed(self, provider: str) -> None:
        self._refresh_vision_models(provider)

    def _refresh_vision_models(self, provider: str) -> None:
        from ..ai.vision_adapter import VisionAdapter

        self._vision_model_combo.clear()
        models = VisionAdapter.get_models_for_provider(provider)
        for m in models:
            self._vision_model_combo.addItem(m)
        if self._config.vision_model in models:
            self._vision_model_combo.setCurrentText(self._config.vision_model)
        elif models:
            self._vision_model_combo.setCurrentIndex(0)

    def _apply_theme_setting(self) -> None:
        """Apply the selected theme immediately."""
        if self._radio_light.isChecked():
            theme = "light"
        elif self._radio_dark.isChecked():
            theme = "dark"
        else:
            theme = "system"

        try:
            from ..ui.theme_manager import ThemeManager

            ThemeManager.get_instance().set_theme(theme)
        except RuntimeError:
            # ThemeManager not initialized (e.g. tests) — just save to config
            self._config.theme = theme
            self._config.save()
            log.warning("ThemeManager not available — saved theme to config only")

        if self._on_theme_change:
            self._on_theme_change(theme)

        log.info("Theme setting changed to '%s'", theme)

    def _apply_language(self) -> None:
        """Switch the UI language immediately."""
        idx = self._lang_combo.currentIndex()
        if idx < 0:
            return
        lang_code = self._lang_combo.itemData(idx)
        if not lang_code:
            return

        # Persist to config
        self._config.locale = lang_code
        self._config.save()

        # Switch translator
        setup_i18n(lang_code)

        # Retranslate this dialog
        self.retranslate_ui()

        # Notify app to retranslate other open windows
        if self._on_language_change:
            self._on_language_change(lang_code)

        log.info("Language changed to '%s'", lang_code)

    # ------------------------------------------------------------------ #
    # License helpers
    # ------------------------------------------------------------------ #

    def _make_license_manager(self):
        """Build LicenseManagerV2."""
        from ..license.license_manager_v2 import LicenseManagerV2
        return LicenseManagerV2()

    def _activate_license(self) -> None:
        key = self._license_edit.text().strip()
        if not key:
            self._lic_status.setText(_("settings.about.lic_no_key_error"))
            return
        self._btn_activate.setEnabled(False)
        self._lic_status.setText(_("settings.about.lic_activating"))
        threading.Thread(target=self._do_activate, args=(key,), daemon=True).start()

    def _do_activate(self, key: str) -> None:
        """Run activation in background thread."""
        mgr = self._make_license_manager()
        result = mgr.activate(key)
        if result.success:
            hours = (result.time_remaining or 0) // 3600
            msg = _("settings.about.lic_activated", exp=f" ({hours}h remaining)")
        else:
            msg = _("settings.about.lic_invalid_key", error=result.error or "Activation failed")
        QTimer.singleShot(0, lambda: self._on_activate_done(msg))

    def _on_activate_done(self, msg: str) -> None:
        self._lic_status.setText(msg)
        self._btn_activate.setEnabled(True)

    def _revoke_license(self) -> None:
        from ..license.license_manager_v2 import LicenseManagerV2
        LicenseManagerV2().clear_session()
        self._license_edit.clear()
        self._lic_status.setText(_("settings.about.lic_revoked"))

    def _refresh_license_status(self) -> None:
        """Display current license status."""
        from ..license.license_manager_v2 import LicenseManagerV2, LicenseStatus
        mgr = LicenseManagerV2()
        status = mgr.get_status()
        if status == LicenseStatus.NO_SESSION:
            self._lic_status.setText(_("settings.about.lic_status_no_key"))
        elif status in (LicenseStatus.VALID, LicenseStatus.GRACE_PERIOD):
            remaining = mgr.get_time_remaining()
            hours = remaining // 3600
            self._lic_status.setText(_("settings.about.lic_status_active", exp=f" ({hours}h left)"))
        else:
            self._lic_status.setText(_("settings.about.lic_status_invalid"))

    # ------------------------------------------------------------------ #
    # Accept / save
    # ------------------------------------------------------------------ #

    def _on_accept(self) -> None:
        # Save provider / model
        self._config.provider = self._provider_combo.currentText()
        self._config.model = self._model_combo.currentText()

        # Ollama base URL
        if self._config.provider == "ollama":
            url = self._ollama_url_edit.text().strip()
            if url:
                self._config.ollama_base_url = url

        # Behaviour
        self._config.stream_enabled = self._stream_cb.isChecked()
        self._config.cache_enabled = self._cache_cb.isChecked()
        self._config.cache_ttl_seconds = self._cache_ttl_spin.value() * 86400

        # Hotkey — apply only if it changed AND "Apply" wasn't already clicked
        ks = self._hotkey_edit.keySequence().toString()
        if ks:
            new_hotkey = self._normalize_keyseq(ks)
            if new_hotkey != self._config.hotkey:
                if self._on_hotkey_change:
                    ok = bool(self._on_hotkey_change(new_hotkey))
                    if ok:
                        self._config.hotkey = new_hotkey
                else:
                    self._config.hotkey = new_hotkey

        # Prompts hotkey
        pks = self._prompts_hotkey_edit.keySequence().toString()
        if pks:
            new_prompts_hk = self._normalize_keyseq(pks)
            if new_prompts_hk != self._config.prompts_hotkey:
                if self._on_prompts_hotkey_change:
                    ok_p = bool(self._on_prompts_hotkey_change(new_prompts_hk))
                    if ok_p:
                        self._config.prompts_hotkey = new_prompts_hk
                else:
                    self._config.prompts_hotkey = new_prompts_hk

        # Vision hotkey
        vks = self._vision_hotkey_edit.keySequence().toString()
        if vks:
            new_vision_hk = self._normalize_keyseq(vks)
            if new_vision_hk != self._config.vision_hotkey:
                if self._on_vision_hotkey_change:
                    ok_v = bool(self._on_vision_hotkey_change(new_vision_hk))
                    if ok_v:
                        self._config.vision_hotkey = new_vision_hk
                else:
                    self._config.vision_hotkey = new_vision_hk

        # Vision provider / model / enabled
        self._config.vision_enabled = self._vision_enabled_cb.isChecked()
        self._config.vision_provider = self._vision_provider_combo.currentText()
        self._config.vision_model = self._vision_model_combo.currentText()

        # Prompts slash commands
        self._config.prompts_slash_commands_enabled = self._slash_cb.isChecked()

        # GitHub repo
        self._config.github_repo = self._repo_edit.text().strip()

        self._config.save()
        self.accept()
