;;; reindent.el --- batch reindent to cc-mode "stroustrup"  -*- lexical-binding: t -*-

;; Reindents each file named on the command line, in place, to the project's
;; canonical style: cc-mode "stroustrup", 4 columns per level, tabs 8 columns
;; wide. See AGENTS.md -> "Coding Style".
;;
;; The style is set explicitly here rather than read from .dir-locals.el so that
;; the result does not depend on where the file lives -- the pre-commit hook
;; runs this over temporary copies outside the project tree.
;;
;; Usage: emacs --batch -Q -l scripts/reindent.el -- FILE...

;;; Code:

(require 'cc-mode)

(setq make-backup-files nil
      create-lockfiles nil
      auto-save-default nil
      vc-handled-backends nil
      inhibit-message t)

(defun akgl-retab-leading-whitespace ()
  "Rewrite every line's leading whitespace as tabs-then-spaces at `tab-width'.

`indent-region' fixes the column a line starts at, but `indent-line-to' leaves
a line alone when it is already at the right column even if the bytes are
wrong -- eight spaces where the canonical form is one tab. This pass closes
that gap.

Deliberately not `tabify': that function's `tabify-regexp' is \" [ \\t]+\",
which is NOT anchored to the line start, so it rewrites runs of spaces
anywhere on the line and destroys the hand-aligned columns in the bit-flag
tables in actor.h and iterator.h. Only leading whitespace is touched here, and
lines beginning inside a string literal are skipped outright."
  (goto-char (point-min))
  (while (not (eobp))
    (let ((bol (point)))
      (unless (nth 3 (syntax-ppss bol))
        (skip-chars-forward " \t" (line-end-position))
        (let ((col (current-column)))
          (unless (or (zerop col) (eolp))
            (let ((want (concat (make-string (/ col tab-width) ?\t)
                                (make-string (% col tab-width) ?\s))))
              (unless (string= want (buffer-substring bol (point)))
                (delete-region bol (point))
                (insert want)))))))
    (forward-line 1)))

(defun akgl-reindent-file (path)
  "Reindent PATH in place. Returns t if the file changed on disk."
  (let ((before (with-temp-buffer
                  (insert-file-contents path)
                  (buffer-string))))
    (with-current-buffer (find-file-noselect path t)
      (c-mode)
      (c-set-style "stroustrup")
      (setq indent-tabs-mode t
            tab-width 8
            c-basic-offset 4
            require-final-newline t)
      ;; 1. Put every line at its correct column.
      (indent-region (point-min) (point-max))
      ;; 2. Convert leading whitespace to the canonical tab/space mix.
      (akgl-retab-leading-whitespace)
      ;; 3. Trailing whitespace and a single final newline.
      (delete-trailing-whitespace)
      (goto-char (point-max))
      (unless (bolp) (insert "\n"))
      (let ((changed (not (string= before (buffer-string)))))
        (when changed (save-buffer))
        (kill-buffer)
        changed))))

;; Emacs leaves the "--" separator in `command-line-args-left'; drop it, along
;; with any empty argument, so the remainder is exactly the file list.
(dolist (path (seq-remove (lambda (a) (or (string= a "--") (string= a "")))
                          command-line-args-left))
  (when (akgl-reindent-file path)
    (princ (format "reindented %s\n" path))))

;; Exit 0 on success whether or not anything changed, so that any non-zero
;; status from this script means a real failure. Callers detect "something
;; changed" from stdout, or by comparing files themselves.
(kill-emacs 0)

;;; reindent.el ends here
