;;; init.el -*- lexical-binding: t; -*-
(defun chris/display-startup-time ()
  (message "Emacs loaded in %s with %d garbage collections."
           (format "%.2f seconds"
                 (float-time
                (time-subtract after-init-time before-init-time)))
         gcs-done))
(add-hook 'emacs-startup-hook #'chris/display-startup-time)

(setq inhibit-startup-message t)

(scroll-bar-mode -1)
(tool-bar-mode -1)
(tooltip-mode -1)
(set-fringe-mode +1)

(menu-bar-mode -1)
(blink-cursor-mode -1)
(column-number-mode +1)
(setq-default indent-tabs-mode nil)

(setq comp-deferred-compilation-deny-list nil)
(setq frame-resize-pixelwise t)

(if (string-equal (system-name) "syl")
    (defvar chris/default-font-size 120)
  (defvar chris/default-font-size 120))

(defun chris/set-font-faces ()
  "Set the faces for our fonts"
  (message "Setting faces!")
  (set-face-attribute 'default nil :font "VictorMono Nerd Font"
                      :height chris/default-font-size)
  (set-face-attribute 'fixed-pitch nil :font "VictorMono Nerd Font"
                      :height chris/default-font-size)
  (set-face-attribute 'variable-pitch nil :font "Noto Sans"
                      :height (+ chris/default-font-size (/ chris/default-font-size 12))
                      :weight 'regular))

(defun chris/set-transparency ()
  "Set the frame to be transparent but not the text"
  (set-frame-parameter (selected-frame) 'alpha-background 100)
  (add-to-list 'default-frame-alist '(alpha-background . 100))
  (add-to-list 'initial-frame-alist '(alpha-background . 100)))

(if (daemonp)
    (add-hook 'after-make-frame-functions
              (lambda (frame)
                (with-selected-frame frame
                  (chris/set-font-faces)
                  (chris/set-transparency)
                  (tool-bar-mode -1)))
              (chris/set-font-faces)))

(setq display-line-numbers-type 'relative)
(global-display-line-numbers-mode +1)
(add-hook 'prog-mode-hook (display-line-numbers-mode +1))
(global-visual-line-mode +1)

(defun on-frame-open (frame)
  (if (not (display-graphic-p frame))
    (set-face-background 'default "unspecified-bg" frame)))
(add-hook 'after-make-frame-functions 'on-frame-open)

;; always avoid GUI
(setq use-dialog-box nil)
;; Don't display floating tooltips; display their contents in the echo-area,
;; because native tooltips are ugly.
(when (bound-and-true-p tooltip-mode)
  (tooltip-mode -1))
;; ...especially on linux
(setq x-gtk-use-system-tooltips nil)

 ;; Favor vertical splits over horizontal ones. Screens are usually wide.
(setq split-width-threshold 160
      split-height-threshold nil)

(setq doc-view-resolution 192)

(fset 'evil-redirect-digit-argument 'ignore)

(global-set-key (kbd "<escape>") 'keyboard-escape-quit)

(recentf-mode +1)

(add-to-list 'exec-path "/home/chris/bin")

(setq initial-major-mode 'org-mode)
(setq initial-scratch-message "#+TITLE: SCRATCH\n#+DESCRIPTION: This buffer is for temporary things")

(dolist (path load-path)
  (when (string-match-p "/nix/store/[a-z0-9]\\{32\\}-emacs-packages-deps.*" path)
    (dolist (autoload-file (directory-files path t "-autoloads.el"))
      (with-demoted-errors "init.el error: %s"
        (load autoload-file nil t)))))

(eval-when-compile (require 'use-package))

(setq use-package-verbose t)
(defalias 'yes-or-no-p 'y-or-n-p)

  (use-package command-log-mode
    :commands command-log-mode)

(use-package all-the-icons)

(use-package doom-modeline
  :ensure t
  :init
  (doom-modeline-mode 1)
  (setq doom-modeline-height 30
        doom-modeline-bar-width 3
        all-the-icons-scale-factor 0.9
        doom-modeline-hud nil
        doom-modeline-buffer-file-name-style 'file-name
        doom-modeline-buffer-encoding nil
        doom-modeline-mu4e t
        doom-modeline-enable-word-count t)
  (if (daemonp)
      (add-hook 'after-make-frame-functions
                (lambda (frame)
                  (with-selected-frame frame
                    (setq doom-modeline-icon t))))))

(use-package doom-themes
  :ensure t
  :init (load-theme 'doom-snazzy t))

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

(use-package smartparens
  :defer 1
  :config
  (smartparens-global-mode +1))

(use-package aggressive-indent
  :defer 1
  :config
  (aggressive-indent-mode -1))

(use-package adaptive-wrap
  :defer t)

(use-package which-key
  :config
  (setq which-key-idle-delay 0.3)
  (which-key-mode)
  :defer 1)

(setenv "WAYLAND_DISPLAY" "wayland-1")

(executable-find "ssh")
(setq ispell-program-name "hunspell"
      ispell-local-dictionary "en_US"
      ispell-local-dictionary-alist
      ;; Please note the list `("-d" "en_US")` contains ACTUAL parameters passed to hunspell
      ;; You could use `("-d" "en_US,en_US-med")` to check with multiple dictionaries
      '(("en_US" "[[:alpha:]]" "[^[:alpha:]]" "[']" nil ("-d" "en_US") nil utf-8)))
  (add-hook 'org-mode-hook 'chris/org-mode-setup)

(use-package no-littering)

;; no-littering doesn't set this by default so we must place
;; auto save files in the same path as it uses for sessions
(setq auto-save-file-name-transforms
      `((".*" ,(no-littering-expand-var-file-name "auto-save/") t)))

(use-package evil
  :init
  (setq evil-want-integration t
        evil-want-keybinding nil
        evil-want-C-i-jump nil
        evil-want-C-u-scroll t
        evil-respect-visual-line-mode t
        evil-want-C-u-delete t
        evil-undo-system 'undo-redo
        scroll-conservatively 101
        hscroll-margin 2
        scroll-margin 0
        scroll-preserve-screen-position t
        hscroll-step 1
        evil-vsplit-window-right t)
  :config
  (evil-mode +1))

(use-package evil-collection
  :after evil
  :config (evil-collection-init))

(use-package general
  :init
  (general-evil-setup)
  :config
  (defun chris/edit-emacs-config ()
    "open the emacs config to edit"
    (interactive)
    (find-file (expand-file-name "README.org" user-emacs-directory)))
  (defun chris/open-bible ()
    "find a bible to open"
    (interactive)
    (find-file "~/org/bibles/"))
  (general-create-definer chris/leader-keys
    :keymaps '(normal visual emacs)
    :prefix "SPC")
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "b" '(:ignore t :which-key "buffer")
    "t" '(:ignore t :which-key "toggle")
    "f" '(:ignore t :which-key "file")
    "w" '(:ignore t :which-key "window")
    "s" '(:ignore t :which-key "search")
    "i" '(:ignore t :which-key "insert")
    "o" '(:ignore t :which-key "open")
    "oa" '(:ignore t :which-key "org agenda")
    "of" '(:ignore t :which-key "elfeed")
    "h" '(:ignore t :which-key "help")
    "n" '(:ignore t :which-key "notes")
    "m" '(:ignore t :which-key "music")
    "l" '(:ignore t :which-key "lsp")
    "sp" '(:ignore t :which-key "passwords")
    "bs" '(consult-buffer :which-key "buffer search")
    "bd" '(kill-this-buffer :which-key "kill buffer")
    "bi" '(ibuffer :which-key "ibuffer")
    "tt" '(consult-theme :which-key "choose theme")
    "tl" '(toggle-truncate-lines :which-key "truncate lines")
    "ts" '(ispell :which-key "spell check")
    "ff" '(find-file :which-key "find file")
    "fb" '(chris/open-bible :which-key "find bible book")
    "fr" '(consult-recent-file :which-key "recent file")
    "fs" '(save-buffer :which-key "save")
    "fE" '(consult-file-externally :which-key "find file externally")
    "fe" '(chris/edit-emacs-config :which-key "open config")
    "hf" '(helpful-callable :which-key "describe-function")
    "hv" '(helpful-variable :which-key "describe-variable")
    "hk" '(helpful-key :which-key "describe-key")
    "hF" '(describe-face :which-key "describe-face")
    "hb" '(general-describe-keybindings :which-key "describe-bindings")
    "hi" '(info :which-key "info manual")
    "ht" '(which-key-show-top-level :which-key "show top-level keybindings")
    "ss" '(consult-line :which-key "consult search")
    "sr" '(consult-ripgrep :which-key "consult ripgrep")
    "sd" '(dictionary-search :which-key "search the dictionary")
    "oP" '(proced :which-key "proced")
    "wo" '(other-window :which-key "other window")
    "wd" '(delete-window :which-key "other window")
    "wv" '(evil-window-vsplit :which-key "split window vertically")
    "ws" '(evil-window-split :which-key "split window horizontally")
    "wj" '(evil-window-down :which-key "down window")
    "wk" '(evil-window-up :which-key "up window")
    "wh" '(evil-window-left :which-key "left window")
    "wl" '(evil-window-right :which-key "right window")
    "wH" '(evil-window-move-far-left :which-key "right window")
    "wK" '(evil-window-move-very-top :which-key "right window")
    "wJ" '(evil-window-move-very-bottom :which-key "right window")
    "wL" '(evil-window-move-far-right :which-key "right window")
    ";" '(execute-extended-command :which-key "execute command")
    ":" '(eval-expression :which-key "evaluate expression"))
  (general-def 'minibuffer-local-map
    "C-v" 'evil-paste-after)
  (general-def 'normal
    "gcc" 'comment-line
    "K" 'helpful-at-point
    "C-+" 'text-scale-increase
    "C-_" 'text-scale-decrease
    "C-S-l" 'evil-window-increase-width
    "C-S-j" 'evil-window-decrease-height
    "C-S-k" 'evil-window-increase-height
    "C-S-h" 'evil-window-decrease-width)
  (general-def 'normal Info-mode-map
    "RET" 'Info-follow-nearest-node
    "p" 'Info-prev
    "n" 'Info-next)
  (general-def 'normal prog-mode-map
    :states 'normal
    :keymaps 'override
    "go" 'ff-find-other-file
    "TAB" 'indent-according-to-mode)
  (general-def 'visual prog-mode-map
    :states 'visual
    :keymaps 'override
    "TAB" 'indent-region)
  (general-def 'insert prog-mode-map
    :states 'insert
    :keymaps 'override
    "TAB" 'indent-according-to-mode))

(use-package evil-escape
  :after evil
  :init (evil-escape-mode +1)
  :config
  (setq evil-escape-key-sequence "fd"
        evil-escape-delay 0.3))

(use-package evil-surround
  :after evil
  :config
  (global-evil-surround-mode +1))

(setq lpr-command "lpr -o sides=two-sided-long-edge -# ")
(defun chris/org-mode-setup ()
  (interactive)
  (org-indent-mode +1)
  (toc-org-mode +1)
  (visual-fill-column-mode +1)
  (display-line-numbers-mode -1)
  (variable-pitch-mode +1)
  (flyspell-mode +1)
  (setq visual-fill-column-width 100
        visual-fill-column-center-text t)

  ;;Let's make sure org-mode faces are inheriting fixed pitch faces.
  (dolist (face '(org-block
                  org-block-begin-line
                  org-block-end-line
                  org-code
                  org-document-info-keyword
                  org-meta-line
                  org-table
                  org-date
                  org-verbatim))
    (set-face-attribute `,face nil :inherit 'fixed-pitch))

  ;; changing height and boldness
  (dolist (face
           '((org-level-1 1.4 ultra-bold)
             (org-level-2 1.2 extra-bold)
             (org-level-3 1.1 bold)
             (org-level-4 1.0 semi-bold)
             (org-level-5 1.0 normal)))
    (set-face-attribute (nth 0 face) nil :weight (nth 2 face) :height (nth 1 face)))

  ;; changing colors
  (dolist (face
           '((outline-2 "#5af78e")
             (outline-3 "#ffb86c")
             (outline-4 "#f3f99d")))
    (set-face-attribute (nth 0 face) nil :foreground (nth 1 face)))

  (set-face-attribute 'org-block-end-line nil :inherit 'org-block-begin-line)
  (set-face-attribute 'org-quote nil :background "#242631" :inherit 'fixed-pitch))

(defun chris/org-convert-csv-table (beg end)
  (interactive (list (mark) (point)))
  (org-table-convert-region beg end ","))

(defun chris/org-agenda-setup ()
  (interactive)
  (org-indent-mode +1)
  (toc-org-mode +1)
  (visual-fill-column-mode +1)
  (display-line-numbers-mode -1)
  (variable-pitch-mode -1)
  (toggle-truncate-lines +1)
  (evil-normal-state)
  (setq visual-fill-column-width 120
        visual-fill-column-center-text t))

(defun chris/org-mpv (url)
  (interactive)
  (bongo-insert-uri url (format "%s ==> %s" title url)))

(use-package org
  :defer 1
  :config
  (setq org-startup-indented t
        org-edit-src-content-indentation 0
        org-agenda-sticky t
        org-fontify-quote-and-verse-blocks t
        org-src-preserve-indentation t
        org-src-window-setup 'other-window
        org-export-with-broken-links t
        org-agenda-current-time-string "⭠ now ────────────────"
        org-log-into-drawer t
        org-latex-active-timestamp-format "\\textit{%s}"
        org-enforce-todo-dependencies t)

  (add-hook 'org-mode-hook 'chris/org-mode-setup)

  (org-babel-do-load-languages 'org-babel-load-languages
                               '((emacs-lisp . t)
                                 (python . t)
                                 (ditaa . t)
                                 (shell . t)))

  (setq org-ditaa-jar-path "/usr/bin/ditaa")

  (require 'org-tempo)
  (add-to-list 'org-structure-template-alist '("el" . "src emacs-lisp"))
  (add-to-list 'org-structure-template-alist '("cl" . "src common-lisp"))
  (add-to-list 'org-structure-template-alist '("py" . "src python"))
  (add-to-list 'org-structure-template-alist '("sh" . "src shell"))
  (add-to-list 'org-structure-template-alist '("yaml" . "src yaml"))
  (add-to-list 'org-structure-template-alist '("yml" . "src yaml"))
  (add-to-list 'org-structure-template-alist '("q" . "quote"))
  (add-to-list 'org-structure-template-alist '("rc" . "src restclient"))

  (setq org-capture-templates
        '(("t" "Personal todo" entry
           (file "todo/todo.org")
           (file ".templates/tasks.org") :prepend t)
          ("n" "Personal notes" entry
           (file "todo/notes.org")
           "* %u %?\n%i\n%a" :prepend t)
          ("j" "Journal" entry
           (file+olp+datetree +org-capture-journal-file)
           "* %U %?\n%i\n%a" :prepend t)
          ("p" "TFC Plan" entry
           (function chris/org-roam-capture-lesson-file)
           (file ".templates/tfcplantemplate.org")
           :prepend nil
           :jump-to-captured t
           :empty-lines 1)
          ("P" "TFC Posts" entry
           (file+headline "nvtfc_social_media.org" "Posts")
           (file ".templates/posts.org")
           :prepend t
           :jump-to-captured t)
          ("r" "Templates for projects")
          ("rt" "Project-local todo" entry
           (file+headline chris/project-todo "Inbox")
           "* TODO %?\n%a\n%i\n\n" :prepend t)
          ("rn" "Project-local notes" entry
           (file+headline chris/project-todo "Notes")
           "* %U %?\n%a\n%i\n\n" :prepend t)
          ("rc" "Project-local changelog" entry
           (file+headline chris/project-changelog "Changelog")
           "* %U %?\n%a\n%i\n\n" :prepend t))
        org-capture-use-agenda-date t
        org-agenda-timegrid-use-ampm t
        org-agenda-show-inherited-tags nil
        org-agenda-tags-column -100)

  (setq org-agenda-prefix-format '((agenda . " %i %?-12t% s")
                                   (todo . " %i %-12:c")
                                   (tags . " %i %-12:c")
                                   (search . " %i %-12:c")))

  (setq org-agenda-category-icon-alist
        '(("todo" "~/org/icons/task.png" nil nil :ascent center)
          ("lesson" "~/org/icons/book.png" nil nil :ascent center)
          ("dev" "~/org/icons/coding.png" nil nil :ascent center)
          ("TODO" "~/org/icons/coding.png" nil nil :ascent center)))

  (setq org-imenu-depth 4
        org-odt-styles-file "/home/chris/org/style.odt"
        org-export-with-toc nil
        org-export-with-author nil
        org-todo-keywords
        '((sequence "TODO(t)" "PROJ(p)" "STRT(s)" "WAIT(w)" "HOLD(h)" "|" "DONE(d)" "CNCL(c)")
          (sequence "[ ](T)" "[-](S)" "[?](W)" "|" "[X](D)"))
        org-agenda-files
        '("/home/chris/org/todo/notes.org"
          "/home/chris/org/repetition.org"
          "/home/chris/org/tfc_plans.org"
          "/home/chris/org/ministry_team.org"
          "/home/chris/org/todo/todo.org"
          "/home/chris/org/newsletter.org"
          "/home/chris/org/archive.org"
          "/home/chris/org/nvtfc_social_media.org"
          "/home/chris/dev/church-presenter/TODO.org"
          "/home/chris/org/lessons/")
        org-id-method 'ts
        org-agenda-tags-column -75)

  (defun chris/org-columns-view ()
    "Turn on org-columns overlay and turn off olivetti-mode"
    (interactive)
    (goto-char (point-min))
    (org-content)
    (org-columns)
    (setq visual-fill-column-width 150))

  (defun chris/org-columns-quit ()
    "Remove the org-columns overlay and turn on olivetti-mode"
    (interactive)
    (org-columns-quit)
    (chris/org-mode-setup))

  ;; (add-hook 'org-agenda-finalize-hook 'evil-normal-state)
  (add-hook 'org-agenda-finalize-hook 'chris/org-agenda-setup)

  (advice-add 'org-agenda-todo :after #'org-save-all-org-buffers)

  (setq org-refile-targets '((org-agenda-files . (:maxlevel . 6))))

  (setq org-agenda-window-setup 'current-window)

  (defun chris/org-agenda ()
    "create a window that houses my org-agenda"
    (interactive)
    (with-selected-frame (make-frame
                          '((name . "org-agenda")
                            (width . 100)))
      (org-agenda-list)))

  (setq org-latex-packages-alist '(("margin=2cm" "geometry" nil)))

  :general
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "c" 'org-capture
    "rr" 'org-refile
    "e" 'org-export-dispatch
    "oa" 'org-agenda-list
    "gt" 'org-babel-tangle
    "il" 'org-insert-link
    "it" 'org-insert-todo-subheading)
  (chris/leader-keys
    :states 'normal
    :keymaps 'org-mode-map
    "is" 'org-time-stamp)
  (chris/leader-keys
    :states 'visual
    :keymaps 'override
    "il" 'org-insert-link)
  ('normal org-agenda-mode-map
           "q" 'org-agenda-quit
           "r" 'org-agenda-redo
           "d" 'org-agenda-deadline
           "s" 'org-agenda-schedule
           "t" 'org-agenda-todo
           "c" 'org-agenda-capture)
  ('normal org-columns-map
           "j" 'outline-next-heading
           "h" 'outline-previous-heading
           "q" 'chris/org-columns-quit)
  ('normal org-mode-map
           "RET" 'chris/org-dwim-at-point
           "gC" 'chris/org-columns-view
           "ge" 'org-edit-src-code
           "gr" 'revert-buffer
           "ze" 'org-emphasize
           "zn" 'org-narrow-to-subtree
           "zw" 'widen
           "zp" 'org-set-property
           "S" 'org-schedule
           "t" 'org-todo
           "gf" 'org-footnote-action
           "gl" 'org-id-copy)
  ('visual org-mode-map
           "gf" 'org-footnote-action)
  ('insert org-mode-map
           "C-i" 'completion-at-point)
  ('normal 'org-src-mode-map
           "q" 'org-edit-src-abort))

(defun chris/org-roam-capture-lesson-file ()
  "Function to return the lesson file that is needed for TFC plan capture and move to correct position for plan insertion"
  (interactive)
  ;; (unless org-roam-mode (org-roam-mode +1))
  (let ((node (org-roam-node-read)))
    (org-roam-node-visit node)
    (goto-char (point-min))
    (search-forward "PLAN")))

(defun chris/project-todo ()
  (concat (projectile-project-root) "TODO.org"))
(defun chris/project-changelog ()
  (concat (projectile-project-root) "CHANGELOG.org"))

(defun chris/org-babel-tangle-config ()
  (when (string-equal (buffer-file-name)
                      (expand-file-name "README.org" user-emacs-directory))
    (let ((org-confirm-babel-evaluate nil))
      (org-babel-tangle))))

(add-hook 'org-mode-hook (lambda () (add-hook 'after-save-hook #'chris/org-babel-tangle-config
                                              :append :local)))

(use-package evil-org
  :ensure t
  :after org
  :hook (org-mode . (lambda () evil-org-mode))
  :config
  ;; (add-to-list 'evil-digit-bound-motions 'evil-org-beginning-of-line)
  ;; (evil-define-key 'motion 'evil-org-mode
  ;;   (kbd "0") 'evil-org-beginning-of-line)
  (require 'evil-org-agenda)
  (evil-org-agenda-set-keys))

(use-package org-super-agenda
  :after org
  :init
  (setq org-super-agenda-groups '((:name "📅 Today"
                                   :time-grid t
                                   :scheduled today)
                                  (:name "Due Today"
                                   :deadline today)
                                  (:name "Important"
                                   :priority "A")
                                  (:name "Development"
                                   :category "TODO"
                                   :category "dev")
                                  (:name "Overdue"
                                   :time-grid t
                                   :scheduled past
                                   :deadline past)
                                  (:name "Due soon"
                                   :deadline future)))
  :config
  (org-super-agenda-mode +1)
  (setq org-super-agenda-header-map nil))

(use-package org-roam
  :after org
  :ensure t
  :init
  (setq org-roam-v2-ack t)
  :config
  (setq org-roam-directory "~/org"
        org-roam-buffer-width 0.25
        org-roam-file-exclude-regexp ".stversion.*\|.stfolder.*\|.*~.*\|.*sync.*"
        org-roam-db-location "~/.dotemacs/org-roam.db"
        org-roam-completion-everywhere t
        org-roam-capture-templates
        '(("d" "default" plain "%?"
           :if-new (file+head "${slug}.org"
                               "#+TITLE: ${title}\n#+AUTHOR: Chris Cochrun\n#+CREATED: %<%D - %I:%M %p>\n\n * %?")
           :unnarrowed t)
          ("b" "bible" plain "%?"
           :if-new (file+head "${slug}.org"
                              "#+TITLE: ${title}\n#+AUTHOR: Chris Cochrun\n#+CREATED: %<%D - %I:%M %p>\n- tags %^G\n\n* %?")
           :unnarrowed t)
          ("l" "TFC Lesson" plain (file ".templates/lessontemplate.org")
           :if-new (file+head "lessons/${slug}.org"
                               "#+TITLE: ${title}\n#+AUTHOR: Chris Cochrun\n#+CREATED: %<%D - %I:%M %p>\n")
           :unnarrowed t))
        org-roam-dailies-capture-templates
        '(("d" "daily" plain "%?"
           :immediate-finish nil
           :file-name "%<%Y-%m-%d>"
           :head "#+TITLE: %<%Y-%m-%d>\n#+AUTHOR: Chris Cochrun\n#+CREATED: %<%D - %I:%M %p>\n\n* HFL\n* Tasks\n* Family\n** How Do I Love Abbie?"
           :target (file+head "%<%Y-%m-%d>.org"
                               "#+TITLE: %<%Y-%m-%d>\n#+AUTHOR: Chris Cochrun\n#+CREATED: %<%D - %I:%M %p>\n\n* HFL\n* Tasks\n* Family\n** How Do I Love Abbie?\n* Bible")
           :unnarrowed t
           )
          ("b" "biblical daily" plain "%?"
           :file-name "%<%Y-%m-%d>-bib"
           :target (file+head "%<%Y-%m-%d>-bib.org" "#+TITLE: %<%Y-%m-%d> - Biblical\n#+AUTHOR: Chris Cochrun\n#+CREATED: %<%D - %I:%M %p>\n\n* Notes")
           :unnarrowed t)))
  (org-roam-setup)
  :general
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "nf" '(org-roam-node-find :which-key "org roam ff")
    "nr" 'org-roam-buffer-toggle
    "ni" 'org-roam-node-insert
    "nc" 'org-roam-capture
    "nt" 'org-roam-dailies-goto-today
    "ng" 'org-roam-graph))

(use-package websocket)
(use-package org-roam-ui
  :after org-roam
  :config
  (setq 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-present
  :commands org-present
  :config
  (add-hook 'org-present-mode-hook
            (lambda ()
              (org-present-big)
              (org-present-hide-cursor)))
  (add-hook 'org-present-mode-quit-hook
            (lambda ()
              (org-present-small)
              (org-present-show-cursor)))
  :general
  (chris/leader-keys 'normal
    "oo" 'org-present)
  ('normal org-present-mode-map
           "q" 'org-present-quit
           "j" 'org-present-next
           "k" 'org-present-prev
           ))

(use-package org-modern
  :config

  (setq org-modern-timestamp nil)
  
  (custom-set-faces
   '(org-modern-tag ((t :background "#9aedfe" :foreground "#282a36")))
   )
  (global-org-modern-mode +1)
  )

(defun chris/org-dwim-at-point (&optional arg)
  "Do-what-I-mean at point.

If on a:
- checkbox list item or todo heading: toggle it.
- clock: update its time.
- headline: cycle ARCHIVE subtrees, toggle latex fragments and inline images in
  subtree; update statistics cookies/checkboxes and ToCs.
- footnote reference: jump to the footnote's definition
- footnote definition: jump to the first reference of this footnote
- table-row or a TBLFM: recalculate the table's formulas
- table-cell: clear it and go into insert mode. If this is a formula cell,
  recaluclate it instead.
- babel-call: execute the source block
- statistics-cookie: update it.
- latex fragment: toggle it.
- link: follow it
- otherwise, refresh all inline images in current tree."
  (interactive "P")
  (let* ((context (org-element-context))
         (type (org-element-type context)))
    ;; skip over unimportant contexts
    (while (and context (memq type '(verbatim code bold italic underline strike-through subscript superscript)))
      (setq context (org-element-property :parent context)
            type (org-element-type context)))
    (pcase type
      (`headline
       (cond ((memq (bound-and-true-p org-goto-map)
                    (current-active-maps))
              (org-goto-ret))
             ((and (fboundp 'toc-org-insert-toc)
                   (member "TOC" (org-get-tags)))
              (toc-org-insert-toc)
              (message "Updating table of contents"))
             ((string= "ARCHIVE" (car-safe (org-get-tags)))
              (org-force-cycle-archived))
             ((or (org-element-property :todo-type context)
                  (org-element-property :scheduled context))
              (org-todo
               (if (eq (org-element-property :todo-type context) 'done)
                   (or (car (chris/org-get-todo-keywords-for (org-element-property :todo-keyword context)))
                       'todo)
                 'done))))
       ;; Update any metadata or inline previews in this subtree
       (org-update-checkbox-count)
       (org-update-parent-todo-statistics)
       (when (and (fboundp 'toc-org-insert-toc)
                  (member "TOC" (org-get-tags)))
         (toc-org-insert-toc)
         (message "Updating table of contents"))
       (let* ((beg (if (org-before-first-heading-p)
                       (line-beginning-position)
                     (save-excursion (org-back-to-heading) (point))))
              (end (if (org-before-first-heading-p)
                       (line-end-position)
                     (save-excursion (org-end-of-subtree) (point))))
              (overlays (ignore-errors (overlays-in beg end)))
              (latex-overlays
               (cl-find-if (lambda (o) (eq (overlay-get o 'org-overlay-type) 'org-latex-overlay))
                           overlays))
              (image-overlays
               (cl-find-if (lambda (o) (overlay-get o 'org-image-overlay))
                           overlays)))
         (chris/org--toggle-inline-images-in-subtree beg end)
         (if (or image-overlays latex-overlays)
             (org-clear-latex-preview beg end)
           (org--latex-preview-region beg end))))

      (`clock (org-clock-update-time-maybe))

      (`footnote-reference
       (org-footnote-goto-definition (org-element-property :label context)))

      (`footnote-definition
       (org-footnote-goto-previous-reference (org-element-property :label context)))

      ((or `planning `timestamp)
       (org-follow-timestamp-link))

      ((or `table `table-row)
       (if (org-at-TBLFM-p)
           (org-table-calc-current-TBLFM)
         (ignore-errors
           (save-excursion
             (goto-char (org-element-property :contents-begin context))
             (org-call-with-arg 'org-table-recalculate (or arg t))))))

      (`table-cell
       (org-table-blank-field)
       (org-table-recalculate arg)
       (when (and (string-empty-p (string-trim (org-table-get-field)))
                  (bound-and-true-p evil-local-mode))
         (evil-change-state 'insert)))

      (`babel-call
       (org-babel-lob-execute-maybe))

      (`statistics-cookie
       (save-excursion (org-update-statistics-cookies arg)))

      ((or `src-block `inline-src-block)
       (org-babel-execute-src-block))

      ((or `latex-fragment `latex-environment)
       (org-latex-preview))

      (`link
       (let* ((lineage (org-element-lineage context '(link) t))
              (path (org-element-property :path lineage)))
         (if (or (equal (org-element-property :type lineage) "img")
                 (and path (image-type-from-file-name path)))
             (chris/org--toggle-inline-images-in-subtree
              (org-element-property :begin lineage)
              (org-element-property :end lineage))
           (org-open-at-point arg))))

      (`paragraph
       (let ((match (and (org-at-item-checkbox-p) (match-string 1))))
         (org-toggle-checkbox)))

      (_
       (if (or (org-in-regexp org-ts-regexp-both nil t)
               (org-in-regexp org-tsr-regexp-both nil  t)
               (org-in-regexp org-link-any-re nil t))
           (call-interactively #'org-open-at-point)
         (chris/org--toggle-inline-images-in-subtree
          (org-element-property :begin context)
          (org-element-property :end context)))))))


(defun chris/org-get-todo-keywords-for (&optional keyword)
  "Returns the list of todo keywords that KEYWORD belongs to."
  (when keyword
    (cl-loop for (type . keyword-spec)
             in (cl-remove-if-not #'listp org-todo-keywords)
             for keywords =
             (mapcar (lambda (x) (if (string-match "^\\([^(]+\\)(" x)
                                     (match-string 1 x)
                                   x))
                     keyword-spec)
             if (eq type 'sequence)
             if (member keyword keywords)
             return keywords)))

(defun chris/org--toggle-inline-images-in-subtree (&optional beg end refresh)
  "Refresh inline image previews in the current heading/tree."
  (let ((beg (or beg
                 (if (org-before-first-heading-p)
                     (line-beginning-position)
                   (save-excursion (org-back-to-heading) (point)))))
        (end (or end
                 (if (org-before-first-heading-p)
                     (line-end-position)
                   (save-excursion (org-end-of-subtree) (point)))))
        (overlays (cl-remove-if-not (lambda (ov) (overlay-get ov 'org-image-overlay))
                                    (ignore-errors (overlays-in beg end)))))
    (dolist (ov overlays nil)
      (delete-overlay ov)
      (setq org-inline-image-overlays (delete ov org-inline-image-overlays)))
    (when (or refresh (not overlays))
      (org-display-inline-images t t beg end)
      t)))

(use-package org-re-reveal
  :ensure nil
  :config
  (setq org-re-reveal-root "file:///home/chris/org/presentations/reveal.js/"
        org-re-reveal-theme "serif"
        org-re-reveal-transition "slide"
        org-re-reveal-mobile-app t)
  )

(use-package ox-spectacle
  :config

  )

(use-package unicode-fonts
  :ensure t
  :config
  (unicode-fonts-setup))

(use-package emojify
  :ensure t
  :hook (after-init . global-emojify-mode)
  :config
  (setq emojify-display-style 'image)
  :general
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "ie" '(emojify-insert-emoji :which-key "insert emoji")))

(global-auto-revert-mode 1)
(setq global-auto-revert-non-file-buffers t)

(use-package visual-fill-column
  :after org
  :config
  (setq visual-fill-column-width 100
        visual-fill-column-center-text t))

(use-package toc-org
  :after org)

(use-package pulsar
  :config
  (setq pulsar-pulse-functions
      ;; NOTE 2022-04-09: The commented out functions are from before
      ;; the introduction of `pulsar-pulse-on-window-change'.  Try that
      ;; instead.
      '(recenter-top-bottom
        move-to-window-line-top-bottom
        reposition-window
        ;; bookmark-jump
        ;; other-window
        ;; delete-window
        ;; delete-other-windows
        forward-page
        backward-page
        scroll-up-command
        scroll-down-command
        ;; windmove-right
        ;; windmove-left
        ;; windmove-up
        ;; windmove-down
        ;; windmove-swap-states-right
        ;; windmove-swap-states-left
        ;; windmove-swap-states-up
        ;; windmove-swap-states-down
        ;; tab-new
        ;; tab-close
        ;; tab-next
        org-next-visible-heading
        org-previous-visible-heading
        org-forward-heading-same-level
        org-backward-heading-same-level
        outline-backward-same-level
        outline-forward-same-level
        outline-next-visible-heading
        outline-previous-visible-heading
        outline-up-heading))

  (setq pulsar-pulse-on-window-change t)
  (setq pulsar-pulse t)
  (setq pulsar-delay 0.055)
  (setq pulsar-iterations 10)
  (setq pulsar-face 'ffap)
  (setq pulsar-highlight-face 'pulsar-yellow)

  (pulsar-global-mode 1)
  ;; integration with the `consult' package:
  (add-hook 'consult-after-jump-hook #'pulsar-recenter-top)
  (add-hook 'consult-after-jump-hook #'pulsar-reveal-entry)
  
  ;; integration with the built-in `imenu':
  (add-hook 'imenu-after-jump-hook #'pulsar-recenter-top)
  (add-hook 'imenu-after-jump-hook #'pulsar-reveal-entry))

(defun chris/eww-mpv ()
  "Launch the url in mpv"
  (interactive)
  (let ((pt (avy-with ace-link-eww
              (avy-process
               (mapcar #'cdr (ace-link--eww-collect))
               (avy--style-fn avy-style)))))
    
    (when (number-or-marker-p pt)
      (goto-char pt)
      (let ((url (get-text-property (point) 'shr-url)))
        (if (start-process "mpv" "mpv-output" "mpv" url)
            (message (concat"mpv started => " url))
          (message "mpv failed, maybe this isn't a link?"))))))

(general-def 'normal eww-mode-map
  "gv" 'chris/eww-mpv)

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

  ;; Different scroll margin
  ;; (setq vertico-scroll-margin 0)

  ;; Show more candidates
  (setq vertico-count 10)

  ;; Grow and shrink the Vertico minibuffer
  (setq vertico-resize t)

  ;; Optionally enable cycling for `vertico-next' and `vertico-previous'.
  (setq vertico-cycle t)
  :general
  (general-def 'vertico-map
    "C-j" 'vertico-next
    "C-k" 'vertico-previous)
  )

;; Persist history over Emacs restarts. Vertico sorts by history position.
(use-package savehist
  :init
  (savehist-mode))

;; A few more useful configurations...
(use-package emacs
  :init
  ;; Add prompt indicator to `completing-read-multiple'.
  ;; Alternatively try `consult-completing-read-multiple'.
  (defun crm-indicator (args)
    (cons (concat "[CRM] " (car args)) (cdr args)))
  (advice-add #'completing-read-multiple :filter-args #'crm-indicator)

  ;; Do not allow the cursor in the minibuffer prompt
  (setq minibuffer-prompt-properties
        '(read-only t cursor-intangible t face minibuffer-prompt))
  (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)

  ;; Emacs 28: Hide commands in M-x which do not work in the current mode.
  ;; Vertico commands are hidden in normal buffers.
  ;; (setq read-extended-command-predicate
  ;;       #'command-completion-default-include-p)

  ;; Enable recursive minibuffers
  (setq enable-recursive-minibuffers t))

(use-package consult
  :after vertico
  :config
  (setq consult-narrow-key "'"
        consult-project-root-function 'projectile-project-root)

  (defun chris/consult-ripgrep-for-stepdata ()
    "Make ripgrep search hidden files for stepdata"
    (interactive)
    (let ((consult-ripgrep-args
           (concat "rg "
                   "--null "
                   "--line-buffered "
                   "--color=never "
                   "--max-columns=1000 "
                   "--path-separator / "
                   "--no-heading "
                   "--line-number "
                   ;; adding these to default
                   "--smart-case "
                   "--hidden "
                   ;; defaults
                   "."
                   )))
      (consult-ripgrep)))
  
  
  :general
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "si" 'consult-imenu
    "so" 'consult-org-heading
    "sf" 'consult-find
    "sm" 'bookmark-jump
    "sf" 'consult-flymake
    "sy" 'consult-yank-from-kill-ring))

(use-package marginalia
  :bind (:map minibuffer-local-map
              ("C-M-a" . marginalia-cycle)
              ;; :map embark-general-map
              ;;     ("A" . marginalia-cycle)
              )

  ;; The :init configuration is always executed (Not lazy!)
  :init

  ;; Must be in the :init section of use-package such that the mode gets
  ;; enabled right away. Note that this forces loading the package.
  (marginalia-mode)

  ;; When using Selectrum, ensure that Selectrum is refreshed when cycling annotations.
  ;; (advice-add #'marginalia-cycle :after
  ;;             (lambda () (when (bound-and-true-p selectrum-mode) (selectrum-exhibit))))

  ;; Prefer richer, more heavy, annotations over the lighter default variant.
  (setq marginalia-annotators '(marginalia-annotators-heavy marginalia-annotators-light nil))
  :after vertico
  :config
  (setq marginalia--cache-size 60000))

(use-package all-the-icons-completion
  :after vertico
  :config
  (all-the-icons-completion-mode))

(use-package embark
  :ensure t
  :general
  ('vertico-map
   "C-'" 'embark-act)
  ('normal 'org-mode-map
           "C-'" 'embark-act)

  :config
  (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))))))))

  (setq embark-indicators
        '(embark-which-key-indicator
          embark-highlight-indicator
          embark-isearch-highlight-indicator))

  (defun embark-hide-which-key-indicator (fn &rest args)
    "Hide the which-key indicator immediately when using the completing-read prompter."
    (which-key--hide-popup-ignore-command)
    (let ((embark-indicators
           (remq #'embark-which-key-indicator embark-indicators)))
      (apply fn args)))

  (advice-add #'embark-completing-read-prompter
              :around #'embark-hide-which-key-indicator)
  )

(use-package embark-consult)

(use-package corfu
  :ensure t
  ;; Optional customizations
  :custom
  (corfu-cycle t)                ;; Enable cycling for `corfu-next/previous'
  (corfu-auto t)                 ;; Enable auto completion
  (corfu-separator ?\s)          ;; Orderless field separator
  (corfu-quit-no-match 'separator)      ;; Never quit, even if there is no match
  (corfu-preview-current 'insert)    ;; Enable current candidate preview
  (corfu-preselect-first nil)    ;; Enable candidate preselection
  (corfu-on-exact-match 'insert)     ;; Configure handling of exact matches
  (corfu-echo-documentation '(1.0 . 0.2)) ;; Disable documentation in the echo area
  (corfu-scroll-margin 5)        ;; Use scroll margin
  (corfu-count 15)
  (corfu-auto-prefix 2)

  ;; You may want to enable Corfu only for certain modes.
  ;; :hook ((prog-mode . corfu-mode)
  ;;        (shell-mode . corfu-mode)
  ;;        (eshell-mode . corfu-mode))

  ;; Recommended: Enable Corfu globally.
  ;; This is recommended since dabbrev can be used globally (M-/).
  :init
  (global-corfu-mode)
  :general
  ('corfu-map
   "C-j" 'corfu-next
   "C-k" 'corfu-previous
   "M-SPC" 'corfu-insert-separator))

;; Optionally use the `orderless' completion style. See `+orderless-dispatch'
;; in the Consult wiki for an advanced Orderless style dispatcher.
;; Enable `partial-completion' for files to allow path expansion.
;; You may prefer to use `initials' instead of `partial-completion'.
(use-package orderless
  :ensure t
  :init
  ;; Configure a custom style dispatcher (see the Consult wiki)
  ;; (setq orderless-style-dispatchers '(+orderless-dispatch)
  ;;       orderless-component-separator #'orderless-escapable-split-on-space)
  (setq completion-styles '(orderless)
        completion-category-defaults nil
        completion-category-overrides '((file (styles . (partial-completion)))))

  (defun flex-if-twiddle (pattern _index _total)
    (when (string-suffix-p "~" pattern)
      `(orderless-flex . ,(substring pattern 0 -1))))

  (defun first-initialism (pattern index _total)
    (if (= index 0) 'orderless-initialism))

  (defun without-if-bang (pattern _index _total)
    (cond
     ((equal "!" pattern)
      '(orderless-literal . ""))
     ((string-prefix-p "!" pattern)
      `(orderless-without-literal . ,(substring pattern 1)))))

  (setq orderless-matching-styles '(orderless-literal orderless-regexp)
        orderless-style-dispatchers '(flex-if-twiddle
                                      without-if-bang))
  )

;; Use dabbrev with Corfu!
(use-package dabbrev
  :after corfu
  ;; Swap M-/ and C-M-/
  :bind (("M-/" . dabbrev-completion)
         ("C-M-/" . dabbrev-expand)))

(use-package cape
  :ensure t
  ;; Bind dedicated completion commands
  :bind (("C-c p p" . completion-at-point) ;; capf
         ("C-c p t" . complete-tag)        ;; etags
         ("C-c p d" . cape-dabbrev)        ;; or dabbrev-completion
         ("C-c p f" . cape-file)
         ("C-c p k" . cape-keyword)
         ("C-c p s" . cape-symbol)
         ("C-c p a" . cape-abbrev)
         ("C-c p i" . cape-ispell)
         ("C-c p l" . cape-line)
         ("C-c p w" . cape-dict)
         ("C-c p \\" . cape-tex)
         ("C-c p _" . cape-tex)
         ("C-c p ^" . cape-tex)
         ("C-c p &" . cape-sgml)
         ("C-c p r" . cape-rfc1345))
  :init
  ;; Add `completion-at-point-functions', used by `completion-at-point'.
  (add-to-list 'completion-at-point-functions #'cape-file)
  ;; (add-to-list 'completion-at-point-functions #'cape-tex)
  (add-to-list 'completion-at-point-functions #'cape-keyword)
  (add-to-list 'completion-at-point-functions #'cape-dabbrev)
  ;;(add-to-list 'completion-at-point-functions #'cape-sgml)
  ;;(add-to-list 'completion-at-point-functions #'cape-rfc1345)
  ;;(add-to-list 'completion-at-point-functions #'cape-abbrev)
  (add-to-list 'completion-at-point-functions #'cape-ispell)
  ;;(add-to-list 'completion-at-point-functions #'cape-dict)
  ;; (add-to-list 'completion-at-point-functions #'cape-symbol)
  ;;(add-to-list 'completion-at-point-functions #'cape-line)

  :config
  (setq cape-dabbrev-min-length 2)  
  )

(use-package devdocs
  :general
  (chris/leader-keys 'normal
    "hd" 'devdocs-lookup))

(use-package yasnippet
  :config
  (setq yas-snippet-dirs (list (expand-file-name "yasnippets/" user-emacs-directory)))
  (yas-global-mode 1))

(use-package tempel
  :bind (("M-+" . tempel-complete) ;; Alternative tempel-expand
         ("M-'" . tempel-insert)
         ("C-M-<return>" . tempel-done))

  :init

  ;; Setup completion at point
  (defun tempel-setup-capf ()
    ;; Add the Tempel Capf to `completion-at-point-functions'. `tempel-expand'
    ;; only triggers on exact matches. Alternatively use `tempel-complete' if
    ;; you want to see all matches, but then Tempel will probably trigger too
    ;; often when you don't expect it.
    ;; NOTE: We add `tempel-expand' *before* the main programming mode Capf,
    ;; such that it will be tried first.
    (setq-local completion-at-point-functions
                (cons #'tempel-complete
                      completion-at-point-functions)))

  (add-hook 'prog-mode-hook 'tempel-setup-capf)
  (add-hook 'text-mode-hook 'tempel-setup-capf)

  ;; Optionally make the Tempel templates available to Abbrev,
  ;; either locally or globally. `expand-abbrev' is bound to C-x '.
  ;; (add-hook 'prog-mode-hook #'tempel-abbrev-mode)
  ;; (tempel-global-abbrev-mode)
  :general
  (chris/leader-keys
    "ic" 'tempel-insert)
)

(use-package projectile
  :ensure t
  :general
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "op" 'projectile-switch-open-project
    "gc" 'projectile-compile-project
    "gr" 'projectile-run-project
    "fp" 'project-find-file
    "fP" 'project-switch-project))

(use-package simple-httpd
  :ensure t)

(use-package avy
  :after evil)

(use-package evil-avy
  :after avy
  :general
  (general-define-key
   :states 'normal
   :keymaps '(override magit-mode-map)
   "F" 'magit-pull)
  (general-def 'normal
    "gl" 'avy-goto-line))

(use-package ace-link
  :after avy
  :general
  (general-def 'normal
    "gL" 'ace-link))

(setq display-buffer-alist
      '(("\\*e?shell\\*"
         (display-buffer-in-side-window)
         (side . bottom)
         (window-height . 0.25))
        ("*helpful*"
         (display-buffer-in-side-window)
         (side . right)
         (window-width . 0.4))
        ("*compilation*"
         (display-buffer-in-side-window)
         (side . right)
         (window-width . 0.4))
        ("*org-roam*"
         (display-buffer-in-side-window)
         (side . right)
         (window-width . 0.4))
        ("\\*elfeed-entry\\*"
         (display-buffer-in-side-window)
         (side . bottom)
         (window-height . 0.60))
        ("*Agenda Commands*"
         (display-buffer-in-side-window)
         (side . right)
         (window-width . 0.30))
        ("\\*Bongo-Elfeed Queue\\*"
         (display-buffer-in-side-window)
         (side . bottom)
         (window-height . 0.25))
        ("\\*Async Shell Command\\*"
         (display-buffer-no-window))
        ))

(defun chris/kill-buffer-frame ()
  "Kills the active buffer and frame"
  (interactive)
  (kill-this-buffer)
  (delete-frame))

(use-package ace-window
  :config (ace-window-display-mode)
  :general
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "ww" '(ace-window :which-key "select window")))

(use-package helpful
  :ensure nil
  :commands (helpful-callable helpful-variable helpful-command helpful-key)
  :general
  (general-def 'normal 'helpful-mode-map
    "q" 'helpful-kill-buffers)
  :config
  (defun helpful--autoloaded-p (sym buf)
    "Return non-nil if function SYM is autoloaded."
    (-when-let (file-name (buffer-file-name buf))
      (setq file-name (s-chop-suffix ".gz" file-name))
      (condition-case nil
          (help-fns--autoloaded-p sym file-name)
                                        ; new in Emacs 29.0.50
                                        ; see https://github.com/Wilfred/helpful/pull/283
        (error (help-fns--autoloaded-p sym)))))

  (defun helpful--skip-advice (docstring)
    "Remove mentions of advice from DOCSTRING."
    (let* ((lines (s-lines docstring))
           (relevant-lines
            (--take-while
             (not (or (s-starts-with-p ":around advice:" it)
                      (s-starts-with-p "This function has :around advice:" it)))
             lines)))
      (s-trim (s-join "\n" relevant-lines)))))

(use-package format-all
  :config
  (format-all-mode +1)
  (setq format-all-formatters '("Emacs Lisp" emacs-lisp))
  :defer 1)

(defvar read-symbol-positions-list nil)
(add-hook 'comint-mode-hook 'ansi-color-for-comint-mode-on)

(add-hook 'comint-mode-hook 'hl-line-mode)
(add-hook 'prog-mode-hook 'hl-line-mode)
(add-hook 'prog-mode-hook 'hs-minor-mode)

(add-to-list 'auto-mode-alist '("\\.yuck?\\'" . lisp-data-mode))

(use-package tree-sitter
  :config
  (global-tree-sitter-mode +1)
  )
(use-package tree-sitter-langs)

(defun astyle-this-buffer ()
  "Use astyle command to auto format c/c++ code."
  (interactive "r")
  (let* ((original-point (point))) ;; save original point before processing, thanks to @Scony
    (progn
      (if (executable-find "astyle")
          (shell-command-on-region
           (point-min) (point-max)
           (concat
            "astyle"
            " --style=" cc-mode-code-style
            " --indent=spaces=" (number-to-string c-basic-offset)
            " --pad-oper"
            " --pad-header"
            " --break-blocks"
            " --delete-empty-lines"
            " --align-pointer=type"
            " --align-reference=name")
           (current-buffer) t
           (get-buffer-create "*Astyle Errors*") t)
        (message "Cannot find binary \"astyle\", please install first."))
      (goto-char original-point)))) ;; restore original point

(defun astyle-before-save ()
  "Auto styling before saving."
  (interactive)
  (when (member major-mode '(cc-mode c++-mode c-mode))
    (astyle-this-buffer)))

(add-hook 'c-mode-common-hook (lambda () (add-hook 'before-save-hook 'astyle-before-save)))

(use-package rustic
  :config
  ;; comment to disable rustfmt on save
  (setq rustic-format-on-save t
        rustic-lsp-client 'eglot))

(use-package web-mode
  :mode "\\.html\\'"
  :config
  (add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.phtml?\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.tpl\\.php\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.[agj]sp\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.erb\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.mustache\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.djhtml?\\'" . web-mode))

  (setq web-mode-enable-auto-pairing t
        web-mode-enable-auto-expanding t
        web-mode-enable-auto-closing t
        web-mode-enable-current-column-highlight t
        web-mode-enable-current-element-highlight t)

  :general
  (general-def 'normal web-mode-map
    "TAB" 'indent-according-to-mode)
  (general-def 'insert web-mode-map
    "TAB" 'indent-according-to-mode)
  )

(use-package lua-mode
  :mode ("\\.lua\\'" . lua-mode))

(use-package nix-mode
  :mode "\\.nix\\'")

(use-package eglot
  :commands eglot
  :hook
  (c++-mode . eglot-ensure)
  (rust-mode . eglot-ensure))

(use-package consult-eglot
  :general
  (general-def 'normal eglot-mode-map
    "gs" 'consult-eglot-symbols))

(use-package cmake-mode
  :mode ("\\CMakeLists.txt\\'" . cmake-mode))

(use-package fennel-mode
  :mode ("\\.fnl\\'" . fennel-mode))

(use-package yaml-mode
  :mode ("\\.yml\\'" . yaml-mode))

(use-package docker
  :defer t
  :config
  (setq docker-run-as-root t))

(use-package docker-tramp
  :after docker)

(use-package fish-mode
  :mode ("\\.fish\\'" . fish-mode))

(use-package markdown-mode
  :mode ("\\.md\\'" . markdown-mode)
  :config
  (setq markdown-fontify-code-blocks-natively t)
  (add-hook 'markdown-mode-hook 'chris/org-mode-setup))

(use-package qml-mode
  :mode ("\\.qml\\'" . qml-mode))

;; (use-package company-qml
;;   :after qml-mode
;;   :config
;;   ;; (add-to-list 'company-backends 'company-qml)
;;   )

;; (setq company-backends
;;       '(company-bbdb company-semantic company-cmake company-capf company-clang company-files
;;               (company-dabbrev-code company-gtags company-etags company-keywords)
;;               company-oddmuse company-dabbrev))

;; (use-package qt-pro-mode
;;   :after qml-mode)

(use-package csv-mode
  :mode ("\\.csv\\'" . csv-mode))

(use-package restclient
  :commands (restclient-mode))

(use-package ob-restclient
  :after org)

(use-package dart-mode
  :mode ("\\.dart\\'" . dart-mode)
  :hook (dart-mode . lsp-deferred)
  :general
  (general-def 'normal dart-mode-map
    "gr" 'flutter-run-or-hot-reload
    "gR" 'lsp-dart-dap-flutter-hot-restart))

(use-package flutter
  :after dart
  :general
  (chris/leader-keys dart-mode-map
    "rf" 'flutter-run-or-hot-reload))
(use-package hover
  :after dart)

(add-to-list 'exec-path "/opt/android-sdk/cmdline-tools/latest/bin")

(use-package direnv
  :config
  (direnv-mode))

(use-package dired
  :ensure nil
  :config
  (defun chris/dired-open-xdg ()
    "Open the file-at-point in the appropriate program"
    (interactive)
    (let ((file (file-truename (ignore-errors (dired-get-file-for-visit)))))
      (message file)
      (call-process "xdg-open" nil 0 nil file)))

  (defun chris/setup-dired ()
    "setup dired"
    (setq truncate-lines t)
    (visual-line-mode nil)
    (dired-hide-details-mode t))

  (setq dired-dwim-target t
        delete-by-moving-to-trash t
        dired-mouse-drag-files t)

  (setq dired-listing-switches "-aoh --group-directories-first")
  (setq dired-hide-details-hide-symlink-targets nil
        dired-kill-when-opening-new-dired-buffer t)
  (add-hook 'dired-mode-hook 'chris/setup-dired)

  :general
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "od" '(dired-jump :which-key "open dired here")
    "oD" '(dired :which-key "open dired select"))
  ('normal dired-mode-map
    "q" 'kill-this-buffer
    "C-<return>" 'chris/dired-open-xdg
    "M-<return>" 'ffap-other-window
    "C-'" 'embark-act))

(defun chris/dired-yank-filename ()
  "Get the full filename from file at point and put into kill-ring"
  (interactive)
  (let* ((file (dired-get-filename)))
    (clipboard-kill-ring-save nil nil file)))

(use-package all-the-icons-dired
  :hook (dired-mode . all-the-icons-dired-mode))

(use-package dired-single
  :after dired
  :general
  (general-def 'normal dired-mode-map
    "h" 'dired-single-up-directory
    "l" 'dired-single-buffer))

(use-package dired-rainbow
  :after dired
  :config
  (defconst chris/dired-media-files-extensions
    '("mp3" "mp4" "MP3" "MP4" "avi" "mpg" "flv" "ogg" "opus")
    "Media files.")

  (defconst chris/dired-image-files-extensions
    '("png" "jpg" "PNG" "JPG" "jpeg" "JPEG" "gif" "GIF")
    "image files")

  (dired-rainbow-define html "#4e9a06" ("htm" "html" "xhtml"))
  (dired-rainbow-define media "#f3f99d" chris/dired-media-files-extensions)
  (dired-rainbow-define image "#5af78e" chris/dired-image-files-extensions)

  (dired-rainbow-define log (:inherit default :italic t) ".*\\.log"))

(use-package diredfl
  :after dired
  :config (diredfl-global-mode +1))

(use-package dired-rsync
  :general
  (general-def 'normal dired-mode-map
    "C" 'dired-rsync))

(require 'tramp)
(add-to-list 'tramp-default-proxies-alist
             '(nil "\\`root\\'" "/ssh:%h:"))
(add-to-list 'tramp-default-proxies-alist
             '((regexp-quote (system-name)) nil nil))

(use-package ledger-mode)

(use-package mu4e
  :load-path "/usr/share/emacs/site-lisp/mu4e/"
  :init
  (setq mu4e-maildir "~/Maildir"
        user-full-name "Chris Cochrun"
        mu4e-change-filenames-when-moving t
        mu4e-get-mail-command "mbsync -a"
        mu4e-update-interval (* 15 60)
        mu4e-attachment-dir "/home/chris/nextcloud/home/Documents/attachments"
        mu4e-completing-read-function #'completing-read
        mu4e-mu-binary "/etc/profiles/per-user/chris/bin/mu"
        mu4e-compose-signature-auto-include nil)

  :config
  (setq mail-user-agent 'mu4e-user-agent)

  (setq mu4e-contexts
        (list
         (make-mu4e-context
          :name "work"
          :match-func
          (lambda (msg)
            (when msg
              (string-prefix-p "/office" (mu4e-message-field msg :maildir))))
          :vars '((user-mail-address      . "chris@tfcconnection.org")
                  (mu4e-sent-folder       . "/office/Sent Items/")
                  (mu4e-drafts-folder     . "/office/Drafts")
                  (mu4e-trash-folder      . "/office/Deleted Items")
                  (mu4e-refile-folder     . "/office/Archive")
                  (smtpmail-smtp-user     . "chris@tfcconnection.org")
                  (smtpmail-starttls-credentials . '(("smtp.office365.com" 587 nil nil)))
                  (smtpmail-auth-credentials . '(("smtp.office365.com" 587 "chris@tfcconnection.org" nil)))
                  (smtpmail-smtp-server . "smtp.office365.com")
                  (mu4e-compose-signature . "Praising God in all things,\nChris Cochrun")))
         (make-mu4e-context
          :name "outlook"
          :match-func
          (lambda (msg)
            (when msg
              (string-prefix-p "/outlook" (mu4e-message-field msg :maildir))))
          :vars '((user-mail-address      . "chris.cochrun@outlook.com")
                  (mu4e-sent-folder       . "/outlook/Sent/")
                  (mu4e-drafts-folder     . "/outlook/Drafts")
                  (mu4e-trash-folder      . "/outlook/Deleted")
                  (mu4e-refile-folder     . "/outlook/Archive")
                  (smtpmail-smtp-user     . "chris.cochrun@outlook.com")
                  (mu4e-compose-signature . "Praising God in all things,\nChris Cochrun")))
         (make-mu4e-context
          :name "personal"
          :match-func
          (lambda (msg)
            (when msg
              (string-prefix-p "/cochrun" (mu4e-message-field msg :maildir))))
          :vars '((user-mail-address      . "chris@cochrun.xyz")
                  (mu4e-sent-folder       . "/cochrun/Sent/")
                  (mu4e-drafts-folder     . "/cochrun/Drafts")
                  (mu4e-trash-folder      . "/cochrun/Trash")
                  (mu4e-refile-folder     . "/cochrun/Archive")
                  (smtpmail-smtp-user     . "chris@cochrun.xyz")
                  (smtpmail-starttls-credentials . '(("mail.cochrun.xyz" 587 nil nil)))
                  (smtpmail-auth-credentials . '(("mail.cochrun.xyz" 587 "chris@cochrun.xyz" nil)))
                  (smtpmail-smtp-server . "mail.cochrun.xyz")
                  (mu4e-compose-signature . "Praising God in all things,\nChris Cochrun\nchris@tfcconnection.org\nchris@cochrun.xyz")))
         (make-mu4e-context
          :name "gmail"
          :match-func
          (lambda (msg)
            (when msg
              (string-prefix-p "/gmail" (mu4e-message-field msg :maildir))))
          :vars '((user-mail-address      . "ccochrun21@gmail.com")
                  (mu4e-sent-folder       . "/gmail/[Gmail].Sent Mail/")
                  (smtpmail-smtp-user     . "ccochrun21@gmail.com")
                  (mu4e-compose-signature . "Praising God in all things,\nChris Cochrun")))))

  ;; Add the ability to send email
  (setq message-send-mail-function 'smtpmail-send-it
        starttls-use-gnutls t
        smtpmail-default-smtp-server "mail.cochrun.xyz"
        smtpmail-smtp-service 587)

  ;; shortcuts in the jumplist by pressing "J" in the mu4e buffer
  (setq mu4e-maildir-shortcuts
        '((:maildir "/office/Archive"                :key ?a)
          (:maildir "/office/INBOX"                  :key ?i)
          (:maildir "/cochrun/INBOX"                  :key ?p)
          (:maildir "/outlook/INBOX"                 :key ?l)
          (:maildir "/office/Junk Email"             :key ?j)
          (:maildir "/office/INBOX/Website Forms"    :key ?f)
          (:maildir "/gmail/INBOX"                   :key ?g)
          (:maildir "/office/Sent Items"             :key ?s)))

  ;; (add-to-list mu4e-headers-actions ("org capture message" . mu4e-org-store-and-capture))

  (setq mu4e-bookmarks
        '((:name "Unread messages"
                 :query "flag:unread AND NOT flag:trashed AND NOT maildir:\"/outlook/Junk\" AND NOT maildir:\"/office/Junk Email\" AND NOT maildir:\"/outlook/Deleted\" AND NOT maildir:\"/office/Deleted Items\" AND NOT maildir:\"/office/Archive\" AND NOT maildir:\"/outlook/Archive\" AND NOT maildir:\"/cochrun/Archive\""
                 :key 117)
          (:name "Today's messages"
                 :query "date:today..now"
                 :key 116)
          (:name "Last 7 days"
                 :query "date:7d..now"
                 :hide-unread t
                 :key 119)
          (:name "Messages with images"
                 :query "mime:image/*"
                 :key 112)))

  (setq mu4e-view-prefer-html nil
        shr-color-visible-luminance-min 80)

  (setq mu4e-use-fancy-chars t
        mu4e-headers-draft-mark '("D" . "")
        mu4e-headers-flagged-mark '("F" . "")
        mu4e-headers-new-mark '("N" . " ")
        mu4e-headers-passed-mark '("P" . "")
        mu4e-headers-replied-mark '("R" . "")
        mu4e-headers-seen-mark '("S" . " ")
        mu4e-headers-trashed-mark '("T" . " ")
        mu4e-headers-attach-mark '("a" . " ")
        mu4e-headers-encrypted-mark '("x" . "")
        mu4e-headers-signed-mark '("s" . " ")
        mu4e-headers-unread-mark '("u" . " "))

  (setq mu4e-headers-fields
        '((:human-date . 12)
          (:flags . 6)
          (:from . 22)
          (:subject)))

  (setq mu4e-view-actions
        '(("capture message" . mu4e-action-capture-message)
          ("view in browser" . mu4e-action-view-in-browser)
          ("show this thread" . mu4e-action-show-thread)))

  (defun chris/setup-mu4e-headers ()
    (toggle-truncate-lines +1)
    (display-line-numbers-mode -1))


  (defun chris/setup-mu4e-view ()
    (display-line-numbers-mode -1)
    (toggle-truncate-lines +1)
    (setq visual-fill-column-center-text t)
    (setq visual-fill-column-width 100)
    (visual-fill-column-mode +1))

  (remove-hook 'mu4e-main-mode-hook '(display-line-numbers-mode -1))
  (add-hook 'mu4e-headers-mode-hook #'chris/setup-mu4e-headers)
  (add-hook 'mu4e-view-mode-hook #'chris/setup-mu4e-view)

  
  (mu4e t)
  :general
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "om" 'mu4e)
  (general-def 'normal mu4e-view-mode-map
    "ga" 'mu4e-view-save-attachments))

(use-package org-msg
  :hook (mu4e-compose-mode . org-msg-edit-mode)
  :config
  (org-msg-mode)
  (setq mail-user-agent 'mu4e-user-agent
        org-msg-options "html-postamble:nil H:5 num:nil ^:{} toc:nil author:nil email:nil \\n:t"
	org-msg-startup "hidestars indent inlineimages"
	org-msg-greeting-fmt "\nHi%s,\n\n"
	org-msg-recipient-names '(("chris@tfcconnection.org" . "Chris Cochrun"))
	org-msg-greeting-name-limit 3
	org-msg-default-alternatives '((new		. (text html))
				       (reply-to-html	. (text html))
				       (reply-to-text	. (text)))
	org-msg-convert-citation t
	org-msg-signature "

 Regards,

 #+begin_signature
 --
 *Chris*
 /Praising God in all things!/
 #+end_signature"))

(use-package calfw
  :commands chris/calfw-calendar-open
  :config
  (defun chris/calfw-calendar-open ()
    (interactive)
    (cfw:open-calendar-buffer
     :contents-sources
     (list
      (cfw:org-create-source
       "Cyan")  ; org-agenda source
      (cfw:ical-create-source
       "NV" "https://thrillshare-cmsv2.services.thrillshare.com/api/v4/o/6958/cms/events/generate_ical?filter_ids&section_ids" "Green")  ; School Calendar
      (cfw:ical-create-source
       "Outlook" "https://outlook.office365.com/owa/calendar/62a0d491bec4430e825822afd2fd1c01@tfcconnection.org/9acc5bc27ca24ce7a900c57284959f9d8242340735661296952/S-1-8-2197686000-2519837503-3687200543-3873966527/reachcalendar.ics" "Yellow")  ; Outlook Calendar
      )))
  (custom-set-faces
   '(cfw:face-title ((t (:weight bold :height 2.0 :inherit fixed-pitch))))
   '(cfw:face-header ((t (:slant italic :weight bold))))
   '(cfw:face-sunday ((t :weight bold)))
   '(cfw:face-saturday ((t :weight bold)))
   '(cfw:face-holiday ((t :weight bold)))
   ;; '(cfw:face-grid ((t :foreground "DarkGrey")))
   '(cfw:face-default-content ((t :height 0.7)))
   ;; '(cfw:face-periods ((t :foreground "cyan")))
   '(cfw:face-day-title ((t (:background nil))))
   '(cfw:face-default-day ((t :weight bold :inherit cfw:face-day-title)))
   '(cfw:face-annotation ((t :inherit cfw:face-day-title)))
   '(cfw:face-disable ((t :inherit cfw:face-day-title)))
   '(cfw:face-today-title ((t :weight bold)))
   '(cfw:face-today ((t :weight bold)))
   ;; '(cfw:face-select ((t :background "#2f2f2f")))
   '(cfw:face-toolbar ((t :foreground "Steelblue4" :background "Steelblue4")))
   '(cfw:face-toolbar-button-off ((t :weight bold)))
   '(cfw:face-toolbar-button-on ((t :weight bold))))
  (setq cfw:fchar-junction ?╋
        cfw:fchar-vertical-line ?┃
        cfw:fchar-horizontal-line ?━
        cfw:fchar-left-junction ?┣
        cfw:fchar-right-junction ?┫
        cfw:fchar-top-junction ?┯
        cfw:fchar-top-left-corner ?┏
        cfw:fchar-top-right-corner ?┓)
  :general
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "oc" 'chris/calfw-calendar-open)
  (general-def cfw:calendar-mode-map
    "q" 'kill-this-buffer
    "RET" 'cfw:show-details-command)
  (general-def 'normal cfw:details-mode-map
    "q" 'cfw:details-kill-buffer-command))

(use-package calfw-org
  :after calfw)

(use-package calfw-ical
  :after calfw)

(use-package org-caldav
  :after org
  :config
  (setq org-caldav-url "https://staff.tfcconnection.org/remote.php/dav/calendars/chris"
        org-caldav-calendar-id "org"
        org-caldav-inbox "/home/chris/org/todo/inbox.org"
        org-caldav-files '("/home/chris/org/todo/todo.org"
                          "/home/chris/org/todo/notes.org"
                          "/home/chris/org/todo/prayer.org")
        org-icalendar-alarm-time 15
        org-icalendar-use-scheduled '(todo-start event-if-todo)))

(use-package org-wild-notifier
  :after org
  :config
  (setq alert-default-style 'libnotify)
  (org-wild-notifier-mode +1))

(use-package magit
  :ensure nil
  :commands (magit-status magit-get-current-branch)
  :general
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "g g" 'magit-status)
  :custom
  (magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1))

(use-package eshell
  :ensure nil
  :config
  (require 'em-tramp)

  (with-eval-after-load 'esh-module   ;; REVIEW: It used to work, but now the early `provide' seems to backfire.
    (unless (boundp 'eshell-modules-list)
      (load "esh-module"))   ;; Don't print the banner.
    (push 'eshell-tramp eshell-modules-list))

  (setq password-cache t
        password-cache-expiry 3600)

  (setq eshell-history-size 1024)


  ;;; Extra execution information
  (defvar chris/eshell-status-p t
    "If non-nil, display status before prompt.")
  (defvar chris/eshell-status--last-command-time nil)
  (make-variable-buffer-local 'chris/eshell-status--last-command-time)
  (defvar chris/eshell-status-min-duration-before-display 0
    "If a command takes more time than this, display its duration.")

  (defun chris/eshell-status-display ()
    (if chris/eshell-status--last-command-time
        (let ((duration (time-subtract (current-time) chris/eshell-status--last-command-time)))
          (setq chris/eshell-status--last-command-time nil)
          (when (> (time-to-seconds duration) chris/eshell-status-min-duration-before-display)
            (format "  %.3fs %s"
                    (time-to-seconds duration)
                    (format-time-string "| %F %T" (current-time)))))
      (format "  0.000s")))

  (defun chris/eshell-status-record ()
    (setq chris/eshell-status--last-command-time (current-time)))

  (add-hook 'eshell-pre-command-hook 'chris/eshell-status-record)

  (setq eshell-prompt-function
        (lambda nil
          (let ((path (abbreviate-file-name (eshell/pwd))))
            (concat
             (if (or (string= system-name "archdesktop") (string= system-name "syl"))
                 nil
               (format
                (propertize "\n(%s@%s)" 'face '(:foreground "#606580"))
                (propertize (user-login-name) 'face '(:inherit compilation-warning))
                (propertize (system-name) 'face '(:inherit compilation-warning))))
             (if (and (require 'magit nil t) (or (magit-get-current-branch) (magit-get-current-tag)))
                 (let* ((root (abbreviate-file-name (magit-rev-parse "--show-toplevel")))
                        (after-root (substring-no-properties path (min (length path) (1+ (length root))))))
                   (format
                    (propertize "\n[ %s | %s@%s ]" 'face font-lock-comment-face)
                    (propertize root 'face `(:inherit org-warning))
                    (propertize after-root 'face `(:inherit org-level-1 :height 1.0))
                    (propertize (or (magit-get-current-branch) (magit-get-current-tag)) 'face `(:inherit org-macro))))
               (format
                (propertize "\n[%s]" 'face font-lock-comment-face)
                (propertize path 'face `(:inherit org-level-1))))
             (when chris/eshell-status-p
               (propertize (or (chris/eshell-status-display) "") 'face font-lock-comment-face))
             (propertize "\n" 'face '(:inherit org-todo :weight ultra-bold))
             " "))))

  ;;; If the prompt spans over multiple lines, the regexp should match
  ;;; last line only.
  (setq-default eshell-prompt-regexp "^ ")
  (setq eshell-destroy-buffer-when-process-dies t)

  (defun chris/pop-eshell ()
    "Make an eshell frame on the bottom"
    (interactive)
    (unless pop-eshell
      (setq pop-eshell (eshell 100))
      (with-current-buffer pop-eshell
        (eshell/clear-scrollback)
        (rename-buffer "*eshell-pop*")
        (display-buffer-in-side-window pop-eshell '((side . bottom))))))

  (setq eshell-banner-message "")

  (setq eshell-path-env "/usr/local/bin:/usr/bin:/opt/android-sdk/cmdline-tools/latest/bin")

  ;; this makes it so flutter works properly
  (setenv "ANDROID_SDK_ROOT" "/opt/android-sdk")
  (setenv "CHROME_EXECUTABLE" "/usr/bin/qutebrowser")
  (setenv "JAVA_HOME" "/usr/lib/jvm/default")

  (add-hook 'eshell-mode-hook (lambda () (display-line-numbers-mode -1)))

  (setq eshell-command-aliases-list
        '(("q" "exit")
          ("f" "find-file $1")
          ("ff" "find-file $1")
          ("d" "dired $1")
          ("bd" "eshell-up $1")
          ("rg" "rg --color=always $*")
          ("ll" "ls -lah $*")
          ("less" "view-file $1")
          ("gg" "magit-status")
          ("clear" "clear-scrollback")
          ("!!" "eshell-previous-input 2")
          ("yay" "paru $1")
          ("yeet" "paru -Rns $1")
          ("nixs" "nix search nixpkgs $1")
          ("myip" "curl icanhazip.com")
          ("ytd" "yt-dlp -o ~/Videos/%(title)s.%(ext)s")
          ("nupg" "upgrade-nix")
          ("nupd" "update-nix")))

  (defun chris/eshell-new()
    "Open a new eshell buffer"
    (interactive)
    (eshell 'N))

  (defun chris/eshell-switch()
    "interactively switch between eshell buffers"
    (interactive)
    (consult-buffer))

  :general
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "oe" 'chris/eshell-new
    "be" 'eshell)
  (general-def '(normal insert) eshell-mode-map
    "C-d" 'kill-buffer-and-window))

(use-package esh-autosuggest
  :hook (eshell-mode . esh-autosuggest-mode)
  :ensure t)

(setq vterm-buffer-name-string "vterm %s")

(use-package sly
  :mode
  ("\\.lisp\\'" . sly-mode)
  ("\\.lisp\\'" . lisp-mode)
  :config
  (defun chris/start-nyxt-repl ()
      "Start the repl and sly connection for nyxt"
    (interactive)
    (sly-connect "localhost" 4006)))

(use-package pdf-tools
  :mode ("\\.pdf\\'" . pdf-view-mode)
  :init
  ;; (setq pdf-info-epdfinfo-program "/nix/store/6pc8vs42xbfah1h8h050a77p7vpr12kc-emacs-packages-deps/share/emacs/site-lisp/elpa/pdf-tools-20220823/epdfinfo"
  ;;       pdf-tools-directory "/nix/store/6pc8vs42xbfah1h8h050a77p7vpr12kc-emacs-packages-deps/share/emacs/site-lisp/elpa/pdf-tools-20220823/")
  (pdf-tools-install)
  :config
  (custom-set-variables '(pdf-misc-print-program "/usr/bin/lpr")
                        '(pdf-misc-print-program-args (quote ("-o media=Letter" "-o fitplot"))))
  (add-hook 'pdf-view-mode 'pdf-view-fit-page-to-window))

(use-package nov
  :mode ("\\.epub\\'" . nov-mode)
  :config

  (defun chris/setup-nov-mode 
    (interactive)
    (visual-fill-column-mode)
    (display-line-numbers-mode -1)
    (variable-pitch-mode +1)
    (setq visual-fill-column-width 130
          visual-fill-column-center-text t))

  (add-hook 'nov-mode-hook 'chris/setup-nov-mode))

(use-package elfeed
  :commands (elfeed)
  :config
  (defvar chris/elfeed-bongo-playlist "*Bongo-Elfeed Queue*"
    "Name of the Elfeed+Bongo multimedia playlist.")

  (defun chris/elfeed-bongo-insert-item ()
    "Insert `elfeed' multimedia links in `bongo' playlist buffer.

The playlist buffer has a unique name so that it will never
interfere with the default `bongo-playlist-buffer'."
    (interactive)
    (let* ((entry (elfeed-search-selected :ignore-region))
           (link (elfeed-entry-link entry))
           (enclosure (elt (car (elfeed-entry-enclosures entry)) 0))
           (url (if (string-prefix-p "https://thumbnails" enclosure)
                    link
                  (if (string= enclosure nil)
                      link
                    enclosure)))
           (title (elfeed-entry-title entry))
           (bongo-pl chris/elfeed-bongo-playlist)
           (buffer (get-buffer-create bongo-pl)))
      (message "link is %s" link)
      (message "enclosure is %s" enclosure)
      (message "url is %s" url)
      (message "title is %s" title)
      (elfeed-search-untag-all-unread)
      (unless (bongo-playlist-buffer)
        (bongo-playlist-buffer))
      (display-buffer buffer)
      (with-current-buffer buffer
        (when (not (bongo-playlist-buffer-p))
          (bongo-playlist-mode)
          (setq-local bongo-library-buffer (get-buffer "*elfeed-search*"))
          (setq-local bongo-enabled-backends '(mpv))
          (bongo-progressive-playback-mode))
        (goto-char (point-max))
        (bongo-insert-uri url (format "%s ==> %s" title url))
        (let ((inhibit-read-only t))
          (delete-duplicate-lines (point-min) (point-max)))
        (bongo-recenter))
      (message "Enqueued %s “%s” in %s"
               (if enclosure "podcast" "video")
               (propertize title 'face 'italic)
               (propertize bongo-pl 'face 'bold))))

  (defun chris/elfeed-bongo-insert-link ()
    "Inserts the hovered link into bongo for playback in mpv"
    (interactive)
    (let* ((link (shr--current-link-region))
           (entry elfeed-show-entry)
           (title (elfeed-entry-title entry))
           (url (get-text-property (point) 'shr-url))
           (bongo-pl chris/elfeed-bongo-playlist)
           (buffer (get-buffer-create bongo-pl)))
      (message "url is %s" url)
      (message "title is %s" title)
      (unless (bongo-playlist-buffer)
        (bongo-playlist-buffer))
      (display-buffer buffer)
      (with-current-buffer buffer
        (when (not (bongo-playlist-buffer-p))
          (bongo-playlist-mode)
          (setq-local bongo-library-buffer (get-buffer "*elfeed-search*"))
          (setq-local bongo-enabled-backends '(mpv))
          (bongo-progressive-playback-mode))
        (goto-char (point-max))
        (bongo-insert-uri url (format "%s ==> %s" title url))
        (let ((inhibit-read-only t))
          (delete-duplicate-lines (point-min) (point-max)))
        (bongo-recenter))
      (message "Enqueued item “%s” in %s"
               (propertize title 'face 'italic)
               (propertize bongo-pl 'face 'bold))))
  
  (defun chris/elfeed-bongo-switch-to-playlist ()
    (interactive)
    (let* ((bongo-pl chris/elfeed-bongo-playlist)
           (buffer (get-buffer bongo-pl)))
      (if buffer
          (switch-to-buffer buffer)
        (message "No `bongo' playlist is associated with `elfeed'."))))

  (setq elfeed-search-filter "@6-days-ago +unread")

  (defun chris/elfeed-ui-setup ()
    (display-line-numbers-mode -1)
    (toggle-truncate-lines +1))

  (defun chris/elfeed-show-ui-setup ()
    (display-line-numbers-mode -1)
    (setq visual-fill-column-width 130
          visual-fill-column-center-text t)
    (toggle-truncate-lines -1)
    (visual-fill-column-mode +1))

  (add-hook 'elfeed-search-mode-hook 'chris/elfeed-ui-setup)
  (add-hook 'elfeed-show-mode-hook 'chris/elfeed-show-ui-setup)

  (setq shr-use-colors nil)

  (defun chris/elfeed-eww-browse ()
    (interactive)
    (let* ((entry (elfeed-search-selected :ignore-region))
           (link (elfeed-entry-link entry)))
      (message link)
      (eww link)))

  :general
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "of" 'elfeed)

  (general-def 'normal elfeed-search-mode-map
    "v" 'chris/elfeed-bongo-insert-item
    "h" 'chris/elfeed-bongo-switch-to-playlist
    "b" 'chris/elfeed-eww-browse)
  (general-def 'normal elfeed-show-mode-map
    "b" 'eww))

(use-package elfeed-org
  :after elfeed
  :config
  (setq rmh-elfeed-org-files (list "~/org/elfeed.org"))
  (elfeed-org)
  (elfeed-update))

(use-package bongo
  :commands (bongo bongo-playlist-buffer)
  :config
  (setq bongo-default-directory "~/Music"
        bongo-prefer-library-buffers nil
        bongo-insert-whole-directory-trees t
        bongo-logo nil
        bongo-display-playback-mode-indicator t
        bongo-display-inline-playback-progress t
        bongo-field-separator (propertize " · " 'face 'shadow))

  (define-bongo-backend mpv
                        :constructor 'bongo-start-mpv-player
                        :program-name 'mpv
                        :extra-program-arguments '("--profile=fast --input-ipc-server=/tmp/mpvsocket")
                        :matcher '((local-file "file:" "http:" "ftp:" "lbry:")
                                   "mpg" "mpeg" "vob" "avi" "ogm" "mp4"
                                   "mkv" "mov" "asf" "wmv" "rm" "rmvb" "ts")
                        :matcher '(("mms:" "mmst:" "rtp:" "rtsp:" "udp:" "unsv:"
                                    "dvd:" "vcd:" "tv:" "dvb:" "mf:" "cdda:" "cddb:"
                                    "cue:" "sdp:" "mpst:" "tivo:") . t)
                        :matcher '(("http:" "https:" "lbry:") . t))

  (define-bongo-backend mpv-music
                        :constructor 'bongo-start-mpv-player
                        :program-name-variable 'mpv
                        :extra-program-arguments '("--profile=slow --input-ipc-server=/tmp/mpvsocket")
                        :matcher '((local-file "file:" "http:" "ftp:" "lbry:")
                                   "mka" "wav" "wma" "ogm" "opus" 
                                   "ogg" "flac" "mp3" "mka" "wav")
                        :matcher '(("http:" "https:" "lbry:") . t))

  (setq bongo-custom-backend-matchers '((mpv-music local-file
                                                   "mka" "wav" "wma" "ogm" "opus" 
                                                   "ogg" "flac" "mp3" "mka" "wav")
                                        (mpv local-file
                                             "mpg" "mpeg" "vob" "avi" "ogm" "mp4"
                                             "mkv" "mov" "asf" "wmv" "rm" "rmvb" "ts")))

  (setq bongo-enabled-backends '(mpv mpv-music)
        bongo-track-mark-icon-file-name "track-mark-icon.png")

  (defun chris/bongo-mark-line-forward ()
    (interactive)
    (bongo-mark-line)
    (goto-char (bongo-point-after-object))
    (next-line))

  (defun chris/bongo-mpv-pause/resume ()
    (interactive)
    (bongo-mpv-player-pause/resume bongo-player))

  (defun chris/bongo-mpv-speed-up ()
    (interactive)
    (bongo--run-mpv-command bongo-player "speed" "set" "speed" "1.95"))

  (defun chris/bongo-open-elfeed-queue-buffer ()
    (interactive)
    (display-buffer "*Bongo-Elfeed Queue*"))

  (defun chris/bongo-ytdlp ()
    "Download the video or item using yt-dlp"
    (interactive)
    (while bongo-uri-p
      (message bongo-file-name)))

  :general
  (chris/leader-keys
    "ob" 'bongo
    "oB" 'chris/bongo-open-elfeed-queue-buffer
    "mi" 'bongo-insert-enqueue
    "mp" 'bongo-pause/resume)
  (general-def 'normal bongo-playlist-mode-map
    "RET" 'bongo-dwim
    "d" 'bongo-kill-line
    "u" 'bongo-unmark-region
    "p" 'bongo-pause/resume
    "P" 'bongo-yank
    "H" 'bongo-switch-buffers
    "q" 'bury-buffer
    "+" 'chris/bongo-mpv-speed-up
    "m" 'chris/bongo-mark-line-forward)
  (general-def 'normal bongo-library-mode-map
    "RET" 'bongo-dwim
    "d" 'bongo-kill-line
    "u" 'bongo-unmark-region
    "e" 'bongo-insert-enqueue
    "p" 'bongo-pause/resume
    "H" 'bongo-switch-buffers
    "q" 'bury-buffer))

(use-package emms
  :config
  (emms-all)
  (evil-collection-emms-setup)
  (setq emms-player-list '(emms-player-vlc))
  (setq emms-source-file-default-directory "~/Music/"
        emms-tag-editor-tag-ogg-program "mid3v2")

  (defun chris/emms-delete-song ()
    "Deletes files in the emms browser by default. Maybe I'll have a yes or no thingy..."
    (interactive)
    (if (yes-or-no-p "delete the file too?")
        (emms-browser-remove-tracks t)
      (emms-browser-remove-tracks)))

  :general
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "mo" 'emms
    "ml" 'emms-browser
    "mp" 'emms-pause
    "ma" 'emms-add-dired
    "mr" 'emms-toggle-repeat-track
    "mn" 'emms-next
    "mb" 'emms-previous
    "m]" 'emms-seek-forward
    "m[" 'emms-seek-backward)
  (general-def 'normal emms-playlist-mode-map
    "q" 'bury-buffer
    "d" 'emms-playlist-mode-kill-track
    "D" 'emms-playlist-mode-goto-dired-at-point)
  (general-def 'normal emms-browser-mode-map
    "q" 'bury-buffer
    "d" 'chris/emms-delete-song
    "D" 'emms-browser-view-in-dired))

(use-package transmission
  :commands (transmission)
  :config

  (if (string-equal (system-name) "archdesktop")
      (setq transmission-host "home.cochrun.xyz"
            transmission-rpc-path "/transmission/rpc"
            transmission-refresh-modes '(transmission-mode
                                         transmission-files-mode
                                         transmission-info-mode
                                         transmission-peers-mode))
    (setq transmission-host "192.168.1.2"
          transmission-rpc-path "/transmission/rpc"
          transmission-refresh-modes '(transmission-mode
                                       transmission-files-mode
                                       transmission-info-mode
                                       transmission-peers-mode)))
    :general
    (chris/leader-keys
      :states 'normal
      :keymaps 'override
      "ot" 'transmission))

(use-package hass
  :init
  (setq hass-host "home.cochrun.xyz"
        hass-port "443"
        hass-apikey "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiI2NDE1YTU5NzAzZDA0NDY0YTM0MmYzNTI1MjU0NjMwNyIsImlhdCI6MTY1Mjc1NjcwOSwiZXhwIjoxOTY4MTE2NzA5fQ.gGuxtwE2Xc1nGSgqpHiT2us-q04GWjtqnpgSHxvXgNI")
  (hass-setup)
  :general
  (chris/leader-keys
    "oh" 'hass-dash-open))

(use-package auth-source-pass
  :defer 1
  :config (auth-source-pass-enable))

(use-package pass
  :defer 1)

(use-package password-store
  :after pass
  :general
  (chris/leader-keys
    "sp" 'password-store-copy))

(use-package password-store-otp
  :after password-store
  :general
  (chris/leader-keys
    "st" 'password-store-otp-token-copy))

(use-package plz)

(use-package ement
  :config
  (setq ement-room-images t
        ement-save-sessions t)
  :general
  (general-def 'normal ement-room-mode-map
    "q" 'bury-buffer
    "RET" 'ement-room-send-message
    "r" 'ement-room-send-reply
    "gr" 'ement-room-sync
    "R" 'ement-room-send-reaction)
  (chris/leader-keys
    "oM" 'ement-list-rooms))

(use-package mastodon
  :config
  (setq mastodon-instance-url "https://mastodon.online"
        mastodon-active-user "chriscochrun")
  :general
  (chris/leader-keys
    "oF" 'mastodon)
  (general-def 'normal mastodon-mode-map
    :states 'normal
    "q" 'bury-buffer
    "p" 'mastodon-toot
    "r" 'mastodon-tl--update
    "b" 'mastodon-toot--toggle-boost
    "H" 'mastodon-tl--get-home-timeline
    "F" 'mastodon-tl--get-federated-timeline
    "v" 'chris/elfeed-bongo-insert-item
    "N" 'mastodon-notifications--timeline))

(use-package langtool)

(use-package qrencode)

;; Reduce rendering/line scan work for Emacs by not rendering cursors or regions
;; in non-focused windows.
(setq-default cursor-in-non-selected-windows nil)
(setq highlight-nonselected-windows nil)

;; More performant rapid scrolling over unfontified regions. May cause brief
;; spells of inaccurate syntax highlighting right after scrolling, which should
;; quickly self-correct.
(setq fast-but-imprecise-scrolling t)

;; Don't ping things that look like domain names.
(setq ffap-machine-p-known 'reject)

;; Emacs "updates" its ui more often than it needs to, so we slow it down
;; slightly from 0.5s:
(setq idle-update-delay 1.0)

;; Font compacting can be terribly expensive, especially for rendering icon
;; fonts on Windows. Whether disabling it has a notable affect on Linux and Mac
;; hasn't been determined, but we inhibit it there anyway. This increases memory
;; usage, however!
;; (setq inhibit-compacting-font-caches t)

;; Introduced in Emacs HEAD (b2f8c9f), this inhibits fontification while
;; receiving input, which should help with performance while scrolling.
(setq redisplay-skip-fontification-on-input t)

(setq gc-cons-threshold (* 32 1024 1024))
(setq garbage-collection-messages nil)

(use-package gcmh
  :ensure t
  :init
  (gcmh-mode)
  :config
  (setq gcmh-idle-delay 5
        gcmh-high-cons-threshold (* 128 1024 1024)  ; 128mb
        gcmh-verbose nil))

(setq warning-suppress-types '((comp)))