#!/usr/bin/env bash
#
# Purpose: Suggested local pre-commit guardrail to keep commits hygienic and to nudge agents
#          toward the repo’s conventions (explicitness, docs coherence, task hygiene).
# Exports: N/A (git hook entry point).
# Role: Copy into `.git/hooks/pre-commit` if you want local enforcement; CI stays authoritative.
# Invariants: Always prints the policy banner to stderr.
# Invariants: When Rust-relevant paths are staged, requires fmt/clippy/tests to pass.
set -euo pipefail

repo_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"

print_policy_banner() {
  local banner_path
  banner_path="${repo_root}/docs/suggested-hooks/banner.txt"
  if [[ -f "${banner_path}" ]]; then
    cat >&2 <"${banner_path}"
    return 0
  fi

  cat >&2 <<'BANNER'
plasmite local hooks reminder:
- Prefer small, explicit, testable changes; avoid hidden state (env vars only for secrets).
- Keep docs coherent with code changes.
- Avoid destructive planning operations without explicit instruction.
BANNER
}

staged_paths() {
  git diff --cached --name-only --diff-filter=ACMR
}

is_rust_relevant_change() {
  # Cargo.lock changes should trigger the gate even if no .rs files were edited.
  staged_paths | grep -Eq '(^|/)(Cargo\.(toml|lock)|build\.rs)$|\.rs$'
}

format_staged_rust_and_restage() {
  local -a staged_rust_files
  mapfile -t staged_rust_files < <(staged_paths | grep -E '\.rs$' || true)

  if [[ ${#staged_rust_files[@]} -eq 0 ]]; then
    return 0
  fi

  if ! command -v cargo >/dev/null 2>&1; then
    echo "error: cargo not found (required for formatting)" >&2
    return 1
  fi

  # Goal: "format only staged files". rustfmt/cargo fmt work at the crate/workspace level.
  # We approximate the intent by:
  #   1) stashing unstaged/untracked changes (keep staged),
  #   2) running `cargo fmt --all`,
  #   3) re-staging the originally-staged Rust files,
  #   4) reverting any other formatter-induced changes,
  #   5) restoring the stash.
  local stash_needed=0
  if ! git diff --quiet || ! git diff --quiet --staged || [[ -n "$(git ls-files --others --exclude-standard)" ]]; then
    stash_needed=1
  fi

  if [[ "${stash_needed}" -eq 1 ]]; then
    git stash push --keep-index --include-untracked -m "pre-commit: stash unstaged changes" >/dev/null 2>&1 || true
  fi

  local cleanup_status=0
  {
    echo "pre-commit: cargo fmt --all" >&2
    cargo fmt --all

    if [[ ${#staged_rust_files[@]} -gt 0 ]]; then
      git add -- "${staged_rust_files[@]}"
    fi

    # Revert formatter changes outside the originally staged Rust set.
    local -a changed_after_fmt
    mapfile -t changed_after_fmt < <(git diff --name-only)
    if [[ ${#changed_after_fmt[@]} -gt 0 ]]; then
      local f
      for f in "${changed_after_fmt[@]}"; do
        local keep=0
        local s
        for s in "${staged_rust_files[@]}"; do
          if [[ "${f}" == "${s}" ]]; then
            keep=1
            break
          fi
        done
        if [[ "${keep}" -eq 0 ]]; then
          git restore --worktree -- "${f}"
        fi
      done
    fi
  } || cleanup_status=$?

  if [[ "${stash_needed}" -eq 1 ]]; then
    git stash pop >/dev/null 2>&1 || true
  fi

  return "${cleanup_status}"
}

warn_docs_drift_if_likely() {
  # Heuristic: if CLI-facing code or specs changed, remind the author to review canonical docs.
  # Actionable: lists concrete files + commands.
  local changed_cli=0
  if staged_paths | grep -Eq '^(src/main\.rs|src/bin/|src/core/|spec/|README\.md)$'; then
    changed_cli=1
  fi
  if [[ "${changed_cli}" -eq 0 ]]; then
    return 0
  fi

  # If neither README nor spec is staged, warn.
  if ! staged_paths | grep -Eq '^(README\.md|spec/v0/SPEC\.md)$'; then
    echo "note: staged changes likely affect CLI/docs; consider reviewing:" >&2
    echo "  - README.md" >&2
    echo "  - spec/v0/SPEC.md" >&2
    echo "verify with:" >&2
    echo "  rg -n \"--where|--since|--format|--jsonl|--color\" src/main.rs" >&2
    echo "  cargo test -q" >&2
  fi
}

warn_planning_log_not_staged_if_present() {
  # Optional warning only if a planning/task log exists in this repo.
  local planning_log=".ergo/events.jsonl"
  if [[ ! -f "${planning_log}" ]]; then
    return 0
  fi
  if git diff --name-only | grep -qx "${planning_log}"; then
    if ! staged_paths | grep -qx "${planning_log}"; then
      echo "note: ${planning_log} changed but is not staged (planning snapshot may drift)" >&2
      echo "next: git add ${planning_log}" >&2
    fi
  fi
}

run_quick_rust_checks() {
  if ! command -v cargo >/dev/null 2>&1; then
    echo "error: cargo not found (required for Rust hygiene gate)" >&2
    return 1
  fi

  if ! command -v just >/dev/null 2>&1; then
    echo "error: 'just' not found; install it or run checks manually:" >&2
    echo "  just clippy" >&2
    echo "  just test" >&2
    return 1
  fi

  echo "pre-commit: just clippy" >&2
  just clippy

  echo "pre-commit: cargo test -q" >&2
  cargo test -q
}

main() {
  if [[ -z "${repo_root}" ]]; then
    echo "error: not in a git repo" >&2
    exit 1
  fi
  print_policy_banner
  warn_planning_log_not_staged_if_present
  warn_docs_drift_if_likely

  if is_rust_relevant_change; then
    format_staged_rust_and_restage
    run_quick_rust_checks
  fi
}

main "$@"
