#!/usr/bin/env bash
#
# pre-commit：**提交前**拦住"docs/API.md 围栏奇数/未配对"。
#
# ★ 为什么要有这个钩子，而不是只靠内联 heredoc / 判据：
#   内联 gate（`python3 -c "...sys.exit(0 if ...)"` 打在一段 bash 里）只在
#   **那次调用的那个上下文**里有效 —— 下一个会话、同一 agent 的新上下文
#   都看不见它 ⇒ 退化成"无判据"（533e39c 之前就是 419 奇照走）。
#   ⇒ 判据/内联 gate 是"当时当刻"的；这个钩子是"**每次提交**都跑"的。
#   两件事都要有：内联 gate 拦"我改的这个文件"；钩子兜底"任何会话提交的 docs"。
#
# 这个钩子由 `deploy/install.sh --git-hooks` 装（`core.hooksPath` 指向 `.githooks`），
# 所以它**跟着仓库走**：换一台机器 clone 下来，装一次就都装上了
# （与 pre-push 同一条理由：`.githooks/` 进版本库，`.git/hooks/` 别人 clone 不到）。
#
# 生效范围：**仅当本次提交暂存了 docs/API.md**。查的是**暂存区**（`git show :path`），
# 也就是"即将被写进提交的那些字节"，不是工作区（工作区可能与暂存区不同）。
#
# 退出码：0 放行，1 拦下（git 会中止提交）。**绝不返回 2** ——
# 钩子里非 0 一律中止，所以"钩子自己坏了"与"真的有违规"都会拦下来，
# 与 pre-push 同一方向：**宁可提交不出，也不要静默提交一个奇数围栏的 docs**。
set -uo pipefail

TARGET="docs/API.md"

# 只有暂存了 TARGET 才需要查（其他提交不碰它就不用拦）
if ! git diff --cached --name-only -- "$TARGET" | grep -qx "$TARGET"; then
  exit 0
fi

# 读**暂存区**版本的字节 —— `git show :TARGET` 失败 = 钩子自己坏了 ⇒ 拦（fail-closed）
staged_blob="$(git show ":$TARGET" 2>/dev/null)" || {
  echo "pre-commit: 无法读取暂存区的 $TARGET —— 中止提交（宁可提交不出，也不要盲提）" >&2
  exit 1
}

# 围栏判定：行首去空白后以 ``` 开头的行（与内联 gate 同一谓词，保证两侧一致）
#  - 总数必须为**偶**（每个 ``` 都有配对的闭合）
#  - 扫描配对后**不得剩未配对的开围栏**
n=0
declare -a stack=()
line_no=0
while IFS= read -r line; do
  line_no=$((line_no + 1))
  stripped="${line#"${line%%[![:space:]]*}"}"   # 去行首空白
  case "$stripped" in
    '```'*)
      n=$((n + 1))
      if [ "${#stack[@]}" -gt 0 ]; then
        unset 'stack[${#stack[@]}-1]'
      else
        stack+=("$line_no")
      fi
      ;;
  esac
done <<< "$staged_blob"

if [ $((n % 2)) -eq 0 ] && [ "${#stack[@]}" -eq 0 ]; then
  echo "pre-commit: $TARGET 围栏=$n（偶）配对=$((n / 2)) 未配对=无 —— 放行"
  exit 0
fi

# 拦下：打印诊断
parity="偶"
[ $((n % 2)) -eq 1 ] && parity="奇"
echo "pre-commit: $TARGET 围栏=$n（$parity）未配对=${stack[*]:-无} —— **拦下**" >&2
if [ "${#stack[@]}" -gt 0 ]; then
  echo "  ⇒ 未配对的**开**围栏在第 ${stack[*]} 行（缺一个闭合围栏）" >&2
else
  echo "  ⇒ 总数为奇 ⇒ 有一个开围栏没配对（可能多开或缺闭）" >&2
fi
echo "  ⇒ 修法：补上缺失的闭合围栏后再提交" >&2
exit 1