#!/usr/bin/env python3
"""把 finding 渲染成 GitCode 行级评论(远程他检出口)。

两条远程语义(见设计 §1):
- 同一文件同一规则超过 FOLD_THRESHOLD 条 → 折叠成一条,避免刷屏
- 评论用纯文本、无 markdown 语法、语气像同事,不写"审查通过"类正面确认

    python3 render_comments.py <findings.json> [--fold N]
"""
from __future__ import annotations

import argparse
import json
import sys
from collections import defaultdict

FOLD_THRESHOLD = 5
SEV_ICON = {"high": "🔴", "mid": "🟡", "low": "🟢"}
SEV_ORDER = {"high": 0, "mid": 1, "low": 2}


# 「某项缺失」与「因为它缺失所以某检查跑不了」是同一件事的两面,提给作者时该并成一条。
# 键是"缺失物",值是被它连累的规则。
CONSEQUENCE_OF = {
    "op_graph": ["G6-proto-not-found"],
}


def _merge_consequences(findings: list[dict]) -> list[dict]:
    missing = {f["file"].rsplit("/", 1)[-1] for f in findings
               if f["rule"] == "G9-deliverable-missing"}
    dropped = {r for k, rules in CONSEQUENCE_OF.items() if k in missing for r in rules}
    if not dropped:
        return findings
    kept = []
    for f in findings:
        if f["rule"] in dropped:
            continue
        if f["rule"] == "G9-deliverable-missing" and f["file"].rsplit("/", 1)[-1] in CONSEQUENCE_OF:
            f = {**f, "_consequence": True}
        kept.append(f)
    return kept


def build(findings: list[dict], fold: int) -> list[dict]:
    # 锚点落在本次 diff 之外的不生成评论。
    #
    # 8 个 checker 是 glob 整个算子目录的(三角类检查必须如此,否则看不见未改动的测试文件),
    # 于是 finding 可能落在作者本次没碰过的文件上。**看得全是能力,报得准是纪律**——
    # 在别人的 PR 上评论 ta 没改的行属于噪音,这类留在报告里当上下文即可。
    dropped = [f for f in findings if f.get("out_of_diff")]
    findings = [f for f in findings if not f.get("out_of_diff")]
    if dropped:
        # 不静默丢弃:数量与规则打到 stderr,让"没提"和"没查到"能区分(纪律一)。
        import collections
        import sys as _sys
        cnt = collections.Counter(f["rule"] for f in dropped)
        print(f"[render_comments] 锚点在 diff 外、未生成评论 {len(dropped)} 条:"
              + "、".join(f"{k}×{v}" for k, v in cnt.most_common()), file=_sys.stderr)
    findings = _merge_consequences(findings)
    buckets: dict[tuple[str, str], list[dict]] = defaultdict(list)
    for f in findings:
        buckets[(f["file"], f["rule"])].append(f)

    comments: list[dict] = []
    for (path, rule), group in buckets.items():
        group.sort(key=lambda x: x["line"])
        if len(group) > fold:
            first = group[0]
            # 锚定行本身已经在评论所在处,列表里不再重复它
            rest = [g["line"] for g in group[1:]]
            lines = "、".join(str(n) for n in rest[:15])
            more = "……" if len(rest) > 15 else ""
            # 修法提示按规则来源给:资料规范(组 12)pre-commit 查不到,别乱指工具;
            # 代码格式类(组 10)才归 pre-commit。组号改过一次,这里跟着改(曾漏改导致
            # 中文空格问题被建议"跑 pre-commit",而 pre-commit 根本不查这个)。
            fix_hint = ("建议一起改掉" if first["group"] == 12 else
                        "本地跑一遍 pre-commit 能统一处理")
            comments.append({
                "path": path,
                "line": first["line"],
                "severity": first["severity"],
                "group": first["group"],
                "rule": rule,
                "folded": len(group),
                "body": f"{first['message']}这个文件里同类的还有 {len(group) - 1} 处,"
                        f"行号 {lines}{more},{fix_hint},就不逐行标了。",
            })
        else:
            for f in group:
                suffix = ""
                if f.get("needs_human"):
                    suffix = "(这条是静态扫描给的提示,如果上界确实有别处保证,忽略即可)"
                comments.append({
                    "path": path,
                    "line": f["line"],
                    "severity": f["severity"],
                    "group": f["group"],
                    "rule": rule,
                    "folded": 0,
                    "body": f["message"] + suffix,
                })

    comments.sort(key=lambda c: (SEV_ORDER[c["severity"]], c["path"], c["line"]))
    return comments


def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("findings")
    ap.add_argument("--fold", type=int, default=FOLD_THRESHOLD)
    ap.add_argument("--json", action="store_true")
    a = ap.parse_args()

    data = json.load(open(a.findings, encoding="utf-8"))
    comments = build(data["findings"], a.fold)

    if a.json:
        print(json.dumps(comments, ensure_ascii=False, indent=2))
        return 0

    print(f"共 {len(comments)} 条评论(原始 finding {len(data['findings'])} 条,"
          f"折叠 {sum(1 for c in comments if c['folded'])} 组)\n")
    print("| # | 严重 | 文件 | 行号 | 评论内容(提交内容) |")
    print("|---|---|---|---|---|")
    for i, c in enumerate(comments, 1):
        body = c["body"].replace("|", "\\|")
        print(f"| {i} | {SEV_ICON[c['severity']]} | {c['path'].split('/')[-1]} | {c['line']} | {body} |")
    return 0


if __name__ == "__main__":
    sys.exit(main())