#!/bin/sh
# Pre-commit hook for Signal Fish Server
# Runs code formatting, linting, and validation checks before each commit.
#
# To install: ./scripts/enable-hooks.sh
# To bypass (not recommended): git commit --no-verify
#
# Checks performed:
#   1. Code formatting (cargo fmt)
#   2. Clippy lints (cargo clippy)
#   3. Panic-prone patterns
#   4. MSRV consistency (if Cargo.toml or config files modified)
#   5. Workflow AWK scripts (if workflow files modified)
#   6. YAML lint (if workflow files modified)
#   7. AWK file syntax (if .awk files modified)
#   8. Shellcheck for CI scripts (if .github/scripts/ files modified)
#   9. Markdown relative link validation (if docs/ markdown modified)
#  10. Markdown linting (if markdown files modified)
#  11. Link checking (if markdown files modified)
#  12. Markdown code block validation (if markdown files modified)

set -e

# Color output helpers
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Disable colors if not a TTY
if [ ! -t 1 ]; then
    RED=''
    GREEN=''
    YELLOW=''
    BLUE=''
    NC=''
fi

TOTAL_CHECKS=12

echo "${BLUE}[pre-commit]${NC} Running pre-commit checks..."
echo ""

CHECKS_PASSED=0
CHECKS_FAILED=0

# Helper to report check results
check_pass() {
    echo "${GREEN}✓ PASS${NC}: $1"
    CHECKS_PASSED=$((CHECKS_PASSED + 1))
}

check_fail() {
    echo "${RED}✗ FAIL${NC}: $1"
    echo "${RED}[pre-commit] ERROR:${NC} $2"
    CHECKS_FAILED=$((CHECKS_FAILED + 1))
}

check_skip() {
    echo "${YELLOW}⊘ SKIP${NC}: $1 ($2)"
}

# Check 1: Code formatting (always run)
echo "${BLUE}[1/${TOTAL_CHECKS}]${NC} Checking code formatting..."
if cargo fmt --check 2>/dev/null; then
    check_pass "Code formatting"
else
    check_fail "Code formatting" "Run 'cargo fmt' to fix formatting issues."
    echo "${YELLOW}Tip:${NC} cargo fmt --all"
    echo ""
fi

# Check 2: Clippy lints (fast check on staged Rust files only)
echo "${BLUE}[2/${TOTAL_CHECKS}]${NC} Running clippy on staged files..."
# Get list of staged Rust files
STAGED_RS_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.rs$' || true)

if [ -n "$STAGED_RS_FILES" ]; then
    # Run clippy on workspace (faster than per-file)
    # Use --all-features to catch all issues, but allow a longer timeout
    if ! cargo clippy --all-targets --all-features -- -D warnings 2>&1; then
        check_fail "Clippy lints" "Fix clippy warnings before committing."
        echo "${YELLOW}Tip:${NC} cargo clippy --fix --allow-dirty --all-targets --all-features"
        echo ""
    else
        check_pass "Clippy lints"
    fi
else
    check_skip "Clippy lints" "no Rust files staged"
fi

# Check 3: Panic-prone patterns (fast pattern scan only)
echo "${BLUE}[3/${TOTAL_CHECKS}]${NC} Checking for panic-prone patterns..."
if [ -x scripts/check-no-panics.sh ]; then
    if scripts/check-no-panics.sh patterns 2>&1 | grep -q "ERROR"; then
        check_fail "Panic patterns" "Remove .unwrap(), panic!(), expect() from production code."
        echo ""
    else
        check_pass "Panic patterns"
    fi
else
    check_skip "Panic patterns" "check-no-panics.sh not found or not executable"
fi

# Check 4: MSRV consistency (only if relevant files changed)
echo "${BLUE}[4/${TOTAL_CHECKS}]${NC} Checking MSRV consistency..."
MSRV_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '(Cargo.toml|rust-toolchain.toml|clippy.toml|Dockerfile)$' || true)

if [ -n "$MSRV_FILES" ]; then
    if [ -x scripts/check-msrv-consistency.sh ]; then
        if scripts/check-msrv-consistency.sh 2>&1 | grep -q "FAILED"; then
            check_fail "MSRV consistency" "MSRV mismatch across configuration files."
            echo "${YELLOW}Tip:${NC} ./scripts/check-msrv-consistency.sh for details"
            echo ""
        else
            check_pass "MSRV consistency"
        fi
    else
        check_skip "MSRV consistency" "check-msrv-consistency.sh not found"
    fi
else
    check_skip "MSRV consistency" "no MSRV-related files changed"
fi

# Check 5: Workflow AWK validation (only if workflow files changed)
echo "${BLUE}[5/${TOTAL_CHECKS}]${NC} Validating AWK scripts in workflows..."
WORKFLOW_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.github/workflows/.*\.ya?ml$' || true)

if [ -n "$WORKFLOW_FILES" ]; then
    if [ -x scripts/validate-workflow-awk.sh ]; then
        # Pass the specific workflow files to check
        if echo "$WORKFLOW_FILES" | xargs scripts/validate-workflow-awk.sh 2>&1 | grep -q "FAILED"; then
            check_fail "AWK validation" "AWK script syntax errors in workflow files."
            echo "${YELLOW}Tip:${NC} ./scripts/validate-workflow-awk.sh"
            echo ""
        else
            check_pass "AWK validation"
        fi
    else
        check_skip "AWK validation" "validate-workflow-awk.sh not found"
    fi
else
    check_skip "AWK validation" "no workflow files changed"
fi

# Check 6: YAML lint (only if workflow files changed)
echo "${BLUE}[6/${TOTAL_CHECKS}]${NC} Running yamllint on workflow files..."

if [ -n "$WORKFLOW_FILES" ]; then
    if command -v yamllint >/dev/null 2>&1; then
        YAMLLINT_ERRORS=0
        for wf_file in $WORKFLOW_FILES; do
            if [ -f "$wf_file" ]; then
                YAMLLINT_OUT=$(yamllint -c .yamllint.yml "$wf_file" 2>&1)
                if echo "$YAMLLINT_OUT" | grep -q "\[error\]"; then
                    echo "${RED}  YAML lint errors:${NC} $wf_file"
                    echo "$YAMLLINT_OUT" | grep "\[error\]" || true
                    YAMLLINT_ERRORS=$((YAMLLINT_ERRORS + 1))
                fi
            fi
        done
        if [ "$YAMLLINT_ERRORS" -gt 0 ]; then
            check_fail "YAML lint" "Fix YAML lint errors in workflow files."
            echo "${YELLOW}Tip:${NC} yamllint -c .yamllint.yml .github/workflows/"
            echo ""
        else
            check_pass "YAML lint"
        fi
    else
        check_skip "YAML lint" "yamllint not installed (pip install yamllint)"
    fi
else
    check_skip "YAML lint" "no workflow files changed"
fi

# Check 7: AWK file syntax (only if .awk files are staged)
echo "${BLUE}[7/${TOTAL_CHECKS}]${NC} Validating AWK file syntax..."
STAGED_AWK_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.awk$' || true)

if [ -n "$STAGED_AWK_FILES" ]; then
    AWK_ERRORS=0
    for awk_file in $STAGED_AWK_FILES; do
        if [ -f "$awk_file" ]; then
            if ! awk -f "$awk_file" < /dev/null > /dev/null 2>&1; then
                echo "${RED}  AWK syntax error:${NC} $awk_file"
                awk -f "$awk_file" < /dev/null 2>&1 || true
                AWK_ERRORS=$((AWK_ERRORS + 1))
            fi
        fi
    done
    if [ "$AWK_ERRORS" -gt 0 ]; then
        check_fail "AWK file syntax" "Fix AWK syntax errors in staged .awk files."
        echo ""
    else
        check_pass "AWK file syntax"
    fi
else
    check_skip "AWK file syntax" "no .awk files staged"
fi

# Check 8: Shellcheck for CI scripts (only if .github/scripts/ files are staged)
echo "${BLUE}[8/${TOTAL_CHECKS}]${NC} Running shellcheck on CI scripts..."
STAGED_CI_SCRIPTS=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.github/scripts/.*\.sh$' || true)

if [ -n "$STAGED_CI_SCRIPTS" ]; then
    if command -v shellcheck >/dev/null 2>&1; then
        SC_ERRORS=0
        for script in $STAGED_CI_SCRIPTS; do
            if [ -f "$script" ]; then
                if ! shellcheck -s bash "$script" > /dev/null 2>&1; then
                    echo "${RED}  shellcheck errors:${NC} $script"
                    shellcheck -s bash "$script" 2>&1 | head -10
                    SC_ERRORS=$((SC_ERRORS + 1))
                fi
            fi
        done
        if [ "$SC_ERRORS" -gt 0 ]; then
            check_fail "Shellcheck CI scripts" "Fix shellcheck errors in .github/scripts/."
            echo ""
        else
            check_pass "Shellcheck CI scripts"
        fi
    else
        check_skip "Shellcheck CI scripts" "shellcheck not installed"
    fi
else
    check_skip "Shellcheck CI scripts" "no .github/scripts/*.sh files staged"
fi

# Check 9: Markdown relative link validation (only if docs/ markdown files are staged)
echo "${BLUE}[9/${TOTAL_CHECKS}]${NC} Validating markdown relative links in docs/..."
STAGED_DOCS_MD=$(git diff --cached --name-only --diff-filter=ACM | grep -E '^docs/.*\.md$' || true)

if [ -n "$STAGED_DOCS_MD" ]; then
    LINK_ERRORS=0
    for md_file in $STAGED_DOCS_MD; do
        if [ -f "$md_file" ]; then
            base_dir=$(dirname "$md_file")
            # Extract relative links with fence-aware AWK (skips links inside code blocks
            # and inline code spans to avoid false positives on example links).
            for url in $(awk '
              /^```/ {
                if (!fence_width) {
                  n=0;s=$0;while(substr(s,1,1)=="`"){n++;s=substr(s,2)};fence_width=n
                } else {
                  n=0;s=$0;while(substr(s,1,1)=="`"){n++;s=substr(s,2)}
                  if(n>=fence_width&&s~/^[ \t]*$/)fence_width=0
                }
                next
              }
              !fence_width { line=$0; gsub(/`[^`]+`/,"",line)
                while(match(line,/\[([^]]+)\]\(([^)]+)\)/)){
                  u=substr(line,RSTART,RLENGTH);sub(/.*\(/,"",u);sub(/\).*/,"",u);print u
                  line=substr(line,RSTART+RLENGTH)
                }
              }
            ' "$md_file" | grep -vE '^(https?://|mailto:|#)' || true); do
                # Strip anchor for file check
                file_part="${url%%#*}"
                [ -z "$file_part" ] && continue

                resolved="$base_dir/$file_part"
                if [ ! -f "$resolved" ] && [ ! -d "$resolved" ]; then
                    echo "${RED}  Broken link:${NC} $md_file -> $url"
                    LINK_ERRORS=$((LINK_ERRORS + 1))
                fi

                # Check for .llm/ links missing ../ prefix
                case "$url" in
                    .llm/*)
                        echo "${RED}  Invalid prefix:${NC} $md_file -> $url (should be ../$url)"
                        LINK_ERRORS=$((LINK_ERRORS + 1))
                        ;;
                esac
            done
        fi
    done
    if [ "$LINK_ERRORS" -gt 0 ]; then
        check_fail "Docs link validation" "Fix broken or incorrect relative links in docs/."
        echo "${YELLOW}Tip:${NC} ./scripts/validate-ci.sh --links"
        echo ""
    else
        check_pass "Docs link validation"
    fi
else
    check_skip "Docs link validation" "no docs/*.md files staged"
fi

# Check 10: Markdown linting (only if markdown files changed)
echo "${BLUE}[10/${TOTAL_CHECKS}]${NC} Checking markdown files..."
STAGED_MD_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.md$' || true)

if [ -n "$STAGED_MD_FILES" ]; then
    if command -v markdownlint-cli2 >/dev/null 2>&1; then
        if [ -x scripts/check-markdown.sh ]; then
            if scripts/check-markdown.sh 2>&1 | grep -q "failed"; then
                check_fail "Markdown linting" "Markdown files have formatting issues."
                echo "${YELLOW}Tip:${NC} ./scripts/check-markdown.sh fix"
                echo ""
            else
                check_pass "Markdown linting"
            fi
        else
            check_skip "Markdown linting" "check-markdown.sh not found"
        fi
    else
        check_skip "Markdown linting" "markdownlint-cli2 not installed"
    fi
else
    check_skip "Markdown linting" "no markdown files changed"
fi

# Check 11: Link checking (only if markdown files changed)
echo "${BLUE}[11/${TOTAL_CHECKS}]${NC} Checking links in markdown files..."

if [ -n "$STAGED_MD_FILES" ]; then
    if command -v lychee >/dev/null 2>&1; then
        # Use a temporary file to avoid issues with filenames containing spaces
        TEMP_FILE=$(mktemp)
        echo "$STAGED_MD_FILES" > "$TEMP_FILE"
        # Run lychee with fast settings (offline mode for pre-commit speed)
        if ! xargs -a "$TEMP_FILE" lychee --offline --quiet --config .lychee.toml 2>/dev/null; then
            echo "${YELLOW}⚠ WARNING${NC}: Link check found issues in staged markdown files."
            echo "${YELLOW}Note:${NC} This is a warning only (offline mode)."
            echo "${YELLOW}Tip:${NC} Run 'lychee --config .lychee.toml <file>' for full check."
            # Non-blocking warning
        else
            check_pass "Link checking (offline)"
        fi
        rm -f "$TEMP_FILE"
    else
        check_skip "Link checking" "lychee not installed"
    fi
else
    check_skip "Link checking" "no markdown files changed"
fi

# Check 12: Markdown code block validation (only if markdown files changed)
# Validates JSON (jq), YAML (yq), TOML (taplo), and Bash (bash -n) code blocks
# extracted from staged markdown files, matching the CI doc-validation.yml workflow.
echo "${BLUE}[12/${TOTAL_CHECKS}]${NC} Validating code blocks in markdown files..."

if [ -n "$STAGED_MD_FILES" ]; then
    # Filter out test fixture directories from staged markdown files
    CB_MD_FILES=$(echo "$STAGED_MD_FILES" | grep -vE '^(\.github/test-fixtures/|test-fixtures/)' || true)

    if [ -n "$CB_MD_FILES" ]; then
        CB_TEMP_DIR=$(mktemp -d)
        # Use file-based counters to avoid subshell scope issues
        CB_COUNTER_FILE="$CB_TEMP_DIR/counters"
        # Counter file format: 2 space-separated integers (total failed)
        echo "0 0" > "$CB_COUNTER_FILE"

        # AWK program that extracts code blocks and writes each to a file.
        # This matches the fence tracking pattern used in doc-validation.yml:
        #   - Outer fences (4+ backticks) skip nested 3-backtick examples
        #   - Closing fences allow trailing spaces/tabs per CommonMark spec
        #
        # AWK writes each block to OUTDIR/block_NNN.EXT and emits
        # "line_number<TAB>filename" to stdout for the shell to process.
        # This avoids NUL-delimited reads (read -d is a bashism, not POSIX sh).
        #
        # Parameters (via -v):
        #   LANG_PATTERN  - regex to match opening fence tags
        #   OUTDIR        - directory to write extracted block files
        #   EXT           - file extension for extracted blocks
        EXTRACT_BLOCKS_AWK='
          # Skip content inside outer fences (4+ backticks) which may contain
          # nested 3-backtick examples (e.g., documentation about code blocks).
          /^````/ {
            if (!outer_fence) {
              n = 0; s = $0; while (substr(s,1,1)=="`") { n++; s = substr(s,2) }
              outer_fence = n
            } else {
              n = 0; s = $0; while (substr(s,1,1)=="`") { n++; s = substr(s,2) }
              if (n >= outer_fence && s ~ /^[ \t]*$/) outer_fence = 0
            }
            next
          }
          outer_fence { next }
          # Match opening fence for target language code blocks
          $0 ~ LANG_PATTERN {
            in_block = 1
            block_start = NR
            block_num++
            content = ""
            next
          }
          # Match closing fence only if in a block
          # CommonMark: closing fences may have trailing spaces/tabs
          /^```[ \t]*$/ && in_block {
            if (content != "") {
              outfile = OUTDIR "/block_" block_num "." EXT
              printf "%s", content > outfile
              close(outfile)
              print block_start "\t" outfile
            }
            in_block = 0
            next
          }
          # Accumulate content while in block
          in_block {
            content = content $0 "\n"
          }
          # Handle unclosed blocks at end of file
          END {
            if (in_block && content != "") {
              outfile = OUTDIR "/block_" block_num "." EXT
              printf "%s", content > outfile
              close(outfile)
              print block_start "\t" outfile
            }
          }
        '

        # Check which validation tools are available
        HAS_JQ=0
        HAS_YQ=0
        HAS_TAPLO=0

        if command -v jq >/dev/null 2>&1; then HAS_JQ=1; fi
        if command -v yq >/dev/null 2>&1; then HAS_YQ=1; fi
        if command -v taplo >/dev/null 2>&1; then HAS_TAPLO=1; fi

        # --- Validate JSON code blocks ---
        if [ "$HAS_JQ" -eq 1 ]; then
            for md_file in $CB_MD_FILES; do
                [ -f "$md_file" ] || continue
                awk -v LANG_PATTERN='^```json$' \
                    -v OUTDIR="$CB_TEMP_DIR" \
                    -v EXT="json" \
                    "$EXTRACT_BLOCKS_AWK" "$md_file" | while IFS='	' read -r line_num outfile; do
                    [ -z "$outfile" ] && continue
                    read -r cb_total cb_failed < "$CB_COUNTER_FILE"
                    cb_total=$((cb_total + 1))

                    if ! jq empty "$outfile" 2>/dev/null; then
                        echo "${RED}  Invalid JSON in $md_file at line $line_num${NC}"
                        cb_failed=$((cb_failed + 1))
                    fi

                    echo "$cb_total $cb_failed" > "$CB_COUNTER_FILE"
                done
            done
        fi

        # --- Validate YAML code blocks ---
        if [ "$HAS_YQ" -eq 1 ]; then
            for md_file in $CB_MD_FILES; do
                [ -f "$md_file" ] || continue
                awk -v LANG_PATTERN='^```(yaml|yml)$' \
                    -v OUTDIR="$CB_TEMP_DIR" \
                    -v EXT="yaml" \
                    "$EXTRACT_BLOCKS_AWK" "$md_file" | while IFS='	' read -r line_num outfile; do
                    [ -z "$outfile" ] && continue
                    read -r cb_total cb_failed < "$CB_COUNTER_FILE"
                    cb_total=$((cb_total + 1))

                    if ! yq eval . "$outfile" >/dev/null 2>&1; then
                        echo "${RED}  Invalid YAML in $md_file at line $line_num${NC}"
                        cb_failed=$((cb_failed + 1))
                    fi

                    echo "$cb_total $cb_failed" > "$CB_COUNTER_FILE"
                done
            done
        fi

        # --- Validate TOML code blocks (skip if taplo not available) ---
        if [ "$HAS_TAPLO" -eq 1 ]; then
            for md_file in $CB_MD_FILES; do
                [ -f "$md_file" ] || continue
                awk -v LANG_PATTERN='^```toml$' \
                    -v OUTDIR="$CB_TEMP_DIR" \
                    -v EXT="toml" \
                    "$EXTRACT_BLOCKS_AWK" "$md_file" | while IFS='	' read -r line_num outfile; do
                    [ -z "$outfile" ] && continue
                    read -r cb_total cb_failed < "$CB_COUNTER_FILE"
                    cb_total=$((cb_total + 1))

                    if ! taplo check "$outfile" 2>/dev/null; then
                        echo "${RED}  Invalid TOML in $md_file at line $line_num${NC}"
                        cb_failed=$((cb_failed + 1))
                    fi

                    echo "$cb_total $cb_failed" > "$CB_COUNTER_FILE"
                done
            done
        fi

        # --- Validate Bash code blocks (bash -n syntax check, always available) ---
        for md_file in $CB_MD_FILES; do
            [ -f "$md_file" ] || continue
            awk -v LANG_PATTERN='^```(bash|sh|shell)$' \
                -v OUTDIR="$CB_TEMP_DIR" \
                -v EXT="sh" \
                "$EXTRACT_BLOCKS_AWK" "$md_file" | while IFS='	' read -r line_num outfile; do
                [ -z "$outfile" ] && continue
                # Skip placeholder/example-only blocks
                if grep -qE '^#.*example|^# Note:|^\.\.\.$' "$outfile"; then
                    continue
                fi
                read -r cb_total cb_failed < "$CB_COUNTER_FILE"
                cb_total=$((cb_total + 1))

                if ! bash -n "$outfile" 2>/dev/null; then
                    echo "${RED}  Invalid Bash syntax in $md_file at line $line_num${NC}"
                    cb_failed=$((cb_failed + 1))
                fi

                echo "$cb_total $cb_failed" > "$CB_COUNTER_FILE"
            done
        done

        # Read final counters
        read -r cb_total cb_failed < "$CB_COUNTER_FILE"
        rm -rf "$CB_TEMP_DIR"

        # Build a summary of which validators ran
        CB_TOOLS_USED=""
        if [ "$HAS_JQ" -eq 1 ]; then CB_TOOLS_USED="${CB_TOOLS_USED}JSON "; fi
        if [ "$HAS_YQ" -eq 1 ]; then CB_TOOLS_USED="${CB_TOOLS_USED}YAML "; fi
        if [ "$HAS_TAPLO" -eq 1 ]; then CB_TOOLS_USED="${CB_TOOLS_USED}TOML "; fi
        CB_TOOLS_USED="${CB_TOOLS_USED}Bash"

        if [ "$cb_failed" -gt 0 ]; then
            check_fail "Code block validation" "$cb_failed/$cb_total blocks invalid (checked: $CB_TOOLS_USED)"
            echo ""
        else
            if [ "$cb_total" -gt 0 ]; then
                check_pass "Code block validation ($cb_total blocks: $CB_TOOLS_USED)"
            else
                check_pass "Code block validation (no blocks found)"
            fi
        fi
    else
        check_skip "Code block validation" "only test fixture markdown files staged"
    fi
else
    check_skip "Code block validation" "no markdown files changed"
fi

# Summary
echo ""
echo "=========================================="
if [ "$CHECKS_FAILED" -gt 0 ]; then
    echo "${RED}[pre-commit] ${CHECKS_FAILED} check(s) FAILED, ${CHECKS_PASSED} passed${NC}"
    echo ""
    echo "${YELLOW}Fix the errors above before committing.${NC}"
    echo ""
    echo "${YELLOW}Quick fixes:${NC}"
    echo "  cargo fmt                                    # Fix formatting"
    echo "  cargo clippy --fix --allow-dirty            # Fix clippy warnings"
    echo "  ./scripts/check-markdown.sh fix             # Fix markdown"
    echo "  ./scripts/validate-ci.sh                    # Validate CI config"
    echo "  ./scripts/run-local-ci.sh --fix             # Fix all issues"
    echo ""
    echo "${YELLOW}To bypass (NOT recommended):${NC}"
    echo "  git commit --no-verify"
    echo ""
    exit 1
else
    echo "${GREEN}[pre-commit] All checks passed! (${CHECKS_PASSED} passed)${NC}"
    echo ""
    exit 0
fi
