#!/usr/bin/env bash
# ai-review-local.sh — 本地 AI Review 前置自检
#
# 在 commit / 跑 /simplify 之前过一遍 7 维度审视，把方向性问题（漏文档 /
# 段名悬空 / 高风险路径漏单独 PR / 测试覆盖缺失）暴露在推 PR 之前。
#
# 跟 scripts/ops/ai-review-runner.sh 的差异：
#   - 输入是本地 git diff（不需要 PR_NUMBER）
#   - 输出到 stdout + /tmp/ai-review-local.json（不发 Gitea）
#   - exit code 反映 verdict（pass=0 / risk=1 / fix=2 / block=3），方便 hook 判断
#
# 用法：
#   bash scripts/dev/ai-review-local.sh                   # 默认 vs origin/develop
#   bash scripts/dev/ai-review-local.sh --base staging    # 比 staging
#   bash scripts/dev/ai-review-local.sh --staged          # 只看 staged
#   bash scripts/dev/ai-review-local.sh --working         # 含 working tree（含未 add）
#
# Schema 单源：scripts/ops/ai-review-schema.json（跟 PR runner 共用）

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(git rev-parse --show-toplevel)"
SCHEMA_FILE="${REPO_ROOT}/scripts/ops/ai-review-schema.json"
# shellcheck source=../ops/_ai_review_common.sh
source "${REPO_ROOT}/scripts/ops/_ai_review_common.sh"

BASE_BRANCH="develop"
DIFF_MODE="branch"  # branch | staged | working

while [[ $# -gt 0 ]]; do
  case "$1" in
    --base)    BASE_BRANCH="$2"; shift 2 ;;
    --staged)  DIFF_MODE="staged"; shift ;;
    --working) DIFF_MODE="working"; shift ;;
    -h|--help)
      sed -n '2,18p' "$0" | sed 's/^# \?//'
      exit 0 ;;
    *) echo "未知参数: $1" >&2; exit 2 ;;
  esac
done

command -v claude >/dev/null || { echo "❌ claude CLI 未安装" >&2; exit 2; }
command -v jq >/dev/null     || { echo "❌ jq 未安装" >&2; exit 2; }
[[ -f "$SCHEMA_FILE" ]]      || { echo "❌ schema 文件缺失: $SCHEMA_FILE" >&2; exit 2; }

# 拉取最新 base ref（轻量，只更新 ref，不下载 objects 除非需要）
git fetch origin "$BASE_BRANCH" --quiet 2>/dev/null || true

case "$DIFF_MODE" in
  branch)
    DIFF_CMD=(git diff "origin/${BASE_BRANCH}...HEAD")
    DIFF_DESC="分支累计改动 vs origin/${BASE_BRANCH}（三点 = merge-base 起算，PR review 标准语义）"
    DIFF_BASE="git diff origin/${BASE_BRANCH}...HEAD" ;;
  staged)
    DIFF_CMD=(git diff --cached)
    DIFF_DESC="staged 改动"
    DIFF_BASE="git diff --cached" ;;
  working)
    DIFF_CMD=(git diff HEAD)
    DIFF_DESC="working tree 改动（含未 commit）"
    DIFF_BASE="git diff HEAD" ;;
esac

DIFF_BYTES=$("${DIFF_CMD[@]}" 2>/dev/null | wc -c)
if [[ "$DIFF_BYTES" -eq 0 ]]; then
  echo "ℹ️  无改动可审（$DIFF_DESC 为空）"
  exit 0
fi
echo "[info] $DIFF_DESC ($DIFF_BYTES bytes, agent 自探查)"

PROMPT=$(cat <<EOF
按照 .agents/skills/code-review/SKILL.md 和 .agents/skills/code-review/references/ai-review-comment-template.md 的规则做**本地前置审查**，结论按 schema 输出。

本地模式说明：
- 这是 commit / push 前的自检，不是 PR 已开后的正式 review
- 重点暴露方向性问题：契约面漏文档、段名悬空、高风险路径漏单独 PR、测试覆盖缺口、迁移风险、文档不一致
- 软规则（命名 / 重构 / 风格）一律 severity=suggestion，不要返 hard_block
- verdict 五级语义（#259 新发现 2）：pass / pass_with_risk / should_fix（强烈建议修但不阻断）/ needs_fix / block。verdict=block 仅当存在 hard_block finding

【探查方式 — agent 模式】
仓库已 checkout 到当前目录。**不在 prompt 里给你 diff**，你用工具自己探查：

1. 看清单：先跑 \`${DIFF_BASE} --stat\` 看哪些文件改了 / 改了多少行
2. 按文件取 diff：\`${DIFF_BASE} -- <file>\` 看具体改动
3. 看上下文：Read 改动文件本身，理解改动行**周围**的代码
4. 对照契约（**最重要**）：改动涉及 API / schema / state machine / UI 规格时，Read \`docs/modules/{module}/\` 下对应文档验证一致性
5. 跳过：lock / generated / dist / *.snapshot / 二进制 / 大段 JSON 数据资源

本次 diff 范围：$DIFF_DESC（${DIFF_BYTES} bytes）

$(ai_review_emit_global_checks)

输出要求（强约束）：
- summary 必须 ≤200 字，一句话讲完审查核心结论（不要复述 diff 内容）
- dimensions 至少返回 7 个核心维度：契约面 / 数据迁移 / 代码逻辑 / 测试覆盖 / 安全/权限 / 文档一致性 / 高风险路径合规
- findings 按 severity 分级
- 每个 finding 必须含 stable_id（kebab-case 3-64 字符，命名 \`<category-slug>-<问题短描述>\`，如 \`contract-07api-missing-verify\`）。本地自检无跨轮去重场景，但 schema 强制要求；构造能反映问题语义的 ID 即可
- recommended_action 必须 ≤200 字，告诉作者要不要修、修什么（例："直接 commit" / "先补 .env.example XYZ 后 commit"）
- 引用文件/行号时尽量精确（用 \`path/to/file.ts:42\` 格式）
EOF
)

SCHEMA=$(cat "$SCHEMA_FILE")

echo "[info] 调用 claude CLI（agent 模式，只读工具白名单） ..."
RESPONSE=$(echo "$PROMPT" | claude --print \
  --output-format json \
  --json-schema "$SCHEMA" \
  --permission-mode auto \
  --allowedTools "${AI_REVIEW_ALLOWED_TOOLS[@]}" 2>&1) || {
  echo "❌ claude CLI 失败:" >&2
  echo "$RESPONSE" >&2
  exit 2
}

JSON=$(echo "$RESPONSE" | jq -c '.structured_output // empty')
if [[ -z "$JSON" ]]; then
  echo "❌ structured_output 字段缺失（schema 不通过或 CLI 异常）" >&2
  echo "$RESPONSE" | jq -r '{subtype, is_error, api_error_status} | to_entries[] | "\(.key)=\(.value)"' 2>/dev/null >&2 \
    || echo "$RESPONSE" | head -20 >&2
  exit 2
fi

# 保留完整 JSON 到 /tmp 方便 debug
echo "$JSON" > /tmp/ai-review-local.json

VERDICT=$(echo "$JSON" | jq -r .verdict)
SUMMARY=$(echo "$JSON" | jq -r '.summary // ""')
ACTION=$(echo "$JSON" | jq -r '.recommended_action // ""')

# 轻量文本输出（本地反馈给作者自己看，不需要 markdown 表格）
echo
case "$VERDICT" in
  pass)           echo "✅ Verdict: pass — 无方向性问题" ;;
  pass_with_risk) echo "⚠️  Verdict: pass_with_risk — 可推但有关注点" ;;
  should_fix)     echo "🔶 Verdict: should_fix — 强烈建议修，但不阻断 push" ;;
  needs_fix)      echo "🔧 Verdict: needs_fix — 建议先修" ;;
  block)          echo "🚫 Verdict: block — 有硬阻断，不要推" ;;
  *)              echo "❓ Verdict: $VERDICT" ;;
esac
echo
echo "一句话：$SUMMARY"
echo

# 维度警告（只列 warn / block，不列 ok 减噪音）
DIM_WARN=$(echo "$JSON" | jq -r '.dimensions[] | select(.status == "warn" or .status == "block") | "  [\(.status | ascii_upcase)] \(.name): \(.note // "—")"')
if [[ -n "$DIM_WARN" ]]; then
  echo "维度警告："
  echo "$DIM_WARN"
  echo
fi

# 发现按 severity 分级
for sev in hard_block risk suggestion; do
  COUNT=$(echo "$JSON" | jq --arg sev "$sev" '[.findings[] | select(.severity == $sev)] | length')
  [[ "$COUNT" -eq 0 ]] && continue
  case "$sev" in
    hard_block) LABEL="🚫 硬阻断" ;;
    risk)       LABEL="⚠️  风险" ;;
    suggestion) LABEL="💡 建议" ;;
  esac
  echo "$LABEL ($COUNT 项)："
  echo "$JSON" | jq -r --arg sev "$sev" '
    .findings[]
    | select(.severity == $sev)
    | "  - [\(.category)] \(if .file then "(\(.file)\(if .line then ":\(.line)" else "" end)) " else "" end)\(.message)"
  '
  echo
done

echo "建议行动：$ACTION"
echo
echo "详细 JSON: /tmp/ai-review-local.json"

# Exit code 反映 verdict（方便 hook / 脚本判断）
# should_fix / needs_fix 共用 exit 2（pre-push 视为"警告但允许"，区别仅在文案）
case "$VERDICT" in
  pass)           exit 0 ;;
  pass_with_risk) exit 1 ;;
  should_fix)     exit 2 ;;
  needs_fix)      exit 2 ;;
  block)          exit 3 ;;
  *)              exit 2 ;;
esac
