;;; syd-general.el -*- lexical-binding: t; -*-

(use-package general
  :custom (general-use-package-emit-autoloads t))

(require 'general)

(defvar syd-leader-key "SPC"
  "A prefix key akin to Vim's <Leader>.")

(defvar syd-localleader-key "SPC m"
  "A prefix key akin to Vim's <LocalLeader>.")

(defvar syd-alt-leader-key "M-SPC"
  "`syd-leader', but for the states specified in `syd-alt-leader-key-states'.

Often, your \"usual\" leader key will be something unavailable in the Insert
state.  This key exists as a fallback for when you need your Leader, but must
remain in the Insert state.  Substitute \"Insert state\" for your states of
choice with `syd-alt-leader-key-states'.")

(defvar syd-alt-localleader-key "M-SPC m"
  "`syd-localleader', but for the states specified in `syd-alt-leader-key-states'.

See `syd-alt-leader-key' for rationale.")

(defvar syd-leader-key-states '(normal visual motion)
  "States for which the Leader keys (`syd-leader-key', `syd-localleader-key')
are active.")

(defvar syd-alt-leader-key-states '(emacs insert)
  "States for which the alternative Leader keys are active.  See
`syd-alt-leader-key' and `syd-alt-localleader-key'.")

(defvar-keymap syd-leader-map
  :doc "Leader-prefixed commands")

(defun syd-initialise-leader ()
  "Set up the (empty) keymap associated with `syd-leader-key',
`syd-localleader-key', `syd-alt-leader-key', and `syd-alt-localleader-key'."
  (require 'evil)
  ;; Define `syd/leader' as a command corresponding to the prefix map
  ;; `syd-leader-map'.
  (define-prefix-command 'syd/leader 'syd-leader-map)
  ;; This should help make the Leader key close to universally available.
  ;; Ideally, *nothing* takes precedence over Leader — it's an incredibly
  ;; important key!
  ;; https://github.com/noctuid/evil-guide?tab=readme-ov-file#undoprevent-overridingintercept-maps
  ;; See `evil-make-overriding-map'.
  (define-key syd-leader-map [override-state] 'all)
  ;; Finally, we shall bind the damned keys. }:)
  (let ((map general-override-mode-map))
    (evil-define-key* syd-leader-key-states map (kbd syd-leader-key) 'syd/leader)
    (evil-define-key* syd-alt-leader-key-states map (kbd syd-alt-leader-key) 'syd/leader))
  (general-override-mode 1))

(defvar syd-escape-hook nil
  "A hook run when C-g is pressed (or ESC in Evil's normal state).

More specifically, when `syd/escape' is pressed.  If any hook returns non-nil,
all hooks after it are ignored.")

;;
;;; Universal, non-nuclear escape

;; `keyboard-quit' is too much of a nuclear option. I want ESC/C-g to
;; do-what-I-mean.  It serves four purposes (in order):
;;
;; 1. Quit active states; e.g. highlights, searches, snippets, iedit,
;;    multiple-cursors, recording macros, etc.
;; 2. Close popup windows remotely (if it is allowed to)
;; 3. Refresh buffer indicators, like diff-hl and flycheck
;; 4. Or fall back to `keyboard-quit'
;;
;; And it should do these things incrementally, rather than all at once. And it
;; shouldn't interfere with recording macros or the minibuffer.  This may
;; require you press ESC/C-g two or three times on some occasions to reach
;; `keyboard-quit', but this is much more intuitive.

(defun syd/escape (&optional interactive)
  "Run `syd-escape-hook'."
  (interactive (list 'interactive))
  (let ((inhibit-quit t))
    (cond ((minibuffer-window-active-p (minibuffer-window))
           ;; quit the minibuffer if open.
           (when interactive
             (setq this-command 'abort-recursive-edit))
           (abort-recursive-edit))
          ;; Run all escape hooks. If any returns non-nil, then stop there.
          ((run-hook-with-args-until-success 'syd-escape-hook))
          ;; don't abort macros
          ((or defining-kbd-macro executing-kbd-macro) nil)
          ;; Back to the default
          ((unwind-protect (keyboard-quit)
             (when interactive
               (setq this-command 'keyboard-quit)))))))

(with-eval-after-load 'eldoc
  (eldoc-add-command 'syd/escape))

;; In normal state, pressing escape should run `syd-escape-hook'.
(with-eval-after-load 'evil
  (defun evil-syd/escape-a (&rest _)
    "Call `syd/escape' if `evil-force-normal-state' is called interactively."
    (when (called-interactively-p 'any)
      (call-interactively #'syd/escape)))
  (advice-add #'evil-force-normal-state
              :after #'evil-syd/escape-a))

(provide 'syd-general)
