;;; -*- lexical-binding: t -*-
;;; SPDX-FileCopyrightText: 2025 Hilton Chain <hako@ultrarare.space>
;;;
;;; SPDX-License-Identifier: GPL-3.0-or-later

(setopt custom-file (locate-user-emacs-file "custom.el"))
(if (not (file-exists-p custom-file))
    (make-empty-file custom-file)
  (load custom-file))

(let ((font-config (locate-user-emacs-file "fonts.el")))
  (when (file-exists-p font-config)
    (load-file font-config)))

;; Tweak garbage collection strategy.
;;guix:emacs-gcmh
(use-package gcmh
  :config
  (gcmh-mode 1))

;; Set default storage locations for various packages.
;;guix:emacs-no-littering
(use-package no-littering
  :config
  (no-littering-theme-backups))

(use-package emacs
  :custom
  ;; Workaround to use fish as login shell.
  (shell-file-name "/bin/sh")
  ;; CJK support.
  (word-wrap-by-category t))


;;;
;;; Interface
;;;

(use-package emacs
  :custom
  (blink-cursor-mode nil)
  (browse-url-firefox-program "librewolf")
  (enable-recursive-minibuffers t)
  (inhibit-splash-screen t)
  (uniquify-buffer-name-style 'forward)
  ;; Exclude unavailable completions.
  (read-extended-command-predicate 'command-completion-default-include-p)
  ;; Case-insensitive completion.
  (completion-ignore-case t)
  (read-buffer-completion-ignore-case t)
  (read-file-name-completion-ignore-case t)
  :bind
  ([remap list-buffers] . switch-to-buffer)
  :hook
  (prog-mode . display-line-numbers-mode)
  ;; Scrolling enhancement.
  (after-init . pixel-scroll-precision-mode)
  ;; Indicatior for recursive minibuffers.
  (after-init . minibuffer-depth-indicate-mode)
  ;; Save minibuffer history.
  (after-init . savehist-mode)
  ;; Indicator for `fill-column'.
  (prog-mode . display-fill-column-indicator-mode))

;; Theming
(use-package emacs
  :custom
  (fringe-mode 0)
  (modus-themes-italic-constructs t)
  (scroll-bar-mode nil)
  (tool-bar-mode nil)
  (tooltip-mode nil)
  :config
  (load-theme 'modus-operandi-tinted :no-confirm)
  :hook
  (after-init . menu-bar-mode))

;;guix:emacs-corfu
(use-package corfu
  :custom
  ;; Auto-completion.
  (corfu-auto t)
  (corfu-auto-delay 0.3)
  :config
  ;; Free the `RET' key for less intrusive behavior.
  (keymap-unset corfu-map "RET")
  :hook
  (after-init . global-corfu-mode))

;;guix:emacs-doom-modeline
(use-package doom-modeline
  :custom
  (doom-modeline-icon nil)
  (doom-modeline-height 18)
  :hook
  (after-init . doom-modeline-mode))

;;guix:emacs-helpful
(use-package helpful
  :bind
  (([remap describe-function] . helpful-callable)
   ([remap describe-variable] . helpful-variable)
   ([remap describe-key]      . helpful-key)
   ([remap describe-command]  . helpful-command)
   ([remap describe-symbol]   . helpful-symbol)
   ("C-c C-d" . helpful-at-point)))

;;guix:emacs-hl-todo
(use-package hl-todo
  :custom
  (hl-todo-highlight-punctuation ":")
  :hook
  (prog-mode . hl-todo-mode))

(use-package isearch
  :custom
  (isearch-lazy-count t)
  (isearch-wrap-pause 'no-ding)
  (regexp-search-ring-max 200)
  (search-ring-max 200))

;;guix:emacs-mwim
(use-package mwim
  :bind
  (([remap move-beginning-of-line] . mwim-beginning)
   ([remap move-end-of-line] . mwim-end)))

;;guix:emacs-orderless
(use-package orderless
  :custom
  (completion-styles '(orderless basic))
  (completion-category-overrides '((file (styles basic partial-completion))))
  (orderless-matching-styles '(orderless-literal orderless-flex orderless-regexp)))

(use-package paren
  :custom
  (show-paren-context-when-offscreen 'overlay)
  :hook
  (after-init . show-paren-mode))

;;guix:emacs-rainbow-delimiters
(use-package rainbow-delimiters
  :hook
  (prog-mode . rainbow-delimiters-mode))

;;guix:emacs-vertico
(use-package vertico
  :hook
  (after-init . vertico-mode))

(use-package vertico-directory
  :after (vertico)
  :bind
  (:map vertico-map
        ("RET" . vertico-directory-enter)
        ("DEL" . vertico-directory-delete-char)
        ("M-DEL" . vertico-directory-delete-word))
  :hook
  (rfn-eshadow-update-overlay . vertico-directory-tidy))

(use-package which-key
  :config
  (which-key-setup-side-window-right-bottom)
  :hook
  (after-init . which-key-mode))


;;;
;;; Editing
;;;

(use-package emacs
  :custom
  ;; Disable tab indentation.
  (indent-tabs-mode nil)
  :config
  ;; Avoid re-indenting current line after entering `RET'.
  (setopt electric-indent-inhibit t)
  :hook
  (before-save . delete-trailing-whitespace)
  ;; Use Ctrl-C/X/Z for copy, cut, paste.
  (after-init . cua-mode)
  ;; Automatic parenthesis pairing.
  (after-init . electric-pair-mode))

;; Check syntax on the fly.
;;guix:emacs-flycheck
(use-package flycheck
  :hook
  (after-init . global-flycheck-mode))

;;guix:emacs-flycheck-guile
(use-package flycheck-guile
  :after (flycheck geiser-guile))

;;guix:emacs-geiser
(use-package geiser
  :custom
  (geiser-autodoc-identifier-format "%s → %s")
  (geiser-mode-smart-tab-p t)
  (geiser-mode-start-repl-p t)
  (geiser-repl-query-on-kill-p nil)
  :init
  ;; Context menu on right click.
  ;; Taken from guile-studio: https://elephly.net/guile-studio/
  (defun context-menu ()
    (let ((menu (make-sparse-keymap)))
      (pcase major-mode
        ('scheme-mode
         (define-key menu (vector 'switch-to-repl)
                     '("Switch to REPL" . geiser-repl-switch))
         (define-key menu (vector 'eval-buffer)
                     '("Evaluate buffer" . geiser-eval-buffer))
         (define-key menu (vector 'lookup-documentation)
                     '("Show documentation". geiser-doc-symbol-at-point))
         menu)
        (_
         (mouse-menu-major-mode-map)))))
  :bind
  ([mouse-3] . (lambda (event)
                 (interactive "e")
                 (mouse-set-point event)
                 (popup-menu (context-menu)))))

;;guix:emacs-geiser-guile
(use-package geiser-guile
  :after (geiser)
  :custom
  (geiser-active-implementation '(guile))
  (geiser-default-implementation 'guile)
  :config
  ;; TODO: Make `flycheck-guile' support `guix repl'.
  (dolist (path
           (mapcar
            #'expand-file-name
            '("~/.config/guix/current/lib/guile/3.0/site-ccache"
              "~/.config/guix/current/share/guile/site/3.0"
              "~/.guix-profile/lib/guile/3.0/site-ccache"
              "~/.guix-profile/share/guile/site/3.0"
              "~/.guix-home/profile/lib/guile/3.0/site-ccache"
              "~/.guix-home/profile/share/guile/site/3.0"
              "/run/current-system/profile/lib/guile/3.0/site-ccache"
              "/run/current-system/profile/share/guile/site/3.0")))
    (add-to-list 'geiser-guile-load-path path t)))

;;guix:emacs-macrostep
(use-package macrostep
  :bind
  ("C-c e" . macrostep-expand))

;;guix:emacs-puni
(use-package puni
  :hook
  ((eval-expression-minibuffer-setup nxml-mode prog-mode sgml-mode tex-mode)
   . puni-mode))


;;;
;;; Miscellaneous
;;;

;;guix:emacs-daemons
(use-package daemons
  :custom
  (daemons-list-fill-frame t))

(use-package dired
  :custom
  (dired-listing-switches
   "-lv --all --group-directories-first --human-readable")
  :bind
  ([remap list-directory] . dired))

(use-package dired-aux
  :custom
  (dired-compress-directory-default-suffix ".tar.zst")
  (dired-create-destination-dirs 'ask)
  (dired-create-destination-dirs-on-trailing-dirsep t)
  (dired-vc-rename-file t))

;;guix:emacs-envrc
(use-package envrc
  :hook
  (after-init . envrc-global-mode))

;;guix:emacs-magit
(use-package magit
  :custom
  (git-commit-cd-to-toplevel t))

(use-package project
  :config
  (add-to-list 'project-switch-commands '(magit-project-status "Magit") t)
  :bind
  (:map project-prefix-map
        ("m" . magit-project-status)))

;;guix:emacs-forge
(use-package forge
  :after (magit))


;;;
;;; Set up initial screen.
;;;

(progn
  (setopt initial-scratch-message
          ";;; Type your Guile program here and evaluate it.\n\n")
  (scheme-mode)
  (geiser-repl-import-module "(rosenthal)")
  (geiser-repl-import-module "(nonguix transformations)")
  (delete-window)
  (display-splash-screen))
