#!/bin/bash
# Universal RouteRTL Project Pre-Commit Hook
# 
# This hook reads configuration from project.yml via project_manager.py
# and executes the requested checks (linting, yaml check, tests).

# 0. Source project-local hook extensions (if present)
if [ -f "hooks/pre-commit.local" ]; then
    echo "🔌 Sourcing hooks/pre-commit.local..."
    source hooks/pre-commit.local
fi

# ── Markdown-Only Detection ──
# If the commit only touches .md files, skip heavyweight checks (linting,
# regression, simulation) but still run docs-related checks (link
# validation, MkDocs build).
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACMR)
NON_MD_FILES=$(echo "$STAGED_FILES" | grep -v '\.md$' || true)
MD_ONLY=false
if [ -z "$NON_MD_FILES" ] && [ -n "$STAGED_FILES" ]; then
    MD_ONLY=true
    echo "📝 Markdown-only commit — skipping HDL/sim checks, running docs checks."
fi

# 1. Detect Python interpreter (use venv if available) — needed early so
#    the SDK lookup can probe site-packages for a pip-installed routertl.
if [ -f ".venv/bin/python3" ]; then
    PYTHON_CMD=".venv/bin/python3"
else
    PYTHON_CMD="python3"
fi

# 2. Locate SDK Tools
#    Post-P2.39: sdk/ is the canonical layout (engine/, infra/, generators/, …).
#    Pre-P2.39:  tools/ held the same content (project-manager/, docs/, …).
#    Check sdk/ first so consumers that updated their submodule get the right
#    paths even if a leftover tools/ directory still exists on disk.  When
#    none of the on-disk layouts match, probe the active Python for a
#    pip-installed routertl SDK (RTL-P2.346: hooks used to silently skip
#    for `pip install routertl` consumers).
if [ -d "vendor/routertl/sdk" ]; then
    SDK_TOOLS="vendor/routertl/sdk"
    PROJECT_MANAGER="$SDK_TOOLS/engine/project_manager.py"
    CHECK_LINKS="$SDK_TOOLS/infra/docs/check_links.py"
elif [ -d "vendor/routertl/tools" ]; then
    # Legacy layout (pre-P2.39 consumers)
    SDK_TOOLS="vendor/routertl/tools"
    PROJECT_MANAGER="$SDK_TOOLS/project-manager/project_manager.py"
    CHECK_LINKS="$SDK_TOOLS/docs/check_links.py"
elif [ -n "$ROUTERTL_ROOT" ]; then
    SDK_TOOLS="$ROUTERTL_ROOT/sdk"
    PROJECT_MANAGER="$SDK_TOOLS/engine/project_manager.py"
    CHECK_LINKS="$SDK_TOOLS/infra/docs/check_links.py"
elif [ -d "sdk/engine" ]; then
    # Fallback for the SDK repo itself
    SDK_TOOLS="sdk"
    PROJECT_MANAGER="$SDK_TOOLS/engine/project_manager.py"
    CHECK_LINKS="$SDK_TOOLS/infra/docs/check_links.py"
elif [ -d "tools/project-manager" ]; then
    # Legacy fallback for the SDK repo itself (pre-P2.39)
    SDK_TOOLS="tools"
    PROJECT_MANAGER="$SDK_TOOLS/project-manager/project_manager.py"
    CHECK_LINKS="$SDK_TOOLS/docs/check_links.py"
elif PIP_SDK=$($PYTHON_CMD -c 'import os, sdk; print(os.path.dirname(sdk.__file__))' 2>/dev/null) && [ -n "$PIP_SDK" ] && [ -d "$PIP_SDK/engine" ]; then
    # Pip-installed routertl — the SDK lives under site-packages/sdk/.
    SDK_TOOLS="$PIP_SDK"
    PROJECT_MANAGER="$SDK_TOOLS/engine/project_manager.py"
    CHECK_LINKS="$SDK_TOOLS/infra/docs/check_links.py"
    echo "🔍 Using pip-installed RouteRTL SDK: $SDK_TOOLS"
else
    # No SDK anywhere.  Loudly refuse — silent skip used to mask the
    # fact that hooks.pre_commit.tests wasn't running at all, so users
    # believed commits were gated when they weren't (RTL-P2.346).
    HAS_HOOK_CONFIG=""
    if [ -f "project.yml" ] && grep -qE '^[[:space:]]*(hooks|pre_commit):' project.yml 2>/dev/null; then
        HAS_HOOK_CONFIG="yes"
    fi
    echo "" >&2
    echo "❌ RouteRTL SDK tools not found." >&2
    echo "   Looked in:" >&2
    echo "     • vendor/routertl/sdk/"           >&2
    echo "     • vendor/routertl/tools/ (legacy)" >&2
    echo "     • \$ROUTERTL_ROOT/sdk/"           >&2
    echo "     • sdk/ (in-tree SDK repo)"        >&2
    echo "     • tools/project-manager/ (legacy)" >&2
    echo "     • $PYTHON_CMD site-packages (pip-installed routertl)" >&2
    echo "" >&2
    echo "   Fix by one of:" >&2
    echo "     • pip install routertl (into the active venv)" >&2
    echo "     • export ROUTERTL_ROOT=/path/to/routertl" >&2
    echo "     • add routertl as a git submodule at vendor/routertl" >&2
    echo "" >&2
    if [ -n "$HAS_HOOK_CONFIG" ]; then
        echo "   project.yml declares hooks — aborting commit so configured" >&2
        echo "   tests actually run (set HOOK_SKIP_IF_MISSING=1 to override)." >&2
        if [ "$HOOK_SKIP_IF_MISSING" = "1" ]; then
            echo "   HOOK_SKIP_IF_MISSING=1 — skipping anyway." >&2
            exit 0
        fi
        exit 1
    else
        echo "   project.yml has no hooks configured — skipping." >&2
        exit 0
    fi
fi

if [ ! -f "project.yml" ]; then
    echo "⚠️ project.yml not found. Skipping hook."
    exit 0
fi

# 3. Extract Configuration
echo "🔍 Reading hook configuration from project.yml..."

# Inject SDK repo root into PYTHONPATH so project_manager can import
# 'sdk.*' and 'routertl_core.*'.  When SDK_TOOLS is a bare name like
# "sdk" (SDK repo itself), %/* has no effect → fall back to CWD.
SDK_REPO_ROOT="${SDK_TOOLS%/*}"
if [ "$SDK_REPO_ROOT" = "$SDK_TOOLS" ]; then
    SDK_REPO_ROOT="."
fi
export PYTHONPATH="$SDK_REPO_ROOT:$PYTHONPATH"

echo "🔍 Using Python interpreter: $PYTHON_CMD"
CONFIG=$($PYTHON_CMD "$PROJECT_MANAGER" project.yml --gen-hook-config)

if [ $? -ne 0 ]; then
    echo "❌ Failed to parse project.yml. Commit aborted."
    exit 1
fi

eval "$CONFIG"

# 3. Check Enabled Status
if [ "$HOOK_ENABLED" != "true" ]; then
    echo "⏩ Hooks disabled in project.yml. Skipping."
    exit 0
fi

# 3.5. Frozen Directory Guard
if [ -n "$HOOK_FROZEN_DIRS" ]; then
    STAGED_FILES=$(git diff --cached --name-only)
    for dir in $HOOK_FROZEN_DIRS; do
        VIOLATIONS=$(echo "$STAGED_FILES" | grep "^${dir}/" || true)
        if [ -n "$VIOLATIONS" ]; then
            echo "❌ Frozen directory violation: '$dir' is marked frozen in project.yml"
            echo "   Offending files:"
            echo "$VIOLATIONS" | sed 's/^/     /'
            echo ""
            echo "   Remove these files from staging (git reset HEAD <file>) or update project.yml."
            exit 1
        fi
    done
    echo "✅ Frozen directory check passed."
fi

# 4. Phase 1.5: Markdown Link Validation
if [ "$HOOK_CHECK_LINKS" = "true" ]; then
    echo "🔗 Validating Markdown Links..."
    EXCLUDE_ARGS=""
    for dir in $HOOK_EXCLUDE_DOC_DIRS; do
        EXCLUDE_ARGS="$EXCLUDE_ARGS --exclude $dir"
    done
    $PYTHON_CMD "$CHECK_LINKS" --dir docs $EXCLUDE_ARGS
    if [ $? -ne 0 ]; then
        echo "❌ Markdown link validation failed. Commit aborted."
        exit 1
    fi
fi

# 4.5 MkDocs Documentation Build (strict mode)
if [ "$HOOK_CHECK_DOCS" = "true" ] && [ -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 "✅ Documentation build passed."
fi

# 4. Check YAML (implicit in the parsing above, but good to be explicit)
if [ "$HOOK_CHECK_YAML" = "true" ]; then
    echo "✅ YAML check passed (implicit)."
fi

# 4.7 Python Linting (ruff)
if [ "$HOOK_LINT_PYTHON" = "true" ] && [ "$MD_ONLY" = "false" ]; then
    echo "🐍 Running Python linting (ruff check)..."
    ruff check .
    if [ $? -ne 0 ]; then
        echo "❌ Python linting failed. Commit aborted."
        exit 1
    fi
    echo "✅ Python linting passed."
fi

# RTL-P2.597: Heavyweight steps below invoke `rr` directly via the same
# Python interpreter + PYTHONPATH the hook already resolved, instead of
# delegating to `make linting / clean / regression / simulation`.
# The Makefile-driven shape was incompatible with pure-routertl projects
# (no Makefile) — `rr` is the canonical entry per CLAUDE.md, and a
# Makefile shim simply forwarded to the same commands anyway.
RR="$PYTHON_CMD -m sdk.cli.main"

# 4.9 Immediate()-on-ready sim lint (RTL-P3.855) — the Haz.73 footgun gate.
#     Cross-repo enforcement of the lint routertl self-guards via
#     test_routertl_sim_tree_is_clean: a consumer `*_ready`/`*_tready` driven by
#     cocotb `Immediate()` lands in the same timestep as a post-edge monitor
#     sample and manufactures a bogus `*_DATA_HOLD` violation (the false
#     DDR-T2.9). Scans ONLY the staged .py files (cheap AST check, per-line
#     `# noqa: immediate-ready` escape) so unrelated commits pay nothing.
if [ "$MD_ONLY" = "false" ]; then
    STAGED_PY=$(git diff --cached --name-only --diff-filter=ACMR | grep -E '\.py$' || true)
    if [ -n "$STAGED_PY" ]; then
        echo "🚦 Linting staged tests for Immediate()-on-ready (rr sim lint-immediate)..."
        # shellcheck disable=SC2086
        $RR sim lint-immediate $STAGED_PY
        if [ $? -ne 0 ]; then
            echo "❌ Immediate()-on-ready lint failed (RTL-P3.853/Haz.73). Commit aborted."
            exit 1
        fi
    fi
fi

# 4.10 VALID-into-reset static lint (RTL-P3.858) — the AXIS_RST footgun gate.
#      A combinational VALID strobe (tvalid/OUT_VALID/BVALID) driven off an FSM
#      state with NO reset term stays HIGH for the first reset cycle (AMBA
#      §2.7.1). Hit 5x (RTL-T2.26, DS-T2.47, VHD-T2.9/T2.10). Scans ONLY staged
#      .vhd/.vhdl (cheap line/regex check, per-line `-- noqa: valid-into-reset`).
if [ "$MD_ONLY" = "false" ]; then
    STAGED_VHD=$(git diff --cached --name-only --diff-filter=ACMR | grep -E '\.vhdl?$' || true)
    if [ -n "$STAGED_VHD" ]; then
        echo "🚦 Linting staged VHDL for VALID-into-reset (rr sim lint-valid-into-reset)..."
        # shellcheck disable=SC2086
        $RR sim lint-valid-into-reset $STAGED_VHD
        if [ $? -ne 0 ]; then
            echo "❌ VALID-into-reset lint failed (RTL-P3.858/AMBA §2.7.1). Commit aborted."
            exit 1
        fi
    fi
fi

# 5. Run Linting
if [ "$HOOK_LINT" = "true" ] && [ "$MD_ONLY" = "false" ]; then
    echo "🧹 Running HDL Linting (rr lint)..."
    LINT_EXCLUDE_ARGS=""
    for pat in $HOOK_EXCLUDE_LINT; do
        LINT_EXCLUDE_ARGS="$LINT_EXCLUDE_ARGS --exclude $pat"
    done
    # shellcheck disable=SC2086
    $RR lint $LINT_EXCLUDE_ARGS
    if [ $? -ne 0 ]; then
        echo "❌ Linting failed. Commit aborted."
        exit 1
    fi
fi

# 5.5 Run Regression (if enabled)
if [ "$HOOK_RUN_REGRESSION" = "true" ] && [ "$MD_ONLY" = "false" ]; then
    echo "🧪 Running full regression suite (rr sim run --all)..."
    $RR sim run --all
    if [ $? -ne 0 ]; then
        echo "❌ Regression failed. Commit aborted."
        exit 1
    fi
fi

# 6. Run Selected Tests
if [ -n "$HOOK_TESTS" ] && [ "$MD_ONLY" = "false" ]; then
    echo "🧪 Running configured tests: $HOOK_TESTS"

    # Clean simulation workspace to avoid false positives from stale artifacts
    echo "🧹 Cleaning simulation workspace (rr workspace clean --sim)..."
    $RR workspace clean --sim || true

    for test in $HOOK_TESTS; do
        # Check if test is in the exclusion list
        SKIP_TEST=false
        for excluded in $HOOK_EXCLUDE_TESTS; do
            if [ "$test" = "$excluded" ]; then
                echo "   ⏭️  Skipping (excluded): $test"
                SKIP_TEST=true
                break
            fi
        done

        if [ "$SKIP_TEST" = "true" ]; then
            continue
        fi

        # HOOK_TESTS may carry dotted module names from `tests: auto`
        # discovery (e.g. tests.units.test_edge). `rr sim run` accepts the
        # bare testbench name, so strip everything up to the last dot.
        TB="${test##*.}"
        echo "   ▶ rr sim run $TB"
        $RR sim run "$TB"
        if [ $? -ne 0 ]; then
            echo "❌ Test '$TB' failed. Commit aborted."
            exit 1
        fi
    done
fi

# ── Re-stage files modified by this hook ──
# rr workspace clean wipes simulation artifacts, then `rr sim run` writes
# a fresh regression_summary.txt. If the file is tracked, git would show
# a staged vs working-tree mismatch (`MM`) that silently prevents commit.
# Fix: re-add any tracked files the hook itself touched.
RESTAGE_FILES="hw/sim/logs/regression_summary.txt"
for f in $RESTAGE_FILES; do
    if [ -f "$f" ] && git ls-files --error-unmatch "$f" >/dev/null 2>&1; then
        git add "$f"
    fi
done

echo "✅ All checks passed."
exit 0
