#!/usr/bin/env sh

set -eu

SERVER_DIR=server
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACMR)

# Contract auto-regeneration: If contracts or generator changed, regenerate and stage
CONTRACTS_CHANGED=$(printf '%s\n' "$STAGED_FILES" | grep "^server/tooling/contracts/.*\.json$" || true)
GENERATOR_CHANGED=$(printf '%s\n' "$STAGED_FILES" | grep "^server/scripts/generate-contracts\.ts$" || true)

if [ -n "$CONTRACTS_CHANGED" ] || [ -n "$GENERATOR_CHANGED" ]; then
  echo "📝 Contract source changed - auto-regenerating..."
  npm --prefix "$SERVER_DIR" run -s generate:contracts
  # Stage regenerated files
  git add server/src/mcp/contracts/schemas/_generated/
  echo "✅ Generated files updated and staged"
fi

# Prevent direct edits to generated files (without contract/generator changes)
# Exception: Deletions are allowed if the source contract doesn't exist (feature removal)
MANUAL_GEN_EDITS=$(printf '%s\n' "$STAGED_FILES" | grep "^server/src/.*_generated/.*\.ts$" || true)
if [ -n "$MANUAL_GEN_EDITS" ] && [ -z "$CONTRACTS_CHANGED" ] && [ -z "$GENERATOR_CHANGED" ]; then
  # Check if these are deletions vs modifications
  MODIFIED_GEN_FILES=$(git diff --cached --name-only --diff-filter=M | grep "^server/src/.*_generated/.*\.ts$" || true)
  ADDED_GEN_FILES=$(git diff --cached --name-only --diff-filter=A | grep "^server/src/.*_generated/.*\.ts$" || true)
  DELETED_GEN_FILES=$(git diff --cached --name-only --diff-filter=D | grep "^server/src/.*_generated/.*\.ts$" || true)

  # Block modifications and additions (must go through regeneration)
  if [ -n "$MODIFIED_GEN_FILES" ] || [ -n "$ADDED_GEN_FILES" ]; then
    echo ""
    echo "❌ Direct edits to generated files detected!"
    echo ""
    if [ -n "$MODIFIED_GEN_FILES" ]; then
      echo "Modified: $MODIFIED_GEN_FILES"
    fi
    if [ -n "$ADDED_GEN_FILES" ]; then
      echo "Added: $ADDED_GEN_FILES"
    fi
    echo ""
    echo "Edit source files instead:"
    echo "  - tooling/contracts/*.json (contract definitions)"
    echo "  - scripts/generate-contracts.ts (generator logic)"
    echo ""
    echo "Then run: npm run generate:contracts"
    exit 1
  fi

  # Allow deletions - these are valid when removing features/contracts
  if [ -n "$DELETED_GEN_FILES" ]; then
    echo "📝 Generated file deletions detected (feature removal):"
    echo "   $DELETED_GEN_FILES"
    echo "   ✅ Allowed - ensure source contracts were also removed"
  fi
fi

# Run server-scoped lint-staged for files inside server/
SERVER_STAGED=$(printf '%s\n' "$STAGED_FILES" | grep '^server/' || true)
if [ -n "$SERVER_STAGED" ]; then
  (cd "$SERVER_DIR" && npm run -s lint:staged)
fi

# Check formatting of repo-level JSON/Markdown/YAML files with server's Prettier.
#
# CHECK, not write (changed 2026-08-25). This used to `prettier --write` the staged paths and
# then `git add` them, which meant the bytes committed were not the bytes anything had
# validated: every gate in this hook ran against one version of the file and the commit
# captured another. Formatting-only, so the blast radius was small -- but it is the same shape
# as the defects the security review kept finding, where a step silently changes the artifact
# after the check that blessed it.
#
# It also broke editing tools. A rewrite between an agent's read and its next anchored edit
# makes fixed-string replacements miss silently, which cost real work in this repo on
# 2026-08-25 (plan implementation notes reported as written while the file was unchanged).
#
# `--check` is additionally a strict subset of CI, which runs `validate:format` (also
# `--check`). The previous `--write` was a step CI does not run -- exactly what CLAUDE.md
# § Validation Gates forbids.
ROOT_FORMATTABLE=$(printf '%s\n' "$STAGED_FILES" | grep -v '^server/' | grep -E '\.(json|md|ya?ml)$' || true)
if [ -n "$ROOT_FORMATTABLE" ]; then
  echo "🔍 Checking formatting of staged repo-level text files..."
  if ! printf '%s\n' "$ROOT_FORMATTABLE" | xargs npx --prefix "$SERVER_DIR" prettier --check; then
    echo ""
    echo "❌ Staged files are not Prettier-formatted."
    echo ""
    echo "   Fix them:  npm --prefix $SERVER_DIR run format"
    echo "   Then re-stage:  git add -f -- <the files above>"
    echo ""
    echo "   -f because some tracked paths (.claude/rules/) are gitignored, and a plain"
    echo "   re-add refuses those."
    exit 1
  fi
fi

# Project guidance compatibility projection. Claude-owned files are canonical; AGENTS.md is the
# generated filename Codex and OpenCode prefer. Render from the INDEX so a partially staged source
# file cannot leak unstaged guidance into the commit. This runs after formatting so both source and
# projection contain the same committed bytes.
GUIDANCE_CHANGES=$(printf '%s\n' "$STAGED_FILES" | grep -E '^(CLAUDE\.md|AGENTS\.md|\.claude/rules/.*\.md|scripts/sync-project-guidance\.js)$' || true)
if [ -n "$GUIDANCE_CHANGES" ]; then
  echo "📝 Regenerating project AGENTS.md from canonical Claude guidance..."
  node scripts/sync-project-guidance.js --write --from-index
  git add -f -- AGENTS.md
  echo "✅ Project guidance projection updated and staged"
fi

# Restore local Python lint/format parity when hooks change
HOOK_CHANGES=$(printf '%s\n' "$STAGED_FILES" | grep '^hooks/' || true)
if [ -n "$HOOK_CHANGES" ]; then
  echo "🐍 Validating Python hooks..."
  npm --prefix "$SERVER_DIR" run -s validate:python
fi

# Route by CHANGED PATHS, using the same classifier pre-push and CI use.
#
# `scripts/classify-validation-scope.js` is the declared changed-path SSOT (CLAUDE.md § Validation
# Gates). pre-push and ci.yml both consult it; this hook did not, so a markdown-only commit paid a
# whole-`src` lint and a whole-project typecheck that examined zero changed lines. The classifier
# is deliberately conservative: only documented handbooks/docs/plans/READMEs reach `docs`, only
# `hooks/**` (plus docs) reaches `hooks`, and every empty, mixed, executable, config, dependency or
# unrecognized change falls through to `full`.
#
# Scope by path CLASS, never by diff size: a 3-line concurrency fix is decision-bearing while a
# 40-line generated update is mechanical, so a line-count threshold would gate the wrong thing.
VALIDATION_SCOPE=$(printf '%s\n' "$STAGED_FILES" | node scripts/classify-validation-scope.js --scope)

if [ "$VALIDATION_SCOPE" = "docs" ] || [ "$VALIDATION_SCOPE" = "hooks" ]; then
  echo "✅ Pre-commit: $VALIDATION_SCOPE-only change — skipping typecheck (pre-push still runs it)."
  exit 0
fi

# Typecheck. `ci-release.md` § Required Hooks lists this for pre-commit under a <10s
# budget; measured at 6.1s wall clock on this repo, so the budget holds.
#
# `lint:ratchet` deliberately does NOT run here. It ran in this hook AND in pre-push, at 38s a
# call, and it is a whole-project DIRECTION measure -- CLAUDE.md: "a project-wide ratchet measures
# direction, not conformance". Direction is a push concern; per-commit conformance is `lint:staged`
# above, which lints exactly what is staged. Running it per-commit also meant any unrelated
# violation anywhere in `src` blocked every commit, which in a shared worktree blocks on work that
# is not yours. Coverage is unchanged: pre-push runs it, CI runs it, and every local route stays a
# strict subset of CI.
npm --prefix "$SERVER_DIR" run -s typecheck
