;;; init.el --- bandali's emacs configuration -*- lexical-binding: t -*-

;; Copyright (c) 2018-2025 Amin Bandali <bandali@gnu.org>

;; 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:

;; bandali's opinionated GNU Emacs configs.  I tend to use the latest
;; development trunk of emacs.git, but I try to maintain backward
;; compatibility with a few of the recent older GNU Emacs releases
;; so I could easily reuse it on machines stuck with older Emacsen.

;;; Code:


;;; Initial setup

(setq
 debug-on-error init-file-debug
 debug-on-quit init-file-debug)

;; whoami
(setq
 user-full-name "Amin Bandali"
 user-mail-address "bandali@kelar.org")

(eval-and-compile
  (defsubst b/emacs.d (path)
    "Expand path PATH relative to `user-emacs-directory'."
    (expand-file-name
     (convert-standard-filename path) user-emacs-directory))

  ;; Wrappers around the new keybinding functions, with fallback to
  ;; the corresponding older lower level function on older Emacsen.
  (defsubst b/keymap-set (keymap key definition)
    (if (version< emacs-version "29")
        (define-key keymap (kbd key) definition)
      (keymap-set keymap key definition)))
  (defsubst b/keymap-global-set (key command)
    (if (version< emacs-version "29")
        (global-set-key (kbd key) command)
      (keymap-global-set key command)))
  (defsubst b/keymap-local-set (key command)
    (if (version< emacs-version "29")
        (local-set-key (kbd key) command)
      (keymap-local-set key command)))
  (defsubst b/keymap-global-unset (key)
    (if (version< emacs-version "29")
        (global-unset-key (kbd key))
      (keymap-global-unset key 'remove)))
  (defsubst b/keymap-local-unset (key)
    (if (version< emacs-version "29")
        (local-unset-key (kbd key))
      (keymap-local-unset key 'remove)))

  (when (version< emacs-version "29")
    ;; Emacs 29 introduced the handy `setopt' macro for setting user
    ;; options (defined with `defcustom') with a syntax similar to
    ;; `setq'.  So, we define it on older Emacsen that don't have it.
    (defmacro setopt (&rest pairs)
      "Set VARIABLE/VALUE pairs, and return the final VALUE.
This is like `setq', but is meant for user options instead of
plain variables.  This means that `setopt' will execute any
`custom-set' form associated with VARIABLE.

\(fn [VARIABLE VALUE]...)"
      (declare (debug setq))
      (unless (zerop (mod (length pairs) 2))
        (error "PAIRS must have an even number of variable/value members"))
      (let ((expr nil))
        (while pairs
          (unless (symbolp (car pairs))
            (error "Attempting to set a non-symbol: %s" (car pairs)))
          (push `(setopt--set ',(car pairs) ,(cadr pairs))
                expr)
          (setq pairs (cddr pairs)))
        (macroexp-progn (nreverse expr))))

    (defun setopt--set (variable value)
      (custom-load-symbol variable)
      ;; Check that the type is correct.
      (when-let ((type (get variable 'custom-type)))
        (unless (widget-apply (widget-convert type) :match value)
          (warn "Value `%S' does not match type %s" value type)))
      (put variable 'custom-check-value (list value))
      (funcall (or (get variable 'custom-set) #'set-default) variable value))))

;; Separate custom file (don't want it mixing with init.el).
(setopt custom-file (b/emacs.d "custom.el"))
(with-eval-after-load 'custom
  (load custom-file 'noerror))


;;; Package management

(require 'package)
(package-initialize)

(setopt
 ;; Explicitly set `package-archives', in part to ensure https ones
 ;; are used, and also to have NonGNU ELPA on older Emacsel as well.
 package-archives
 '(("gnu" . "https://elpa.gnu.org/packages/")
   ("nongnu" . "https://elpa.nongnu.org/nongnu/")
   ("melpa" . "https://melpa.org/packages/"))
 package-archive-priorities
 '(("gnu" . 3)
   ("nongnu" . 2)
   ("melpa" . 1))
 ;; List of the packages I use from GNU ELPA and NonGNU ELPA.
 package-selected-packages
 '(delight
   debbugs
   elpher
   eat
   vertico marginalia orderless corfu consult embark embark-consult
   wgrep
   slack))

(unless package-archive-contents
  (package-refresh-contents))

(package-install-selected-packages)


;;; Emacs server

;; Start Emacs server.
;; https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.html
(run-with-idle-timer 0.5 nil #'require 'server)
(with-eval-after-load 'server
  (declare-function server-edit "server")
  (b/keymap-global-set "C-c F D" #'server-edit)
  (declare-function server-running-p "server")
  (or (server-running-p) (server-mode)))


;;; Essential packages

(add-to-list 'load-path (b/emacs.d "lisp"))

(require 'bandali-essentials)
(require 'bandali-utils)
(require 'bandali-prog)
;; (require 'bandali-exwm)
(require 'bandali-ibuffer)
(require 'bandali-dired)
;; Email with Gnus and message
(require 'bandali-gnus)
(require 'bandali-message)
;; (with-eval-after-load 'sendmail
;;   (setopt mail-header-separator ""))
;; (with-eval-after-load 'smtpmail
;;   (setopt smtpmail-queue-mail t
;;           smtpmail-queue-dir (concat b/maildir "queue/")))
;; IRC with ERC
(require 'bandali-erc)
(require 'bandali-misc)
(require 'bandali-po)

;;; init.el ends here