#!/bin/bash
export ROUTERTL_PRECOMMIT=1

# ── Context Guard: SDK vs Consumer ──
# When core.hooksPath points here, git runs THIS hook for both the SDK
# and consumer projects.  If we detect a consumer context (vendor/routertl
# exists), delegate to the consumer-specific project-pre-commit hook.
HOOK_DIR="$(cd "$(dirname "$0")" && pwd)"
# Consumer detection: submodule OR pip install (has project.yml, no sdk/ dir)
IS_CONSUMER=false
if [ -d "vendor/routertl" ]; then
    IS_CONSUMER=true
elif [ -f "project.yml" ] && [ ! -d "sdk" ]; then
    IS_CONSUMER=true
fi
if [ "$IS_CONSUMER" = true ] && [ -f "$HOOK_DIR/project-pre-commit" ]; then
    exec "$HOOK_DIR/project-pre-commit"
fi

# ── Markdown-Only Fast Path ──
# If the commit only touches .md files, run docs checks only (link
# validation + MkDocs strict build), then exit before heavyweight stages.
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACMR)
NON_MD_FILES=$(echo "$STAGED_FILES" | grep -v '\.md$' || true)
if [ -z "$NON_MD_FILES" ] && [ -n "$STAGED_FILES" ]; then
    echo "📝 Markdown-only commit — running docs checks only."

    # Link validation
    if [ -f "sdk/infra/docs/check_links.py" ]; then
        echo "🔗 Validating Markdown Links..."
        python3 sdk/infra/docs/check_links.py --dir docs --exclude docs/internal
        if [ $? -ne 0 ]; then
            echo "❌ Markdown link validation failed. Commit aborted."
            exit 1
        fi
        echo "✅ Markdown link validation passed."
    fi

    # MkDocs strict build
    if [ -f "mkdocs.yml" ]; then
        echo "📚 Building documentation (mkdocs build --strict)..."
        MKDOCS_TMPDIR="/tmp/mkdocs-precommit-$$"
        mkdocs build --strict --site-dir "$MKDOCS_TMPDIR" 2>&1
        if [ $? -ne 0 ]; then
            echo "❌ MkDocs build failed. Commit aborted."
            rm -rf "$MKDOCS_TMPDIR"
            exit 1
        fi
        rm -rf "$MKDOCS_TMPDIR"
        echo "✅ MkDocs build passed."
    fi

    echo "🎉 Docs checks passed — skipping code/sim checks."
    exit 0
fi

# Step 0: Clear all caches — tests must always run clean
echo "Clearing build caches..."
make clean 2>/dev/null
rm -rf sim/work/ sim_build/ __pycache__ .pytest_cache
echo "✅ Caches cleared."

# 0.5 Version sync — generate VERSION from pyproject.toml (single source of truth)
echo "Syncing VERSION from pyproject.toml..."
python3 sdk/cli/sync_version.py
if [ $? -ne 0 ]; then
    echo "❌ Version sync failed. Commit aborted."
    exit 1
fi
git add VERSION
echo "✅ VERSION synced and staged."

# Helper: read excluded tests and lint targets from project.yml
EXCLUDE_TESTS=$(python3 -c "
import yaml, sys
with open('project.yml') as f:
    cfg = yaml.safe_load(f) or {}
excludes = cfg.get('hooks',{}).get('pre_commit',{}).get('exclude',{}).get('tests',[])
# Convert dotted module names to filenames
for e in excludes:
    parts = e.rsplit('.', 1)
    print(parts[-1] + '.py')
" 2>/dev/null)

HOOK_EXCLUDE_LINT=$(python3 -c "
import yaml
with open('project.yml') as f:
    cfg = yaml.safe_load(f) or {}
excludes = cfg.get('hooks',{}).get('pre_commit',{}).get('exclude',{}).get('lint',[])
print(' '.join(excludes))
" 2>/dev/null)

# Build exclude flags from project.yml
REGRESSION_EXCLUDES=""
SIM_EXCLUDES=""
for t in $EXCLUDE_TESTS; do
    REGRESSION_EXCLUDES="$REGRESSION_EXCLUDES $t"
    SIM_EXCLUDES="$SIM_EXCLUDES --exclude $t"
done

# 1. Python linting (ruff) — fastest check first
echo "Running Python linting (ruff)..."
ruff check sdk/ sim/cocotb/ --config pyproject.toml
if [ $? -ne 0 ]; then
    echo "❌ Python linting failed. Commit aborted."
    echo "   Run 'ruff check --fix sdk/ sim/cocotb/' to auto-fix."
    exit 1
fi
echo "✅ Python linting passed."

# 1b. Bare except Exception guard — no new heads on the hydra
# Baseline: 0 violations — all bare handlers are gone as of 2026-03-06 Wave 3.
EXCEPT_BASELINE=0
echo "Checking for bare except Exception..."
VIOLATIONS=$(grep -rn 'except Exception' sdk/ --include='*.py' \
  | grep -v 'resilience.py' | grep -v '/tests/' || true)
if [ -z "$VIOLATIONS" ]; then
    VIOLATION_COUNT=0
else
    VIOLATION_COUNT=$(echo "$VIOLATIONS" | wc -l | tr -d ' ')
fi
if [ "$VIOLATION_COUNT" -gt "$EXCEPT_BASELINE" ]; then
    echo "❌ except Exception count grew: $VIOLATION_COUNT (baseline: $EXCEPT_BASELINE)"
    echo "$VIOLATIONS"
    echo "   Use @cli_resilient or cli_resilient_block instead."
    exit 1
fi
echo "✅ except Exception check passed ($VIOLATION_COUNT ≤ $EXCEPT_BASELINE baseline)."

# 1c. CI requirements import check — catch dep drift before CI does
echo "Checking CI requirements importability..."
CI_REQS_FAIL=0
for reqfile in ci/requirements-fast.txt ci/requirements-sim.txt; do
    if [ -f "$reqfile" ]; then
        while IFS= read -r pkg; do
            # Skip comments and blank lines
            [[ "$pkg" =~ ^#.*$ || -z "$pkg" ]] && continue
            # Normalize package name: cocotbext-axi → cocotbext.axi, rich-click → rich_click
            import_name=$(echo "$pkg" | sed 's/-/_/g; s/\./_/g' | tr '[:upper:]' '[:lower:]')
            # Special cases for known package→import mismatches
            case "$pkg" in
                pyyaml)       import_name="yaml" ;;
                pillow)       import_name="PIL" ;;
                cocotbext-axi) import_name="cocotbext.axi" ;;
                cocotb-bus)   import_name="cocotb_bus" ;;
                cocotb-test)  import_name="cocotb_test" ;;
                rich-click)   import_name="rich_click" ;;
            esac
            python3 -c "import ${import_name}" 2>/dev/null
            if [ $? -ne 0 ]; then
                echo "  ⚠️  $pkg (import $import_name) — not installed locally"
                CI_REQS_FAIL=1
            fi
        done < "$reqfile"
    fi
done
if [ "$CI_REQS_FAIL" -ne 0 ]; then
    echo "❌ CI requirements not importable locally — CI will likely fail."
    echo "   Install missing packages or update ci/requirements-*.txt"
    exit 1
fi
echo "✅ CI requirements import check passed."

# 1d. Shell script linting (shellcheck) — catch bash bugs early
echo "Running shell script linting (shellcheck)..."
if command -v shellcheck > /dev/null 2>&1; then
    # Scope: all maintained script directories
    SHELL_FILES=$(find sdk/infra/docker/ sdk/scripts/ sdk/infra/hooks/ sdk/scripts/ -name '*.sh' -type f 2>/dev/null)
    if [ -n "$SHELL_FILES" ]; then
        # SC1091: Can't follow sourced files (expected in container scripts)
        # SC1090: Can't follow non-constant source (dynamic settings paths)
        # SC2046: Quote $(cmd) to prevent word splitting ($(nproc) is idiomatic)
        # SC2053: Quote RHS of == in [[ ]] (intentional glob matching)
        # SC2086: Double-quote word splitting (intentional in many Makefile-adjacent scripts)
        SHELLCHECK_FAIL=0
        echo "$SHELL_FILES" | xargs shellcheck --severity=warning --exclude=SC1090,SC1091,SC2046,SC2053,SC2086 2>&1 || SHELLCHECK_FAIL=1
        if [ "$SHELLCHECK_FAIL" -ne 0 ]; then
            echo "❌ Shell script linting failed. Commit aborted."
            echo "   Run 'shellcheck --severity=warning --exclude=SC1090,SC1091,SC2046,SC2053,SC2086 <file>' to see details."
            exit 1
        fi
        echo "✅ Shell linting passed ($(echo "$SHELL_FILES" | wc -l | tr -d ' ') scripts)."
    fi
else
    echo "⚠️  shellcheck not installed — skipping shell linting."
    echo "   Install: sudo apt install shellcheck"
fi

# 2. Regbank-utils unit tests
echo "Running regbank-utils unit tests..."
python3 -m unittest discover sdk/generators/regbank/tests/
if [ $? -ne 0 ]; then
    echo "❌ Unit tests failed. Commit aborted."
    exit 1
fi
echo "✅ Unit tests passed."

# 3. Makefile tests
echo "Running Makefile tests..."
python3 sdk/infra/makefile-tests/run_tests.py
if [ $? -ne 0 ]; then
    echo "❌ Makefile tests failed. Commit aborted."
    exit 1
fi
echo "✅ Makefile tests passed."

# 4. Project YAML Regression Tests (excludes from project.yml)
echo "Running Project YAML Regression Tests..."
REG_LOG="/tmp/rr-regression-$$.log"
make regression HOOK_EXCLUDE_TESTS="$REGRESSION_EXCLUDES" 2>&1 | tee "$REG_LOG"
RC=${PIPESTATUS[0]}
if [ $RC -ne 0 ]; then
    echo "❌ Project YAML Regression failed. Commit aborted."
    rm -f "$REG_LOG"
    exit 1
fi
# Sanity: ensure tests were actually discovered (not silently skipped)
if ! grep -q 'Found [1-9]' "$REG_LOG"; then
    echo "❌ SANITY FAIL: Project Manager test discovery returned 0 tests."
    echo "   This likely means filter_git_tracked() is broken in hook context."
    rm -f "$REG_LOG"
    exit 1
fi
rm -f "$REG_LOG"
echo "✅ Project YAML Regression passed."

# 5. VHDL Linting
echo "Running VHDL Linting (Smart)..."
rm -f sim/lint.status
make update-config 2>/dev/null
make linting HOOK_EXCLUDE_LINT="$HOOK_EXCLUDE_LINT"
if [ $? -ne 0 ]; then
    echo "❌ VHDL Linting (Make) failed. Commit aborted."
    exit 1
fi

if [ -f "sim/lint.status" ]; then
    LINT_STATUS=$(cat sim/lint.status)
    if [ "$LINT_STATUS" != "0" ]; then
        echo "❌ VHDL Linting failed ($LINT_STATUS errors). Commit aborted."
        exit 1
    fi
else
    echo "❌ Linting skipped or did not generate status file. Commit aborted."
    exit 1
fi
echo "✅ VHDL Linting passed."

# 6. Cocotb Sim Regression (excludes from project.yml)
echo "Running Cocotb Unit Smoke Tests..."
SIM_LOG="/tmp/rr-sim-regression-$$.log"
make clean sim-regression SIM_EXTRA_ARGS="$SIM_EXCLUDES" 2>&1 | tee "$SIM_LOG"
RC=${PIPESTATUS[0]}
if [ $RC -ne 0 ]; then
    echo "❌ Cocotb tests failed. Commit aborted."
    rm -f "$SIM_LOG"
    exit 1
fi
# Sanity: ensure tests were actually discovered (not silently skipped)
if ! grep -q 'Found [1-9]' "$SIM_LOG"; then
    echo "❌ SANITY FAIL: Cocotb sim test discovery returned 0 tests."
    echo "   This likely means filter_git_tracked() is broken in hook context."
    rm -f "$SIM_LOG"
    exit 1
fi
rm -f "$SIM_LOG"
echo "✅ Cocotb tests passed."
echo "🎉 All pre-commit checks passed!"
exit 0
