;;; -*- lexical-binding: t; -*-

(require 'syd/base)


;;; Hide commands in M-x which do not work in the current mode

(setq read-extended-command-predicate
      #'command-completion-default-include-p)


;;; Consult

;;;###autoload
(defun syd-enable-recentf-mode-a ()
  "Used as :before advice for `consult-recent-file' to ensure recentf mode is
enabled."
  ;; REVIEW: Shall we take any extra precautions to only enable `recentf-mode'
  ;; if it was going to be enabled anyway?
  (recentf-mode 1))

;; Consult adds various search and navigation tools using Emacs' completing-read
;; function; i.e., in our case, Vertico.
(use-package consult
  :defer t
  :config
  (advice-add #'consult-recent-file
              :before #'syd-enable-recentf-mode-a))

(use-package embark-consult
  :after (embark consult))


;;; Vertico

;;;###autoload
(defun syd-vertico-embark-preview ()
  "Previews candidate in vertico buffer, unless it's a consult command"
  (interactive)
  (unless (bound-and-true-p consult--preview-function)
    (if (fboundp 'embark-dwim)
        (save-selected-window
          (let (embark-quit-after-action)
            (embark-dwim)))
      (user-error "Embark not installed, aborting..."))))

;; Vertico is a simple completion engine that replaces Emacs' built-in
;; completion engine, achieving Just Works™ compatibility.  This is in contrast
;; to e.g. Helm and Ivy, which spawn ecosystems orthogonal to Emacs, and
;; diametrically-opposed to each other.
(use-package vertico
  :hook (on-first-input . vertico-mode)
  :general
  (:keymaps 'vertico-map
            "DEL"   #'vertico-directory-delete-char
            "C-SPC" #'syd-vertico-embark-preview
            "C-j"   #'vertico-next
            "C-k"   #'vertico-previous
            "C-M-j" #'vertico-next-group
            "C-M-k" #'vertico-previous-group)
  (:keymaps 'vertico-map
   :states 'normal
   "j" #'vertico-next
   "k" #'vertico-previous
   "RET" #'vertico-exit)
  :custom ((vertico-resize nil)
           (vertico-count 17)
           (vertico-cycle t))
  :config
  (setq-default completion-in-region-function #'consult-completion-in-region)
  ;; Cleans up path when moving directories with shadowed paths syntax, e.g.
  ;; cleans ~/foo/bar/// to /, and ~/foo/bar/~/ to ~/.
  (add-hook 'rfn-eshadow-update-overlay-hook #'vertico-directory-tidy))

(defun syd-vertico-orderless-dispatch (pattern _index _total)
  "Like `orderless-affix-dispatch', but allows affixes to be escaped.

Shamelessly stolen from Doom. }:3"
  (let ((len (length pattern))
        (alist orderless-affix-dispatch-alist))
    (when (> len 0)
      (cond
       ;; Ignore single dispatcher character
       ((and (= len 1) (alist-get (aref pattern 0) alist)) #'ignore)
       ;; Prefix
       ((when-let ((style (alist-get (aref pattern 0) alist))
                   ((not (char-equal (aref pattern (max (1- len) 1)) ?\\))))
          (cons style (substring pattern 1))))
       ;; Suffix
       ((when-let ((style (alist-get (aref pattern (1- len)) alist))
                   ((not (char-equal (aref pattern (max 0 (- len 2))) ?\\))))
          (cons style (substring pattern 0 -1))))))))


;;; Orderless

;; Orderless provides a completion style that divides the pattern into
;; space-separated components, and matches candidates that match all of the
;; components in any order.  Each component can match in any one of several
;; ways: literally, as a regexp, as an initialism, in the flex style, or as
;; multiple word prefixes.  By default, regexp and literal matches are enabled.
(use-package orderless
  :after vertico
  :custom ((completion-styles '(orderless basic))
           (completion-category-overrides
            '((file
               (styles                  ;basic-remote
                orderless partial-completion))))
           (orderless-style-dispatchers '(syd-vertico-orderless-dispatch))
           ;; TODO: See Doom's `+vertico-orderless-dispatch'.
           (orderless-affix-dispatch-alist
            '((?! . orderless-without-literal)
              (?& . orderless-annotation)
              ;; %1 -> {1, ₁, ꘡, ⒈, ...}
              (?% . char-fold-to-regexp)
              ;; ,wcb -> {with-current-buffer, widget-convert-button, ...}
              (?, . orderless-initialism)
              (?= . orderless-literal)
              (?^ . orderless-literal-prefix)
              (?~ . orderless-flex)))))



;;;###autoload
(defun embark-which-key-indicator ()
  "An embark indicator that displays keymaps using which-key.  The which-key
help message will show the type and value of the current target followed by an
ellipsis if there are further targets."
  (lambda (&optional keymap targets prefix)
    (if (null keymap)
        (which-key--hide-popup-ignore-command)
      (which-key--show-keymap
       (if (eq (plist-get (car targets) :type) 'embark-become)
           "Become"
         (format "Act on %s '%s'%s"
                 (plist-get (car targets) :type)
                 (embark--truncate-target (plist-get (car targets) :target))
                 (if (cdr targets) "…" "")))
       (if prefix
           (pcase (lookup-key keymap prefix 'accept-default)
             ((and (pred keymapp) km) km)
             (_ (key-binding prefix 'accept-default)))
         keymap)
       nil nil t (lambda (binding)
                   (not (string-suffix-p "-argument" (cdr binding))))))))

;; TODO: Mark the Embark export buffer as a popup.
(use-package embark
  :after vertico
  :defer t
  :custom ((which-key-use-C-h-commands nil)
           (prefix-help-command #'embark-prefix-help-command)
           ;; Embark uses their own custom interface that is essentially
           ;; equivalent to which-key; just use which-key. }:)
           (embark-indicators '(embark-which-key-indicator)))
  :general
  (:keymaps 'minibuffer-local-map
            "C-;" `("Actions" . ,#'embark-act))
  (:keymaps 'syd-leader-map
            "a" `("Actions" . ,#'embark-act)))



(provide 'syd/completion)
