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

(require 'syd-prelude)

(defvar syd-fixed-pitch-font
  (font-spec :family "VictorMono Nerd Font" :size 13)
  "Default fixed-pitch (monospace) font.")

(defvar syd-variable-pitch-font
  (font-spec :family "IBM Plex Serif" :size 15)
  "Default variable-pitch font.")

(defvar syd-alt-fixed-pitch-font
  (font-spec :family "JuliaMono" :size 16)
  "A monospace font secondary to `syd-fixed-pitch-font'.")

(defface syd-alt-fixed-pitch
  `((t (:inherit fixed-pitch :font ,syd-alt-fixed-pitch-font)))
  "TODO")

(defface syd-apl
  `((t (:inherit syd-alt-fixed-pitch)))
  "Face for APL code")

;; Beautiful theme in dark and light.
(use-package kanagawa-themes
  :config
  (load-theme 'kanagawa-wave t))

(defun syd-init-fonts-h ()
  "Loads `syd-fixed-pitch-font' and `syd-variable-pitch-font'."
  (dolist (map `((default . ,syd-fixed-pitch-font)
                 (fixed-pitch . ,syd-fixed-pitch-font)
                 (variable-pitch . ,syd-variable-pitch-font)))
    (pcase-let ((`(,face . ,font) map))
      (set-face-attribute face nil
                          :width 'normal :weight 'normal
                          :slant 'normal :font font))))

(defun syd-configure-default-frame-h ()
  "Customise the default frame, primarily by adding to `default-frame-alist'."
  ;; Maximise the frame.
  ;; (add-to-list 'default-frame-alist '(fullscreen . maximized))
  ;; Disable the titlebar and borders (decorations).
  (add-to-list 'default-frame-alist '(undecorated . t)))

(let ((hook (if (daemonp)
                'server-after-make-frame-hook
              'after-init-hook)))
  (add-hook hook #'syd-init-fonts-h)
  (add-hook hook #'syd-configure-default-frame-h))

;; Use JuliaMono as a fallback for some glyphs that VictorMono does not cover.
(dolist (char-range '((#x0250 . #x02af) ; IPA extensions
                      (#x2200 . #x22FF))) ; Mathematical operators
  (set-fontset-font "fontset-default" char-range "JuliaMono"))

(use-package emacs
  ;; Display (relative) line numbers only in prog-mode derivatives.
  :hook ((prog-mode-hook . display-line-numbers-mode)
         ;; (on-init-ui-hook . syd-configure-default-frame-h)
         ;; (on-init-ui-hook . syd-init-fonts-h)
         )
  :custom ((display-line-numbers-type 'relative)
           ;; Always ask "y/n"; never "yes/no".
           (use-short-answers t)
           ;; Scroll compilation buffer to follow output.
           (compilation-scroll-output t)
           ;; Allow `fit-window-to-buffer' to make horizontal adjustments.
           (fit-window-to-buffer-horizontally t)
           ;; I don't like that `grep' asks me to save unsaved files.  It makes
           ;; me think it's about to kill my buffers.
           (grep-save-buffers nil)
           ;; The default value is `ask', meaning that Emacs will ask for
           ;; confirmation any time you follow a symlink to a file under version
           ;; control.  The documentation claims this is "dangerous, and
           ;; probably not what you want;" I personally don't see it, and it's
           ;; usually what I want.
           (vc-follow-symlinks t)
           ;; Log native-compiler warnings, but don't display the
           ;; buffer.  Most of the warnings are "`X' is not known to
           ;; be defined" which are typically nothing worth concerning.
           (native-comp-async-report-warnings-errors 'silent)
           ;; Don't recenter the view unless >10 lines are scrolled off-screen
           ;; in a single movement.
           (scroll-conservatively 10)
           ;; In modes making use of `recenter-top-bottom' (e.g. Comint,
           ;; Eshell), this will make the command behave more like a plain old
           ;; `clear` invocation.
           (recenter-positions '(top)))
  :config
  ;; Disable the menu bar, scroll bar, and tool bar.
  (menu-bar-mode -1)
  (scroll-bar-mode -1)
  (tool-bar-mode -1)
  ;; Remember allowed risky variabled.
  (advice-add 'risky-local-variable-p :override #'ignore))

(defun syd-init-kill-process-buffer-on-exit ()
  (defun syd-comint--kill-buffer-sentinel (process _output)
    "Process sentinel to auto kill associated buffer once PROCESS dies."
    ;; Note that `process-exit-status' returns zero when the process has not yet
    ;; died.
    (unless (and process (zerop (process-exit-status process)))
      (let ((buffer (process-buffer process)))
        (with-current-buffer buffer
          (goto-char (point-max))
          (insert (format "\n[Process exited with exit status %d]"
                          (process-exit-status process))))
        (kill-buffer (process-buffer process)))))
  
  (defun syd-comint--add-kill-on-exit-sentinel ()
    "Replace current process sentinel with a new sentinel composed of the
current one and `syd-comint--kill-buffer-sentinel'."
    (let* ((process (get-buffer-process (current-buffer)))
           (original-sentinel (process-sentinel process))
           (sentinel-list
            (-remove #'null
                     (list original-sentinel #'syd-comint--kill-buffer-sentinel)))
           (combined-sentinel
            (lambda (process line)
              (--each sentinel-list
                (funcall it process line)))))
      (setf (process-sentinel process) combined-sentinel)))

  (defvar syd-comint--kill-on-exit-h-has-run nil
    "Whether or not `syd-comint--kill-on-exit-h' has run or not.  We need this
buffer-local var to prevent the hook from running several times, as can happen
for example when calling `shell'.")

  (defun syd-comint--async-funcall (fn &optional buffer args delay)
    "Run FUNCTION with ARGS in the buffer after a short DELAY."
    (run-at-time (or delay 0.2) nil
                 `(lambda ()
                    (with-current-buffer ,buffer ,(cons fn args)))))

  (syd-add-hook 'comint-mode-hook
    (defun syd-comint--kill-on-exit-h ()
      (unless syd-comint--kill-on-exit-h-has-run
        (setq-local syd-comint--kill-on-exit-h-has-run t)
        (syd-comint--async-funcall
         #'syd-comint--add-kill-on-exit-sentinel
         (current-buffer))))))

(with-eval-after-load 'comint
  (require 'syd-kanagawa)
  (custom-theme-set-faces
   'user
   ;; Default prompt face is very ugly.  Give it a more subtle look.
   `(comint-highlight-prompt
     ((t :foreground ,(syd-kanagawa-get 'old-white)
         :background unspecified
         :weight bold))))

  (general-def
    :keymaps 'comint-mode-map
    :states '(normal insert)
    "C-k" #'comint-previous-input
    "C-j" #'comint-next-input
    "C-s" #'consult-history)
  (general-def
    :keymaps 'comint-mode-map
    :states 'insert
    "C-d" #'comint-delchar-or-maybe-eof))

(syd-add-hook 'on-init-ui-hook
  (defun syd-init-ui-hacks ()
    (set-popup-rule! "*Backtrace*"
      :size #'doom-popup-shrink-to-fit)
    (set-popup-rule! "*Messages*"
      :ttl nil
      :quit t)
    (syd-init-kill-process-buffer-on-exit)
    ;; Required for :quit t to do anything.
    (evil-set-initial-state 'messages-buffer-mode 'motion)
    (evil-set-initial-state 'debugger-mode 'normal)
    (set-popup-rule! shell-command-buffer-name-async
      :slot -2
      :modeline nil
      :ttl nil)))

;; Display form-feed characters as horizontal lines.
(use-package form-feed-st
  :config
  ;; Set the colour of the horizontal lines to be the same as comments.
  (custom-theme-set-faces
   'user
   `(form-feed-st-line
     ((t :strike-through
         ,(plist-get (custom-face-attributes-get font-lock-comment-face
                                                 (selected-frame))
                     :foreground)))))
  (global-form-feed-st-mode))

(use-package fcitx
  :hook (on-first-input . fcitx-aggressive-setup)
  :custom ((fcitx-use-dbus 'fcitx5)
           (fcitx-remote-command "fcitx5-remote")))

(provide 'syd-ui)
