;;; init.el --- Description -*- lexical-binding: t; -*-
;;
;; Copyright (C) 2023 Daniel Ziltener
;;
;; Author: Daniel Ziltener <dziltener@lyrion.ch>
;; Maintainer: Daniel Ziltener <dziltener@lyrion.ch>
;; Created: November 13, 2023
;; Modified: November 13, 2023
;; Version: 0.0.1
;; Keywords: abbrev bib c calendar comm convenience data docs
;; emulations extensions faces files frames games hardware help
;; hypermedia i18n internal languages lisp local maint mail matching
;; mouse multimedia news outlines processes terminals tex tools unix
;; vc wp
;; Homepage: https://github.com/dziltener@lyrion.ch/init
;; Package-Requires: ((emacs "24.3"))
;;
;; This file is not part of GNU Emacs.
;;
;;; Commentary:
;;
;;  Description
;;
;;; Code:
;;;; Requirements
;; Since I am using `cl-defun` in this init file, I need to require
;; `cl-macs`.
(require 'cl-macs)

;;;; Early Variables
(setq custom-file "~/.config/emacs/custom.el")
(when (file-exists-p custom-file)
  (load custom-file))

;;;; Personal Information Setup
(setq user-full-name "Daniel Ziltener"
      user-mail-address "dziltener@lyrion.ch")

;; I use `pass` as password storage.
(auth-source-pass-enable)

;;;; Straight.el

(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
      (bootstrap-version 6))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
        (url-retrieve-synchronously
         "https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
         'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))
(setq straight-use-package-by-default t)

;;;; Package Management Configuration
;; See: https://github.com/radian-software/el-patch
(use-package el-patch)

;;;;; Guix Management
(use-package guix)

;;;; Helper Functions
(cl-defun conditional-keybind
    (filter-fn target-fn
               &optional (fail-fn #'self-insert-command))
  "Creates a keybinding that checks `filter-fn`.  If it succeeds,
  `target-fn` is run, otherwise `fail-fn`. If no fail-fn is given,
  `self-insert-command` is run instead.
  `target-fn` and `fail-fn` must both be interactive."
  (lambda (_prefix)
    (interactive "P")
    (if (funcall filter-fn)
        (call-interactively target-fn)
      (call-interactively fail-fn))))

;;;; Emacs
(use-package emacs
  :delight (eldoc-mode " 󰙎")
  :custom
  (completion-cycle-threshold 10)
  (display-time-mode t)
  (enable-recursive-minibuffers t)
  (enable-remote-dir-locals t)
  (fill-column 100)
  (global-hl-line-mode t)
  (indent-tabs-mode nil)
  (menu-bar-mode nil)
  (minibuffer-prompt-properties (read-only t cursor-intangible t face minibuffer-prompt))
  (read-extended-command-predicate #'command-completion-default-include-p)
  (recentf-mode t)
  (scroll-bar-mode nil)
  (tab-always-indent 'complete)
  (tool-bar-mode nil)
  :custom-face
  (default ((t (:weight bold :height 113 :width normal :family "VictorMono Nerd Font"))))
  :hook
  (minibuffer-setup . cursor-intangible-mode)
  :config
  (advice-add 'risky-local-variable-p :override #'ignore)
  (global-display-fill-column-indicator-mode t))

;;;; Org Mode
(use-package org
  :ensure t
  :custom
  (org-babel-load-languages '((emacs-lisp . t)
                              (scheme . t)))
  :hook
  (org-mode . org-indent-mode)
  (org-src-mode . hack-local-variables)
  (org-mode . visual-line-mode)
  :config
  (setq org-directory "~/org")
  (add-to-list 'org-modules 'collector))

(use-package org-roam
  :ensure t
  :custom
  (org-roam-directory "~/org/roam")
  :config
  (org-roam-db-autosync-mode))

(use-package consult-notes
  :config
  (consult-notes-org-roam-mode t))

(use-package websocket
  :ensure t
  :after org-roam)

(use-package org-roam-ui
  :ensure t
  :after org-roam
  :custom
  (org-roam-ui-sync-theme t)
  (org-roam-ui-follow t)
  (org-roam-ui-update-on-save t)
  (org-roam-ui-open-on-start t))

(use-package org-modern
  :ensure t
  :hook
  ((org-mode . org-modern-mode)
   (org-agenda-finalize . org-modern-agenda)))

(use-package org-rainbow-tags
  :hook
  (org-mode . org-rainbow-tags-mode))

;;;; Design
(use-package color-theme-modern)
(use-package moe-theme)
(use-package catppuccin-theme
  :custom
  (catppuccin-flavor 'mocha)
  (catppuccin-highlight-matches t)
  (catppuccin-italic-comments t)
  :custom-face
  (font-lock-doc-face ((t (:inherit font-lock-comment-face
                                    :foreground "#fab387"))))
  :config
  ;;(load-theme 'catppuccin)
  )

(set-frame-parameter nil 'alpha-background 80)
(add-to-list 'default-frame-alist '(alpha-background . 80))

;; (use-package powerline
;;   :init
;;   (require 'powerline)
;;   :config
;;   (powerline-default-theme))

;; (use-package airline-themes
;;   :after powerline
;;   :init (require 'airline-themes)
;;   :config
;;   (load-theme 'airline-badwolf t))

(use-package lambda-line
  :straight (:type git :host github :repo "lambda-emacs/lambda-line")
  :custom
  (lambda-line-icon-time t) ;; requires ClockFace font (see below)
  (lambda-line-clockface-update-fontset "ClockFaceRect") ;; set clock icon
  (lambda-line-position 'bottom) ;; Set position of status-line 
  (lambda-line-abbrev t) ;; abbreviate major modes
  (lambda-line-hspace "  ")  ;; add some cushion
  (lambda-line-prefix t) ;; use a prefix symbol
  (lambda-line-prefix-padding nil) ;; no extra space for prefix 
  (lambda-line-status-invert nil)  ;; no invert colors
  (lambda-line-gui-ro-symbol  " ⨂") ;; symbols
  (lambda-line-gui-mod-symbol " ⬤") 
  (lambda-line-gui-rw-symbol  "  ")
  (lambda-line-vc-symbol "  ")
  (lambda-line-space-top +.50)  ;; padding on top and bottom of line
  (lambda-line-space-bottom -.50)
  (lambda-line-symbol-position 0.1) ;; adjust the vertical placement of symbol
  :config
  ;; add evil indicator
  (advice-add 'lambda-line-compose :around
              (lambda (orig-fun status name primary tertiary secondary)
                (funcall orig-fun
                         status
                         (concat name evil-mode-line-tag)
                         primary tertiary secondary)))
  ;; activate lambda-line 
  (lambda-line-mode)
  (lambda-line-clockface-update-fontset "ClockFaceRect")
  (customize-set-variable 'flymake-mode-line-counter-format '("" flymake-mode-line-error-counter flymake-mode-line-warning-counter flymake-mode-line-note-counter ""))
  (customize-set-variable 'flymake-mode-line-format '(" " flymake-mode-line-exception flymake-mode-line-counters))
  ;; set divider line in footer
  (when (eq lambda-line-position 'top)
    (setq-default mode-line-format (list "%_"))
    (setq mode-line-format (list "%_"))))

(use-package lambda-themes
  :straight (:type git :host github :repo "lambda-emacs/lambda-themes") 
  :custom
  (lambda-themes-set-italic-comments t)
  (lambda-themes-set-italic-keywords t)
  (lambda-themes-set-variable-pitch t) 
  :config
  ;; load preferred theme 
  (load-theme 'lambda-dark))

(use-package visual-fill-column
  :custom
  (visual-fill-column-center-text t)
  (visual-fill-column-enable-sensible-window-split t)
  :hook visual-line-mode)

(use-package adaptive-wrap
  :hook visual-fill-column)

;;;; Security
(use-package keychain-environment
  :init
  (keychain-refresh-environment))

(use-package pass)
(use-package password-store)
(use-package password-store-otp)

;;;; Evil Mode
(use-package evil
  ;; :after evil-leader
  :ensure t
  :init
  (setq evil-want-keybinding nil
        evil-emacs-state-tag " 󰯸 "
        evil-normal-state-tag " 󰰓 "
        evil-visual-state-tag " 󰰬 "
        evil-insert-state-tag " 󰰅 "
        evil-motion-state-tag " 󰬔 "
        evil-operator-state-tag " 󰰕 ")
  :config
  (evil-set-leader 'normal (kbd "<SPC>"))
  (evil-set-leader 'normal (kbd "z") t)
  (evil-mode 1)
  :bind
  (:map evil-normal-state-map
        ("<leader>f" . 'find-file)
        ("<leader>k" . 'kill-buffer)))

(use-package evil-collection
  :after evil
  :ensure t
  :delight evil-collection-unimpaired-mode
  :custom
  (evil-collection-outline-bind-tab-p 1)
  :commands evil-collection-init
  :init
  (evil-collection-init))

;;;; Search, Completion, Execution
;;;;; Preliminary Packages
(use-package savehist
  :init
  (savehist-mode))

(use-package orderless
  :after consult
  :config
  (with-eval-after-load 'eglot
    (setq completion-category-defaults nil))
  (setq completion-styles '(orderless basic)
        completion-category-defaults nil
        completion-category-overrides '((file (styles basic partial-completion)))))

(use-package consult
  :config
  (require 'consult-flymake)
  (require 'consult-xref)
  :bind
     (:map evil-normal-state-map
        ("<leader><SPC>" . 'consult-find)
        ("<leader>gs" . 'consult-eglot-symbols)
        ("<leader>b" . 'consult-buffer)
        ("<leader>gb" . 'consult-project-buffer)
        ("<leader>g/" . 'consult-git-grep)
        ("<leader>/" . 'consult-grep)
        ("/" . 'consult-line)))

(use-package embark
  :bind
  ("C-<SPC>" . 'embark-act)
  ("C-M-<return>" . 'embark-bindings))

(use-package embark-consult
  :after (embark consult)
  :hook
  (embark-collect-mode . consult-preview-at-point-mode))

(use-package wgrep
  :config
  (setq wgrep-auto-save-buffer t))

(use-package vertico
  :commands
  vertico-mode
  :init
  (vertico-mode))

(use-package marginalia
  :commands
  marginalia-mode
  :init
  (marginalia-mode))

(use-package nerd-icons-completion
  :after marginalia
  :commands
  nerd-icons-completion-mode
  :hook
  (marginalia-mode-hook . nerd-icons-completion-marginalia-setup)
  :init
  (nerd-icons-completion-mode))

(use-package which-key
  :commands
  which-key-setup-minibuffer
  :init
  (which-key-setup-minibuffer))

;;;;; Code Completion
(use-package cape
  :init
  (defun my/capf ()
    (interactive)
    (setq-local completion-at-point-functions
                (list (cape-capf-super
                       #'tempel-complete
                       #'cape-dabbrev
                       #'cape-file
                       #'cape-dict
                       #'cape-line
                       #'cape-emoji
                       #'cape-keyword))))
  (advice-add 'eglot-completion-at-point :around #'cape-wrap-buster)
  (add-hook 'org-mode-hook #'my/capf)
  (add-hook 'prog-mode-hook #'my/capf))

(defun my/corfu-combined-sort (candidates)
  "Sort CANDIDATES using both display-sort-function and corfu-sort-function."
  (let ((candidates
         (let ((display-sort-func (corfu--metadata-get 'display-sort-function)))
           (if display-sort-func
               (funcall display-sort-func candidates)
             candidates))))
    (if corfu-sort-function
        (funcall corfu-sort-function candidates)
      candidates)))

(use-package corfu
  :custom
  (corfu-cycle t)
  (corfu-preselect 'prompt)
  (corfu-auto t)
  (corfu-scroll-margin 5)
  (corfu-quit-no-match 'separator)
  (evil-collection-corfu-key-themes '(tab-n-go))
  (corfu-popupinfo-delay '(0.1 . 0.5))
  :config
  (setq corfu-sort-override-function #'my/corfu-combined-sort)
  (add-to-list 'savehist-additional-variables 'corfu-history)
  :commands
  global-corfu-mode
  :init
  (global-corfu-mode)
  (corfu-popupinfo-mode 1)
  (corfu-history-mode 1))

(use-package nerd-icons-corfu
  :after corfu
  :init
  (add-to-list 'corfu-margin-formatters 'nerd-icons-corfu-formatter))

(use-package corfu-terminal
  :if (not (display-graphic-p))
  :after corfu
  :hook
  global-corfu-mode)

(use-package tempel)

(use-package xref-union
  :hook
  cider-connected)

;;;; Basic Navigation
(use-package goto-chg
  :bind
  (:map evil-normal-state-map
        ("g," . 'goto-last-change)
        ("g;" . 'goto-last-change-reverse)))

(use-package evil-snipe
  :delight (evil-snipe-local-mode " 󰓾 ")
  :custom
  (evil-snipe-scope 'whole-visible)
  (evil-snipe-repeat-scope 'whole-buffer)
  (evil-snipe-spillover-scope 'whole-buffer)
  (evil-snipe-tab-increment t)
  :hook
  evil-mode
  (evil-mode . evil-snipe-override-mode)
  (magit-mode . turn-off-evil-snipe-override-mode)
  ;; See https://github.com/hlissner/evil-snipe/issues/95
  ;; :config
  ;; (evilem-define "gs" 'evil-snipe-repeat
  ;;                :bind ((evil-snipe-scope 'buffer)
  ;;                       (evil-snipe-enable-highlight)
  ;;                       (evil-snipe-enable-incremental-highlight)))
  )

(use-package evil-easymotion
  :config
  (evilem-default-keybindings "<leader>"))

;;;; Lisp Navigation and Editing
;;;;; Helper Functions
(defun cc/move-sexp-backward ()
  "Move balanced expression (sexp) to the right of point backward one sexp.
Point must be at the beginning of balanced expression (sexp)."
  (interactive)
  (transpose-sexps 1)
  (forward-sexp -2))

(defun cc/move-sexp-forward ()
  "Move balanced expression (sexp) to the right of point forward one sexp.
Point must be at the beginning of balanced expression (sexp)."
  (interactive)
  (forward-sexp 1)
  (transpose-sexps 1)
  (forward-sexp -1))

(defun looking-at-opening-paren ()
  (looking-at (rx (or "(" "{" "["))))

(defun being-past-closing-paren ()
  (looking-back (rx (or ")" "}" "]"))))

;;;;; Basic
(use-package smartparens
  :delight (smartparens-mode " 󰅲 ")
  :hook
  (smartparens-mode . show-smartparens-mode)
  (smartparens-mode . smartparens-strict-mode)
  clojure-ts-mode
  emacs-lisp-mode
  scheme-mode
  :config
  (add-to-list 'sp-clojure-modes 'clojure-ts-mode)
  (add-to-list 'sp-clojure-modes 'clojurec-ts-mode)
  (add-to-list 'sp-clojure-modes 'clojurescript-ts-mode)
  (add-to-list 'sp-lisp-modes 'clojure-ts-mode)
  (add-to-list 'sp-lisp-modes 'clojurec-ts-mode)
  (add-to-list 'sp-lisp-modes 'clojurescript-ts-mode)
  (evil-define-key 'insert smartparens-mode-map
    (kbd "r") (conditional-keybind #'looking-at-opening-paren
                                   #'sp-raise-sexp)
    (kbd "w") (conditional-keybind #'looking-at-opening-paren 
                                   #'cc/move-sexp-backward)
    (kbd "s") (conditional-keybind #'looking-at-opening-paren
                                   #'cc/move-sexp-forward)
    (kbd "c") (conditional-keybind #'looking-at-opening-paren 
                                   #'sp-clone-sexp)
    (kbd "DEL") (conditional-keybind #'being-past-closing-paren
                                     #'sp-backward-delete-sexp
                                     #'evil-delete-backward-char-and-join)
    (kbd ">") (conditional-keybind #'being-past-closing-paren
                                   (lambda (prefix)
                                     (interactive "P")
                                     (call-interactively #'backward-char)
                                     (call-interactively #'sp-forward-slurp-sexp)
                                     (call-interactively #'sp-forward-sexp)
                                     (call-interactively #'forward-char)))
    (kbd "<") (conditional-keybind #'being-past-closing-paren
                                   (lambda (prefix)
                                     (interactive "P")
                                     (call-interactively #'backward-char)
                                     (call-interactively #'sp-forward-barf-sexp)
                                     (call-interactively #'forward-char)))))

(use-package evil-cleverparens
  :after smartparens
  :hook smartparens)

;;;;; Visual Aid
;; (use-package rainbow-delimiters
;;   :hook
;;   emacs-lisp-mode
;;   scheme-mode)

;;;; Programming
(use-package editorconfig
  :delight editorconfig-mode
  :commands
  editorconfig-mode
  :init
  (editorconfig-mode 1))

(use-package flymake
  :delight '(:eval (cons "  " (flymake--mode-line-counters))))

(use-package eglot
  :preface
  (defun my/eglot-capf ()
    (interactive)
    (setq-local completion-at-point-functions
                (list (cape-capf-super
                       #'eglot-completion-at-point
                       #'tempel-complete
                       #'cape-dabbrev
                       #'cape-file
                       #'cape-dict
                       #'cape-line
                       #'cape-emoji
                       #'cape-keyword))))
  :custom
  (eglot-connect-timeout 90)
  (eglot-autoshutdown t)
  (eglot-report-progress t)
  :config
  (defun eglot-mode ()
    (eglot-inlay-hints-mode +1)
    (my/eglot-capf))
  (setq-default eglot-workspace-configuration
                '((clojure-lsp (maxCompletions . 300))))
  (add-to-list 'eglot-server-programs
               '((clojure-ts-mode clojurec-ts-mode clojurescript-ts-mode)
                 "clojure-lsp"
                 :initializationOptions
                 (:preferences
                  (:includeInlayParameterNameHints
                   "all"
                   :includeInlayParameterNameHintsWhenArgumentMatchesName
                   t
                   :includeInlayFunctionParameterTypeHints
                   t
                   :includeInlayVariableTypeHints
                   t
                   :includeInlayVariableTypeHintsWhenTypeMatchesName
                   t
                   :includeInlayPRopertyDeclarationTypeHints
                   t
                   :includeInlayFunctionLikeReturnTypeHints
                   t
                   :includeInlayEnumMemberValueHints
                   t))))
  :hook
  (eglot-managed-mode . (lambda ()
                          (add-hook 'before-save-hook
                                    (lambda ()
                                      (call-interactively #'eglot-format-buffer))
                                    nil 'local)))
  (eglot-managed-mode . eglot-inlay-hints-mode)
  (eglot-managed-mode . #'my/eglot-capf))

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

(use-package tree-sitter
  :ensure t)

;;;;; Clojure
(use-package clojure-ts-mode
  :delight ""
  :after tree-sitter
  :config
  (require 'sesman)
  (sesman-install-menu clojure-mode-map)
  :hook
  ((clojure-ts-mode . eglot-ensure)
   (clojure-ts-mode . (lambda ()
                        (setq-local sesman-system 'CIDER)))
   (clojure-ts-mode . (lambda ()
                        (sesman-install-menu clojure-mode-map)))
   (clojurec-ts-mode . (lambda ()
                         (sesman-install-menu clojurec-mode-map)))
   (clojurescript-ts-mode . (lambda ()
                              (sesman-install-menu clojurescript-mode-map))))
  :mode
  ("\\.clj\\'" . #'clojure-ts-mode)
  ("\\.cljc\\'" . #'clojurec-ts-mode)
  ("\\.cljs\\'" . #'clojurescript-ts-mode)
  :init
  (add-to-list 'tree-sitter-major-mode-language-alist
               '(clojure-ts-mode . clojure))
  (add-to-list 'tree-sitter-major-mode-language-alist
               '(clojurec-ts-mode . clojure))
  (add-to-list 'tree-sitter-major-mode-language-alist
               '(clojurescript-ts-mode . clojure)))

(use-package cider
  :delight (cider-mode '(:eval (concat "  [" (cider--modeline-info) "]")))
  :hook
  (clojure-ts-mode . cider-mode)
  :config
  (evil-define-key 'insert 'cider-mode-map
    "C-x C-e" #'cider-eval-last-sexp)
  (evil-define-key 'normal 'cider-mode-map
    (kbd "<localleader>ce") #'cider-eval-sexp-at-point))

;;;;; Scheme

(use-package geiser
  :custom
  (geiser-chicken-match-limit 200)
  :config
  (defalias 'run-geiser 'geiser))

(use-package paren-face
  :hook
  scheme-mode
  emacs-lisp-mode)

(use-package highlight-parentheses
  :hook prog-mode)

;;;;;; Chicken Scheme
(use-package scheme
  :config
  (eval-after-load 'eglot
    '(add-to-list 'eglot-server-programs
                  '(scheme-mode
                    . ("chicken-lsp-server"))))
  :hook
  (scheme-mode . eglot-ensure))

(defun flymake-chicken-init ()
  (add-hook 'flymake-diagnostic-functions
            #'flymake-chicken-backend nil t)
  (flymake-mode))

(use-package geiser-chicken)

;;;;;; Guile

(use-package geiser-guile
  :config
  (when (executable-find "guix")
    (add-to-list 'geiser-guile-load-path
                 (expand-file-name "~/.config/guix/current/share/guile/site/3.0"))))

;; (use-package flymake-chicken
;;   :hook
;;   ((scheme-mode . flymake-chicken-init)
;;    (scheme-mode . flymake-mode))
;;   :custom
;;   (flycheck-global-modes '(not scheme-mode))
;;   :config
;;   (add-hook 'flymake-diagnostic-functions #'flymake-chicken-backend nil t))

;;;; Other Languages

(use-package zig-mode
  :hook
  (zig-mode . eglot-ensure)
  :mode ("\\.zig\\'" . zig-mode))

;; (use-package lsp-tailwindcss
;;   :init
;;   (setq lsp-tailwindcss-add-on-mode t
;;         lsp-tailwindcss-experimental-class-regex ["\"([^\"]*)\""])
;;   :config
;;   (add-to-list 'lsp-tailwindcss-major-modes 'clojurescript-ts-mode)
;;   (add-to-list 'lsp-tailwindcss-major-modes 'clojurec-ts-mode))

(use-package dhall-mode
  :mode "\\.dhall\'"
  :hook
  ((dhall-mode . lsp)))

;;;; Version Control

;;;;; Git
(use-package magit
  :autoload
  magit)

(use-package magit-todos
  :after magit
  :hook
  (magit-mode . magit-todos-mode))

(use-package forge
  :after magit
  :config
  (advice-add 'magit :after (lambda (&rest _args)
                              (call-interactively #'forge-pull))))

(use-package code-review
  :after forge
  :custom
  (code-review-auth-login-marker 'forge))

(use-package git-gutter
  :delight git-gutter-mode
  :hook prog-mode)

;;;; LaTeX
;; https://github.com/politza/pdf-tools/#known-problems
(add-hook 'TeX-after-compilation-finished-functions
          #'TeX-revert-document-buffer)

;;;; Communication
;;;;; IRC
(use-package rcirc
  :ensure t
  :custom
  (rcirc-server-alist
   `(("liberachat.irc.lyrion.ch"
      :server-alias "irc.libera.chat"
      :port 6697
      :nick "zilti"
      :full-name "Daniel Ziltener"
      :user-name "zilti/irc.libera.chat"
      :password ,(password-store-get "Privat/Soju")
      :encryption tls
      :channels ("#chicken")))
   `(("oftc.irc.lyrion.ch"
      :server-alias "irc.oftc.net"
      :port 6697
      :nick "zilti"
      :full-name "Daniel Ziltener"
      :user-name "zilti/irc.oftc.net"
      :password ,(password-store-get "Privat/Soju")
      :encryption tls
      :channels ()))))

(provide 'init)
;;; init.el ends here