"""Doctor CLI command for system health checking.

This module provides the doctor command for verifying all optional dependencies,
authentication status, and overall system health.
"""

from __future__ import annotations

import asyncio
import json
import shutil
import subprocess
import sys
from concurrent.futures import ThreadPoolExecutor
from typing import Any

import click

# Cross-platform installation hints
INSTALL_HINTS: dict[str, dict[str, str]] = {
    "libreoffice": {
        "darwin": "brew install --cask libreoffice",
        "linux": "sudo apt install libreoffice  # Ubuntu/Debian\nsudo dnf install libreoffice  # Fedora\nsudo pacman -S libreoffice-fresh  # Arch",
        "win32": "winget install LibreOffice.LibreOffice",
    },
    "ffmpeg": {
        "darwin": "brew install ffmpeg",
        "linux": "sudo apt install ffmpeg  # Ubuntu/Debian\nsudo dnf install ffmpeg  # Fedora\nsudo pacman -S ffmpeg  # Arch",
        "win32": "winget install FFmpeg.FFmpeg\n# Or: scoop install ffmpeg\n# Or: choco install ffmpeg",
    },
    "playwright": {
        "darwin": "uv run playwright install chromium",
        "linux": "uv run playwright install chromium && uv run playwright install-deps chromium",
        "win32": "uv run playwright install chromium",
    },
    "claude-cli": {
        "darwin": "curl -fsSL https://claude.ai/install.sh | bash",
        "linux": "curl -fsSL https://claude.ai/install.sh | bash",
        "win32": "irm https://claude.ai/install.ps1 | iex",
    },
    "copilot-cli": {
        "darwin": "curl -fsSL https://gh.io/copilot-install | bash",
        "linux": "curl -fsSL https://gh.io/copilot-install | bash",
        "win32": "winget install GitHub.Copilot",
    },
}


def get_install_hint(component: str, platform: str | None = None) -> str:
    """Get platform-specific installation hint for a component.

    Args:
        component: Component name (e.g., "libreoffice", "ffmpeg")
        platform: Target platform. If None, uses current sys.platform.

    Returns:
        Installation command(s) for the platform.
    """
    if platform is None:
        platform = sys.platform

    # Normalize platform
    if platform.startswith("linux"):
        platform = "linux"

    hints = INSTALL_HINTS.get(component, {})
    return hints.get(
        platform, hints.get("linux", f"Install {component} using your package manager")
    )


# Components that --fix can actually install.
# Maps result key -> True (auto-installable) or False (manual hint only).
# Keys not in this mapping (auth checks, vision-model, llm-api) are skipped.
FIXABLE_COMPONENTS: dict[str, bool] = {
    "playwright": True,  # auto-install via uv
    "libreoffice": False,  # manual hint
    "ffmpeg": False,  # manual hint
    "rapidocr": False,  # manual hint
    "claude-agent-sdk": False,  # manual hint
    "copilot-sdk": False,  # manual hint
}


def _has_errors(results: dict[str, dict[str, Any]]) -> bool:
    """Check whether any check result has error or missing status.

    This covers all categories: required deps, optional deps, LLM, auth, vision.

    Args:
        results: The full results dict from doctor checks.

    Returns:
        True if any result has 'error' or 'missing' status.
    """
    return any(info.get("status") in ("error", "missing") for info in results.values())


def _install_component(component: str, *, package_missing: bool = False) -> bool:
    """Attempt to install a missing component.

    Args:
        component: Component name to install.
        package_missing: For playwright, True means the package itself is
            missing (not just the browser). Triggers ``uv add playwright``
            before browser install.

    Returns:
        True if installation succeeded.
    """
    console = get_console()
    hint = get_install_hint(component)

    console.print(f"[yellow]Installing {component}...[/yellow]")

    # Only auto-install safe components
    safe_components = {"playwright"}

    if component not in safe_components:
        console.print("[yellow]Please install manually:[/yellow]")
        console.print(f"  {hint}")
        return False

    if component == "playwright":
        try:
            # If the package itself is missing, install it first
            if package_missing:
                pkg_result = subprocess.run(
                    ["uv", "add", "playwright"],
                    capture_output=True,
                    text=True,
                    timeout=300,
                )
                if pkg_result.returncode != 0:
                    console.print(
                        f"[red]\u2717[/red] Failed to install playwright package: {pkg_result.stderr}"
                    )
                    return False

            # Install browser
            result = subprocess.run(
                ["uv", "run", "playwright", "install", "chromium"],
                capture_output=True,
                text=True,
                timeout=300,
            )
            if result.returncode == 0:
                console.print(f"[green]\u2713[/green] {component} installed")
                return True
            else:
                console.print(f"[red]\u2717[/red] Failed: {result.stderr}")
                return False
        except Exception as e:
            console.print(f"[red]\u2717[/red] Error: {e}")
            return False

    return False


from markitai.cli import ui
from markitai.cli.console import get_console
from markitai.cli.i18n import t
from markitai.config import ConfigManager
from markitai.providers.auth import AuthManager, get_auth_resolution_hint

console = get_console()


def _check_chatgpt_auth() -> dict[str, str]:
    """Check ChatGPT authentication status.

    Returns:
        Result dict with status, message, install_hint
    """
    auth_manager = AuthManager()
    try:
        status = asyncio.run(auth_manager.check_auth("chatgpt"))
        if status.authenticated:
            return {
                "name": "ChatGPT Auth",
                "description": "ChatGPT OAuth authentication status",
                "status": "ok",
                "message": "Authenticated",
                "install_hint": "",
            }
        else:
            return {
                "name": "ChatGPT Auth",
                "description": "ChatGPT OAuth authentication status",
                "status": "error",
                "message": status.error or "Not authenticated",
                "install_hint": get_auth_resolution_hint("chatgpt"),
            }
    except Exception as e:
        return {
            "name": "ChatGPT Auth",
            "description": "ChatGPT OAuth authentication status",
            "status": "error",
            "message": f"Failed to check auth: {e}",
            "install_hint": get_auth_resolution_hint("chatgpt"),
        }


def _check_copilot_auth() -> dict[str, str]:
    """Check Copilot authentication status.

    Returns:
        Result dict with status, message, install_hint
    """
    auth_manager = AuthManager()
    try:
        status = asyncio.run(auth_manager.check_auth("copilot"))
        if status.authenticated:
            user_info = f" ({status.user})" if status.user else ""
            return {
                "name": "Copilot Auth",
                "description": "GitHub Copilot authentication status",
                "status": "ok",
                "message": f"Authenticated{user_info}",
                "install_hint": "",
            }
        else:
            return {
                "name": "Copilot Auth",
                "description": "GitHub Copilot authentication status",
                "status": "error",
                "message": status.error or "Not authenticated",
                "install_hint": get_auth_resolution_hint("copilot"),
            }
    except Exception as e:
        return {
            "name": "Copilot Auth",
            "description": "GitHub Copilot authentication status",
            "status": "error",
            "message": f"Failed to check auth: {e}",
            "install_hint": get_auth_resolution_hint("copilot"),
        }


def _check_claude_auth() -> dict[str, str]:
    """Check Claude Agent authentication status.

    Returns:
        Result dict with status, message, install_hint
    """
    auth_manager = AuthManager()
    try:
        status = asyncio.run(auth_manager.check_auth("claude-agent"))
        if status.authenticated:
            return {
                "name": "Claude Agent Auth",
                "description": "Claude Code CLI authentication status",
                "status": "ok",
                "message": "Authenticated (claude doctor passed)",
                "install_hint": "",
            }
        else:
            return {
                "name": "Claude Agent Auth",
                "description": "Claude Code CLI authentication status",
                "status": "error",
                "message": status.error or "Not authenticated",
                "install_hint": get_auth_resolution_hint("claude-agent"),
            }
    except Exception as e:
        return {
            "name": "Claude Agent Auth",
            "description": "Claude Code CLI authentication status",
            "status": "error",
            "message": f"Failed to check auth: {e}",
            "install_hint": get_auth_resolution_hint("claude-agent"),
        }


def _check_gemini_cli_auth_status() -> dict[str, str]:
    """Check Gemini CLI authentication status."""
    auth_manager = AuthManager()
    try:
        status = asyncio.run(auth_manager.check_auth("gemini-cli"))
        if status.authenticated:
            details = status.details or {}
            parts = [status.user or "gemini-cli"]
            project_id = details.get("project_id")
            source = details.get("source")
            if project_id:
                parts.append(f"project: {project_id}")
            if source:
                parts.append(f"source: {source}")
            return {
                "name": "Gemini CLI Auth",
                "description": "Gemini Code Assist authentication status",
                "status": "ok",
                "message": ", ".join(parts),
                "install_hint": "",
            }
        return {
            "name": "Gemini CLI Auth",
            "description": "Gemini Code Assist authentication status",
            "status": "error",
            "message": status.error or "Not authenticated",
            "install_hint": get_auth_resolution_hint("gemini-cli"),
        }
    except Exception as e:
        return {
            "name": "Gemini CLI Auth",
            "description": "Gemini Code Assist authentication status",
            "status": "error",
            "message": f"Failed to check auth: {e}",
            "install_hint": get_auth_resolution_hint("gemini-cli"),
        }


def _check_playwright() -> dict[str, Any]:
    """Check Playwright installation status.

    Returns:
        Result dict with name, description, status, message, install_hint.
    """
    from markitai.fetch_playwright import (
        is_playwright_available,
        is_playwright_browser_installed,
    )

    if is_playwright_available():
        if is_playwright_browser_installed(use_cache=False):
            return {
                "name": "Playwright",
                "description": "Browser automation for dynamic URLs",
                "status": "ok",
                "message": "Chromium installed",
                "install_hint": "",
            }
        else:
            return {
                "name": "Playwright",
                "description": "Browser automation for dynamic URLs",
                "status": "warning",
                "message": "Playwright installed but browser not found",
                "install_hint": get_install_hint("playwright"),
            }
    else:
        return {
            "name": "Playwright",
            "description": "Browser automation for dynamic URLs",
            "status": "missing",
            "message": "Playwright not installed",
            "install_hint": f"uv add playwright && {get_install_hint('playwright')}",
        }


def _check_libreoffice() -> dict[str, Any]:
    """Check LibreOffice installation status.

    Returns:
        Result dict with name, description, status, message, install_hint.
    """
    from markitai.utils.office import find_libreoffice

    soffice_path = find_libreoffice()

    if soffice_path:
        # LibreOffice found - report "installed" (path available in --json output)
        # Running soffice --version can hang on Windows in non-interactive mode
        return {
            "name": "LibreOffice",
            "description": "Office document conversion (doc, docx, xls, xlsx, ppt, pptx)",
            "status": "ok",
            "message": "installed",
            "path": soffice_path,
            "install_hint": "",
        }
    else:
        return {
            "name": "LibreOffice",
            "description": "Office document conversion (doc, docx, xls, xlsx, ppt, pptx)",
            "status": "missing",
            "message": "soffice/libreoffice command not found",
            "install_hint": get_install_hint("libreoffice"),
        }


def _check_ffmpeg() -> dict[str, Any]:
    """Check FFmpeg installation status.

    Returns:
        Result dict with name, description, status, message, install_hint.
    """
    ffmpeg_path = shutil.which("ffmpeg")
    if ffmpeg_path:
        try:
            proc = subprocess.run(
                [ffmpeg_path, "-version"],
                capture_output=True,
                text=True,
                timeout=10,
            )
            # Extract version: "ffmpeg version 8.0.1 ..." → "v8.0.1"
            version = "unknown"
            if proc.returncode == 0:
                parts = proc.stdout.strip().split()
                if len(parts) >= 3 and parts[0] == "ffmpeg":
                    version = f"v{parts[2]}"
                else:
                    version = parts[0] if parts else "unknown"
            return {
                "name": "FFmpeg",
                "description": "Audio/video file processing (mp3, mp4, wav, etc.)",
                "status": "ok",
                "message": version,
                "path": ffmpeg_path,
                "install_hint": "",
            }
        except Exception as e:
            return {
                "name": "FFmpeg",
                "description": "Audio/video file processing (mp3, mp4, wav, etc.)",
                "status": "error",
                "message": f"Found but failed to run: {e}",
                "install_hint": "Reinstall FFmpeg",
            }
    else:
        return {
            "name": "FFmpeg",
            "description": "Audio/video file processing (mp3, mp4, wav, etc.)",
            "status": "missing",
            "message": "ffmpeg command not found",
            "install_hint": get_install_hint("ffmpeg"),
        }


def _check_rapidocr(cfg: Any) -> dict[str, Any]:
    """Check RapidOCR installation status.

    Args:
        cfg: Configuration object with OCR language settings.

    Returns:
        Result dict with name, description, status, message, install_hint.
    """
    try:
        from importlib.metadata import version as get_version

        import rapidocr

        try:
            rapidocr_version = get_version("rapidocr")
        except Exception:
            rapidocr_version = getattr(rapidocr, "__version__", "unknown")

        # Get configured language
        configured_lang = cfg.ocr.lang if cfg.ocr else "en"
        # RapidOCR supported languages
        supported_langs = {
            "zh",
            "ch",
            "en",
            "ja",
            "japan",
            "ko",
            "korean",
            "ar",
            "arabic",
            "th",
            "latin",
        }
        lang_display = {
            "zh": "Chinese",
            "ch": "Chinese",
            "en": "English",
            "ja": "Japanese",
            "japan": "Japanese",
            "ko": "Korean",
            "korean": "Korean",
            "ar": "Arabic",
            "arabic": "Arabic",
            "th": "Thai",
            "latin": "Latin",
        }

        if configured_lang.lower() in supported_langs:
            return {
                "name": "RapidOCR",
                "description": "OCR for scanned documents (built-in models)",
                "status": "ok",
                "message": f"v{rapidocr_version}, lang: {configured_lang} ({lang_display.get(configured_lang.lower(), configured_lang)})",
                "install_hint": "",
            }
        else:
            return {
                "name": "RapidOCR",
                "description": "OCR for scanned documents (built-in models)",
                "status": "warning",
                "message": f"v{rapidocr_version}, unknown lang '{configured_lang}' (supported: {', '.join(sorted(supported_langs))})",
                "install_hint": "Set ocr.lang to one of: zh, en, ja, ko, ar, th, latin",
            }
    except ImportError:
        return {
            "name": "RapidOCR",
            "description": "OCR for scanned documents (built-in models)",
            "status": "missing",
            "message": "RapidOCR not installed",
            "install_hint": "uv add rapidocr (included in markitai dependencies)",
        }


def _is_active_model(model_config: Any) -> bool:
    """Return whether a model config is active for routing-dependent checks."""
    litellm_params = getattr(model_config, "litellm_params", None)
    weight = getattr(litellm_params, "weight", 1)
    return not isinstance(weight, int | float) or weight > 0


def _doctor_impl(as_json: bool, fix: bool = False) -> None:
    """Implementation of the doctor command.

    Args:
        as_json: Output results as JSON.
        fix: Attempt to install missing components.
    """
    from markitai.fetch_playwright import clear_browser_cache

    manager = ConfigManager()
    cfg = manager.load()

    results: dict[str, dict[str, Any]] = {}

    # Clear Playwright browser cache before parallel checks
    clear_browser_cache()

    # Run independent system tool checks in parallel
    with ThreadPoolExecutor(max_workers=4) as executor:
        future_playwright = executor.submit(_check_playwright)
        future_libreoffice = executor.submit(_check_libreoffice)
        future_ffmpeg = executor.submit(_check_ffmpeg)
        future_rapidocr = executor.submit(_check_rapidocr, cfg)

    # Collect results in deterministic order
    results["playwright"] = future_playwright.result()
    results["libreoffice"] = future_libreoffice.result()
    results["ffmpeg"] = future_ffmpeg.result()
    results["rapidocr"] = future_rapidocr.result()

    # 5. Check LLM API configuration (check model_list for configured models)
    configured_models = cfg.llm.model_list if cfg.llm.model_list else []
    if configured_models:
        # Count distinct API providers (exclude local providers like claude-agent, copilot)
        local_prefixes = ("claude-agent/", "copilot/")
        api_providers: set[str] = set()
        for m in configured_models:
            model_id = m.litellm_params.model
            if not any(model_id.startswith(p) for p in local_prefixes):
                provider = model_id.split("/")[0] if "/" in model_id else "openai"
                api_providers.add(provider)

        if api_providers:
            providers_str = ", ".join(sorted(api_providers))
            message = f"{len(configured_models)} model(s) configured, API provider(s): {providers_str}"
        else:
            message = f"{len(configured_models)} model(s) configured"

        results["llm-api"] = {
            "name": "LLM API",
            "description": "Content enhancement and image analysis",
            "status": "ok",
            "message": message,
            "install_hint": "",
        }
    else:
        results["llm-api"] = {
            "name": "LLM API",
            "description": "Content enhancement and image analysis",
            "status": "missing",
            "message": "No models configured in llm.model_list",
            "install_hint": "Configure llm.model_list in markitai.json",
        }

    # 5a. Check local provider SDKs if configured
    uses_claude_agent = any(
        m.litellm_params.model.startswith("claude-agent/")
        for m in configured_models
        if _is_active_model(m)
    )
    uses_copilot = any(
        m.litellm_params.model.startswith("copilot/")
        for m in configured_models
        if _is_active_model(m)
    )
    uses_chatgpt = any(
        m.litellm_params.model.startswith("chatgpt/")
        for m in configured_models
        if _is_active_model(m)
    )
    uses_gemini_cli = any(
        m.litellm_params.model.startswith("gemini-cli/")
        for m in configured_models
        if _is_active_model(m)
    )

    if uses_claude_agent:
        try:
            import importlib.util

            if importlib.util.find_spec("claude_agent_sdk") is not None:
                # Check if CLI is available
                claude_cli = shutil.which("claude")
                if claude_cli:
                    results["claude-agent-sdk"] = {
                        "name": "Claude Agent SDK",
                        "description": "Claude Code CLI integration",
                        "status": "ok",
                        "message": "SDK + CLI installed",
                        "path": claude_cli,
                        "install_hint": "",
                    }
                else:
                    results["claude-agent-sdk"] = {
                        "name": "Claude Agent SDK",
                        "description": "Claude Code CLI integration",
                        "status": "warning",
                        "message": "SDK installed but 'claude' CLI not found",
                        "install_hint": f"Install Claude Code CLI: {get_install_hint('claude-cli')}",
                    }
            else:
                results["claude-agent-sdk"] = {
                    "name": "Claude Agent SDK",
                    "description": "Claude Code CLI integration",
                    "status": "missing",
                    "message": "claude-agent-sdk not installed",
                    "install_hint": "curl -fsSL https://markitai.ynewtime.com/setup.sh | sh  # or: uv tool install 'markitai[claude-agent]' --upgrade",
                }
        except Exception as e:
            results["claude-agent-sdk"] = {
                "name": "Claude Agent SDK",
                "description": "Claude Code CLI integration",
                "status": "error",
                "message": f"Check failed: {e}",
                "install_hint": "curl -fsSL https://markitai.ynewtime.com/setup.sh | sh  # or: uv tool install 'markitai[claude-agent]' --upgrade",
            }

        # 5b. Check Claude Agent authentication status
        results["claude-agent-auth"] = _check_claude_auth()

    if uses_copilot:
        try:
            import importlib.util

            if importlib.util.find_spec("copilot") is not None:
                # Check if CLI is available
                copilot_cli = shutil.which("copilot")
                if copilot_cli:
                    results["copilot-sdk"] = {
                        "name": "GitHub Copilot SDK",
                        "description": "GitHub Copilot CLI integration",
                        "status": "ok",
                        "message": "SDK + CLI installed",
                        "path": copilot_cli,
                        "install_hint": "",
                    }
                else:
                    results["copilot-sdk"] = {
                        "name": "GitHub Copilot SDK",
                        "description": "GitHub Copilot CLI integration",
                        "status": "warning",
                        "message": "SDK installed but 'copilot' CLI not found",
                        "install_hint": f"Install Copilot CLI: {get_install_hint('copilot-cli')}",
                    }
            else:
                results["copilot-sdk"] = {
                    "name": "GitHub Copilot SDK",
                    "description": "GitHub Copilot CLI integration",
                    "status": "missing",
                    "message": "github-copilot-sdk not installed",
                    "install_hint": "curl -fsSL https://markitai.ynewtime.com/setup.sh | sh  # or: uv tool install 'markitai[copilot]' --upgrade",
                }
        except Exception as e:
            results["copilot-sdk"] = {
                "name": "GitHub Copilot SDK",
                "description": "GitHub Copilot CLI integration",
                "status": "error",
                "message": f"Check failed: {e}",
                "install_hint": "curl -fsSL https://markitai.ynewtime.com/setup.sh | sh  # or: uv tool install 'markitai[copilot]' --upgrade",
            }

        # 5c. Check Copilot authentication status
        results["copilot-auth"] = _check_copilot_auth()

    if uses_chatgpt:
        results["chatgpt-auth"] = _check_chatgpt_auth()

    if uses_gemini_cli:
        results["gemini-cli-auth"] = _check_gemini_cli_auth_status()

    # 6. Check vision model configuration (auto-detect from litellm or config override)
    from markitai.llm import get_model_info_cached

    def is_vision_model(model_config: Any) -> bool:
        """Check if model supports vision (config override, local providers, or auto-detect)."""
        # Config override takes priority
        if (
            model_config.model_info
            and model_config.model_info.supports_vision is not None
        ):
            return model_config.model_info.supports_vision

        # Local providers (claude-agent/, copilot/) always support vision
        from markitai.providers import is_local_provider_model

        if is_local_provider_model(model_config.litellm_params.model):
            return True

        # Auto-detect from litellm
        info = get_model_info_cached(model_config.litellm_params.model)
        return info.get("supports_vision", False)

    vision_models = [m for m in configured_models if is_vision_model(m)]
    if vision_models:
        vision_model_names = [m.litellm_params.model for m in vision_models]
        display_names = ", ".join(vision_model_names[:2])
        if len(vision_model_names) > 2:
            display_names += f" (+{len(vision_model_names) - 2} more)"
        results["vision-model"] = {
            "name": "Vision Model",
            "description": "Image analysis (alt text, descriptions)",
            "status": "ok",
            "message": f"{len(vision_models)} detected: {display_names}",
            "models": vision_model_names,  # Structured list for JSON output
            "install_hint": "",
        }
    else:
        results["vision-model"] = {
            "name": "Vision Model",
            "description": "Image analysis (alt text, descriptions)",
            "status": "warning",
            "message": "No vision model detected (auto-detect or set model_info.supports_vision)",
            "models": [],  # Empty list for JSON output
            "install_hint": "Use vision-capable models like gemini-*, gpt-5.4, claude-*",
        }

    # Output results
    if as_json:
        # Use click.echo for raw JSON (avoid Rich formatting which breaks JSON)
        click.echo(json.dumps(results, indent=2))
        return

    # Unified UI output
    ui.title(t("doctor.title"))

    # Define dependency groups
    required_deps = ["playwright", "libreoffice", "rapidocr"]
    optional_deps = ["ffmpeg"]
    llm_keys = ["llm-api", "vision-model", "claude-agent-sdk", "copilot-sdk"]
    auth_keys = ["claude-agent-auth", "copilot-auth", "chatgpt-auth", "gemini-cli-auth"]

    # Required dependencies
    ui.section(t("doctor.required"))
    passed = 0
    for key in required_deps:
        if key not in results:
            continue
        info = results[key]
        if info["status"] == "ok":
            ui.success(f"{info['name']}: {info['message']}")
            passed += 1
        elif info["status"] == "warning":
            ui.warning(info["name"], detail=info["message"])
        else:
            ui.error(info["name"], detail=info["message"])
    console.print()

    # Optional dependencies
    optional_missing = 0
    has_optional = any(k in results for k in optional_deps)
    if has_optional:
        ui.section(t("doctor.optional"))
        for key in optional_deps:
            if key not in results:
                continue
            info = results[key]
            if info["status"] == "ok":
                ui.success(f"{info['name']}: {info['message']}")
            else:
                ui.warning(
                    f"{info['name']}（{t('missing')}）",
                    detail=info.get("install_hint", ""),
                )
                optional_missing += 1
        console.print()

    # LLM status
    has_llm = any(k in results for k in llm_keys)
    if has_llm:
        ui.section("LLM")
        for key in llm_keys:
            if key not in results:
                continue
            info = results[key]
            if info["status"] == "ok":
                ui.success(f"{info['name']}: {info['message']}")
            elif info["status"] == "warning":
                ui.warning(f"{info['name']}: {info['message']}")
            else:
                ui.error(f"{info['name']}: {info['message']}")
        console.print()

    # Authentication status
    has_auth = any(k in results for k in auth_keys)
    if has_auth:
        ui.section(t("doctor.auth"))
        for key in auth_keys:
            if key not in results:
                continue
            info = results[key]
            if info["status"] == "ok":
                ui.success(f"{info['name']}: {info['message']}")
            else:
                ui.error(f"{info['name']}: {info['message']}")
        console.print()

    # Summary — consider ALL check categories, not just required deps
    has_any_error = _has_errors(results)
    if not has_any_error and optional_missing == 0:
        ui.summary(t("doctor.all_good"))
    else:
        ui.summary(t("doctor.summary", passed=passed, optional=optional_missing))

    # Installation hints (simplified format)
    hints = [
        (info["name"], info["install_hint"])
        for info in results.values()
        if info["status"] in ("missing", "error") and info.get("install_hint")
    ]

    if hints:
        console.print()
        console.print(f"[yellow]{t('doctor.fix_hint')}[/yellow]")
        for name, hint in hints:
            console.print(f"  [dim]\u2022[/dim] {name}: {hint}")

    # Attempt to fix missing components if --fix flag is set
    # Only fixable components (defined in FIXABLE_COMPONENTS) are attempted.
    # Non-fixable items (auth, vision-model, llm-api) are skipped.
    if fix and not as_json:
        fixable = [
            key
            for key, info in results.items()
            if info["status"] in ("missing", "warning") and key in FIXABLE_COMPONENTS
        ]
        if fixable:
            console.print()
            console.print("[bold]Attempting to fix missing components...[/bold]")
            for component in fixable:
                # For playwright, detect whether the package itself is missing
                pkg_missing = (
                    component == "playwright"
                    and results[component].get("status") == "missing"
                )
                _install_component(component, package_missing=pkg_missing)


def suggest_extras() -> list[str]:
    """Return recommended pip extras for installation.

    Always includes all extras whose dependencies are standard PyPI
    packages.  Only conditionally includes extras that depend on SDKs
    which may not be publicly available on PyPI.

    The result is a *stable* list (alphabetical) that the install
    scripts can feed directly into
    ``uv tool install markitai[browser,extra-fetch,...]``.

    This is the **single source of truth** — install scripts should call
    ``markitai doctor --suggest-extras`` instead of reimplementing
    detection logic in shell.
    """
    extras: set[str] = set()

    # --- Always-include extras (pure Python packages from PyPI) ---
    extras.add("browser")  # playwright
    extras.add("gemini-cli")  # google-auth + google-auth-oauthlib
    extras.add("extra-fetch")  # curl-cffi
    extras.add("kreuzberg")  # kreuzberg
    extras.add("svg")  # cairosvg (pip install succeeds; runtime detects missing lib)

    # --- Conditional extras (SDK may not be on PyPI) ---
    # claude-agent — requires claude-agent-sdk
    if shutil.which("claude"):
        extras.add("claude-agent")

    # copilot — requires github-copilot-sdk
    if shutil.which("copilot"):
        extras.add("copilot")

    return sorted(extras)


@click.command("doctor")
@click.option(
    "--json",
    "as_json",
    is_flag=True,
    help="Output as JSON.",
)
@click.option(
    "--fix",
    is_flag=True,
    help="Attempt to install missing components.",
)
@click.option(
    "--suggest-extras",
    "suggest",
    is_flag=True,
    help="Output recommended pip extras for the current environment (machine-readable).",
)
def doctor(as_json: bool, fix: bool, suggest: bool) -> None:
    """Check system health, dependencies, and authentication status.

    This command helps diagnose setup issues by verifying:
    - Playwright (for dynamic URL fetching)
    - LibreOffice (for Office document conversion)
    - RapidOCR (for scanned document processing)
    - LLM API configuration (for content enhancement)
    - Authentication status for local providers (Claude Agent, Copilot, Gemini CLI)
    """
    if suggest:
        click.echo(",".join(suggest_extras()))
        return
    _doctor_impl(as_json, fix=fix)
