"""采集当前进程可见的运行环境（OS、终端线索、常用 CLI、可选 Python 包），写入 HOME/data/runtime_env.yaml。"""

from __future__ import annotations

import os
import platform
import shutil
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

import yaml

from aicd.cli_encoding import subprocess_env_utf8
from aicd.version import VERSION

RUNTIME_ENV_FILENAME = "runtime_env.yaml"
CAPABILITIES_SUMMARY_FILENAME = "env_capabilities_summary.txt"


def runtime_env_path(layout: dict[str, Path]) -> Path:
    return layout["data"] / RUNTIME_ENV_FILENAME


def _utc_now_iso() -> str:
    return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")


def _terminal_profile() -> dict[str, Any]:
    e = os.environ
    out: dict[str, Any] = {
        "TERM": e.get("TERM", "") or None,
        "COLORTERM": e.get("COLORTERM", "") or None,
        "TERM_PROGRAM": e.get("TERM_PROGRAM", "") or None,
        "TERM_PROGRAM_VERSION": e.get("TERM_PROGRAM_VERSION", "") or None,
    }
    if e.get("WT_SESSION"):
        out["windows_terminal_session"] = True
    if e.get("ConEmuBuild"):
        out["conemu_build"] = e.get("ConEmuBuild")
    if e.get("SSH_CONNECTION") or e.get("SSH_CLIENT"):
        out["ssh_session"] = True
    shell = e.get("SHELL") or e.get("ComSpec") or e.get("COMSPEC")
    if shell:
        out["shell_hint"] = shell
    return {k: v for k, v in out.items() if v not in (None, "", False)}


def _os_block() -> dict[str, Any]:
    uname = None
    try:
        uname = platform.uname()._asdict()
    except Exception:  # noqa: BLE001
        pass
    return {
        "system": platform.system(),
        "release": platform.release(),
        "version": platform.version(),
        "machine": platform.machine(),
        "platform": platform.platform(),
        "uname": uname,
    }


def _python_block() -> dict[str, Any]:
    return {
        "implementation": platform.python_implementation(),
        "version": sys.version.split("\n")[0],
        "executable": sys.executable,
        "prefix": sys.prefix,
    }


def _optional_imports() -> dict[str, Any]:
    names = (
        "pytesseract",
        "asyncpg",
        "aiomysql",
        "playwright",
        "numpy",
        "cv2",
        "vsdx",
        "markdown",
        "reportlab",
        "pyautogui",
        "mss",
    )
    out: dict[str, Any] = {}
    for n in names:
        try:
            __import__(n)
        except ImportError:
            out[n] = {"available": False}
        else:
            out[n] = {"available": True}
    return out


def _command_argv_list() -> list[tuple[str, list[str]]]:
    py = sys.executable
    pairs: list[tuple[str, list[str]]] = [
        ("git", ["git", "--version"]),
        ("python", [py, "--version"]),
        ("pip", [py, "-m", "pip", "--version"]),
        ("node", ["node", "--version"]),
        ("npm", ["npm", "--version"]),
        ("npx", ["npx", "--version"]),
        ("tesseract", ["tesseract", "--version"]),
        ("ffmpeg", ["ffmpeg", "-version"]),
        ("curl", ["curl", "--version"]),
        ("pandoc", ["pandoc", "--version"]),
        ("pwsh", ["pwsh", "--version"]),
        (
            "powershell",
            [
                "powershell",
                "-NoProfile",
                "-NonInteractive",
                "-Command",
                "$PSVersionTable.PSVersion.ToString()",
            ],
        ),
        ("bash", ["bash", "--version"]),
        ("wsl", ["wsl", "--version"]),
        ("playwright_cli", ["playwright", "--version"]),
        ("playwright_module", [py, "-m", "playwright", "--version"]),
        ("wmctrl", ["wmctrl", "-m"]),
        ("xdotool", ["xdotool", "--version"]),
    ]
    return pairs


def _run_argv(argv: list[str], *, timeout: float = 6.0) -> tuple[int, str]:
    try:
        p = subprocess.run(
            argv,
            capture_output=True,
            text=True,
            timeout=timeout,
            encoding="utf-8",
            errors="replace",
            env=subprocess_env_utf8(),
        )
        out = (p.stdout or "") + (p.stderr or "")
        return p.returncode, out.strip()[:2000]
    except subprocess.TimeoutExpired:
        return -1, "<timeout>"
    except FileNotFoundError:
        return -1, "<not found>"
    except OSError as e:
        return -1, repr(e)


def _probe_commands() -> dict[str, Any]:
    result: dict[str, Any] = {}
    for logical_name, argv in _command_argv_list():
        exe = argv[0]
        path = shutil.which(exe)
        if path is None:
            result[logical_name] = {"available": False, "path": None, "version_line": None}
            continue
        code, line = _run_argv(argv)
        result[logical_name] = {
            "available": path is not None and code == 0,
            "path": path,
            "version_line": line if line else None,
            "exit_code": code,
        }
    return result


def collect_runtime_env() -> dict[str, Any]:
    """生成可序列化快照（不写盘）。"""
    return {
        "generated_at": _utc_now_iso(),
        "aicd_version": VERSION,
        "process": {
            "pid": os.getpid(),
            "argv": list(sys.argv),
            "cwd": str(Path.cwd().resolve()),
        },
        "os": _os_block(),
        "terminal": _terminal_profile(),
        "python": _python_block(),
        "environment_select": {
            "AICD_HOME": os.environ.get("AICD_HOME", "") or None,
            "PATH_head": (os.environ.get("PATH", "")[:500] + "…")
            if len(os.environ.get("PATH", "")) > 500
            else os.environ.get("PATH", ""),
        },
        "python_modules_optional": _optional_imports(),
        "commands": _probe_commands(),
    }


def write_runtime_env(path: Path, data: dict[str, Any]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    with path.open("w", encoding="utf-8") as f:
        yaml.safe_dump(data, f, allow_unicode=True, sort_keys=False, default_flow_style=False)


def capabilities_summary_path(layout: dict[str, Path]) -> Path:
    return layout["data"] / CAPABILITIES_SUMMARY_FILENAME


def write_capabilities_summary(layout: dict[str, Path], data: dict[str, Any]) -> Path:
    """将面向模型的能力摘要写入 ``data/env_capabilities_summary.txt``。"""
    p = capabilities_summary_path(layout)
    p.parent.mkdir(parents=True, exist_ok=True)
    yp = str(runtime_env_path(layout).resolve())
    p.write_text(
        format_env_capabilities_for_prompt(data, yaml_path=yp),
        encoding="utf-8",
    )
    return p


def refresh_runtime_env_file(layout: dict[str, Path]) -> tuple[Path, dict[str, Any]]:
    """探测并写入 ``layout['data']/runtime_env.yaml`` 与能力摘要，返回路径与快照。"""
    data = collect_runtime_env()
    path = runtime_env_path(layout)
    write_runtime_env(path, data)
    write_capabilities_summary(layout, data)
    return path, data


def load_runtime_env_snapshot(layout: dict[str, Path]) -> dict[str, Any]:
    """读取已写入的 ``runtime_env.yaml``；若缺失或损坏则做一次内存探测（不写盘）。"""
    p = runtime_env_path(layout)
    if p.is_file():
        try:
            with p.open("r", encoding="utf-8") as f:
                raw = yaml.safe_load(f)
            if isinstance(raw, dict) and raw.get("generated_at"):
                return raw
        except Exception:  # noqa: BLE001
            pass
    return collect_runtime_env()


def summarize_commands(data: dict[str, Any]) -> tuple[int, int]:
    cmds = data.get("commands") or {}
    if not isinstance(cmds, dict):
        return 0, 0
    total = len(cmds)
    avail = 0
    for v in cmds.values():
        if isinstance(v, dict) and v.get("available"):
            avail += 1
    return avail, total


def _cmd_available(commands: dict[str, Any], name: str) -> bool:
    c = commands.get(name)
    return isinstance(c, dict) and bool(c.get("available"))


def format_env_capabilities_for_prompt(
    data: dict[str, Any], *, yaml_path: str = ""
) -> str:
    """注入系统提示的「主机能力快照」：缺工具非错误，但模型应跳过依赖并如实说明原因。"""
    lines: list[str] = [
        "## Host capability snapshot (startup probe; gaps are normal)",
        "- Missing tools are **not errors**: skip dependent steps or tell the user *because X is missing, "
        "Y was not done*; suggest install or `runtime_env_refresh` after setup.",
    ]
    if yaml_path:
        lines.append(f"- Full YAML probe: `{yaml_path}`")
    ga = data.get("generated_at")
    if ga:
        lines.append(f"- Probe UTC time: {ga}")

    cmds: dict[str, Any] = data.get("commands") or {}
    if not isinstance(cmds, dict):
        cmds = {}

    def line_for_cli(
        key: str,
        display: str,
        affects_en: str,
        explain_zh: str,
    ) -> None:
        ok = _cmd_available(cmds, key)
        if ok:
            lines.append(f"- **{display}** (`{key}`): available — {affects_en}")
        else:
            lines.append(
                f"- **{display}** (`{key}`): **NOT available** — skip or explain to user: {explain_zh}. "
                f"Affects: {affects_en}."
            )

    line_for_cli("git", "Git", "clone, version control", "未检测到 git，无法依赖命令行 Git 操作")
    line_for_cli("node", "Node.js", "script_syntax_check(js/mjs)", "未安装 Node，无法进行 JS 语法检查")
    line_for_cli("bash", "Bash", "script_syntax_check(bash/sh), some shell scripts", "无 bash 时跳过 bash -n 类校验")
    line_for_cli("curl", "curl", "CLI downloads in instructions", "无 curl 时可改用 http_request/http_download 或其它方式")
    line_for_cli(
        "pandoc",
        "Pandoc",
        "markdown_to_pdf engine=pandoc",
        "无 pandoc 时请使用 markdown_to_pdf 默认 pymupdf 引擎或自行安装 Pandoc",
    )
    line_for_cli("tesseract", "Tesseract CLI", "OCR with doc_extract use_ocr", "无 Tesseract 时不要启用 PDF OCR")
    line_for_cli("ffmpeg", "ffmpeg", "media tooling", "无 ffmpeg 时跳过相关音视频处理")

    ps_ok = _cmd_available(cmds, "pwsh") or _cmd_available(cmds, "powershell")
    if ps_ok:
        lines.append(
            "- **PowerShell** (pwsh or powershell): available — script_syntax_check(ps1)"
        )
    else:
        lines.append(
            "- **PowerShell**: **NOT available** — skip ps1 syntax check tool; explain: 未检测到 PowerShell。"
            " Affects: script_syntax_check(ps1)."
        )

    pw_cli = _cmd_available(cmds, "playwright_cli") or _cmd_available(
        cmds, "playwright_module"
    )
    mods = data.get("python_modules_optional") or {}
    if not isinstance(mods, dict):
        mods = {}
    pw_mod = isinstance(mods.get("playwright"), dict) and mods["playwright"].get(
        "available"
    )
    from aicd.tools.drawio_embed_export import drawio_export_png_allowed

    drawio_export_note = (
        "**diagram_drawio_export_png**（需 **AICD_ENABLE_DRAWIO_EXPORT_PNG=1** 才注册；"
        "启用后默认路径依赖 **embed.diagrams.net**，或 **AICD_DRAWIO_CLI** 本机导出）"
        if drawio_export_png_allowed()
        else "**diagram_drawio_export_png**（默认关闭，当前不可用；插图用 **diagram_mermaid_png** 或手导 PNG）"
    )
    if pw_cli or pw_mod:
        lines.append(
            "- **Playwright**: available (CLI and/or Python module) — browser_cdp_navigate、"
            f"**diagram_mermaid_png**（本地 mermaid.min.js，一般不需外网）、{drawio_export_note}。"
            " 若 Chromium 未下载，请执行: python -m playwright install chromium。"
        )
    else:
        lines.append(
            "- **Playwright**: **NOT available** — skip headless browser tools; explain: 未安装 Playwright。"
            " Affects: browser_cdp_navigate、**diagram_mermaid_png**、"
            "（若已启用）**diagram_drawio_export_png**、"
            "`playwright` CLI。安装: pip install playwright && python -m playwright install chromium。"
            " Draw.io PNG：默认不启用；启用后离线可设 **AICD_DRAWIO_CLI**。"
        )

    pg_ok = isinstance(mods.get("pyautogui"), dict) and mods["pyautogui"].get("available")
    mss_ok = isinstance(mods.get("mss"), dict) and mods["mss"].get("available")
    wm_ok = _cmd_available(cmds, "wmctrl")
    is_linux = str((data.get("os") or {}).get("system") or "").lower() == "linux"
    if pg_ok and mss_ok:
        linux_note = (
            " Linux 建议安装 **wmctrl**、**xdotool**（`apt install wmctrl xdotool`）以便列窗与聚焦。"
            if is_linux and not wm_ok
            else ""
        )
        lines.append(
            "- **Desktop GUI (pyautogui + mss)**: available — **desktop_gui_automation**"
            f"（原生窗口列表、截图、鼠标键盘）。{linux_note}"
            " Wayland 下键鼠/截图可能受限，建议 X11 会话。"
        )
    else:
        lines.append(
            "- **Desktop GUI**: **NOT available** — install **`pip install aicd[desktop]`** "
            "(pyautogui + mss) for **desktop_gui_automation**; Linux add wmctrl/xdotool."
        )

    for mod_name, label_en, affects_en, explain_zh in (
        (
            "pytesseract",
            "pytesseract (Python)",
            "OCR pipelines with pytesseract",
            "未安装 pytesseract 模块，OCR 相关勿假定可用",
        ),
        (
            "asyncpg",
            "asyncpg",
            "PostgreSQL via db_inspect/db_query",
            "未安装 asyncpg，PostgreSQL 工具不可用（pip install aicd[db]）",
        ),
        (
            "aiomysql",
            "aiomysql",
            "MySQL via db_inspect/db_query",
            "未安装 aiomysql，MySQL 工具不可用（pip install aicd[db]）",
        ),
        (
            "vsdx",
            "vsdx (Python)",
            "visio_flowchart_write / visio_vsdx_summarize",
            "未安装 vsdx，无法程序化写入 .vsdx（pip install aicd[visio] 或 pip install vsdx；勿用不存在的 python-visio）",
        ),
        (
            "markdown",
            "markdown (Python)",
            "markdown_to_pdf (pymupdf engine)",
            "未安装 markdown 模块，markdown_to_pdf 不可用（pip install markdown>=3.5 或 pip install -e .）",
        ),
        (
            "reportlab",
            "reportlab (Python)",
            "pdf_compose",
            "未安装 reportlab，pdf_compose 不可用（pip install aicd[pdf] 或 pip install reportlab）",
        ),
    ):
        info = mods.get(mod_name)
        ok = isinstance(info, dict) and info.get("available")
        if ok:
            lines.append(f"- **{label_en}**: import OK — {affects_en}")
        else:
            lines.append(
                f"- **{label_en}**: **NOT importable** — skip related DB/OCR paths; explain: {explain_zh}. "
                f"Affects: {affects_en}."
            )

    text = "\n".join(lines)
    max_len = 6000
    if len(text) > max_len:
        text = text[: max_len - 20] + "\n…(truncated)"
    return text


# TUI 启动横幅：行文本 + 语义样式（见 startup_env_chat_banner_lines）
BANNER_NEUTRAL = "neutral"
BANNER_CRITICAL = "critical"
BANNER_RECOMMEND = "recommend"


def _playwright_ok(cmds: dict[str, Any], mods: dict[str, Any]) -> bool:
    if _cmd_available(cmds, "playwright_cli") or _cmd_available(
        cmds, "playwright_module"
    ):
        return True
    info = mods.get("playwright")
    return isinstance(info, dict) and bool(info.get("available"))


_DIGEST_CLI_KEYS = (
    "git",
    "node",
    "bash",
    "curl",
    "pandoc",
    "tesseract",
    "ffmpeg",
    "playwright_cli",
    "playwright_module",
    "wmctrl",
    "xdotool",
    "pwsh",
    "powershell",
)
_DIGEST_MOD_KEYS = (
    "pyautogui",
    "mss",
    "pytesseract",
    "asyncpg",
    "aiomysql",
    "cv2",
    "vsdx",
    "reportlab",
    "markdown",
    "numpy",
)


def build_environment_digest(
    data: dict[str, Any],
    *,
    ai_workspace_cwd: str,
    ai_output_root: str,
    ai_tmp_dir: str,
    runtime_yaml_path: str,
    capabilities_summary_path_str: str,
    agent_max_tool_rounds: int,
    sub_agent_max_tool_rounds: int,
    llm_proto: str,
    llm_model: str,
    llm_api_key_configured: bool,
) -> dict[str, Any]:
    """
    供 **agent_environment_digest** 返回：紧凑结构化环境摘要，避免反复读取整份 YAML。
    """
    cmds: dict[str, Any] = data.get("commands") or {}
    if not isinstance(cmds, dict):
        cmds = {}
    mods: dict[str, Any] = data.get("python_modules_optional") or {}
    if not isinstance(mods, dict):
        mods = {}

    cli_highlights: dict[str, bool] = {
        k: _cmd_available(cmds, k) for k in _DIGEST_CLI_KEYS
    }
    mod_hi: dict[str, bool] = {}
    for k in _DIGEST_MOD_KEYS:
        info = mods.get(k)
        mod_hi[k] = isinstance(info, dict) and bool(info.get("available"))
    mod_hi["playwright"] = _playwright_ok(cmds, mods)

    osb = data.get("os") if isinstance(data.get("os"), dict) else {}
    pyb = data.get("python") if isinstance(data.get("python"), dict) else {}
    proc = data.get("process") if isinstance(data.get("process"), dict) else {}

    checklist_zh = [
        "开工前或遇到 ok:false 时：是否已用 **agent_environment_digest**（或 **runtime_env_refresh**）核对 CLI 与可选 Python 模块？",
        "当前 **AI workspace**（对话系统行）是否与用户以为的工程目录一致？**runtime_env.yaml** 里的 **process.cwd** 仅为探测进程 cwd，未必等于 workspace。",
        "用户目标、范围、交付物格式、验收标准是否足够明确？若否，是否已列出假设并请用户确认？",
        "是否存在多种实现方式（速度/准确度/离线约束）？是否已简要对比并请用户选择？",
        "调试前是否已明确：复现步骤、期望 vs 实际、是环境/工具问题还是逻辑/数据问题？",
        "需要跑测试或编译时：相关解释器/编译器是否在 **cli_highlights** 中可用，或使用 **python.executable** 与项目约定一致？",
    ]

    return {
        "os_summary": {
            "system": osb.get("system"),
            "release": osb.get("release"),
        },
        "python_this_process": {
            "version_line": pyb.get("version"),
            "executable": pyb.get("executable"),
        },
        "probe_process": {
            "pid": proc.get("pid"),
            "cwd_at_probe": proc.get("cwd"),
        },
        "paths": {
            "ai_workspace_cwd": ai_workspace_cwd,
            "ai_output_root": ai_output_root,
            "ai_tmp_dir": ai_tmp_dir,
            "runtime_env_yaml": runtime_yaml_path,
            "capabilities_summary_txt": capabilities_summary_path_str,
        },
        "cli_highlights": cli_highlights,
        "python_modules_highlights": mod_hi,
        "llm_config_surface": {
            "proto": llm_proto,
            "model": llm_model,
            "api_key_configured": llm_api_key_configured,
        },
        "agent_limits": {
            "max_tool_rounds": agent_max_tool_rounds,
            "sub_agent_max_tool_rounds": sub_agent_max_tool_rounds,
        },
        "introspection_checklist_zh": checklist_zh,
        "user_alignment_hints_zh": [
            "指令模糊时：用简短条目列出待确认点，避免静默采用高风险默认（如批量删除、大范围重构）。",
            "多方案时：每项一行 trade-off，请用户定夺或指定优先级（例如「优先可复现」）。",
            "环境不足时：说明缺失项与影响范围，给出 **一条** 可行修复路径（安装 + **runtime_env_refresh** 或 **agent_environment_digest** 的 **refresh_first**）。",
        ],
    }


def startup_env_chat_banner_lines(
    data: dict[str, Any], *, has_llm_api_key: bool
) -> list[tuple[str, str]]:
    """
    供 TUI 分行着色：neutral 信息、critical 缺则基础不可用、recommend 建议安装。

    返回 ``(行文本, BANNER_*)`` 列表。
    """
    out: list[tuple[str, str]] = []
    N, R, C = BANNER_NEUTRAL, BANNER_RECOMMEND, BANNER_CRITICAL

    out.append(("【系统环境检查】", N))
    osb = data.get("os") or {}
    pyb = data.get("python") or {}
    sys_s = str(osb.get("system") or "?")
    rel = str(osb.get("release") or "")
    if rel:
        sys_s = f"{sys_s} ({rel})"
    py_ver = str(pyb.get("version") or "?")
    if len(py_ver) > 100:
        py_ver = py_ver[:97] + "…"
    out.append((f"  操作系统: {sys_s}", N))
    out.append((f"  Python: {py_ver}", N))

    avail, total = summarize_commands(data)
    out.append(
        (
            f"  已探测 CLI: {avail}/{total} 项可用（完整列表见 data/runtime_env.yaml）",
            N,
        )
    )

    cmds: dict[str, Any] = data.get("commands") or {}
    if not isinstance(cmds, dict):
        cmds = {}

    mods: dict[str, Any] = data.get("python_modules_optional") or {}
    if not isinstance(mods, dict):
        mods = {}

    if not has_llm_api_key:
        out.append(
            (
                "  [阻塞] 未配置 LLM API 密钥：主对话不可用。请在 config.yaml 填写 llm.apiKey，"
                "或设置 OPENAI_API_KEY / DASHSCOPE_API_KEY 等环境变量。",
                C,
            )
        )

    cli_gaps: list[tuple[list[str] | str, str, str]] = [
        (["git"], "Git（克隆/版本管理）", "安装 Git 并确保在 PATH 中"),
        (["node"], "Node.js（JS 语法检查等）", "从 nodejs.org 安装或使用 nvm"),
        (
            ["bash"],
            "Bash（shell 脚本校验）",
            "Windows 可用 Git Bash / WSL；Linux/macOS 通常已自带",
        ),
        (["curl"], "curl（命令行下载）", "Windows 10+ 常自带；也可通过包管理器安装"),
        (
            "playwright",
            "Playwright（流程图 PNG、无头浏览器等）",
            "pip install playwright && python -m playwright install chromium",
        ),
        (
            ["tesseract"],
            "Tesseract（PDF/图片 OCR）",
            "安装 Tesseract 可执行程序并加入 PATH",
        ),
        (
            ["pwsh", "powershell"],
            "PowerShell（.ps1 脚本校验）",
            "安装 PowerShell 7（pwsh）或使用系统内置 Windows PowerShell",
        ),
        (
            ["ffmpeg"],
            "ffmpeg（音视频处理）",
            "通过系统包管理器或 https://ffmpeg.org 安装",
        ),
    ]
    for keys, label, hint in cli_gaps:
        if keys == "playwright":
            ok = _playwright_ok(cmds, mods)
        else:
            ok = any(_cmd_available(cmds, k) for k in keys)
        if not ok:
            out.append((f"  [推荐] {label}：当前未检测到。{hint}", R))

    mod_hints: list[tuple[str, str]] = [
        ("pytesseract", "pip install pytesseract；OCR 还需系统安装 Tesseract"),
        ("numpy", "pip install numpy（部分数据分析/图表）"),
        (
            "cv2",
            "pip install opencv-python-headless 或 pip install aicd[opencv]",
        ),
        ("asyncpg", "PostgreSQL 工具：pip install aicd[db]"),
        ("aiomysql", "MySQL 工具：pip install aicd[db]"),
        ("playwright", "pip install playwright（若上项 Playwright CLI 已就绪可忽略）"),
        ("markdown", "Markdown→PDF：pip install markdown>=3.5（与运行 Aicd 的同一 Python）"),
        ("reportlab", "块式 PDF：pip install aicd[pdf] 或 pip install reportlab"),
        ("vsdx", "Visio：pip install aicd[visio] 或 pip install vsdx"),
    ]
    for mod_name, hint in mod_hints:
        info = mods.get(mod_name)
        mod_ok = isinstance(info, dict) and bool(info.get("available"))
        if mod_ok:
            continue
        if mod_name == "playwright" and _playwright_ok(cmds, mods):
            continue
        out.append((f"  [推荐] 可选 Python 模块 `{mod_name}` 未导入：{hint}", R))

    desk_pg = isinstance(mods.get("pyautogui"), dict) and mods["pyautogui"].get("available")
    desk_ms = isinstance(mods.get("mss"), dict) and mods["mss"].get("available")
    if not (desk_pg and desk_ms):
        out.append(
            (
                "  [推荐] 原生桌面 GUI 自动化（desktop_gui_automation）需 **pyautogui** 与 **mss**："
                " pip install aicd[desktop]",
                R,
            )
        )

    out.append(
        (
            "  —— 若需安装或排查：可在本输入框向 AI 描述环境与目标，"
            "或使用工具 runtime_env_refresh 重新生成环境快照。",
            N,
        )
    )
    return out


def format_startup_env_chat_banner(
    data: dict[str, Any], *, has_llm_api_key: bool
) -> str:
    """供非 TUI 场景使用的纯文本横幅（无着色）。"""
    return "\n".join(
        line
        for line, _ in startup_env_chat_banner_lines(data, has_llm_api_key=has_llm_api_key)
    )
