#!/usr/bin/env bash
# scripts/quality/cmake-format.sh
#
# Run cmake-format (drift check) + cmake-lint (semantic check) over
# every CMakeLists.txt and *.cmake we own.  Skips code/deps/.
#
# Local-only.  Promotion to CI once the existing CMakeLists.txt files
# pass `--strict`.
#
# Usage:
#   bash scripts/quality/cmake-format.sh           # dry-run, exit 0 always
#   bash scripts/quality/cmake-format.sh --strict  # dry-run, exit 1 on drift
#   bash scripts/quality/cmake-format.sh --fix     # apply formatting in place
#
# Exit codes:
#   0  no drift, or drift but --strict not set; lint produced no errors
#   1  drift and --strict, or lint reported errors, or --fix applied
#   2  prerequisite missing

set -uo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$ROOT" || exit 2

# Allow ~/.local/bin (pip-installed tools) in PATH.
export PATH="$HOME/.local/bin:$PATH"

command -v cmake-format >/dev/null 2>&1 || {
    echo "FAIL: cmake-format not in PATH." >&2
    echo "        pip3 install --user cmakelang" >&2
    echo "        export PATH=\"\$HOME/.local/bin:\$PATH\"" >&2
    exit 2
}
command -v cmake-lint >/dev/null 2>&1 || {
    echo "FAIL: cmake-lint not in PATH (ships with cmakelang)." >&2
    exit 2
}

STRICT=0
FIX=0
for arg in "$@"; do
    case "$arg" in
        --strict) STRICT=1 ;;
        --fix)    FIX=1 ;;
        *) echo "Unknown arg: $arg" >&2; exit 2 ;;
    esac
done

FILES="$(find code \
    \( -name "CMakeLists.txt" -o -name "*.cmake" \) \
    -type f 2>/dev/null \
    | grep -v "/deps/" \
    | grep -v "/build" \
    | grep -v " 2\." \
    | sort)"

if [ -z "$FILES" ]; then
    echo "FAIL: no CMakeLists.txt files found" >&2
    exit 2
fi

echo "cmake-format ($(cmake-format --version 2>&1 | head -1))"
echo "cmake-lint   ($(cmake-lint --version 2>&1 | head -1))"

# ── Drift check / fix ──────────────────────────────────────────────────────
n_total=0
n_drift=0
drift_list=""
while IFS= read -r f; do
    [ -z "$f" ] && continue
    n_total=$((n_total + 1))
    if [ "$FIX" -eq 1 ]; then
        cmake-format -i "$f"
    else
        if ! cmake-format --check "$f" >/dev/null 2>&1; then
            n_drift=$((n_drift + 1))
            drift_list="$drift_list  $f
"
        fi
    fi
done <<EOF
$FILES
EOF

if [ "$FIX" -eq 1 ]; then
    echo "FIX mode: applied formatting in place."
    exit 1   # signal to caller that the tree changed
fi

if [ "$n_drift" -gt 0 ]; then
    echo
    echo "DRIFT: $n_drift / $n_total CMake file(s) do not match .cmake-format.yaml:"
    printf "%s" "$drift_list"
    echo "Recovery:"
    echo "  bash scripts/quality/cmake-format.sh --fix   # apply"
    if [ "$STRICT" -eq 1 ]; then
        exit 1
    fi
fi

# ── Semantic lint (always runs, never fails unless --strict) ───────────────
echo
echo "── cmake-lint ──"
n_lint_err=0
while IFS= read -r f; do
    [ -z "$f" ] && continue
    out="$(cmake-lint "$f" 2>&1 || true)"
    if [ -n "$out" ]; then
        echo "$out"
        # Each warning line begins with the filename → count them.
        cnt=$(printf '%s' "$out" | grep -c "^$f:" || true)
        n_lint_err=$((n_lint_err + cnt))
    fi
done <<EOF
$FILES
EOF

echo
echo "── Summary ──"
echo "  CMake files checked: $n_total"
echo "  cmake-format drift:  $n_drift  (recover: --fix)"
echo "  cmake-lint findings: $n_lint_err"

if [ "$STRICT" -eq 1 ] && [ $((n_drift + n_lint_err)) -gt 0 ]; then
    exit 1
fi
exit 0
