#!/bin/sh
#
# Reindent C sources to the project's canonical style (cc-mode "stroustrup",
# 4-column offset, 8-column tabs). See AGENTS.md -> "Coding Style".
#
#   scripts/reindent.sh              reindent every tracked C source in place
#   scripts/reindent.sh FILE...      reindent only the named files
#   scripts/reindent.sh --check ...  report non-conforming files, change nothing
#
# Exit status: 0 if everything already conforms (or was reindented), 1 if
# --check found a file that needs reindenting, 2 on a usage or environment
# error.

set -eu

root=$(git rev-parse --show-toplevel)
elisp="$root/scripts/reindent.el"

# Directories whose C sources are hand-maintained. Anything outside these is
# vendored (deps/) or generated (include/akgl/SDL_GameControllerDB.h) and is
# left alone.
#
# `tools` and `examples` joined the list with the documentation harness. Both
# hold ordinary hand-written C -- the doc-example validators and the two
# tutorial games -- and while they were outside the scope neither `--check` nor
# the pre-commit hook could see them, so the one thing that keeps this tree a
# fixed point of `indent-region` was not being applied to its newest sources.
SCOPE='src include tests util tools examples'
GENERATED='include/akgl/SDL_GameControllerDB.h'

check=0
if [ "${1:-}" = "--check" ]; then
    check=1
    shift
fi

if ! command -v emacs >/dev/null 2>&1; then
    echo "reindent: emacs not found; cannot verify or apply the canonical style" >&2
    exit 2
fi
if [ ! -f "$elisp" ]; then
    echo "reindent: missing $elisp" >&2
    exit 2
fi

# Build the file list: explicit arguments, or every tracked C source in scope.
if [ "$#" -gt 0 ]; then
    files=$(for f in "$@"; do printf '%s\n' "$f"; done)
else
    files=$(cd "$root" && git ls-files $SCOPE | grep -E '\.[ch]$' || true)
fi

# Drop generated files and anything that no longer exists on disk.
files=$(printf '%s\n' "$files" | grep -v -x -F "$GENERATED" || true)
files=$(cd "$root" && for f in $files; do [ -f "$f" ] && printf '%s\n' "$f"; done)

if [ -z "$files" ]; then
    exit 0
fi

if [ "$check" -eq 0 ]; then
    # Reindent in place. A non-zero status here is a real failure -- never
    # swallow it, or a broken Emacs would look like "everything already conforms".
    # shellcheck disable=SC2086
    (cd "$root" && emacs --batch -Q -l "$elisp" -- $files) || {
        echo "reindent: emacs failed; no files were reindented" >&2
        exit 2
    }
    exit 0
fi

# --check: reindent throwaway copies and report which originals differ. The
# copies keep their extension so cc-mode still selects the right major mode.
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT INT TERM

# Accept both repo-relative and absolute paths: the default file list is
# relative, but callers (including the pre-commit hook) pass absolute ones.
abspath() {
    case "$1" in
        /*) printf '%s' "$1" ;;
        *)  printf '%s/%s' "$root" "$1" ;;
    esac
}

copies=''
for f in $files; do
    dest="$tmp/$(printf '%s' "$f" | tr '/' '_')"
    cp "$(abspath "$f")" "$dest"
    copies="$copies $dest"
done

# shellcheck disable=SC2086
emacs --batch -Q -l "$elisp" -- $copies >/dev/null || {
    echo "reindent: emacs failed; cannot determine whether sources conform" >&2
    exit 2
}

status=0
for f in $files; do
    dest="$tmp/$(printf '%s' "$f" | tr '/' '_')"
    if ! cmp -s "$(abspath "$f")" "$dest"; then
        echo "$f"
        status=1
    fi
done
exit $status
