#!/usr/bin/env bash
# scripts/quality/cppcheck.sh
#
# Run cppcheck over the public headers.  Complementary to clang-tidy:
# cppcheck has different heuristics, fewer false-positives on heavy
# template code (CGAL/Eigen), and catches some bugs (unused includes,
# memory leaks in detail/) that clang-tidy is bad at.
#
# Local-only.  Promotion to CI when the existing tree is finding-free
# at the chosen severity level.
#
# Usage:
#   bash scripts/quality/cppcheck.sh           # error+warning only
#   bash scripts/quality/cppcheck.sh --strict  # +style, exit 1 on any
#   bash scripts/quality/cppcheck.sh --all     # absolute everything,
#                                                useful for diffs only
#
# Exit codes:
#   0  no findings at the chosen severity, or findings but not --strict
#   1  findings + --strict
#   2  prerequisite missing

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

command -v cppcheck >/dev/null 2>&1 || {
    echo "FAIL: cppcheck not in PATH." >&2
    echo "        macOS: brew install cppcheck" >&2
    echo "        Linux: sudo apt install cppcheck" >&2
    exit 2
}

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

ENABLE="warning"
if [ "$STRICT" -eq 1 ]; then ENABLE="warning,style"; fi
if [ "$ALL"    -eq 1 ]; then ENABLE="all"; fi

BUILD_DIR="build-cppcheck"
LOG="$BUILD_DIR/cppcheck.log"
mkdir -p "$BUILD_DIR"

echo "cppcheck ($(cppcheck --version 2>&1 | head -1))"
echo "  enable:  $ENABLE"
echo "  log:     $LOG"
echo

# Suppress noise classes that are not actionable in our project:
#   missingIncludeSystem — CGAL/Eigen/Boost headers are intentionally
#                          included implicitly; cppcheck cannot resolve.
#   unmatchedSuppression — cosmetic.
#   unusedFunction       — header-only; many `inline` helpers ARE used,
#                          cppcheck can't see across TUs.
#   normalCheckLevelMaxBranches — informational, not a finding.
#
# We point cppcheck at code/include/ only.  The deps tree is third-party
# code and out of scope.

cppcheck \
    --enable="$ENABLE" \
    --std=c++17 \
    --quiet \
    --error-exitcode=2 \
    --inline-suppr \
    --suppress=missingIncludeSystem \
    --suppress=unmatchedSuppression \
    --suppress=unusedFunction \
    --suppress=normalCheckLevelMaxBranches \
    -I code/include \
    code/include 2>&1 | tee "$LOG"
rc=$?

echo
echo "── Summary ──"
n=$(grep -cE "\[(error|warning|style|performance|portability)\]" "$LOG" || true)
echo "  total findings: $n"
echo "  full log:       $LOG"

if [ "$STRICT" -eq 1 ] && [ "$n" -gt 0 ]; then
    exit 1
fi
if [ "$rc" -eq 2 ] && [ "$STRICT" -ne 1 ]; then
    # cppcheck signalled "error" severity but caller didn't ask --strict.
    echo
    echo "NOTE: cppcheck reported an `error`-severity finding.  Even"
    echo "      without --strict, please review the log."
fi
exit 0
