#!/usr/bin/env bash
# remagraph-managed-hook v1
# remagraph-fields-schema-version: 2
# ── RemaGraph post-commit 記憶寫回 hook ─────────────────────────────────────
# 由 `remagraph install-hooks` 安裝／升級，安裝到本機任一 git repo 的
# post-commit hook。目的：commit 完成後自動把摘要寫回 RemaGraph，不依賴任何
# agent 自己記得手動呼叫 `remagraph store`，而是把它嵌進 commit 流程本身，
# 變成 commit 這個動作自帶的副作用。
#
# agent-agnostic：純 shell + git 原生機制，任何底層 agent（Claude Code、
# OpenCode、Grok、Codex、AGY……）只要執行了 `git commit`，就會觸發，不需要
# agent 本身認識 RemaGraph。
#
# 安全原則（不可違反）：
#   1. 沒裝 remagraph → 靜默略過，僅印一行提示到 stderr，commit 正常完成。
#   2. 任何內部錯誤都要被捕捉，不得讓使用者看到 stack trace，也不得讓
#      commit 失敗（post-commit 本來就無法阻擋 commit，但仍要求乾淨降級）。
#
# 相容：bash 3.2+（macOS 內建 bash）、無外部依賴（純 git + coreutils）
# 安裝／重新安裝／升級此檔案：`remagraph install-hooks [--global] [--force]`
# （不要手動編輯本檔案 —— 下次 install-hooks 執行時會被覆蓋）
# ──────────────────────────────────────────────────────────────────────────

set -uo pipefail

# ── 小工具：slugify（符合 RemaGraph project_id/task_id/agent_id 格式）────
# RemaGraph 規則：^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$；agent_id 另外要求
# 全小寫、長度 3–64（^[a-z0-9_-]+$）。這裡一律轉小寫、輸出同時滿足兩者。
_slugify() {
    local input="$1" fallback="$2" s
    s=$(printf '%s' "$input" | tr '[:upper:]' '[:lower:]' | LC_ALL=C sed -E 's/[^a-z0-9_-]+/-/g; s/-+/-/g; s/^-+//; s/-+$//')
    if [[ -z "$s" ]]; then
        s="$fallback"
    fi
    while [[ ${#s} -lt 3 ]]; do
        s="${s}0"
    done
    if [[ ! "$s" =~ ^[a-z0-9] ]]; then
        s="a${s}"
    fi
    printf '%.64s' "$s"
}

# ── 小工具：從 project.json 讀 project_id（無 jq 依賴；該檔由
# remagraph init 以 json.dumps 產生，格式穩定）─────────────────────────────
_read_project_id_from_meta() {
    sed -n 's/.*"project_id"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$1" 2>/dev/null | head -n1
}

# ── 小工具：JSON 字串 escape（不含前後引號）──────────────────────────────
_json_escape() {
    local s="$1"
    s="${s//\\/\\\\}"
    s="${s//\"/\\\"}"
    s="${s//$'\r'/}"
    s="${s//$'\n'/\\n}"
    s="${s//$'\t'/\\t}"
    printf '%s' "$s"
}

# ── 小工具：把換行分隔的清單包成 JSON 字串陣列 ───────────────────────────
_to_json_array() {
    local line esc json="[" first=1
    while IFS= read -r line; do
        [[ -z "$line" ]] && continue
        esc=$(_json_escape "$line")
        if [[ $first -eq 1 ]]; then
            json="${json}\"${esc}\""
            first=0
        else
            json="${json},\"${esc}\""
        fi
    done
    json="${json}]"
    printf '%s' "$json"
}

main() {
    if ! command -v remagraph >/dev/null 2>&1; then
        echo "post-commit: 未安裝 remagraph，略過記憶寫回（不影響本次 commit）" >&2
        return 0
    fi

    local commit_hash short_hash subject branch
    commit_hash=$(git rev-parse HEAD 2>/dev/null) || { echo "post-commit: 無法取得 commit hash，略過" >&2; return 0; }
    short_hash=$(git rev-parse --short HEAD 2>/dev/null) || short_hash="${commit_hash:0:7}"
    subject=$(git log -1 --format=%s HEAD 2>/dev/null)
    branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) || branch="HEAD"

    # ── project_id：從「主 repo」目錄名推導，worktree 安全 ──────────────
    # git worktree 底下 `git rev-parse --show-toplevel` 回傳的是 worktree
    # 自己的目錄，不是主 repo 名稱。用 --git-common-dir（所有 worktree 共用
    # 的 .git）往上一層才能拿到真正的主 repo 目錄名稱。
    local common_dir project_root project_id
    common_dir=$(git rev-parse --git-common-dir 2>/dev/null) || { echo "post-commit: 無法取得 git-common-dir，略過" >&2; return 0; }
    case "$common_dir" in
        /*) : ;;
        *) common_dir="$(pwd)/$common_dir" ;;
    esac
    common_dir=$(cd "$common_dir" 2>/dev/null && pwd) || { echo "post-commit: 無法解析 git-common-dir，略過" >&2; return 0; }
    project_root=$(dirname "$common_dir")
    project_id=$(_slugify "$(basename "$project_root")" "project")

    # ── state_dir 解析（安全閥要求 env 與 project 對映）─────────────────
    # 修復前 hook 從不解析本 repo 對應的 REMAGRAPH_STATE_DIR：裸環境下每次
    # commit 都以 "REMAGRAPH_STATE_DIR is not set" 失敗。解析順序（對抗式
    # 審查後收斂）：
    #   1. 繼承的 env 已設定、且其 project.json 的 project_id 與本 repo 的
    #      slug（不分大小寫）一致 → 尊重 env（使用者可能刻意把 state 遷到
    #      自訂位置，不得被 convention 目錄搶走）。
    #   2. convention 目錄存在（先試 slug、再試 repo 原始目錄名——
    #      remagraph init --project 可能用了含大寫的原名）→ 採用之，並以
    #      其 project.json 記載的 project_id 為權威（大小寫不敏感 FS 上
    #      slug 與 metadata 不一致會被安全閥拒絕，權威值才對得上）。
    #   3. 都沒有且 env 也沒設定 → 給出明確的 init 指引後乾淨降級；env
    #      有設定但不屬於本專案 → 保留原樣，交由 store 的安全閥判定。
    # HOME 未設定的環境（部分 CI 容器/systemd）跳過 convention 解析，
    # 不得因 set -u 讓整個 hook 中途爆掉。
    local home_dir="${HOME:-}"
    local resolved_state_dir="" resolved_project_id="$project_id"
    local env_pid meta_pid cand

    if [[ -n "${REMAGRAPH_STATE_DIR:-}" && -f "${REMAGRAPH_STATE_DIR}/project.json" ]]; then
        env_pid=$(_read_project_id_from_meta "${REMAGRAPH_STATE_DIR}/project.json")
        if [[ -n "$env_pid" ]] && \
           [[ "$(printf '%s' "$env_pid" | tr '[:upper:]' '[:lower:]')" == "$project_id" ]]; then
            resolved_state_dir="${REMAGRAPH_STATE_DIR}"
            resolved_project_id="$env_pid"
        fi
    fi

    if [[ -z "$resolved_state_dir" && -n "$home_dir" ]]; then
        for cand in "${home_dir}/.local/state/remagraph-${project_id}" \
                    "${home_dir}/.local/state/remagraph-$(basename "$project_root")"; do
            if [[ -d "$cand" ]]; then
                resolved_state_dir="$cand"
                if [[ -f "$cand/project.json" ]]; then
                    meta_pid=$(_read_project_id_from_meta "$cand/project.json")
                    if [[ -n "$meta_pid" ]]; then
                        resolved_project_id="$meta_pid"
                    fi
                fi
                break
            fi
        done
    fi

    if [[ -n "$resolved_state_dir" ]]; then
        export REMAGRAPH_STATE_DIR="$resolved_state_dir"
        export REMAGRAPH_PROJECT="$resolved_project_id"
        project_id="$resolved_project_id"
    elif [[ -z "${REMAGRAPH_STATE_DIR:-}" ]]; then
        echo "post-commit: 找不到專案 ${project_id} 的 state dir；請先執行 remagraph init --project ${project_id}，本次略過記憶寫回（不影響 commit）" >&2
        return 0
    fi

    # ── task_id：project 段先截長，保證 -commit-<hash> 尾碼完整保留 ──────
    # 修復前整串組合後才截 64 字元：repo 目錄名夠長時尾碼被整段截掉，所有
    # commit 產生完全相同的 task_id，kind=status_update 的 supersede 邏輯
    # 會讓每次 commit 靜默覆蓋前一個 commit 的記錄（診斷實測確認）。
    local task_id prefix_budget task_prefix
    prefix_budget=$((64 - 8 - ${#short_hash}))   # 8 = ${#"-commit-"}
    task_prefix=$(printf "%.${prefix_budget}s" "$project_id")
    task_id=$(_slugify "${task_prefix}-commit-${short_hash}" "commit-task")

    local agent_id_raw agent_id
    agent_id_raw="${AGENT_ID:-$(git config user.name 2>/dev/null)}"
    agent_id=$(_slugify "$agent_id_raw" "git-committer")

    # ── summary：保證 ASCII 基底 > 30 字元，滿足 RemaGraph 仲裁規則 #1 ───
    # （remagraph store 本身不會像 `remagraph auto` 一樣自動補長度）
    local summary
    summary="Commit ${short_hash} (${commit_hash}) on branch ${branch} in ${project_id}: ${subject}"

    # ── learnings：至少一筆非空內容（仲裁規則 #2 對所有 kind 都強制）───
    # git diff-tree 對兩種常見、完全正常的 commit 預設印不出任何東西：
    #   1. root commit（沒有 parent）──加 --root 讓它視為對空樹的 diff。
    #   2. merge commit（多個 parent）──預設不帶任何 diff 表示法旗標時，
    #      diff-tree 對多 parent 的 commit 什麼都不印。這裡選擇 -m（對每個
    #      parent 分別產生 diff，逐一列出），而非 --cc/-c（combined diff：
    #      只列出「和所有 parent 都不同」的檔案）——因為一般、乾淨、無衝突
    #      的 --no-ff merge，其樹狀態必然完全等於其中一個 parent（沒有實際
    #      合併衝突要解決），這種情況下 --cc/-c 會判定「沒有值得顯示的差異」
    #      而一樣印不出東西，等於沒解決問題；-m 則保證只要 merge 相對任一
    #      parent 有變更，就會被列出，符合本 hook「只要有真正的變更檔案資訊
    #      就不留白 learnings」的既有原則。-m 對單一 parent 的一般 commit
    #      沒有作用（維持原行為），對 merge commit 若同一檔案相對兩個
    #      parent 都不同，可能重複列出，故用 sort -u 去重。
    local changed_files learnings_json
    changed_files=$(git diff-tree --no-commit-id --name-only -r --root -m HEAD 2>/dev/null | LC_ALL=C sort -u)
    if [[ -n "$changed_files" ]]; then
        learnings_json=$(printf '%s\n' "$changed_files" | _to_json_array)
    else
        learnings_json='[]'
    fi
    if [[ "$learnings_json" == "[]" ]]; then
        learnings_json='["auto-captured via git post-commit hook (no file diff)"]'
    fi

    local handoff_note
    handoff_note=$(git log -1 --format=%B HEAD 2>/dev/null)

    local store_output store_status
    store_output=$(remagraph store \
        --project "$project_id" \
        --task-id "$task_id" \
        --agent-id "$agent_id" \
        --kind status_update \
        --summary "$summary" \
        --learnings "$learnings_json" \
        --handoff-note "$handoff_note" \
        2>&1)
    store_status=$?

    if [[ $store_status -ne 0 ]]; then
        echo "post-commit: remagraph store 失敗（exit ${store_status}），已略過，不影響本次 commit" >&2
        echo "post-commit: 詳細訊息：${store_output}" >&2
    fi

    return 0
}

main
exit 0
