#!/usr/bin/env bash
# Commit a curated set of explicit paths with the discipline AGENTS.md
# prescribes:
#   1. show status,
#   2. show minimal diff,
#   3. run `make verify`,
#   4. stage the explicit paths,
#   5. commit.
#
# Paths are read from STDIN, one per line — never from argv. Argv-based
# input would inherit caller-side shell expansion (`commit.sh -- *` lets
# the caller's shell expand `*` into a concrete file list before this
# script ever sees argv), bypassing the bulk-add guard. Stdin gives the
# script unambiguous control over what it inspects, so the bulk-add
# rejection is enforced at the same layer that performs the commit.
#
# Bulk-add patterns (`.`, `..`, glob, `-A`, `--all`) are rejected.
#
# Direct usage:
#   printf '%s\n' src/lib.rs tests/foo.rs | scripts/commit.sh -m "<message>"
# Makefile wrapper:
#   make commit MSG="<message>" PATHS="src/lib.rs tests/foo.rs"
set -euo pipefail

message=""
while (( $# )); do
  case "$1" in
    -m|--message) message="${2:-}"; shift 2 ;;
    -h|--help) sed -n '2,21p' "$0"; exit 0 ;;
    *)
      echo "error: unexpected arg '$1'. Paths are read from stdin (one per line); see -h." >&2
      exit 2 ;;
  esac
done

if [[ -z "$message" ]]; then
  echo "error: -m <message> is required" >&2; exit 2
fi
if [[ -t 0 ]]; then
  {
    echo "error: stdin is a TTY. Pipe paths to commit, one per line. Examples:"
    echo "  printf '%s\\n' src/lib.rs tests/foo.rs | scripts/commit.sh -m '<message>'"
    echo "  make commit MSG='<message>' PATHS='src/lib.rs tests/foo.rs'"
  } >&2
  exit 2
fi

paths=()
while IFS= read -r line; do
  [[ -n "$line" ]] && paths+=("$line")
done

if (( ${#paths[@]} == 0 )); then
  echo "error: no paths received on stdin" >&2; exit 2
fi
for p in "${paths[@]}"; do
  case "$p" in
    .|..|*\**|-A|--all)
      echo "error: bulk-add pattern '$p' rejected. Pass explicit files." >&2
      exit 2 ;;
  esac
done

# Refuse to run if anything outside PATHS is already staged. Without this,
# `git commit` would bundle the caller's stray pre-staged work into the
# commit and silently break the explicit-path-only contract advertised at
# the top of this script.
all_staged=$(git diff --cached --name-only | LC_ALL=C sort -u)
allowed_staged=$(git diff --cached --name-only -- "${paths[@]}" | LC_ALL=C sort -u)
extras=$(comm -23 <(printf '%s\n' "$all_staged") <(printf '%s\n' "$allowed_staged") | sed '/^$/d')
if [[ -n "$extras" ]]; then
  {
    echo "error: the following paths are already staged but not in PATHS:"
    echo "$extras" | sed 's/^/  /'
    echo "Either include them in PATHS or unstage them (git restore --staged <path>) before invoking commit.sh."
  } >&2
  exit 2
fi

echo "==> git status --short"
git status --short
echo
echo "==> git diff --minimal HEAD -- ${paths[*]}"
git diff --minimal HEAD -- "${paths[@]}"
echo
echo "==> make verify"
make verify
echo
git add -- "${paths[@]}"
git commit -m "$message"
