#!/bin/bash
# Git Pre-commit Hook for OrchestKit Plugin
# Mirrors CI checks - fail fast locally before pushing
# Version: 3.0.0 — added frontmatter schema + test-case rule validation

set -uo pipefail

echo "Running pre-commit validations..."

STAGED_FILES=$(git diff --cached --name-only 2>/dev/null)
[[ -z "$STAGED_FILES" ]] && exit 0

ERRORS=0
PROJECT_ROOT="$(git rev-parse --show-toplevel)"
cd "$PROJECT_ROOT"

# ===== 1. Plugin.json Validation =====
if [[ -f ".claude-plugin/plugin.json" ]]; then
  echo -n "  Validating plugin.json... "
  if ! jq empty .claude-plugin/plugin.json 2>/dev/null; then
    echo "FAILED (invalid JSON)"
    ERRORS=$((ERRORS + 1))
  else
    for field in name version description; do
      if [[ "$(jq -r ".$field // empty" .claude-plugin/plugin.json)" == "" ]]; then
        echo "FAILED (missing $field)"
        ERRORS=$((ERRORS + 1))
        break
      fi
    done
    [[ $ERRORS -eq 0 ]] && echo "OK"
  fi
fi

# ===== 2. Shell Script Syntax =====
SHELL_FILES=$(echo "$STAGED_FILES" | grep -E '\.sh$' || true)
if [[ -n "$SHELL_FILES" ]]; then
  echo -n "  Checking shell syntax... "
  SHELL_ERRORS=0
  for file in $SHELL_FILES; do
    if [[ -f "$file" ]] && ! bash -n "$file" 2>/dev/null; then
      echo ""
      echo "    ERROR: $file has syntax errors"
      SHELL_ERRORS=$((SHELL_ERRORS + 1))
    fi
  done
  [[ $SHELL_ERRORS -eq 0 ]] && echo "OK" || ERRORS=$((ERRORS + SHELL_ERRORS))
fi

# ===== 3. JSON Syntax =====
JSON_FILES=$(echo "$STAGED_FILES" | grep -E '\.json$' || true)
if [[ -n "$JSON_FILES" ]]; then
  echo -n "  Checking JSON syntax... "
  JSON_ERRORS=0
  for file in $JSON_FILES; do
    if [[ -f "$file" ]] && ! jq empty "$file" 2>/dev/null; then
      echo ""
      echo "    ERROR: $file is invalid JSON"
      JSON_ERRORS=$((JSON_ERRORS + 1))
    fi
  done
  [[ $JSON_ERRORS -eq 0 ]] && echo "OK" || ERRORS=$((ERRORS + JSON_ERRORS))
fi

# ===== 4. Component Counts Validation (mirrors CI) =====
echo -n "  Validating component counts... "
if [[ -x "bin/validate-counts.sh" ]]; then
  if ! ./bin/validate-counts.sh >/dev/null 2>&1; then
    echo "FAILED"
    echo "    Run './bin/validate-counts.sh' for details"
    ERRORS=$((ERRORS + 1))
  else
    echo "OK"
  fi
else
  echo "SKIP (validator not found)"
fi

# ===== 5. Frontmatter Schema Validation =====
if echo "$STAGED_FILES" | grep -qE "^(src/skills/|src/agents/)"; then
  if [[ -x "bin/validate-frontmatter.sh" ]]; then
    echo -n "  Validating frontmatter schema... "
    if ! ./bin/validate-frontmatter.sh 2>&1 | tail -1 | grep -q "valid frontmatter"; then
      echo "FAILED"
      ./bin/validate-frontmatter.sh 2>&1 | grep "✗" | head -5
      echo "    Run './bin/validate-frontmatter.sh' for details"
      ERRORS=$((ERRORS + 1))
    else
      echo "OK"
    fi
  fi
fi

# ===== 6. Test-cases.json Rule References =====
if echo "$STAGED_FILES" | grep -qE "test-cases.json"; then
  if [[ -x "bin/validate-test-case-rules.sh" ]]; then
    echo -n "  Validating test-case rule refs... "
    if ! ./bin/validate-test-case-rules.sh 2>&1 | tail -1 | grep -q "match actual"; then
      echo "FAILED"
      ./bin/validate-test-case-rules.sh 2>&1 | grep "✗" | head -5
      echo "    Run './bin/validate-test-case-rules.sh' for details"
      ERRORS=$((ERRORS + 1))
    else
      echo "OK"
    fi
  fi
fi

# ===== 7. Quick Lint Tests =====
if echo "$STAGED_FILES" | grep -qE "^(src/hooks/|src/skills/|src/agents/|.claude-plugin/)"; then
  if [[ -x "tests/run-all-tests.sh" ]]; then
    echo -n "  Running quick lint tests... "
    # A hook inherits GIT_INDEX_FILE (absolute in a linked worktree) and can
    # inherit GIT_DIR / GIT_WORK_TREE. Several lint tests build throwaway git
    # repos (git init, git add -A); with that env leaked, their `git add` writes
    # the FIXTURE's tree into the REAL index. Measured 2026-09-06 in a worktree:
    # one commit attempt left 1 of 6947 index entries and staged 6946 deletions
    # (#3822 lane). The lint suite gets a scrubbed environment.
    if ! env -u GIT_INDEX_FILE -u GIT_DIR -u GIT_WORK_TREE -u GIT_PREFIX \
         -u GIT_COMMON_DIR -u GIT_OBJECT_DIRECTORY \
         ./tests/run-all-tests.sh --lint >/dev/null 2>&1; then
      echo "FAILED"
      echo "    Run './tests/run-all-tests.sh --lint' for details"
      ERRORS=$((ERRORS + 1))
    else
      echo "OK"
    fi
  fi
fi

# ===== 8. GitHub Actions Workflow Lint =====
# Catches GHA-native bugs that regular YAML lint + CI miss:
#   - `secrets` context used in step-level `if:` (not allowed)
#   - `cat <<HEREDOC` with body at column 1 breaking `run: |` block scalar
#   - deprecated action versions / event names / context fields
#   - broken `uses:` references
#
# `-shellcheck=` (with empty value) disables shellcheck integration.
# 12 of 25 existing workflows have shellcheck info/style findings (mostly
# SC2086 unquoted vars in `${{ ... }}` GH expressions, harmless in practice).
# Enabling shellcheck would block every PR until those are hand-fixed.
# Re-enable later as a hardening step.
# silent: known-noise (grep returns 1 when no workflows are staged; that's the no-op case)
# Deleted workflows are still listed in STAGED_FILES, and actionlint fails
# hard when asked to read a path that no longer exists ("could not read ...:
# no such file or directory"), so a commit that REMOVES a workflow could never
# pass this gate. Keep only paths that still exist on disk.
# silent: known-noise (grep returns 1 when no workflows are staged; that's the no-op case)
WORKFLOW_FILES=$(echo "$STAGED_FILES" | grep -E '^\.github/workflows/.*\.ya?ml$' | { while read -r wf; do [[ -f "$wf" ]] && echo "$wf"; done; } || true)
if [[ -n "$WORKFLOW_FILES" ]]; then
  echo -n "  Linting GitHub Actions workflows... "
  if command -v actionlint >/dev/null 2>&1; then
    # shellcheck disable=SC2086
    # Not a hardcoded /tmp path: that write is denied in a sandboxed shell, and
    # the failure surfaces as "actionlint FAILED" on a workflow that is clean.
    ACTIONLINT_LOG="${TMPDIR:-/tmp}/actionlint-precommit.log"
    if ! actionlint -shellcheck= $WORKFLOW_FILES >"$ACTIONLINT_LOG" 2>&1; then
      echo "FAILED"
      head -20 "$ACTIONLINT_LOG"
      echo "    Run 'actionlint -shellcheck= .github/workflows/*.yml' for full output"
      ERRORS=$((ERRORS + 1))
    else
      echo "OK"
    fi
  else
    echo "SKIP (actionlint not installed — install: brew install actionlint)"
  fi
fi

# ===== 9. Release-owned hook bundles (#3578) =====
# src/hooks/dist/ and plugins/ork/hooks/dist/ are rebuilt on the release-please
# branch only. A feature commit that carries them re-creates the structural
# conflict #3578 describes (every hooks PR rewriting the same four files) and
# fails ci.yml's "release-owned" gate. Keep the locally built bundles on disk
# for tests; just do not stage them. Release automation sets ORK_DIST_COMMIT=1.
if [[ "${ORK_DIST_COMMIT:-0}" != "1" ]]; then
  # silent: known-noise (grep returns 1 when no bundle is staged; that's the no-op case)
  DIST_STAGED=$(echo "$STAGED_FILES" | grep -E '^(src/hooks/dist|plugins/ork/hooks/dist)/' || true)
  if [[ -n "$DIST_STAGED" ]]; then
    echo "  Hook bundles are release-owned (#3578) and must not be committed from a feature branch:"
    echo "$DIST_STAGED" | sed 's/^/    /'
    echo "    Unstage them: git restore --staged src/hooks/dist plugins/ork/hooks/dist"
    ERRORS=$((ERRORS + 1))
  fi
fi

# ===== Summary =====
if [[ $ERRORS -gt 0 ]]; then
  echo ""
  echo "Pre-commit validation FAILED with $ERRORS error(s)"
  exit 1
fi

echo "Pre-commit validation passed"
exit 0
