;;; auto-capitalize-org.el --- Org support for auto-capitalize.el  -*- lexical-binding: t; -*-

;; Copyright (C) 2026  Abdulnafé Toulaïmat

;; Author: Abdulnafé Toulaïmat <abdulnafe.toulaimat@gmail.com>
;; Assisted-by: OpenCode:Big_Pickle

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; This plugin adds Org support to `auto-capitalize'.

;;; Code:

(require 'auto-capitalize)

(declare-function org-in-src-block-p "org")
(declare-function org-at-comment-p "org")
(declare-function org-at-item-p "org-list")
(defvar org-list-full-item-re)
(defvar org-todo-regexp)
(defvar org-priority-regexp)

(defgroup auto-capitalize-org nil
  "Org support for auto-capitalize."
  :group 'auto-capitalize)

(defcustom auto-capitalize-org-block-org-blocks t
  "Non-nil means to block capitalization of org blocks."
  :type 'boolean)

(defvar auto-capitalize-org--lighter "/Org"
  "Appended to `auto-capitalize--lighter' by `auto-capitalize-org-mode'.")

(defun auto-capitalize-org-blocking-function (_text-start _word-start)
  "Block capitalization in org mode if appropriate.

TEXT-START and WORD-START are the positions of the start of the
current text and the start of the current word, respectively.

Specifically, return non-nil to block capitalization if either:

1) Inside a src-block but not in a comment or a string

2) In a comment/string (either in src-blocks or not) and the
corresponding user option is nil

This predicate is added to `auto-capitalize-blocking-functions' (which
see)."
  (and (derived-mode-p 'org-mode)
       (or
        (and
         (or (not (nth 3 (syntax-ppss)))
             (not auto-capitalize-strings))

         (or (and (not (nth 4 (syntax-ppss)))
                  (not (org-at-comment-p)))
             (not auto-capitalize-comments))

         (or (nth 3 (syntax-ppss))
             (nth 4 (syntax-ppss))
             (org-at-comment-p)
             (org-in-src-block-p)))

        (and auto-capitalize-org-block-org-blocks
             (save-excursion
               (beginning-of-line)
               (looking-at "#\\+"))))))

(defun auto-capitalize-org-trigger-function (_text-start word-start)
  "Trigger capitalization in `org-mode' buffers.

Returns non-nil if WORD-START should be capitalized based on
org-specific context that the default trigger function cannot handle.

This function checks if WORD-START is either:

1) The first word of an org comment \(lines starting with `#'), since
org comments do not play nice with `bounds-of-thing-at-point' or
`start-of-paragraph-text'

2) The first word in an org heading after `org-todo-regexp' and
`org-priority-regexp'

3) The first word of an org list."

  (and (derived-mode-p 'org-mode)
       (or
        (and
         (org-at-comment-p)
         (= word-start
            (save-excursion
              (beginning-of-line)
              (skip-syntax-forward "^w")
              (point))))

        (save-excursion
          (beginning-of-line)
          (when (and (bound-and-true-p outline-regexp)
                     (looking-at outline-regexp))
            (goto-char (match-end 0))
            (when (and (bound-and-true-p org-todo-regexp)
                       ;; We match the first word following the space right
                       ;; after TODO-regexp
                       (looking-at (concat org-todo-regexp "\\(?: \\|$\\)")))
              (goto-char (match-end 0)))
            (when (looking-at org-priority-regexp)
              (goto-char (match-end 0)))
            (skip-syntax-forward "^w")
            (= word-start (point))))

        (and
         (org-at-item-p)
         (= word-start
            (save-excursion
              (beginning-of-line)
              (looking-at org-list-full-item-re)
              (goto-char
               (if (and (match-beginning 4)
                        (string-match-p "[.)]" (match-string 1)))
                   (match-beginning 4)
                 (match-end 0)))
              (point)))))))

;;;###autoload
(define-minor-mode auto-capitalize-org-mode
  "Toggle Org-specific capitalization support in this buffer.

When enabled, this mode adds Org-specific blocking and trigger functions
to `auto-capitalize-blocking-functions' and
`auto-capitalize-trigger-functions' buffer-locally, namely
`auto-capitalize-org-blocking-function' and
`auto-capitalize-org-trigger-function'.

If `auto-capitalize-mode' is not yet enabled in this buffer, it
will be enabled automatically."
  :lighter nil
  (cond
   ((not auto-capitalize-org-mode)
    (remove-hook 'auto-capitalize-blocking-functions
                 #'auto-capitalize-org-blocking-function t)
    (remove-hook 'auto-capitalize-trigger-functions
                 #'auto-capitalize-org-trigger-function t)
    (setq-local auto-capitalize--lighter
                (string-replace
                 auto-capitalize-org--lighter
                 ""
                 auto-capitalize--lighter)))

   (t
    (unless auto-capitalize-mode
      (auto-capitalize-mode 1)
      (message "auto-capitalize-mode enabled for Org support."))
    (add-hook 'auto-capitalize-blocking-functions
              #'auto-capitalize-org-blocking-function nil t)
    (add-hook 'auto-capitalize-trigger-functions
              #'auto-capitalize-org-trigger-function nil t)
    (setq-local auto-capitalize--lighter
                (concat auto-capitalize--lighter
                        auto-capitalize-org--lighter)))))

(provide 'auto-capitalize-org)
;;; auto-capitalize-org.el ends here
