;;; denote-smart-sluggify-title.el --- Denoise denote titles to save filename length  -*- lexical-binding: t; -*-

;; Copyright (C) 2026  Samuel W. Flint

;; Author: Samuel W. Flint <swflint@flintfam.org>
;; Keywords: convenience, denote
;; Package-Version: 1.1.2
;; Package-Revision: 87c60e5e03c2
;; Homepage: https://git.sr.ht/~swflint/denote-smart-sluggify-title
;; Package-Requires: ((emacs "25.1") (denote "4.0.0"))

;; 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 package improves title sluggification to save on filename
;; length.  This is done through one primary function: stopword
;; removal, otherwise, the behavior is similar to the standard
;; approach.  Title sluggification is performed as follows.
;;
;; 1. The title is filtered using
;;    `denote-smart-sluggify-title-pre-filter', which is intended to
;;    be advised with both `:filter-args' and `:filter-return' advice.
;; 2. Non-word symbols are removed, based on
;;    `denote-smart-sluggify-title-symbols-regexp'.
;; 3. Stopwords listed in `denote-smart-sluggify-title-stopwords' are
;;    then removed.
;; 4. The name is hyphenated using `denote-slug-hyphenate'.
;; 5. The name is further filtered/modified using
;;    `denote-smart-sluggify-title-post-filter', similar to
;;    `denote-smart-sluggify-title-pre-filter'.
;; 6. Finally, the case is converted based on
;;    `denote-smart-sluggify-title-downcase'.  The default behavior is
;;    to downcase titles.
;;
;; This behavior may be enabled through the use of the function
;; `denote-smart-sluggify-title-insinuate'.

;;; Code:

(require 'denote)
(require 'cl-lib)
(require 'ucs-normalize)

(defgroup denote-smart-sluggify-title '()
  "Denoise Denote File Titles."
  :group 'denote)

(defcustom denote-smart-sluggify-title-stopwords
  (list "i" "a" "about" "an" "are" "as" "at" "be" "by" "com" "for"
        "from" "how" "in" "is" "it" "of" "on" "or" "that" "the" "this"
        "to" "was" "what" "when" "where" "who" "will" "with" "the" "www")
  "List of stopwords to remove in file titles."
  :group 'denote-smart-sluggify-title
  :type '(repeat string))

(defcustom denote-smart-sluggify-title-symbols-regexp
  "[][{}!@#$%^&*()+'\"?,.\|;:~`‘’“”/=]*"
  "Regular expression of symbols to remove in title slugs."
  :group 'denote-smart-sluggify-titles
  :type 'regexp)

(defcustom denote-smart-sluggify-title-downcase t
  "Whether titles should be downcased after sluggification."
  :group 'denote-smart-sluggify-title
  :type 'boolean)

(defcustom denote-smart-sluggify-title-fold-chars nil
  "Whether non-ASCII characters should be folded."
  :group 'denote-smart-sluggify-title
  :type 'boolean)

(defun denote-smart-sluggify-title-pre-filter (string)
  "Filter STRING before symbol removal.

This function by default will do nothing.  To modify its behavior, it is
recommended to use `:filter-return' or `:filter-args' advice.  Note at
each stage, a single string must be produced."
  string)

(defun denote-smart-sluggify-title-post-filter (string)
  "Filter STRING after title hyphenation.

This function by default will do nothing.  To modify its behavior, it is
recommended to use `:filter-return' or `:filter-args' advice.  Note at
each stage, a single string must be produced."
  string)

(defun denote-smart-sluggify-title--fold-chars (str)
  "Optionally fold characters in STR per Unicode NFKD."
  (if denote-smart-sluggify-title-fold-chars
      (cl-remove-if (lambda (c) (> c 127))
                    (ucs-normalize-NFKD-string str))
    str))

(defun denote-smart-sluggify-title (title)
  "Sluggify TITLE for Denote filename construction.

Title sluggification is performed as follows.

 1. The title is filtered using
    `denote-smart-sluggify-title-pre-filter', which is intended to
    be advised with both `:filter-args' and `:filter-return' advice.

 2. Non-word symbols are removed, based on
   `denote-smart-sluggify-title-symbols-regexp'.

 3. Stopwords listed in `denote-smart-sluggify-title-stopwords' are then
    removed.

 4. The name is hyphenated using `denote-slug-hyphenate'.

 5. The name is further filtered/modified using
    `denote-smart-sluggify-title-post-filter', similar to
    `denote-smart-sluggify-title-pre-filter'.

 6. Finally, the case is converted based on
    `denote-smart-sluggify-title-downcase'.  The default behavior is to
    downcase titles."
  (let* ((pre-filtered-title (denote-smart-sluggify-title-pre-filter title))
         (desymboled-title (replace-regexp-in-string denote-smart-sluggify-title-symbols-regexp ""
                                                     (denote-smart-sluggify-title--fold-chars pre-filtered-title)))
         (title-no-stopwords (replace-regexp-in-string
                              (rx-to-string (list 'and 'bow
                                                  (cons 'or denote-smart-sluggify-title-stopwords)
                                                  'eow))
                              ""
                              desymboled-title
                              nil))
         (hyphenated-title (denote-slug-hyphenate title-no-stopwords))
         (post-filtered-title (denote-smart-sluggify-title-post-filter hyphenated-title)))
    (if denote-smart-sluggify-title-downcase
        (downcase post-filtered-title)
      post-filtered-title)))

(defun denote-smart-sluggify-title-insinuate ()
  "Set Denote's title sluggification to `denote-smart-sluggify-title'.

For information, see `denote-file-name-slug-functions', and
`denote-sluggify-and-apply-rules'."
  (setf denote-file-name-slug-functions
        (cons (cons 'title #'denote-smart-sluggify-title)
              (seq-filter (lambda (x) (not (eq (car x) 'title))) denote-file-name-slug-functions))))

(provide 'denote-smart-sluggify-title)
;;; denote-smart-sluggify-title.el ends here
