"""Settings screen — API keys, provider, transcriber, preferences."""

from __future__ import annotations

import os
import sys
from pathlib import Path

import flet as ft
from ai_interview.i18n import t

from ai_interview.config import save_config

TEAL = "#4ec9b0"
MUTED = "#8b949e"
SZ = 13


def _section(title: str) -> ft.Text:
    return ft.Text(title, size=12, weight=ft.FontWeight.W_600, color=MUTED)


_VISION_MODELS = {"llava", "llama3.2-vision", "moondream", "qwen2-vl", "minicpm-v", "bakllava"}

def _is_vision_model(model: str) -> bool:
    """Check if a model supports vision/images."""
    base = model.split(":")[0].lower()
    return base in _VISION_MODELS or "vision" in base


_MODEL_SPEED_KEY = {
    "qwen2.5-coder:3b": "speed_fastest",
    "qwen2.5-coder:7b": "speed_fast",
    "qwen2.5-coder:14b": "speed_balanced",
    "qwen2.5-coder:32b": "speed_slow",
    "deepseek-coder-v2:16b": "speed_balanced",
    "codellama:7b": "speed_fast",
    "codellama:13b": "speed_balanced",
    "llama3.2-vision:11b": "speed_balanced",
    "moondream:1.8b": "speed_fast",
}


def _model_label(model: str) -> str:
    """Return a translated description for a model (speed + vision tag)."""
    key = _MODEL_SPEED_KEY.get(model)
    if not key:
        import re
        m = re.search(r"(\d+)b", model.lower())
        if m:
            params = int(m.group(1))
            if params <= 3:
                key = "speed_fastest"
            elif params <= 7:
                key = "speed_fast"
            elif params <= 16:
                key = "speed_balanced"
            else:
                key = "speed_slow"
    parts = []
    if key:
        parts.append(t(key))
    if _is_vision_model(model):
        parts.append(t("vision_tag"))
    return ", ".join(parts)


class SettingsScreen(ft.Column):
    """API keys, model, transcriber, and other preferences."""

    def __init__(self, page: ft.Page, saved_config: dict):
        super().__init__(
            expand=True,
            scroll=ft.ScrollMode.AUTO,
            spacing=20,
            horizontal_alignment=ft.CrossAxisAlignment.STRETCH,
        )
        self._page = page
        self._config = saved_config

        # --- API Keys ---
        self._anthropic_key = ft.TextField(
            label=t("anthropic_api_key"), password=True, can_reveal_password=True,
            hint_text="sk-ant-...", value=saved_config.get("anthropic_api_key", ""),
            border_radius=8, text_size=SZ, expand=True, on_blur=lambda _: self._autosave(),
        )
        self._google_key = ft.TextField(
            label=t("google_api_key"), password=True, can_reveal_password=True,
            hint_text=t("hint_google"),
            value=saved_config.get("google_api_key", ""),
            border_radius=8, text_size=SZ, expand=True, on_blur=lambda _: self._autosave(),
        )
        self._deepgram_key = ft.TextField(
            label=t("deepgram_api_key"), password=True, can_reveal_password=True,
            hint_text=t("hint_deepgram"),
            value=saved_config.get("deepgram_api_key", ""),
            border_radius=8, text_size=SZ, expand=True, on_blur=lambda _: self._autosave(),
        )

        def _help_toggle(help_text: ft.Text):
            def _toggle(e):
                help_text.visible = not help_text.visible
                e.control.icon = ft.Icons.EXPAND_LESS if help_text.visible else ft.Icons.HELP_OUTLINE
                self._page.update()
            return _toggle

        def _help_section(text: str, url: str, visible: bool = False) -> ft.Column:
            col = ft.Column([
                ft.Text(text, size=11, color=MUTED),
                ft.TextButton(url, icon=ft.Icons.OPEN_IN_NEW,
                              style=ft.ButtonStyle(text_style=ft.TextStyle(size=11)),
                              on_click=lambda _, u=url: __import__('webbrowser').open(u)),
            ], spacing=2, visible=visible)
            return col

        self._claude_help = _help_section(t("help_claude"), "https://console.anthropic.com/settings/keys")
        self._gemini_help = _help_section(t("help_gemini"), "https://aistudio.google.com/apikey")
        self._deepgram_help = _help_section(t("help_deepgram"), "https://console.deepgram.com")

        claude_help_btn = ft.IconButton(icon=ft.Icons.HELP_OUTLINE, icon_size=16, tooltip=t("how_to_get_key"), on_click=_help_toggle(self._claude_help))
        gemini_help_btn = ft.IconButton(icon=ft.Icons.HELP_OUTLINE, icon_size=16, tooltip=t("how_to_get_key"), on_click=_help_toggle(self._gemini_help))
        deepgram_help_btn = ft.IconButton(icon=ft.Icons.HELP_OUTLINE, icon_size=16, tooltip=t("how_to_get_key"), on_click=_help_toggle(self._deepgram_help))

        api_card = ft.Card(
            content=ft.Container(
                content=ft.Column([
                    _section(t("api_keys")),
                    ft.Row([self._anthropic_key, claude_help_btn], spacing=4),
                    self._claude_help,
                    ft.Row([self._google_key, gemini_help_btn, self._deepgram_key, deepgram_help_btn], spacing=4),
                    self._gemini_help,
                    self._deepgram_help,
                ], spacing=10),
                padding=20,
            ),
        )

        # --- AI Provider ---
        self._provider_dropdown = ft.Dropdown(
            label=t("default_provider"),
            value=saved_config.get("preferred_provider", "local") or "local",
            options=[
                ft.dropdown.Option("local", t("ollama_free")),
                ft.dropdown.Option("claude", "Claude"),
                ft.dropdown.Option("gemini", t("gemini_free")),
            ],
            border_radius=8, text_size=SZ, expand=True,
            on_select=self._on_provider_change,
        )
        self._claude_model = ft.Dropdown(
            label=t("claude_model"),
            value=saved_config.get("claude_model", "sonnet"),
            options=[ft.dropdown.Option(m) for m in ["sonnet", "opus", "haiku"]],
            border_radius=8, text_size=SZ, expand=True,
            on_select=lambda _: self._autosave(),
        )
        self._gemini_model = ft.Dropdown(
            label=t("gemini_model"),
            value=saved_config.get("gemini_model", "flash"),
            options=[ft.dropdown.Option(m) for m in ["flash", "pro"]],
            border_radius=8, text_size=SZ, expand=True,
            on_select=lambda _: self._autosave(),
        )
        self._all_local_models = [
            "qwen2.5-coder:3b", "qwen2.5-coder:7b", "qwen2.5-coder:14b", "qwen2.5-coder:32b",
            "deepseek-coder-v2:16b", "codellama:7b", "codellama:13b",
            "llama3.2-vision:11b", "moondream:1.8b",
        ]
        self._local_model = ft.Dropdown(
            label=t("local_model"),
            value=saved_config.get("local_model", "qwen2.5-coder:3b"),
            options=[ft.dropdown.Option(key=m, text=f"{m} ({s})") if (s := _model_label(m)) else ft.dropdown.Option(key=m, text=m) for m in self._all_local_models],
            border_radius=8, text_size=SZ, expand=True,
            on_select=self._on_local_model_change,
        )
        self._ollama_host = ft.TextField(
            label=t("ollama_host"),
            hint_text=t("hint_ollama_host"),
            value=saved_config.get("ollama_host", "http://localhost:11434"),
            border_radius=8, text_size=SZ, expand=True,
            on_blur=self._on_ollama_host_change,
        )
        self._download_btn = ft.OutlinedButton(
            t("download"), icon=ft.Icons.DOWNLOAD,
            on_click=self._download_model,
        )
        self._download_status = ft.Text("", size=11, color=MUTED)
        self._ollama_status = ft.Text("", size=11, color=MUTED)
        self._model_row = ft.Row([self._provider_dropdown, self._claude_model, self._gemini_model, self._local_model, self._download_btn], spacing=12)
        self._ollama_host_row = ft.Row([self._ollama_host], spacing=12)
        self._installed_models: set[str] = set()
        self._refresh_installed_models()
        self._check_active_downloads()
        self._update_model_visibility()
        self._check_ollama_connection()

        provider_card = ft.Card(
            content=ft.Container(
                content=ft.Column([
                    _section(t("ai_provider")),
                    self._model_row,
                    self._ollama_host_row,
                    self._ollama_status,
                    self._download_status,
                ], spacing=10),
                padding=20,
            ),
        )

        # --- Transcription ---
        self._all_trans_langs = ["en", "es", "pt", "fr", "de", "it", "ja", "ko", "zh", "auto"]
        self._transcriber_dropdown = ft.Dropdown(
            label=t("default_transcriber"),
            value=saved_config.get("preferred_transcriber", "whisper") or "whisper",
            options=[
                ft.dropdown.Option("whisper", t("whisper_free")),
                ft.dropdown.Option("deepgram", "Deepgram"),
            ],
            border_radius=8, text_size=SZ, expand=True,
            on_select=self._on_transcriber_change,
        )
        self._trans_lang_dropdown = ft.Dropdown(
            label=t("transcription_language"),
            value=saved_config.get("transcription_language", "auto"),
            options=[ft.dropdown.Option(lang) for lang in self._all_trans_langs],
            border_radius=8, text_size=SZ, expand=True,
            on_select=lambda _: self._autosave(),
        )
        self._whisper_host = ft.TextField(
            label=t("whisper_host"),
            hint_text=t("hint_whisper_host"),
            value=saved_config.get("whisper_host", "") or "http://localhost:8000",
            border_radius=8, text_size=SZ, expand=True,
            on_blur=self._on_whisper_host_change,
        )
        self._whisper_host_row = ft.Row([self._whisper_host], spacing=12)
        self._whisper_status = ft.Text("", size=11, color=MUTED)
        self._update_trans_lang_options()
        self._update_whisper_host_visibility()
        self._check_whisper_connection()

        transcription_card = ft.Card(
            content=ft.Container(
                content=ft.Column([
                    _section(t("transcription")),
                    ft.Row([self._transcriber_dropdown, self._trans_lang_dropdown], spacing=12),
                    self._whisper_host_row,
                    self._whisper_status,
                ], spacing=10),
                padding=20,
            ),
        )

        # --- CV / Resume ---
        self._cv_path = ft.TextField(
            label=t("cv_pdf"),
            value=saved_config.get("cv_path", ""),
            hint_text=t("hint_cv"),
            border_radius=8, text_size=SZ, expand=True,
        )
        self._cv_picker = ft.FilePicker()
        page.services.append(self._cv_picker)
        cv_browse = ft.OutlinedButton(t("browse"), icon=ft.Icons.UPLOAD_FILE, on_click=self._browse_cv)
        cv_clear = ft.OutlinedButton(t("clear"), icon=ft.Icons.CLEAR, on_click=self._clear_cv)

        cv_card = ft.Card(
            content=ft.Container(
                content=ft.Column([
                    _section(t("cv_resume")),
                    ft.Row([self._cv_path, cv_browse, cv_clear], spacing=12),
                ], spacing=10),
                padding=20,
            ),
        )

        # --- General ---
        self._theme_dropdown = ft.Dropdown(
            label=t("theme"),
            value=saved_config.get("gui_theme", "system"),
            options=[ft.dropdown.Option(t) for t in ["system", "dark", "light"]],
            border_radius=8, text_size=SZ, expand=True,
            on_select=self._on_theme_change,
        )
        self._port_field = ft.TextField(
            label=t("viewer_port"),
            value=str(saved_config.get("port", 7890)),
            border_radius=8, text_size=SZ, expand=True,
            keyboard_type=ft.KeyboardType.NUMBER,
            on_blur=lambda _: self._autosave(),
        )
        self._menubar_check = ft.Checkbox(
            label=t("show_menubar"),
            value=saved_config.get("show_menubar_icon", True),
            on_change=lambda _: self._autosave(),
        )
        self._menubar_icon = ft.Dropdown(
            label=t("menubar_icon"),
            value=saved_config.get("menubar_icon", "●"),
            options=[ft.dropdown.Option(k) for k in ["AI", "◆", "⚡", "●", "▶", "☰"]],
            border_radius=8, text_size=SZ, expand=True,
            on_select=lambda _: self._autosave(),
        )

        self._lang_dropdown = ft.Dropdown(
            label=t("interface_language"),
            value=saved_config.get("gui_language", "en"),
            options=[
                ft.dropdown.Option("en", "English"),
                ft.dropdown.Option("es", "Español"),
                ft.dropdown.Option("pt", "Português"),
            ],
            border_radius=8, text_size=SZ, expand=True,
            on_select=self._on_lang_change,
        )
        self._lang_note = ft.Text("", size=10, color=TEAL, visible=False)

        # Check if LaunchAgent is installed
        self._plist_path = Path.home() / "Library" / "LaunchAgents" / "com.ai-interview-assistant.menubar.plist"
        self._autostart_check = ft.Checkbox(
            label=t("autostart_login"),
            value=self._plist_path.exists(),
            on_change=lambda _: self._autosave(),
        )

        general_card = ft.Card(
            content=ft.Container(
                content=ft.Column([
                    _section(t("general")),
                    ft.Row([self._theme_dropdown, self._lang_dropdown, self._port_field], spacing=12),
                    self._lang_note,
                    ft.Row([self._menubar_check, self._menubar_icon], spacing=12),
                    self._autostart_check,
                ], spacing=10),
                padding=20,
            ),
        )

        self._save_status = ft.Text("", size=11, color=MUTED)

        self.controls = [
            ft.Container(
                content=ft.Column([
                    ft.Text(t("settings"), size=22, weight=ft.FontWeight.W_600),
                    api_card,
                    provider_card,
                    transcription_card,
                    cv_card,
                    general_card,
                    self._save_status,
                ], spacing=14, horizontal_alignment=ft.CrossAxisAlignment.STRETCH),
                padding=30,
            ),
        ]

    def _update_model_visibility(self):
        prov = self._provider_dropdown.value
        self._claude_model.visible = prov == "claude"
        self._gemini_model.visible = prov == "gemini"
        self._local_model.visible = prov == "local"
        self._ollama_host_row.visible = prov == "local"
        self._ollama_status.visible = prov == "local"
        self._update_download_btn()

    def _check_active_downloads(self):
        """If an ollama pull is running, show status and start polling."""
        try:
            import subprocess as _sp
            r = _sp.run(["pgrep", "-af", "ollama pull"], capture_output=True, text=True, timeout=3)
            if r.returncode == 0 and r.stdout.strip():
                for line in r.stdout.strip().split("\n"):
                    parts = line.split("ollama pull ")
                    if len(parts) > 1:
                        downloading = parts[1].strip()
                        self._downloading_model = downloading
                        self._download_status.value = f"{downloading} — downloading…"
                        self._download_status.color = TEAL
                        self._download_status.visible = True
                        self._download_btn.visible = False
                        self._download_btn.disabled = True
                        # Start polling in background
                        self._page.run_thread(self._poll_download, downloading)
                        break
            else:
                self._downloading_model = None
        except Exception:
            self._downloading_model = None

    def _poll_download(self, model: str):
        """Poll until model appears in ollama list."""
        import subprocess as _sp
        import time
        for _ in range(360):
            time.sleep(3)
            try:
                r = _sp.run(["ollama", "list"], capture_output=True, text=True, timeout=5)
                if model in r.stdout:
                    self._downloading_model = None
                    self._refresh_installed_models()
                    self._update_download_btn()
                    try:
                        self._page.update()
                    except Exception:
                        pass
                    return
                # Check if pull process still alive
                r2 = _sp.run(["pgrep", "-f", f"ollama pull {model}"], capture_output=True, text=True)
                if r2.returncode != 0:
                    self._downloading_model = None
                    self._download_status.value = f"{model} — download failed"
                    self._download_status.color = "#f14c4c"
                    self._download_btn.disabled = False
                    try:
                        self._page.update()
                    except Exception:
                        pass
                    return
            except Exception:
                pass

    def _on_ollama_host_change(self, e):
        """When host changes, refresh model list from the new server."""
        self._autosave()
        self._check_ollama_connection()
        self._refresh_installed_models()
        # Update dropdown options with models from the (possibly remote) server
        remote_models = list(self._installed_models)
        if remote_models:
            combined = list(dict.fromkeys(self._all_local_models + remote_models))
            self._local_model.options = [ft.dropdown.Option(key=m, text=f"{m} ({s})") if (s := _model_label(m)) else ft.dropdown.Option(key=m, text=m) for m in combined]
        self._update_download_btn()
        self._page.update()

    @staticmethod
    def _check_url(url: str) -> bool:
        """Check if a URL is reachable using curl (bypasses Flet app sandbox)."""
        import subprocess
        try:
            r = subprocess.run(
                ["curl", "-sf", "--max-time", "3", url],
                capture_output=True, timeout=5,
            )
            return r.returncode == 0
        except Exception:
            return False

    def _check_ollama_connection(self):
        """Check if Ollama server is reachable and update status label."""
        host = (self._ollama_host.value or "http://localhost:11434").strip()
        url = f"{host.rstrip('/')}/api/tags"
        if self._check_url(url):
            self._ollama_status.value = f"● {t('connected')} — {host}"
            self._ollama_status.color = TEAL
        else:
            self._ollama_status.value = f"● {t('not_reachable')} — {host}"
            self._ollama_status.color = "#f14c4c"

    def _check_whisper_connection(self):
        """Check if Whisper server is reachable and update status label."""
        host = (self._whisper_host.value or "http://localhost:8000").strip()
        url = f"{host.rstrip('/')}/health"
        if self._check_url(url):
            self._whisper_status.value = f"● {t('connected')} — {host}"
            self._whisper_status.color = TEAL
        else:
            self._whisper_status.value = f"● {t('not_reachable')} — {host}"
            self._whisper_status.color = "#f14c4c"

    def _refresh_installed_models(self):
        """Check which Ollama models are available (local or remote)."""
        from ai_interview.ollama_utils import get_installed_models
        host = getattr(self, "_ollama_host", None)
        host_val = host.value if host else "http://localhost:11434"
        self._installed_models = set(get_installed_models(host=host_val))

    def _is_model_installed(self, model: str) -> bool:
        return model in self._installed_models

    def _is_remote_host(self) -> bool:
        host = (self._ollama_host.value or "").strip()
        return bool(host) and "localhost" not in host and "127.0.0.1" not in host and host != "http://localhost:11434"

    def _update_download_btn(self):
        model = self._local_model.value or ""
        # Don't overwrite if a download is in progress
        if getattr(self, "_downloading_model", None):
            self._download_btn.visible = False
            self._download_status.visible = self._local_model.visible
            return
        installed = self._is_model_installed(model)
        # Download works both locally (subprocess) and remotely (API)
        self._download_btn.visible = self._local_model.visible and not installed
        self._download_btn.disabled = False
        if installed:
            self._download_status.value = f"{model} — ready"
            self._download_status.color = TEAL
        else:
            self._download_status.value = f"{model} — not downloaded"
            self._download_status.color = "#d29922"
        self._download_status.visible = self._local_model.visible

    def _on_local_model_change(self, e):
        self._update_download_btn()
        self._autosave()
        self._page.update()

    def _download_model(self, e):
        model = self._local_model.value or "qwen2.5-coder:3b"
        self._download_btn.disabled = True
        self._download_status.value = f"Downloading {model}…"
        self._download_status.color = TEAL
        self._page.update()

        if self._is_remote_host():
            self._page.run_thread(lambda: self._do_download_remote(model))
        else:
            self._page.run_thread(lambda: self._do_download_local(model))

    def _do_download_remote(self, model: str):
        """Pull model on a remote Ollama server via curl (bypasses Flet sandbox)."""
        import json
        import subprocess as _sp

        host = (self._ollama_host.value or "").strip().rstrip("/")
        try:
            self._downloading_model = model
            self._download_status.value = f"{model} — downloading…"
            self._download_btn.visible = False
            try:
                self._page.update()
            except Exception:
                pass

            body = json.dumps({"name": model, "stream": True})
            proc = _sp.Popen(
                ["curl", "-sN", "--max-time", "1800",
                 "-X", "POST", f"{host}/api/pull",
                 "-H", "Content-Type: application/json",
                 "-d", body],
                stdout=_sp.PIPE, stderr=_sp.DEVNULL,
            )
            for line in proc.stdout:
                if not line.strip():
                    continue
                try:
                    chunk = json.loads(line)
                    status = chunk.get("status", "")
                    total = chunk.get("total", 0)
                    completed = chunk.get("completed", 0)
                    if total and completed:
                        pct = int(completed / total * 100)
                        self._download_status.value = f"{model} — {status} {pct}%"
                    elif status:
                        self._download_status.value = f"{model} — {status}"
                    try:
                        self._page.update()
                    except Exception:
                        pass
                except json.JSONDecodeError:
                    pass
            proc.wait()

            self._downloading_model = None
            self._refresh_installed_models()
            self._update_download_btn()
            try:
                self._page.update()
            except Exception:
                pass

        except Exception as exc:
            self._downloading_model = None
            self._download_status.value = f"Error: {exc}"
            self._download_status.color = "#f14c4c"
            self._download_btn.disabled = False
            try:
                self._page.update()
            except Exception:
                pass

    def _do_download_local(self, model: str):
        """Pull model on local Ollama via subprocess."""
        import subprocess as _sp
        import time

        try:
            from ai_interview.ollama_utils import is_ollama_installed, start_ollama
            if not is_ollama_installed():
                self._download_status.value = "Ollama not installed. Run: ai-interview version"
                self._download_status.color = "#f14c4c"
                self._download_btn.disabled = False
                try:
                    self._page.update()
                except Exception:
                    pass
                return

            start_ollama()

            # Launch pull as detached process (survives GUI close)
            _sp.Popen(["ollama", "pull", model], start_new_session=True,
                       stdout=_sp.DEVNULL, stderr=_sp.DEVNULL)

            self._downloading_model = model
            self._download_status.value = f"{model} — downloading…"
            self._download_btn.visible = False
            try:
                self._page.update()
            except Exception:
                pass

            # Poll until done
            self._poll_download(model)

        except Exception as exc:
            self._download_status.value = f"Error: {exc}"
            self._download_status.color = "#f14c4c"
            self._download_btn.disabled = False
            try:
                self._page.update()
            except Exception:
                pass

    def _update_trans_lang_options(self):
        """Hide 'auto' language when Deepgram is selected (requires explicit language)."""
        is_deepgram = self._transcriber_dropdown.value == "deepgram"
        langs = [l for l in self._all_trans_langs if l != "auto"] if is_deepgram else self._all_trans_langs
        self._trans_lang_dropdown.options = [ft.dropdown.Option(l) for l in langs]
        if is_deepgram and self._trans_lang_dropdown.value == "auto":
            self._trans_lang_dropdown.value = "en"

    def _on_transcriber_change(self, e):
        self._update_trans_lang_options()
        self._update_whisper_host_visibility()
        self._autosave()
        self._page.update()

    def _on_whisper_host_change(self, e):
        self._autosave()
        self._check_whisper_connection()
        self._page.update()

    def _update_whisper_host_visibility(self):
        """Show whisper host field only when Whisper is selected."""
        is_whisper = self._transcriber_dropdown.value == "whisper"
        self._whisper_host_row.visible = is_whisper
        self._whisper_status.visible = is_whisper

    def _on_provider_change(self, e):
        self._update_model_visibility()
        self._autosave()
        self._page.update()

    def _update_autostart(self):
        import subprocess as _sp
        import shutil

        if self._autostart_check.value:
            # Install LaunchAgent
            cli = shutil.which("ai-interview") or str(Path(sys.executable).parent / "ai-interview")
            plist_content = f"""\
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.ai-interview-assistant.menubar</string>
    <key>ProgramArguments</key>
    <array>
        <string>{cli}</string>
        <string>menubar</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <false/>
</dict>
</plist>"""
            self._plist_path.parent.mkdir(parents=True, exist_ok=True)
            self._plist_path.write_text(plist_content)
            _sp.run(["launchctl", "load", str(self._plist_path)], capture_output=True)
        else:
            # Remove LaunchAgent
            if self._plist_path.exists():
                _sp.run(["launchctl", "unload", str(self._plist_path)], capture_output=True)
                self._plist_path.unlink(missing_ok=True)

    async def _browse_cv(self, e):
        result = await self._cv_picker.pick_files(
            dialog_title=t("cv_pdf"),
            allowed_extensions=["pdf"],
            allow_multiple=False,
        )
        if result:
            files = result if isinstance(result, list) else getattr(result, "files", result)
            if files and len(files) > 0:
                self._cv_path.value = files[0].path
                self._autosave()
                self._page.update()

    def update_session_state(self, running: bool):
        """No-op — settings doesn't need session state."""
        pass

    def _clear_cv(self, e):
        self._cv_path.value = ""
        self._autosave()
        self._page.update()

    def _on_lang_change(self, e):
        from ai_interview.i18n import set_language
        set_language(e.control.value or "en")
        self._autosave()
        # Restart menubar to pick up new language
        self._restart_menubar()
        # Rebuild entire GUI with new language
        self._page.controls.clear()
        from ai_interview.flet_gui.app import build_page
        build_page(self._page, self._config)

    def _restart_menubar(self):
        import subprocess as _sp
        import signal as _sig
        menubar_pid_file = Path.home() / ".ai-interview-menubar.pid"
        if menubar_pid_file.exists():
            try:
                pid = int(menubar_pid_file.read_text().strip())
                os.kill(pid, _sig.SIGTERM)
            except Exception:
                pass
            menubar_pid_file.unlink(missing_ok=True)
            import time; time.sleep(0.3)
        if self._config.get("show_menubar_icon", True):
            _sp.Popen(
                [sys.executable, "-m", "ai_interview.menubar"],
                start_new_session=True,
                stdout=_sp.DEVNULL, stderr=_sp.DEVNULL, stdin=_sp.DEVNULL,
            )

    def _on_theme_change(self, e):
        theme_map = {"dark": ft.ThemeMode.DARK, "light": ft.ThemeMode.LIGHT, "system": ft.ThemeMode.SYSTEM}
        self._page.theme_mode = theme_map.get(e.control.value, ft.ThemeMode.SYSTEM)
        self._autosave()
        self._page.update()

    def _autosave(self):
        """Save all settings to config immediately."""
        self._config["anthropic_api_key"] = (self._anthropic_key.value or "").strip()
        self._config["google_api_key"] = (self._google_key.value or "").strip()
        self._config["deepgram_api_key"] = (self._deepgram_key.value or "").strip()
        self._config["gui_theme"] = self._theme_dropdown.value or "system"
        prov = self._provider_dropdown.value
        self._config["preferred_provider"] = "" if prov == "auto" else prov
        self._config["claude_model"] = self._claude_model.value or "sonnet"
        self._config["gemini_model"] = self._gemini_model.value or "flash"
        self._config["local_model"] = self._local_model.value or "qwen2.5-coder:3b"
        self._config["ollama_host"] = (self._ollama_host.value or "http://localhost:11434").strip()
        trans = self._transcriber_dropdown.value
        self._config["preferred_transcriber"] = "" if trans == "auto" else trans
        self._config["whisper_host"] = (self._whisper_host.value or "").strip()
        self._config["transcription_language"] = self._trans_lang_dropdown.value or "auto"
        self._config["port"] = int(self._port_field.value or 7890)
        self._config["cv_path"] = (self._cv_path.value or "").strip()
        self._config["gui_language"] = self._lang_dropdown.value or "en"
        self._config["show_menubar_icon"] = self._menubar_check.value
        self._config["menubar_icon"] = self._menubar_icon.value or "●"
        save_config(self._config)
        self._save_status.value = t("saved")
        try:
            self._page.update()
        except Exception:
            pass

    def _save(self, e):
        self._autosave()

        # Restart or stop menu bar icon immediately (restart picks up new icon)
        import subprocess as _sp
        import signal as _sig
        menubar_pid_file = Path.home() / ".ai-interview-menubar.pid"

        # Kill existing if running
        if menubar_pid_file.exists():
            try:
                pid = int(menubar_pid_file.read_text().strip())
                os.kill(pid, _sig.SIGTERM)
            except Exception:
                pass
            menubar_pid_file.unlink(missing_ok=True)
            import time; time.sleep(0.3)

        # Restart if enabled
        if self._menubar_check.value:
            _sp.Popen(
                [sys.executable, "-m", "ai_interview.menubar"],
                start_new_session=True,
                stdout=_sp.DEVNULL, stderr=_sp.DEVNULL, stdin=_sp.DEVNULL,
            )

        # Install or remove LaunchAgent for auto-start on login
        # Force off if menubar is disabled
        if not self._menubar_check.value:
            self._autostart_check.value = False
        self._update_autostart()

        self._save_status.value = "Settings saved!"
        self._save_status.color = TEAL
        self._page.update()
