#!/bin/bash
# PhaseOps post-commit hook — decision auto-annotation.
#
# Responsibility: detect `Implements: D###` and `Implements-docs: D###` lines
# in the just-recorded commit message and append `**Implemented**: commit <sha>,
# Phase <X>, <date>` to each cited decision file under `.planning/decisions/`.
# Idempotent — if the marker already exists for the current SHA, skip.
# Never blocks the commit: every failure mode exits 0.
#
# Two parallel surface guards:
#   - `Implements:`       — annotation requires the commit to touch the
#                           implementation surface (src/, tests/, config/).
#   - `Implements-docs:`  — annotation requires the commit to touch the
#                           docs surface (docs/ or .planning/).
# A commit carrying the wrong trailer for its touched surface does NOT annotate.
#
# Per-decision dedupe applies to both trailer paths: at most one
# `**Implemented**` line per decision file; a later annotation replaces an
# earlier one rather than appending a duplicate.

# Locate the directory containing .planning/decisions/. Probe directly so we
# work both from the project root and from one level deeper.
project_root=""
if [ -d ".planning/decisions" ]; then
  project_root="."
fi

if [ -z "$project_root" ] || [ ! -d "$project_root/.planning/decisions" ]; then
  exit 0
fi

commit_sha=$(git rev-parse HEAD 2>/dev/null)
commit_sha_short=$(git rev-parse --short HEAD 2>/dev/null)
commit_body=$(git log -1 --pretty=%B 2>/dev/null)
commit_date=$(date +%Y-%m-%d)

[ -z "$commit_sha" ] || [ -z "$commit_body" ] && exit 0

# Detect Phase letter from subject line (e.g. "— Phase B B-2").
phase_letter=$(printf '%s\n' "$commit_body" | awk 'BEGIN{found=0} !found && /Phase[[:space:]]+[A-Z]+/ { match($0,/Phase[[:space:]]+[A-Z]+/); print substr($0,RSTART+6,RLENGTH-6); found=1 }' | tr -d ' ')

# Generic extractor: turns `<Trailer>: D123, D124` lines into a newline-list
# of numeric IDs.
extract_ids() {
  local trailer="$1"
  printf '%s\n' "$commit_body" \
    | awk -v t="^${trailer}:" '$0 ~ t { sub(t,""); print }' \
    | tr ',' '\n' \
    | sed -E 's/[^A-Za-z0-9]+/ /g' \
    | tr -s ' ' '\n' \
    | awk 'NF' \
    | sed -E 's/^[Dd]?([0-9]+)$/\1/' \
    | grep -E '^[0-9]+$'
}

implements_ids=$(extract_ids "Implements")
implements_docs_ids=$(extract_ids "Implements-docs")

# Surface guard: only annotate `Implements:` trailers from commits that touch
# implementation surfaces (src/, tests/, config/).
touched_impl=$(git diff-tree --root --no-commit-id --name-only -r HEAD 2>/dev/null \
  | grep -E '^(src/|tests/|config/)' | head -1)
if [ -z "$touched_impl" ]; then
  implements_ids=""
fi

# Docs-surface guard (parallel): only annotate `Implements-docs:` trailers from
# commits that touch docs/ or .planning/.
touched_docs=$(git diff-tree --root --no-commit-id --name-only -r HEAD 2>/dev/null \
  | grep -E '^(docs/|\.planning/)' | head -1)
if [ -z "$touched_docs" ]; then
  implements_docs_ids=""
fi

annotate_decision() {
  local raw_id="$1"
  local numeric_id
  numeric_id=$(printf '%s' "$raw_id" | sed 's/^0*//')
  [ -z "$numeric_id" ] && return 0

  # Match NNN-, 0NNN-, 00NNN-, and DNNN- forms.
  local decision_file
  decision_file=$(ls "$project_root"/.planning/decisions/${numeric_id}-*.md 2>/dev/null | head -1)
  if [ -z "$decision_file" ]; then
    decision_file=$(ls "$project_root"/.planning/decisions/0${numeric_id}-*.md 2>/dev/null | head -1)
  fi
  if [ -z "$decision_file" ]; then
    decision_file=$(ls "$project_root"/.planning/decisions/00${numeric_id}-*.md 2>/dev/null | head -1)
  fi
  if [ -z "$decision_file" ]; then
    decision_file=$(ls "$project_root"/.planning/decisions/D${numeric_id}-*.md 2>/dev/null | head -1)
  fi
  if [ -z "$decision_file" ]; then
    echo "post-commit: D${numeric_id} not found in .planning/decisions/, skipping annotation"
    return 0
  fi

  # Idempotency: skip when this exact SHA is already annotated.
  if grep -q "^\*\*Implemented\*\*: commit ${commit_sha_short}" "$decision_file"; then
    return 0
  fi
  if grep -q "^\*\*Implemented\*\*: commit ${commit_sha}" "$decision_file"; then
    return 0
  fi

  local phase_field=""
  if [ -n "$phase_letter" ]; then
    phase_field=", Phase ${phase_letter}"
  fi
  local new_line="**Implemented**: commit ${commit_sha_short}${phase_field}, ${commit_date}"

  # Per-decision dedupe: at most ONE **Implemented** line per decision file.
  # If the decision already carries an annotation (for a different SHA),
  # REPLACE it rather than append a second.
  if grep -q "^\*\*Implemented\*\*:" "$decision_file"; then
    local tmp_file
    tmp_file=$(mktemp 2>/dev/null) || tmp_file="${decision_file}.tmp.$$"
    grep -v "^\*\*Implemented\*\*:" "$decision_file" \
      | awk 'NF{last=NR} {buf[NR]=$0} END{for(i=1;i<=last;i++) print buf[i]}' > "$tmp_file" 2>/dev/null
    if [ -s "$tmp_file" ]; then
      printf '\n%s\n' "$new_line" >> "$tmp_file"
      mv "$tmp_file" "$decision_file" 2>/dev/null \
        || { echo "post-commit: could not rewrite ${decision_file} (read-only?), commit proceeds"; rm -f "$tmp_file"; }
    else
      rm -f "$tmp_file"
    fi
    return 0
  fi

  # First annotation — append after a separating blank line.
  if [ -s "$decision_file" ]; then
    local last_char
    last_char=$(tail -c 1 "$decision_file" | od -An -c | tr -d ' ')
    if [ "$last_char" != "\n" ]; then
      printf '\n' >> "$decision_file"
    fi
  fi
  printf '\n%s\n' "$new_line" >> "$decision_file" 2>/dev/null \
    || echo "post-commit: could not append to ${decision_file} (read-only?), commit proceeds"
}

if [ -n "$implements_ids" ]; then
  for raw_id in $implements_ids; do
    annotate_decision "$raw_id"
  done
fi

if [ -n "$implements_docs_ids" ]; then
  for raw_id in $implements_docs_ids; do
    annotate_decision "$raw_id"
  done
fi

exit 0
