"""run_python 语法预检:宿主侧 compile() 拦下语法坏码,返回中文感知的锐化诊断。

背景(2026-07,工具失败面板最大头:run_python/exit SyntaxError,20 条真实样本):
模型手写把中文正文硬拼进 Python 源码生成 docx/pptx(python-docx `T("中文…")`),
引号/标点崩坏 → SyntaxError → 改 → 再崩,同一文件一分钟内连撞 6 次烧 token。三类:
- A 引号提前闭合(最多):中文串里用了 ASCII 引号 `"` 把外层字符串提前闭合,
  Python 却报 `Perhaps you forgot a comma?` / `unterminated string`(**主动误导** ——
  模型照着加逗号越修越错)。
- B 全角标点漏进代码位:`center=True、`、`采购（` —— `invalid character 'X' (U+XXXX)`。
- C 括号/引号结构崩:mangled 内联命令、未闭合三引号。

预检零误伤:host 3.12 ≡ sandbox 3.12,compile() 只解析不执行(与依赖无关);compile
通过则原样放行、行为不变,不通过则本就跑不了 → 提前返更准的话 + 省一次 docker 往返。
另有协同:预检结果以 `[Error]` 前缀返回,run_python 语法失败从 `[stderr]…[exit 1]`
变成 `[Error]` 形态,loop._RepeatGuard 的 err-streak 得以兜住「反复交语法坏码」的循环
(旧的 `[exit 1]` 形态它按非错误放行、抓不到)。

不 import litellm/任何重依赖 —— 纯 stdlib,`tests/test_pysyntax.py` 本机可跑
(loop.py 顶层 import litellm 在本机导入期卡死,守卫逻辑单列才测得了)。
"""
from __future__ import annotations

from typing import Optional


def _has_cjk(s: str) -> bool:
    return any("一" <= c <= "鿿" for c in s)


def precheck_python(code: Optional[str]) -> Optional[str]:
    """对 Python 源码做语法预检。通过返回 None;不通过返回锐化的中文诊断([Error] 前缀)。"""
    if not isinstance(code, str) or not code.strip():
        return None  # 空/非字符串交给现有 bad-arguments 分支,不在此判
    try:
        compile(code, "<script>", "exec")
        return None
    except SyntaxError as e:
        return _format(code, e)
    except (ValueError, TypeError):
        # 含 null 字节等 compile 会抛 ValueError —— 不是语法误导型,交给运行期报错
        return None


def _format(code: str, e: SyntaxError) -> str:
    lines = code.splitlines()
    lineno = e.lineno or 0
    msg = e.msg or "语法错误"
    src = lines[lineno - 1] if 1 <= lineno <= len(lines) else ""
    src = src.rstrip()

    out = [
        "[Error] Python 语法预检未通过(未进沙箱执行,先在宿主解析拦下):",
        f"  SyntaxError: {msg}(第 {lineno} 行)",
    ]
    if src:
        shown = src[:200]
        out.append("  " + shown)
        off = (e.offset or 0) - 1
        if 0 <= off < len(shown):
            out.append("  " + " " * off + "^")

    low = msg.lower()
    has_cjk = _has_cjk(src)
    tips = []
    if "invalid character" in low:
        # Python 已点名具体字符 + 码位,补一句半角/引号内的判断
        tips.append(
            "报错处是全角/中文标点。若它在代码位置(用了全角 ,。、;:()等)请改半角;"
            "若它本属正文,请确认整段正文在字符串引号内。"
        )
    elif has_cjk and any(
        k in low for k in (
            "forgot a comma", "unterminated string", "unmatched",
            "invalid syntax", "was never closed", "closing parenthesis",
        )
    ):
        tips.append(
            "疑似中文正文里的 ASCII 引号 \" 或 ' 提前闭合了字符串。改法任选:"
            "① 整段中文用三引号 '''…''' 包裹;② 正文里的引号写成中文引号 “”/‘’;③ 用 \\\" 转义。"
        )
    if has_cjk:
        tips.append(
            "根治:大段中文正文别硬拼进 .py —— 用 write 写进 .md/.txt,脚本里 read 后再灌进 "
            "docx/pptx,正文就不经过 Python 语法/引号。"
        )
    if tips:
        out.append("  修法:" + " ".join(tips))
    return "\n".join(out)
