;;; 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"
  (interactive)
  (message "Setting faces!")
  (set-face-attribute 'default nil :font "VictorMono Nerd Font Propo"
                      :height chris/default-font-size)
  (set-face-attribute 'fixed-pitch nil :font "VictorMono Nerd Font Propo"
                      :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 85)
  (add-to-list 'default-frame-alist '(alpha-background . 85))
  (add-to-list 'initial-frame-alist '(alpha-background . 85)))

(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 t)
(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)
;; (savehist-mode +1)
;; (add-to-list 'savehist-additional-variables 'register-alist)
;; (add-to-list 'savehist-additional-variables kill-ring)

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

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

(setq require-final-newline nil)

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

(require 'package)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
(package-initialize)

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

  (defun chris/home-manager-reload ()
    (interactive)
    (let* ((which-emacs (shell-command-to-string "nix-store --query $(which emacs)"))
           (pattern (rx "newLoadPath+=(/nix/store/"
                        (group (* any))
                        "-emacs-packages-deps/share/emacs/site-lisp)"))
           (current-deps (when (string-match pattern which-emacs)
                           (match-string 1 which-emacs)))
           (subdirs (concat "/nix/store/" current-deps "-emacs-packages-deps/share/emacs/site-lisp/subdirs.el"))
           (native (concat "/nix/store/" current-deps "-emacs-packages-deps/share/emacs/native-lisp/")))
      (load-file subdirs)
      (when (boundp 'native-comp-eln-load-path)
        (add-to-list 'native-comp-eln-load-path native))))

(use-package all-the-icons)

(use-package doom-modeline
  :ensure t
  :init
  (doom-modeline-mode 0)
  (setq doom-modeline-height 25
        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 nil)
  (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 aggressive-indent
  :config
  (aggressive-indent-mode -1))

(use-package adaptive-wrap)

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

(use-package mini-echo
  :init
  (mini-echo-mode 1)
  (setq mini-echo-default-segments '(:long
                                     ("evil" "major-mode" "buffer-name" "vcs" "buffer-position" "buffer-size"
                                      "flymake" "process" "selection-info" "narrow" "macro" "profiler")
                                     :short
                                     ("buffer-name-short" "buffer-position" "process" "profiler"
                                      "selection-info" "narrow" "macro"))
        mini-echo-rules '((org-mode :long
                                    (("evil" . 1)
                                     ("major-mode" . 2)
                                     ("buffer-name" . 3)
                                     ("word-count" . 4))))
        mini-echo-right-padding 2
        mini-echo-buffer-status-style 'both
        mini-echo-update-interval 0.2)

  
  (defun chris/mini-echo-minibuffer-width-lessp ()
    "Return non-nil if current minibuffer window width less than 120."
    (< (mini-echo-minibuffer-width) 100))

  (setq mini-echo-short-style-predicate 'chris/mini-echo-minibuffer-width-lessp)
  
  :config
  (defun chris/toggle-mode-lines ()
      "Switch between doom and mini-echo"
       (interactive)
       (if mini-echo-mode
           (eval (mini-echo-mode 0) (doom-modeline-mode 1))
         (eval (doom-modeline-mode 0) (mini-echo-mode 1)))))

(setq xref-search-program 'ripgrep)

(add-to-list 'exec-path "/usr/bin")
(setenv "NIX_CONF_DIR" "/etc/nix")
(setenv "NIX_REMOTE" "daemon")
(setenv "XCURSOR_THEME" "phinger-cursors-light")
(setenv "QT_SCALE_FACTOR" "1")

(defun chris/nix-reload ()
  (interactive)
  (let* ((query (shell-command-to-string "nix-store --query $(which emacs)"))
         (store-path (expand-file-name "share/emacs" (string-trim query))))
    (load-file (expand-file-name "site-lisp/subdirs.el" store-path))
    (when (boundp 'native-comp-eln-load-path)
      (add-to-list 'native-comp-eln-load-path (expand-file-name "native-lisp/" store-path)))))

(setenv "WAYLAND_DISPLAY" (if (string= (getenv "XDG_CURRENT_DESKTOP") "Hyprland")
                              "wayland-1"
                            "wayland-0"))

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

(setq proced-auto-update-flag t)

(use-package bluetooth
  :after general
  :general
  (chris/leader-keys
    "oT" 'bluetooth-list-devices))

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

(setq backup-directory-alist '(("." . "~/.emacs.d/backup"))
  backup-by-copying t    ; Don't delink hardlinks
  version-control t      ; Use version numbers on backups
  delete-old-versions t  ; Automatically delete excess backups
  kept-new-versions 20   ; how many of the newest versions to keep
  kept-old-versions 5    ; and how many of the old
  )

(use-package ligature
  :load-path "path-to-ligature-repo"
  :config
  ;; Enable the "www" ligature in every possible major mode
  (ligature-set-ligatures 't '("www"))
  ;; Enable traditional ligature support in eww-mode, if the
  ;; `variable-pitch' face supports it
  (ligature-set-ligatures 'eww-mode '("ff" "fi" "ffi"))
  ;; Enable all Cascadia and Fira Code ligatures in programming modes
  (ligature-set-ligatures 'prog-mode
                        '(;; == === ==== => =| =>>=>=|=>==>> ==< =/=//=// =~
                          ;; =:= =!=
                          ("=" (rx (+ (or ">" "<" "|" "/" "~" ":" "!" "="))))
                          ;; ;; ;;;
                          (";" (rx (+ ";")))
                          ;; && &&&
                          ("&" (rx (+ "&")))
                          ;; !! !!! !. !: !!. != !== !~
                          ("!" (rx (+ (or "=" "!" "\." ":" "~"))))
                          ;; ?? ??? ?:  ?=  ?.
                          ("?" (rx (or ":" "=" "\." (+ "?"))))
                          ;; %% %%%
                          ("%" (rx (+ "%")))
                          ;; |> ||> |||> ||||> |] |} || ||| |-> ||-||
                          ;; |->>-||-<<-| |- |== ||=||
                          ;; |==>>==<<==<=>==//==/=!==:===>
                          ("|" (rx (+ (or ">" "<" "|" "/" ":" "!" "}" "\]"
                                          "-" "=" ))))
                          ;; \\ \\\ \/
                          ("\\" (rx (or "/" (+ "\\"))))
                          ;; ++ +++ ++++ +>
                          ("+" (rx (or ">" (+ "+"))))
                          ;; :: ::: :::: :> :< := :// ::=
                          (":" (rx (or ">" "<" "=" "//" ":=" (+ ":"))))
                          ;; // /// //// /\ /* /> /===:===!=//===>>==>==/
                          ("/" (rx (+ (or ">"  "<" "|" "/" "\\" "\*" ":" "!"
                                          "="))))
                          ;; .. ... .... .= .- .? ..= ..<
                          ("\." (rx (or "=" "-" "\?" "\.=" "\.<" (+ "\."))))
                          ;; -- --- ---- -~ -> ->> -| -|->-->>->--<<-|
                          ("-" (rx (+ (or ">" "<" "|" "~" "-"))))
                          ;; *> */ *)  ** *** ****
                          ("*" (rx (or ">" "/" ")" (+ "*"))))
                          ;; www wwww
                          ("w" (rx (+ "w")))
                          ;; <> <!-- <|> <: <~ <~> <~~ <+ <* <$ </  <+> <*>
                          ;; <$> </> <|  <||  <||| <|||| <- <-| <-<<-|-> <->>
                          ;; <<-> <= <=> <<==<<==>=|=>==/==//=!==:=>
                          ;; << <<< <<<<
                          ("<" (rx (+ (or "\+" "\*" "\$" "<" ">" ":" "~"  "!"
                                          "-"  "/" "|" "="))))
                          ;; >: >- >>- >--|-> >>-|-> >= >== >>== >=|=:=>>
                          ;; >> >>> >>>>
                          (">" (rx (+ (or ">" "<" "|" "/" ":" "=" "-"))))
                          ;; #: #= #! #( #? #[ #{ #_ #_( ## ### #####
                          ("#" (rx (or ":" "=" "!" "(" "\?" "\[" "{" "_(" "_"
                                       (+ "#"))))
                          ;; ~~ ~~~ ~=  ~-  ~@ ~> ~~>
                          ("~" (rx (or ">" "=" "-" "@" "~>" (+ "~"))))
                          ;; __ ___ ____ _|_ __|____|_
                          ("_" (rx (+ (or "_" "|"))))
                          ;; Fira code: 0xFF 0x12
                          ;; ("0" (rx (and "x" (+ (in "A-F" "a-f" "0-9")))))
                          ;; Fira code:
                          ;; "Fl"  "Tl"  "fi"  "fj"  "fl"  "ft"
                          ;; The few not covered by the regexps.
                          "{|"  "[|"  "]#"  "(*"  "}#"  "$>"  "^="))
  ;; Enables ligature checks globally in all buffers. You can also do it
  ;; per mode with `ligature-mode'.
  (global-ligature-mode 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 evil-escape
  :after evil
  :init (evil-escape-mode +1)
  :config
  (setq evil-escape-key-sequence (kbd "fd")
        evil-escape-delay 0.3))

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

(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/find-videos ()
    (interactive)
    (find-file (expand-file-name "vids" "~")))
  
  (defun chris/open-bible ()
    "find a bible to open"
    (interactive)
    (find-file "~/docs/bibles/esv.org")
    )

  (defun chris/transpose-lines (arg)
    "Moves the current line or region and follows"
    (interactive)
    (cond
    ((and mark-active transient-mark-mode)
     (if (> (point) (mark))
            (exchange-point-and-mark))
     (let ((column (current-column))
              (text (delete-and-extract-region (point) (mark))))
       (forward-line arg)
       (move-to-column column t)
       (set-mark (point))
       (insert text)
       (exchange-point-and-mark)
       (setq deactivate-mark nil)))
    (t
     (beginning-of-line)
     (when (or (> arg 0) (not (bobp)))
       (forward-line)
       (when (or (< arg 0) (not (eobp)))
            (transpose-lines arg))
       (forward-line -1)))))

  (defun chris/transpose-lines-up ()
    "Moves the lines up"
    (interactive)
    (transpose-lines 1)
    (forward-line -2)
    (indent-according-to-mode))

  (defun chris/transpose-lines-down ()
    "Moves the lines down"
    (interactive)
    (forward-line 1)
    (transpose-lines 1)
    (forward-line -1)
    (indent-according-to-mode))

  (defun chris/find-todo ()
    "opens my todo file"
    (interactive)
    (find-file "/home/chris/docs/todo/todo.org"))

  (general-create-definer chris/leader-keys
    :keymaps '(normal visual emacs)
    :prefix "SPC")
  (chris/leader-keys
    :states 'visual
    :keymaps 'override
    "d" '(execute-extended-command :which-key "execute command"))
  (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")
    "p" '(:ignore t :which-key "project")
    "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")
    "x" '(:ignore t :which-key "guix")
    "v" '(:ignore t :which-key "mpv")
    "xx" '(guix :which-key "guix")
    "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")
    "fv" '(chris/find-videos :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")
    "ft" '(chris/find-todo :which-key "open todo file")
    "fE" '(consult-file-externally :which-key "find file externally")
    "fe" '(chris/edit-emacs-config :which-key "open config")
    "hf" '(helpful-function :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")
    "sv" '(org-bible-find-verse-imenu :which-key "imenu")
    "oP" '(proced :which-key "proced")
    "ov" '(vterm :which-key "vterm")
    "wo" '(other-window :which-key "other window")
    "wn" '(evil-window-next :which-key "other window")
    "wm" '(maximize-window :which-key "maximize 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")
    "d" '(execute-extended-command :which-key "execute command")
    "&" '(async-shell-command :which-key "async shell command")
    ":" '(eval-expression :which-key "evaluate expression")
    "gc" '(project-compile :which-key "compile project"))
  (chris/leader-keys
    :states 'normal
    :keymaps 'lisp-mode
    "e" '(sly-eval-defun :which-key "evaluate top level"))
  (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
    "zi" 'text-scale-increase
    "zd" 'text-scale-decrease)
  (general-def 'normal
    :states 'normal
    :keymaps 'override
    "C-M-j" 'chris/transpose-lines-down
    "C-M-k" 'chris/transpose-lines-up)
  (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
    "C-<tab>" 'indent-for-tab-command))

(setq lpr-command "lpr -o sides=two-sided-long-edge -# ")
(defun chris/org-mode-setup ()
  (interactive)
  ;; (org-indent-mode +1)
  (toc-org-mode +1)
  (smartparens-mode +1)
  (visual-fill-column-mode +1)
  (display-line-numbers-mode -1)
  (variable-pitch-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)
             (org-column 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-column nil :background "#242631" :inherit 'fixed-pitch)
  (set-face-attribute 'org-quote nil :background "#242631" :inherit 'fixed-pitch)
  (set-face-attribute 'org-table nil :inherit 'fixed-pitch)

  ;; Setup better completion functions for org mode
  (setq-local completion-at-point-functions
              (list (cape-capf-super #'cape-elisp-block #'cape-dabbrev #'cape-dict)))
  (tempel-setup-capf))

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

(defun chris/org-cycle-hide-drawers (state)
  "Re-hide all drawers after a visibility state change."
  (interactive)
  (when (and (derived-mode-p 'org-mode)
             (not (memq state '(overview folded contents))))
    (save-excursion
      (let* ((globalp (memq state '(contents all)))
             (beg (if globalp
                    (point-min)
                    (point)))
             (end (if globalp
                    (point-max)
                    (if (eq state 'children)
                      (save-excursion
                        (outline-next-heading)
                        (point))
                      (org-end-of-subtree t)))))
        (goto-char beg)
        (while (re-search-forward org-drawer-regexp end t)
          (save-excursion
            (beginning-of-line 1)
            (when (looking-at org-drawer-regexp)
              (let* ((start (1- (match-beginning 0)))
                     (limit
                       (save-excursion
                         (outline-next-heading)
                           (point)))
                     (msg (format
                            (concat
                              "org-cycle-hide-drawers:  "
                              "`:END:`"
                              " line missing at position %s")
                            (1+ start))))
                (if (re-search-forward "^[ \t]*:END:" limit t)
                  (outline-flag-region start (point-at-eol) t)
                  (user-error msg))))))))))

(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)
  (if (string= org-agenda-this-buffer-name "*Org Agenda(fa:area+LEVEL=1)*")
      (org-agenda-columns)))

(defun chris/org-mpv (&optional url)
  (interactive)
  (if (string-empty-p url)
      (let (url url-get-url-at-point)
        (bongo-insert-uri url (format "%s ==> %s" title url)))
    (bongo-insert-uri url (format "%s ==> %s" title url))))

(defun chris/bible-imenu ()
  "This is a special call on consult-imenu that will utilize
orderless with a better dispatcher so that we can find our verses
much faster. The hope is to also make this a faster version of imenu."
  (interactive)
  (consult-imenu))

(use-package org
  :config
  (setq org-startup-indented t
        org-edit-src-content-indentation 0
        org-indent-turns-on-hiding-stars nil
        org-indent-mode-turns-on-hiding-stars nil
        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
        org-export-preserve-breaks t
        org-directory "~/docs/notes")

  (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 "~/docs/todo/todo.org")
           (file ".templates/tasks.org") :prepend t)
          ("n" "Personal notes" plain
           (file denote-last-path)
           #'denote-org-capture
           :no-save t
           :immediate-finish nil
           :kill-buffer t
           :jump-to-captured t)
          ("p" "TFC Plan" entry
           (function chris/denote-capture-lesson-file)
           (file ".templates/tfcplantemplate.org")
           :prepend nil
           :jump-to-captured t
           :empty-lines 1)
          ("l" "TFC Lesson" plain
           (function chris/org-capture-denote-file-path)
           (file ".templates/lessontemplate.org")
           :prepend nil
           :jump-to-captured t
           :empyt-lines 1)
          ("P" "TFC Posts" entry
           (file+headline "nvtfc_social_media.org" "Posts")
           (file ".templates/posts.org")
           :prepend t
           :jump-to-captured t)
          ("s" "TFC Supporter" entry
           (file "tfc_supporters.org")
           (file ".templates/supporter.org")
           :prepend t
           :jump-to-captured t)
          ("r" "Templates for projects")
          ("rt" "Project-local todo" entry
           (file+headline chris/project-todo "Tasks")
           "* 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" "~/docs/notes/icons/task.png" nil nil :ascent center)
          ("lesson" "~/docs/notes/icons/book.png" nil nil :ascent center)
          ("dev" "~/docs/notes/icons/coding.png" nil nil :ascent center)
          ("TODO" "~/docs/notes/icons/coding.png" nil nil :ascent center)))

  (setq org-imenu-depth 4
        org-odt-styles-file "/home/chris/docs/style.odt"
        org-export-with-toc nil
        org-export-with-author t
        org-export-with-section-numbers 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
        '("~/docs/todo/todo.org"
          "~/dev/lumina/TODO.org"
          "~/dev/tfcconnection/TODO.org"
          "~/docs/notes/lessons/"
          "~/docs/notes/20230919T090157--house-renovation__area.org")
        org-id-method 'ts
        org-agenda-tags-column -75
        org-agenda-dim-blocked-tasks nil
        org-columns-summary-types '(("+" . org-columns--summary-sum)
                                    ("$" . org-columns--summary-currencies)
                                    ("X" . org-columns--summary-checkbox)
                                    ("X/" . org-columns--summary-checkbox-count)
                                    ("X%" . org-columns--summary-checkbox-percent)
                                    ("max" . org-columns--summary-max)
                                    ("mean" . org-columns--summary-mean)
                                    ("min" . org-columns--summary-min)
                                    (":" . org-columns--summary-sum-times)
                                    (":max" . org-columns--summary-max-time)
                                    (":mean" . org-columns--summary-mean-time)
                                    (":min" . org-columns--summary-min-time)
                                    ("@max" . org-columns--summary-max-age)
                                    ("@mean" . org-columns--summary-mean-age)
                                    ("@min" . org-columns--summary-min-age)
                                    ("est+" . org-columns--summary-estimate)))
  
  (setq org-agenda-custom-commands
        '(("w" todo "WAIT")
          ("f" . "Second brain")
          ("fa" tags "area+LEVEL=1")
          ("fr" tags "resource+LEVEL=1")
          ("fp" . "Projects")
          ("fpp" "All projects" tags "project+LEVEL=1")
          ("fpt" "All TFC projects" tags "projecttfc+LEVEL=1")
          ("fpd" "All Dev projects" tags "projectdev")
          ("fA" tags "archive+LEVEL=1")
          ("h" . "Family searches")
          ("ha" tags "abbie")
          ("hl" tags "luke")
          ("hy" tags "ty")
          ("hj" tags "josiah")))

  (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)))
  (setq org-latex-title-command "\\maketitle\\vspace{-4em}")

  (setq org-publish-project-alist
        `(("home"
           :base-directory "~/docs/notes/site/"
           :base-extension "org"
           :recursive nil
           :html-doctype "html5"
           :html-html5-fancy t
           :html-self-link-headlines t
           :html-head "<link rel=\"stylesheet\" href=\"content/pico/css/pico.css\" type=\"text/css\"/>"
           :publishing-directory "~/cochrun.xyz/"
           :publishing-function org-html-publish-to-html)
          ("posts"
           :base-directory "~/docs/notes/site/content/"
           :base-extension "org"
           :recursive t
           :html-doctype "html5"
           :html-html5-fancy t
           :html-self-link-headlines t
           :htmlized-source t
           :html-head "<link rel=\"stylesheet\" href=\"pico/css/pico.css\" type=\"text/css\"/>"
           :publishing-directory "~/cochrun.xyz/content/"
           :publishing-function org-html-publish-to-html)
          ("static"
           :base-directory "~/docs/notes/site/assets/"
           :base-extension "css\\|txt\\|jpg\\|gif\\|png"
           :exclude "bibles\\|.st.*\\|.templates"
           :recursive t
           :html-doctype "html5"
           :html-html5-fancy t
           :html-head "<link rel=\"stylesheet\" href=\"pico/css/pico.css\" type=\"text/css\"/>"
           :publishing-directory  "~/cochrun.xyz/content/"
           :publishing-function org-publish-attachment)
          ("cochrun.xyz" :components ("home" "posts" "static"))))
  
  :general
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "c" 'org-capture
    "rr" 'org-refile
    "E" 'org-export-dispatch
    "oa" 'org-agenda
    "gt" 'org-babel-tangle
    "td" 'org-toggle-link-display
    "tp" 'org-toggle-pretty-entities
    "il" 'org-insert-link
    "it" 'org-insert-todo-subheading)
  (chris/leader-keys
    :states 'normal
    :keymaps 'org-mode-map
    "is" 'org-time-stamp
    "tp" (chris/org-cycle-hide-drawers 'children)
    "iw" 'org-web-tools-insert-web-page-as-entry)
  (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" '+org/dwim-at-point
           "gC" 'chris/org-columns-view
           "ge" 'org-edit-src-code
           "gr" 'revert-buffer
           "gt" 'org-set-tags-command
           "ze" 'org-emphasize
           "zn" 'org-narrow-to-subtree
           "zw" 'widen
           "zp" 'org-set-property
           "zh" (chris/org-cycle-hide-drawers 'subtree)
           "S" 'org-schedule
           "t" 'org-todo
           "gf" 'org-footnote-action
           "gl" 'org-id-copy
           "gk" 'languagetool-correct-at-point
           "C-M-k" 'org-priority-up
           "C-M-j" 'org-priority-down
           "gs" 'org-time-stamp)
  ('visual org-mode-map
           "gf" 'org-footnote-action
           "gm" 'org-emphasize)
  ('insert org-mode-map
           "C-i" 'completion-at-point)
  ('normal 'org-src-mode-map
           "q" 'org-edit-src-abort))

(defun chris/denote-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* ((files (denote-all-files))
         (lessons (remove nil
                          (cl-loop for file in files
                                   collect (if (string-match "lessons/" file) file))))
         (lesson (completing-read "Select Lesson: " lessons nil nil '(".*_lesson" . 0))))
    (find-file lesson)
    (goto-char (point-min))
    (search-forward "* PLAN")))

(defun chris/org-capture-denote-file-path ()
  "Function returning the file placement using denote for capture"
  (interactive)
  (denote-subdirectory)
  (save-buffer))

(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 'after-save-hook #'chris/org-babel-tangle-config)

(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"
                                   :scheduled nil)
                                  (: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 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 denote
  :config
  (require 'denote-org-dblock)
  (setq denote-directory "/home/chris/docs/notes"
        denote-dired-directories '("/home/chris/docs/notes")
        denote-dired-directories-include-subdirectories t
        denote-modules '(project xref ffap)
        denote-known-keywords '("emacs" "bible" "jesus" "tfc" "lesson" "it" "dev"))

  (setq denote-rename-buffer-format "[D] %>t")
  (denote-rename-buffer-mode +1)
  (setq denote-org-front-matter
        "#+TITLE: %1$s\n#+AUTHOR: Chris Cochrun\n#+CREATED: %2$s\n#+filetags: %3$s\n#+identifier: %4$s\n")
  (add-hook 'dired-mode-hook #'denote-dired-mode-in-directories)

  ;; (defvar denote-faces--file-name-regexp
  ;;   (concat "\\(?1:[0-9]\\{8\\}\\)\\(?2:T[0-9]\\{6\\}\\)"
  ;;           "\\(?:\\(?3:==\\)\\(?4:[[:alnum:][:nonascii:]=]*?\\)\\)?"
  ;;           "\\(?:\\(?5:--\\)\\(?6:[[:alnum:][:nonascii:]-]*?\\)\\)?"
  ;;           "\\(?:\\(?7:__\\)\\(?8:[[:alnum:][:nonascii:]_-]*?\\)\\)?"
  ;;           "\\(?9:\\..*\\)?$")
  ;;   "Regexp of file names for fontification.")

  ;; (defconst denote-faces-file-name-keywords
  ;;   `((,(concat "[\t\s]+" denote-faces--file-name-regexp)
  ;;      (1 'denote-faces-date)
  ;;      (2 'denote-faces-time)
  ;;      (3 'denote-faces-delimiter nil t)
  ;;      (4 'denote-faces-signature nil t)
  ;;      (5 'denote-faces-delimiter nil t)
  ;;      (6 'denote-faces-title nil t)
  ;;      (7 'denote-faces-delimiter nil t)
  ;;      (8 'denote-faces-keywords nil t)
  ;;      (9 'denote-faces-extension nil t )))
  ;;   "Keywords for fontification of file names.")

  :general
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "nf" 'denote-open-or-create
    "nb" 'denote-backlinks
    "nk" 'denote-keywords-add))

(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
        org-modern-table nil
        org-modern-star '("◉" "○" "◈" "◇" "✳"))
  
  (custom-set-faces
   '(org-modern-tag ((t :background "#9aedfe" :foreground "#282a36")))
   )
  (global-org-modern-mode +1)
  )

;;; lang/org/autoload/org.el -*- lexical-binding: t; -*-

;;
;;; Helpers

(defun +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)
                      (save-excursion (point-min))
                    (save-excursion (org-back-to-heading) (point)))))
         (end (or end
                  (if (org-before-first-heading-p)
                      (save-excursion (org-next-visible-heading 1) (point))
                    (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)))

(defun +org--insert-item (direction)
  (let ((context (org-element-lineage
                  (org-element-context)
                  '(table table-row headline inlinetask item plain-list)
                  t)))
    (pcase (org-element-type context)
      ;; Add a new list item (carrying over checkboxes if necessary)
      ((or `item `plain-list)
       (let ((orig-point (point)))
         ;; Position determines where org-insert-todo-heading and `org-insert-item'
         ;; insert the new list item.
         (if (eq direction 'above)
             (org-beginning-of-item)
           (end-of-line))
         (let* ((ctx-item? (eq 'item (org-element-type context)))
                (ctx-cb (org-element-property :contents-begin context))
                ;; Hack to handle edge case where the point is at the
                ;; beginning of the first item
                (beginning-of-list? (and (not ctx-item?)
                                         (= ctx-cb orig-point)))
                (item-context (if beginning-of-list?
                                  (org-element-context)
                                context))
                ;; Horrible hack to handle edge case where the
                ;; line of the bullet is empty
                (ictx-cb (org-element-property :contents-begin item-context))
                (empty? (and (eq direction 'below)
                             ;; in case contents-begin is nil, or contents-begin
                             ;; equals the position end of the line, the item is
                             ;; empty
                             (or (not ictx-cb)
                                 (= ictx-cb
                                    (1+ (point))))))
                (pre-insert-point (point)))
           ;; Insert dummy content, so that `org-insert-item'
           ;; inserts content below this item
           (when empty?
             (insert " "))
           (org-insert-item (org-element-property :checkbox context))
           ;; Remove dummy content
           (when empty?
             (delete-region pre-insert-point (1+ pre-insert-point))))))
      ;; Add a new table row
      ((or `table `table-row)
       (pcase direction
         ('below (save-excursion (org-table-insert-row t))
                 (org-table-next-row))
         ('above (save-excursion (org-shiftmetadown))
                 (+org/table-previous-row))))

      ;; Otherwise, add a new heading, carrying over any todo state, if
      ;; necessary.
      (_
       (let ((level (or (org-current-level) 1)))
         ;; I intentionally avoid `org-insert-heading' and the like because they
         ;; impose unpredictable whitespace rules depending on the cursor
         ;; position. It's simpler to express this command's responsibility at a
         ;; lower level than work around all the quirks in org's API.
         (pcase direction
           (`below
            (let (org-insert-heading-respect-content)
              (goto-char (line-end-position))
              (org-end-of-subtree)
              (insert "\n" (make-string level ?*) " ")))
           (`above
            (org-back-to-heading)
            (insert (make-string level ?*) " ")
            (save-excursion (insert "\n"))))
         (run-hooks 'org-insert-heading-hook)
         (when-let* ((todo-keyword (org-element-property :todo-keyword context))
                     (todo-type    (org-element-property :todo-type context)))
           (org-todo
            (cond ((eq todo-type 'done)
                   ;; Doesn't make sense to create more "DONE" headings
                   (car (+org-get-todo-keywords-for todo-keyword)))
                  (todo-keyword)
                  ('todo)))))))

    (when (org-invisible-p)
      (org-show-hidden-entry))
    (when (and (bound-and-true-p evil-local-mode)
               (not (evil-emacs-state-p)))
      (evil-insert 1))))

;;;###autoload
(defun +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)))


;;
;;; Modes

;;;###autoload
(define-minor-mode +org-pretty-mode
  "Hides emphasis markers and toggles pretty entities."
  :init-value nil
  :lighter " *"
  :group 'evil-org
  (setq org-hide-emphasis-markers +org-pretty-mode)
  (org-toggle-pretty-entities)
  (with-silent-modifications
   ;; In case the above un-align tables
   (org-table-map-tables 'org-table-align t)))


;;
;;; Commands

;;;###autoload
(defun +org/return ()
  "Call `org-return' then indent (if `electric-indent-mode' is on)."
  (interactive)
  (org-return electric-indent-mode))

;;;###autoload
(defun +org/dwim-at-point (&optional arg)
  "Do-what-I-mean at point.

If on a:
- checkbox list item or todo heading: toggle it.
- citation: follow it
- headline: cycle ARCHIVE subtrees, toggle latex fragments and inline images in
  subtree; update statistics cookies/checkboxes and ToCs.
- clock: update its time.
- footnote reference: jump to the footnote's definition
- footnote definition: jump to the first reference of this footnote
- timestamp: open an agenda view for the time-stamp date/range at point.
- 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.
- src block: execute it
- latex fragment: toggle it.
- link: follow it
- otherwise, refresh all inline images in current tree."
  (interactive "P")
  (if (button-at (point))
      (call-interactively #'push-button)
    (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
        ((or `citation `citation-reference)
         (org-cite-follow context arg))

        (`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 (+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)))
           (+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 arg))

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

        (`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)))
               (+org--toggle-inline-images-in-subtree
                (org-element-property :begin lineage)
                (org-element-property :end lineage))
             (org-open-at-point arg))))

        (`paragraph
         (+org--toggle-inline-images-in-subtree))

        ((guard (org-element-property :checkbox (org-element-lineage context '(item) t)))
         (let ((match (and (org-at-item-checkbox-p) (match-string 1))))
           (org-toggle-checkbox (if (equal match "[ ]") '(16)))))

        (_
         (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)
           (+org--toggle-inline-images-in-subtree
            (org-element-property :begin context)
            (org-element-property :end context))))))))

;;;###autoload
(defun +org/shift-return (&optional arg)
  "Insert a literal newline, or dwim in tables.
Executes `org-table-copy-down' if in table."
  (interactive "p")
  (if (org-at-table-p)
      (org-table-copy-down arg)
    (org-return nil arg)))


;; I use these instead of `org-insert-item' or `org-insert-heading' because they
;; impose bizarre whitespace rules depending on cursor location and many
;; settings. These commands have a much simpler responsibility.
;;;###autoload
(defun +org/insert-item-below (count)
  "Inserts a new heading, table cell or item below the current one."
  (interactive "p")
  (dotimes (_ count) (+org--insert-item 'below)))

;;;###autoload
(defun +org/insert-item-above (count)
  "Inserts a new heading, table cell or item above the current one."
  (interactive "p")
  (dotimes (_ count) (+org--insert-item 'above)))


;;;###autoload
(defun +org/toggle-last-clock (arg)
  "Toggles last clocked item.

Clock out if an active clock is running (or cancel it if prefix ARG is non-nil).

If no clock is active, then clock into the last item. See `org-clock-in-last' to
see how ARG affects this command."
  (interactive "P")
  (require 'org-clock)
  (cond ((org-clocking-p)
         (if arg
             (org-clock-cancel)
           (org-clock-out)))
        ((and (null org-clock-history)
              (or (org-on-heading-p)
                  (org-at-item-p))
              (y-or-n-p "No active clock. Clock in on current item?"))
         (org-clock-in))
        ((org-clock-in-last arg))))


;;; Folds
;;;###autoload
(defalias #'+org/toggle-fold #'+org-cycle-only-current-subtree-h)

;;;###autoload
(defun +org/open-fold ()
  "Open the current fold (not but its children)."
  (interactive)
  (+org/toggle-fold t))

;;;###autoload
(defalias #'+org/close-fold #'outline-hide-subtree)

;;;###autoload
(defun +org/close-all-folds (&optional level)
  "Close all folds in the buffer (or below LEVEL)."
  (interactive "p")
  (outline-hide-sublevels (or level 1)))

;;;###autoload
(defun +org/open-all-folds (&optional level)
  "Open all folds in the buffer (or up to LEVEL)."
  (interactive "P")
  (if (integerp level)
      (outline-hide-sublevels level)
    (outline-show-all)))

(defun +org--get-foldlevel ()
  (let ((max 1))
    (save-restriction
      (narrow-to-region (window-start) (window-end))
      (save-excursion
        (goto-char (point-min))
        (while (not (eobp))
          (org-next-visible-heading 1)
          (when (outline-invisible-p (line-end-position))
            (let ((level (org-outline-level)))
              (when (> level max)
                (setq max level))))))
      max)))

;;;###autoload
(defun +org/show-next-fold-level (&optional count)
  "Decrease the fold-level of the visible area of the buffer. This unfolds
another level of headings on each invocation."
  (interactive "p")
  (let ((new-level (+ (+org--get-foldlevel) (or count 1))))
    (outline-hide-sublevels new-level)
    (message "Folded to level %s" new-level)))

;;;###autoload
(defun +org/hide-next-fold-level (&optional count)
  "Increase the global fold-level of the visible area of the buffer. This folds
another level of headings on each invocation."
  (interactive "p")
  (let ((new-level (max 1 (- (+org--get-foldlevel) (or count 1)))))
    (outline-hide-sublevels new-level)
    (message "Folded to level %s" new-level)))


;;
;;; Hooks

;;;###autoload
(defun +org-indent-maybe-h ()
  "Indent the current item (header or item), if possible.
Made for `org-tab-first-hook' in evil-mode."
  (interactive)
  (cond ((not (and (bound-and-true-p evil-local-mode)
                   (evil-insert-state-p)))
         nil)
        ((and (bound-and-true-p org-cdlatex-mode)
              (or (org-inside-LaTeX-fragment-p)
                  (org-inside-latex-macro-p)))
         nil)
        ((org-at-item-p)
         (if (eq this-command 'org-shifttab)
             (org-outdent-item-tree)
           (org-indent-item-tree))
         t)
        ((org-at-heading-p)
         (ignore-errors
           (if (eq this-command 'org-shifttab)
               (org-promote)
             (org-demote)))
         t)
        ((org-in-src-block-p t)
         (save-window-excursion
           (org-babel-do-in-edit-buffer
            (call-interactively #'indent-for-tab-command)))
         t)
        ((and (save-excursion
                (skip-chars-backward " \t")
                (bolp))
              (org-in-subtree-not-table-p))
         (call-interactively #'tab-to-tab-stop)
         t)))

;;;###autoload
(defun +org-cycle-only-current-subtree-h (&optional arg)
  "Toggle the local fold at the point, and no deeper.
`org-cycle's standard behavior is to cycle between three levels: collapsed,
subtree and whole document. This is slow, especially in larger org buffer. Most
of the time I just want to peek into the current subtree -- at most, expand
*only* the current subtree.

All my (performant) foldings needs are met between this and `org-show-subtree'
(on zO for evil users), and `org-cycle' on shift-TAB if I need it."
  (interactive "P")
  (unless (or (eq this-command 'org-shifttab)
              (and (bound-and-true-p org-cdlatex-mode)
                   (or (org-inside-LaTeX-fragment-p)
                       (org-inside-latex-macro-p))))
    (save-excursion
      (org-beginning-of-line)
      (let (invisible-p)
        (when (and (org-at-heading-p)
                   (or org-cycle-open-archived-trees
                       (not (member org-archive-tag (org-get-tags))))
                   (or (not arg)
                       (setq invisible-p (outline-invisible-p (line-end-position)))))
          (unless invisible-p
            (setq org-cycle-subtree-status 'subtree))
          (org-cycle-internal-local)
          t)))))

;;;###autoload
(defun +org-make-last-point-visible-h ()
  "Unfold subtree around point if saveplace places us in a folded region."
  (and (not org-inhibit-startup)
       (not org-inhibit-startup-visibility-stuff)
       ;; Must be done on a timer because `org-show-set-visibility' (used by
       ;; `org-reveal') relies on overlays that aren't immediately available
       ;; when `org-mode' first initializes.
       (run-at-time 0.1 nil #'org-reveal '(4))))

;;;###autoload
(defun +org-remove-occur-highlights-h ()
  "Remove org occur highlights on ESC in normal mode."
  (when org-occur-highlights
    (org-remove-occur-highlights)
    t))

;;;###autoload
(defun +org-enable-auto-update-cookies-h ()
  "Update statistics cookies when saving or exiting insert mode (`evil-mode')."
  (when (bound-and-true-p evil-local-mode)
    (add-hook 'evil-insert-state-exit-hook #'org-update-parent-todo-statistics nil t))
  (add-hook 'before-save-hook #'org-update-parent-todo-statistics nil t))

(general-def 'normal org-mode-map
  "C-<return>" '+org/insert-item-below)
(general-def 'insert org-mode-map
  "C-<return>" '+org/insert-item-below)

(use-package org-re-reveal
  :ensure nil
  :config
  (setq org-re-reveal-root "file:///home/chris/docs/presentations/reveal.js/"
        ;; org-re-reveal-theme "serif"
        org-re-reveal-transition "slide"
        org-re-reveal-mobile-app t
        org-re-reveal-width "90%")
  (add-to-list 'org-re-reveal-plugin-config '(audio-slideshow "RevealAudioSlideshow" "plugin/audio-slideshow/plugin.js"))

(defun chris/org-re-reveal-export-to-html
    (&optional async subtreep visible-only body-only ext-plist backend)
  "Export current buffer to a reveal.js HTML file with a different name
so that it can exists within a static site showing the file as a document
as well as a presentation.
Optional ASYNC, SUBTREEP, VISIBLE-ONLY, BODY-ONLY, EXT-PLIST are passed
to `org-export-to-file'.
Optional BACKEND must be `re-reveal' or a backend derived from it."
  (interactive)
  (let* ((backend (or backend 're-reveal))
         (extension (concat "-presentation" "." org-html-extension))
         (client-ext (concat org-re-reveal-multiplex-client-ext extension))
         (file (org-export-output-file-name extension subtreep))
         (clientfile (org-export-output-file-name client-ext subtreep))
         (org-html-container-element "div"))

    (setq org-re-reveal-client-multiplex nil)
    (org-export-to-file backend file
      async subtreep visible-only body-only ext-plist)

    ;; Export the client HTML file if org-re-reveal-client-multiplex is set true
    ;; by previous call to org-export-to-file
    (if org-re-reveal-client-multiplex
        (org-export-to-file backend clientfile
          async subtreep visible-only body-only ext-plist))
    file)))

(use-package ellama
  :init
  (require 'llm-ollama)
  (setopt ellama-buffer-mode 'markdown-mode
          ellama-user-nick "Chris"
          ellama-assistant-nick "Jeeves"
          ellama-auto-scroll t
          ellama-provider
	  (make-llm-ollama 
           :scheme "https"
           :host "ai.tfcconnection.org"
           :port 443
	   :chat-model "dolphin-mistral" :embedding-model "dolphin-mistral"))
  
  (setopt ellama-providers
          '(("mistral" . (make-llm-ollama
                          :scheme "https"
                          :host "ai.tfcconnection.org"
                          :port 443
                          :chat-model "mistral"
                          :embedding-model "mistral"))
            ("openhermes" . (make-llm-ollama
                             :scheme "https"
                             :host "ai.tfcconnection.org"
                             :port 443
                             :chat-model "openhermes:7b-mistral-v2.5-q6_K"
                             :embedding-model "openhermes:7b-mistral-v2.5-q6_K"))
            ("dolphin" . (make-llm-ollama
                          :scheme "https"
                          :host "ai.tfcconnection.org"
                          :port 443
                          :chat-model "dolphin-uncensored"
                          :embedding-model "dolphin-uncensored"))))
  :config
  (defun chris/ellama-new-session (prompt)
    (interactive "sAsk ellama: ")
    (ellama-provider-select)
    (ellama-new-session ellama-provider prompt))
  :general
  (chris/leader-keys
    :states '(normal visual)
    :keymaps 'override
    "l" '(:ignore t :which-key "llm")
    "la" 'ellama-chat
    "lb" 'ellama-ask-about
    "lc" 'ellama-code-review
    "lec" 'ellama-code-improve
    "leg" 'ellama-improve-grammar
    "leC" 'ellama-improve-conciseness
    "lew" 'ellama-improve-wording
    "ls" 'ellama-provider-select))

(use-package jinx
  ;; :hook (emacs-startup . global-jinx-mode)
  :init (flyspell-mode -1)
  :config (flyspell-mode -1)
  :bind (("M-c" . jinx-correct)
         ("C-M-$" . Jinx-Languages))
  :general
  (general-def jinx-mode-map
    "C-S-f" 'jinx-correct))

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

(use-package posframe)

(use-package vertico-posframe
  :after vertico
  :config
  (setq vertico-posframe-min-height 10))

(setq home-directory "~/")
(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?"))))))

;; (defun chris/)

(defun chris/eww-video-dl ()
  "Download the video at the url"
  (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
             "yt-dlp"
             "yt-dlp-output"
             "yt-dlp" "-o" (concat home-directory "Videos/%(title)s.%(ext)s") url)
            (message (concat "downloading => " url))
          (message "idk this failed I guess..."))
        (pop-to-buffer "yt-dlp-output")
        (comint-mode)
        (evil-normal-state)
        (general-def 'normal comint-mode-map
          "q" 'kill-buffer-and-window)))))

(setq eww-search-prefix "https://search.tfcconnection.org/search?q=")

(general-def 'normal eww-mode-map
  "gv" 'chris/eww-mpv
  "gV" 'chris/eww-video-dl)
(chris/leader-keys
  :state 'normal
  :keymap 'eww-mode-map
  "fs" '(eww-browse :which-key "browse"))

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

;; 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)

  (consult-customize
   consult-org-heading :preview-key nil)

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

  (setq consult-imenu-config
        '((emacs-lisp-mode :toplevel "Functions" :types
                           ((102 "Functions" font-lock-function-name-face)
                            (109 "Macros" font-lock-function-name-face)
                            (112 "Packages" font-lock-constant-face)
                            (116 "Types" font-lock-type-face)
                            (118 "Variables" font-lock-variable-name-face)))))
  
  (setq xref-show-xrefs-function 'consult-xref)

  
  :general
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "si" 'consult-imenu
    "so" 'consult-org-heading
    "sf" 'consult-find
    "sm" 'bookmark-jump
    "sf" 'consult-flymake
    "sx" 'xref-show-xrefs
    "sy" 'consult-yank-from-kill-ring
    "sb" 'consult-eglot-symbols))

(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 wgrep)

(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 nil)      ;; Never quit, even if there is no match
  (corfu-preview-current nil)    ;; 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 10)
  (corfu-auto-prefix 2)
  (corfu-auto-delay 0.5)

  ;; 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
  (add-hook 'global-corfu-mode-hook #'evil-collection-corfu-setup)
  (global-corfu-mode)
  (corfu-echo-mode)
  (advice-remove 'corfu--setup 'evil-normalize-keymaps)
  (advice-remove 'corfu--teardown 'evil-normalize-keymaps)

  (advice-add 'corfu--setup :after (lambda (&rest r) (evil-normalize-keymaps)))
  (advice-add 'corfu--teardown :after  (lambda (&rest r) (evil-normalize-keymaps)))
  :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-prefix-p "~" pattern)
      `(orderless-flex . ,(substring pattern 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)
  ;; (defvar completion-at-point-functions '(cape-dabbrev cape-keyword cape-file tempel-complete
  ;;                                                      pcomplete-completions-at-point
  ;;                                                      ispell-completion-at-point))
  
  ;; (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)
  ;; (remove #'cape-ispell completion-at-point-functions)
  ;; (remove #'cape-symbol completion-at-point-functions)

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

(use-package kind-icon
  :ensure t
  :after corfu
  :custom
  (kind-icon-default-face 'corfu-default) ; to compute blended backgrounds correctly
  :config
  (add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter))

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

(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 'conf-mode-hook 'tempel-setup-capf)
  (add-hook 'prog-mode-hook 'tempel-setup-capf)
  (add-hook 'text-mode-hook 'tempel-setup-capf)
  (add-hook 'org-mode-hook 'tempel-setup-capf)

  (setq tempel-path "/home/chris/.emacs.d/templates")

  ;; 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)
  (general-def 'insert tempel-map
    "C-l" 'tempel-next
    "C-h" 'tempel-previous))

(use-package tempel-collection)

(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
    "gd" 'projectile-run-gdb
    "fp" 'project-find-file
    "fP" 'project-switch-project))

(chris/leader-keys
  :states 'normal
  :keymaps 'override
  "p!"    'project-shell-command
  "p&"    'project-async-shell-command      
  "pD"    'project-dired
  "pF"    'project-or-external-find-file
  "pG"    'project-or-external-find-regexp
  "pb"    'project-switch-to-buffer
  "pc"    'project-compile
  "pd"    'project-find-dir
  "pe"    'project-eshell
  "pf"    'project-find-file
  "pg"    'project-find-regexp
  "pk"    'project-kill-buffers
  "pp"    'project-switch-project
  "pr"    'project-query-replace-regexp
  "ps"    'project-shell
  "pv"    'project-vc-dir
  "px"    'project-execute-extended-command)
(general-def 'normal
  "p" 'evil-paste-after)

(use-package lab
  :config
  (defun chris/gitlab-token ()
    (interactive)
    (string-clean-whitespace (shell-command-to-string "rbw get 'gitlab token'")))

  (setq lab-host "https://gitlab.com"
        lab-token (chris/gitlab-token)))

(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
      (if (string= system-name "syl")
          '(("\\*e?shell\\*"
             (display-buffer-in-side-window)
             (side . bottom)
             (window-height . 0.25))
            ("\\*e?shell-terminal\\*"
             (display-buffer-same-window))
            ("*helpful*"
             (display-buffer-in-side-window)
             (side . right)
             (window-width . 0.4))
            ("*compilation*"
             (display-buffer-in-side-window)
             (side . right)
             (window-width . 0.4))
            ("*rustic-compilation*"
             (display-buffer-in-side-window)
             (side . right)
             (window-width . 0.4))
            ("*gud-presenter*"
             (display-buffer-in-side-window)
             (side . right)
             (window-width . 0.4))
            ("*dired-side*"
             (display-buffer-in-side-window)
             (side . left)
             (window-width . 0.3))
            ("*org-roam*"
             (display-buffer-in-side-window)
             (side . right)
             (window-width . 0.4))
            ("\\*Agenda Commands\\*"
             (display-buffer-in-side-window)
             (side . bottom)
             (window-height . 0.30))
            ("\\*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))
            ("*Ledger Report*"
             (display-buffer-in-side-window)
             (side . right)
             (window-width . 0.40))
            ("\\*Bongo-Elfeed Queue\\*"
             (display-buffer-in-side-window)
             (side . bottom)
             (window-height . 0.25))
            ("\\*Async Shell Command\\*"
             (display-buffer-no-window))
            )

        '(("\\*e?shell\\*"
           (display-buffer-in-side-window)
           (side . bottom)
           (window-height . 0.25))
          ("\\*e?shell-terminal\\*"
           (display-buffer-same-window))
          ("*helpful*"
           (display-buffer-in-side-window)
           (side . bottom)
           (window-height . 0.25))
          ("*compilation*"
           (display-buffer-in-side-window)
           (side . bottom)
           (window-height . 0.25))
          ("*rustic-compilation*"
           (display-buffer-in-side-window)
           (side . bottom)
           (window-height . 0.25))
          ("\\*Agenda Commands\\*"
           (display-buffer-in-side-window)
           (side . bottom)
           (window-height . 0.30))
          ("*gud-presenter*"
           (display-buffer-in-side-window)
           (side . bottom)
           (window-height . 0.25))
          ("*org-roam*"
           (display-buffer-in-side-window)
           (side . right)
           (window-width . 0.3))
          ("\\*elfeed-entry\\*"
           (display-buffer-in-side-window)
           (side . bottom)
           (window-height . 0.70))
          ("*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 t)

(defvar read-symbol-positions-list nil)
(add-to-list 'comint-output-filter-functions 'ansi-color-process-output)
(add-hook 'comint-mode-hook 'ansi-color-for-comint-mode-on)
(add-hook 'compilation-filter-hook 'ansi-color-for-comint-mode-on)
(setq compilation-environment '("TERM=xterm-256color")
      compilation-scroll-output t)

(defun my/advice-compilation-filter (f proc string)
  (funcall f proc (xterm-color-filter string)))

(advice-add 'compilation-filter :around #'my/advice-compilation-filter)

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

(use-package smartparens
  :config
  (smartparens-global-mode +1)
  :general
  ('normal emacs-lisp-mode-map
           "gl" 'sp-forward-slurp-sexp
           "gh" 'sp-backward-slurp-sexp
           "C-l" 'sp-forward-sexp
           "C-h" 'sp-backward-sexp)
  ('normal lisp-mode-map
           "gl" 'sp-forward-slurp-sexp
           "gh" 'sp-backward-slurp-sexp
           "C-l" 'sp-forward-sexp
           "C-h" 'sp-backward-sexp)
  ('normal lisp-shared-mode-map
           "gl" 'sp-forward-slurp-sexp
           "gh" 'sp-backward-slurp-sexp
           "C-l" 'sp-forward-sexp
           "C-h" 'sp-backward-sexp)
  ('normal sly-mrepl-mode-map
           "gl" 'sp-forward-slurp-sexp
           "gh" 'sp-backward-slurp-sexp
           "C-l" 'sp-forward-sexp
           "C-h" 'sp-backward-sexp)
  ('normal scheme-mode-map
           "gl" 'sp-forward-slurp-sexp
           "gh" 'sp-backward-slurp-sexp
           "C-l" 'sp-forward-sexp
           "C-h" 'sp-backward-sexp)
  ('normal cider-repl-mode-map
           "gl" 'sp-forward-slurp-sexp
           "gh" 'sp-backward-slurp-sexp
           "C-l" 'sp-forward-sexp
           "C-h" 'sp-backward-sexp)
  ('normal clojurescript-mode-map
           "gl" 'sp-forward-slurp-sexp
           "gh" 'sp-backward-slurp-sexp
           "C-l" 'sp-forward-sexp
           "C-h" 'sp-backward-sexp))

(use-package paredit
  :hook ((lisp-mode . enable-paredit-mode)
         (emacs-lisp-mode . enable-paredit-mode)
         (scheme-mode . enable-paredit-mode))
  :general
  ('normal lisp-mode-map
           "gl" 'paredit-forward-slurp-sexp
           "gh" 'paredit-backward-slurp-sexp
           "C-l" 'paredit-forward
           "C-h" 'paredit-backward)
  ('normal lisp-shared-mode-map
           "gl" 'paredit-forward-slurp-sexp
           "gh" 'paredit-backward-slurp-sexp
           "C-l" 'paredit-forward
           "C-h" 'paredit-backward)
  ('normal sly-mrepl-mode-map
           "gl" 'paredit-forward-slurp-sexp
           "gh" 'paredit-backward-slurp-sexp
           "C-l" 'paredit-forward
           "C-h" 'paredit-backward)
  ('normal scheme-mode-map
           "gl" 'paredit-forward-slurp-sexp
           "gh" 'paredit-backward-slurp-sexp
           "C-l" 'paredit-forward
           "C-h" 'paredit-backward)
  ('normal geiser-mode-map
           "gl" 'paredit-forward-slurp-sexp
           "gh" 'paredit-backward-slurp-sexp
           "C-l" 'paredit-forward
           "C-h" 'paredit-backward)
  ('normal cider-repl-mode-map
           "gl" 'paredit-forward-slurp-sexp
           "gh" 'paredit-backward-slurp-sexp
           "C-l" 'paredit-forward
           "C-h" 'paredit-backward))
;; (remove-hook 'prog-mode-hook 'enable-paredit-mode)

(load (if (string= system-name "kaladin")
          "/home/chris/quicklisp/clhs-use-local.el"
        "/home/chris/.local/share/quicklisp/clhs-use-local.el") t)

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

(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))
  (setq sly-connection-poll-interval 0.1)

  (defun sly-critique-file ()
    "Lint this file with lisp-critic"
    (interactive)
    (sly-eval-async '(ql:quickload :lisp-critic))
    (sly-eval-async
        `(lisp-critic:critique-file ,(buffer-file-name))))

  :general
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "os" 'sly
    "gc" 'compile)

  (general-def 'normal lisp-mode-shared-map
    "gcr" 'sly
    "ge" 'sly-eval-defun
    "gE" 'sly-eval-last-expression
    "gp" 'sly-pprint-eval-last-expression))

;; (general-def 'normal scheme-mode-map
  ;; "gl" 'paredit-)

(use-package geiser
  :config
  :general
  (general-def 'normal geiser-mode-map
    "gcr" 'geiser
    "ge" 'geiser-eval-definition-and-go
    "gE" 'geiser-eval-last-sexp
    "gp" 'geiser-eval-last-sexp-and-print
    "gl" 'paredit-forward-slurp-sexp))

(general-def 'normal emacs-lisp-mode-map
  "ge" 'eval-defun
  "gE" 'eval-last-sexp
  "gp" 'pp-eval-last-sexp)



(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
        rustic-clippy-arguments "-- -W clippy::pedantic")
  (advice-add 'eglot-completion-at-point :around #'cape-wrap-buster)
  (add-to-list 'compilation-error-regexp-alist rustic-compilation-error)
  (add-to-list 'compilation-error-regexp-alist rustic-compilation-warning)
  :general
  (general-def 'normal rustic-mode-map
    "!" 'rustic-run-shell-command
    "gC" 'rustic-cargo-clippy
    "gA" 'rustic-cargo-add
    "gt" 'rustic-cargo-test
    "gT" 'rustic-cargo-current-test)
  (chris/leader-keys 'normal rustic-mode-map
    "gc" 'rustic-compile
    "gr" 'projectile-run-project
    "si" 'consult-imenu-multi))

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

(defun chris/web-mode-setup ()
  "some setup for web development"
  (setq-local completion-at-point-functions
              (list #'cape-dabbrev #'cape-keyword #'tempel-complete)))
(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)
  (add-to-list 'web-mode-hook #'chris/web-mode-setup)

  :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\\'"
  :general
  (chris/leader-keys 'normal 'override
    "xf" 'nix-flake))

(use-package eglot
  :commands eglot
  :hook
  (c++-mode . eglot-ensure)
  (c++-ts-mode . eglot-ensure)
  (rust-mode . eglot-ensure)
  (rustic-mode . eglot-ensure)
  (rust-ts-mode . eglot-ensure)
  :config
  
  (defun chris/eglot-capf ()
    (setq-local completion-at-point-functions
                (list (cape-capf-super
                       #'eglot-completion-at-point
                       #'tempel-expand
                       #'cape-file))))

  (add-hook 'eglot-managed-mode-hook #'chris/eglot-capf)

  :general
  (general-def 'normal eglot-mode-map
    "ga" 'eglot-code-actions
    "gi" 'eglot-find-implementation))

(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)
         ("\\.rmd\\'". markdown-mode))
  :config
  (setq markdown-fontify-code-blocks-natively t)
  (add-hook 'markdown-mode-hook 'chris/org-mode-setup)
  (custom-set-faces '(markdown-code-face ((t (:inherit org-block)))))
  :general
  (general-def 'normal markdown-mode-map
    "C-j" 'markdown-next-visible-heading
    "M-j" 'markdown-move-down
    "C-k" 'markdown-previous-visible-heading
    "M-k" 'markdown-move-up))

(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 php-mode
  :mode ("\\.php\\'" . php-mode))

(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 (concat "Opening ==> " file))
      (call-process "xdg-open" nil 0 nil file)))

  (defun chris/dired-open-wm ()
    "Open dired as file-manager in wm"
    (interactive)
    (with-selected-frame (make-frame '((name . "dired")))
      (dired-jump)
      (toggle-truncate-lines +1)))

  (defun chris/dired-open-videos ()
    "Open dired in Videos as file-manager in wm"
    (interactive)
    (with-selected-frame (make-frame '((name . "dired")))
      (dired "/home/chris/vids/")
      (toggle-truncate-lines +1)))

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

  (defun chris/open-dired-sidewindow ()
    "Open dired as a side window to the main window on the left"
    (interactive)
    (with-current-buffer (get-buffer-create "dired-side")
      (dired-jump)))

  (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"))
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "sF" '(fd-dired :which-key "search in directory with fd"))
  ('normal dired-mode-map
    "q" 'kill-this-buffer
    "C-<return>" 'chris/dired-open-xdg
    "M-<return>" 'ffap-other-window
    "h" 'dired-up-directory
    "l" 'dired-find-file
    "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)))

(defun chris/dired-yank-fullpath ()
  "get the fill file path at point and put into kill-ring"
  (interactive)
  (let* ((file (dired-get-filename)))
    (message file)))

(use-package dired-sidebar
  :ensure t
  :commands (dired-sidebar-toggle-sidebar)
  :general
  (general-def 'normal
    "gD" 'dired-sidebar-toggle-sidebar)
  (general-def 'normal dired-sidebar-mode-map
    "l" 'dired-sidebar-find-file
    "h" 'dired-sidebar-up-directory))

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

(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))
(add-to-list 'tramp-remote-path 'tramp-own-remote-path)

(connection-local-set-profile-variables
 'guix-system
 '((tramp-remote-path . (tramp-own-remote-path))))
(connection-local-set-profiles
 '(:application tramp :protocol "sudo" :machine "hostname")
 'guix-system)

(setq tramp-default-proxies-alist '())

(use-package ledger-mode
  :config
  (setq-mode-local ledger-mode
                   completion-at-point-functions (list
                                                  (cape-capf-super
                                                   #'cape-file
                                                   #'cape-abbrev))
                   tab-always-indent 'complete
                   completion-cycle-threshold t
                   ledger-complete-in-steps t)

  (defun chris/ledger-clean ()
    (interactive)
    (when (eq major-mode #'ledger-mode)
      (ledger-mode-clean-buffer)))
  (add-hook 'after-save-hook #'chris/ledger-clean)

  (setq ledger-default-date-format "%Y-%m-%d")

  :general
  (general-def 'normal ledger-mode-map
    "ga" 'ledger-add-transaction
    "gr" 'ledger-report
    "gp" 'ledger-toggle-current
    "C-j" 'ledger-navigate-next-xact-or-directive
    "C-k" 'ledger-navigate-prev-xact-or-directive))

(use-package mu4e
  ;; :load-path "~/.guix-home/profile/share/emacs/site-lisp/mu4e/"
  :init

  (setq mu4e-maildir "~/mail"
        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/docs/attachments"
        mu4e-completing-read-function #'completing-read
        mu4e-notification-support t
        ;; mu4e-mu-binary "/home/chris/.guix-home/profile/bin/mu"
        ;; mu4e-mu-binary "/etc/profiles/per-user/chris/bin/mu"
        mu4e-compose-signature-auto-include nil
        mu4e-headers-fields 
        '((:human-date . 12)
         (:flags . 10)
         (:from . 22)
         (:subject))
        mu4e-headers-flagged-mark '("F" . " ")
        mu4e-headers-replied-mark '("R" . " ")
        mu4e-headers-personal-mark '("p" . " ")
        mu4e-headers-list-mark '("s" . " "))


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

  (defun mu4e--main-action-str (name func)
    "This seems to be needed until evil-collection supports the latest
  version of mu4e."
    "mu4e--main-action")
  (remove-hook 'mu4e-main-mode-hook 'evil-collection-mu4e-update-main-view)

  (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 "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")))))

  ;; 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 "/cochrun/Archive"               :key ?a)
          (:maildir "/cochrun/Inbox"                 :key ?i)
          (:maildir "/office/Inbox"                  :key ?w)
          (:maildir "/office/Junk Email"             :key ?j)
          (:maildir "/office/INBOX/Website Forms"    :key ?f)
          (: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\" AND NOT maildir:\"/cochrun/Junk\" AND NOT flag:list"
                 :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)
          (:name "Lists"
                 :query "flag:list"
                 :key 108)))

  (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)
    (setq visual-fill-column-center-text t)
    (setq visual-fill-column-width 120)
    (visual-fill-column-mode +1)
    (visual-line-mode -1)
    (toggle-truncate-lines +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
    "q" 'mu4e-view-quit))

(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 "\n%s\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 "

 #+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 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 t
  :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 "kaladin") (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 0.7))
                    (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:/home/chris/.cargo/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)))

  (defun chris/upgrade-nix ()
    "A function for updating my nix config"
    (interactive)
    (let* ((default-directory (file-truename (concat home-directory ".dotfiles/"))))
      (async-shell-command
       "sudo nixos-rebuild switch --show-trace --verbose --impure --flake .#"
       "*upgrade*"
       "*upgrade-errors*")))

  (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 $1")
          ("nupg" "upgrade-nix")
          ("nupd" "update-nix")
          ("ksb" "/home/chris/dev/kde/src/kdesrc-build/kdesrc-build")
          ("ws" "rsync -avzP public/ chris@staff.tfcconnection.org:tfcconnection")
          ("wttr" "curl wttr.in/@home.cochrun.xyz")
          ("gh" "guix home -L ~/.dotfiles/guix reconfigure /home/chris/.dotfiles/guix/home.scm")
          ("gs" ,(concat "sudo guix system -L ~/.dotfiles/guix reconfigure /home/chris/.dotfiles/guix/" (system-name) ".scm"))))

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

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

  (defun chris/eshell-window ()
    "Open a new emacs window with eshell as a sole buffer"
    (interactive)
    (with-selected-frame (make-frame '((name . "*eshell-terminal*")))
      (setq eshell-terminal (eshell 100))
      (display-buffer-same-window
          (generate-new-buffer-name "*eshell-terminal*") '())
      (eshell 100)
      (other-window 1)
      (switch-to-buffer "*eshell*<100>")
      (other-window 1)
      (delete-window)))

  :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
    "<up>" 'eshell-previous-input))

(use-package eat
  :init
  (add-hook 'eshell-load-hook #'eat-eshell-mode))

(setq vterm-buffer-name-string "vterm %s"
      vterm-shell "/run/current-system/sw/bin/zsh")
(defun chris/vterm-setup ()
  "Setup vterm with my preferred settings"
  (display-line-numbers-mode -1))
(add-hook 'vterm-mode-hook 'chris/vterm-setup)

(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

  (defun chris/print-multi-pdf ()
    "Print the pdf in as many copies as needed"
    (interactive)
    (let* ((copies (completing-read "How many copies: " '("1" "2" "3")))
           (sides (completing-read "Print both sides or one? " '("two sided" "one sided")))
           (pdf-misc-print-program-args `("-o media=Letter" ,(format "-# %s" copies) "-o fitplot"
                                          ,(if (string= sides "two sided")
                                              "-o sides=two-sided-long-edge"
                                             ""))))
      (message "printing %s copies." copies)
      (pdf-misc-print-document (buffer-file-name) t)))

  (custom-set-variables '(pdf-misc-print-program-executable "lpr")
                        '(pdf-misc-print-program-args (quote ("-o media=Letter" "-o fitplot" "-o sides=two-sided-long-edge"))))
  (add-hook 'pdf-view-mode-hook 'pdf-view-fit-page-to-window)
  (defun chris/display-line-numbers-off ()
    (display-line-numbers-mode -1))
  (add-hook 'pdf-view-mode-hook 'chris/display-line-numbers-off)
  :general
  (general-def
    :states 'normal
    :keymaps 'pdf-view-mode-map
    "C-p" 'chris/print-multi-pdf))

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

  (defun chris/setup-nov-mode 
    (interactive)
    (visual-fill-column-mode +1)
    (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 empv
  :config
  (setq empv-invidious-instance "https://vid.puffyan.us/api/v1"
        empv-video-file-extensions '("mkv" "mp4" "avi" "mov" "webm")
        empv-mpv-args '("--no-terminal" "--idle" "--input-ipc-server=/tmp/empv-socket")
        empv-video-dir "~/vids")
  (empv-embark-initialize-extra-actions)
  (general-auto-unbind-keys)
  (add-hook 'empv-youtube-results-mode-hook #'evil-normal-state)

  (defun chris/empv-yt-dlp ()
    "Download the current video and and play it"
    (interactive)
    (let* ((item (empv-youtube-results--current-item))
           (video-id (alist-get 'videoId item))
           (playlist-id (alist-get 'playlistId item))
           (title (alist-get 'title item))
           (url (format
                 "https://youtube.com/%s=%s"
                 (if video-id "watch?v" "playlist?list")
                 (or video-id playlist-id))))
      (async-shell-command (concat
                            "yt-dlp" " -o '/home/chris/vids/%(title)s.%(ext)s'"
                            " \"" url "\""))
      (let ((file (format "/home/chris/vids/%s.%s" title "webm")))
        (empv-play file))
      (message url)))

  (defun chris/empv-yt-dlp-jellyfin ()
    "Grabs the current video at point and downloads it to my jellyfin server"
    (interactive)
    (let* ((server "jelly.cochrun.xyz")
           (item (empv-youtube-results--current-item))
           (video-id (alist-get 'videoId item))
           (playlist-id (alist-get 'playlistId item))
           (title (alist-get 'title item))
           (location (completing-read "location: " '("dev" "house" "ministry" "misc")))
           (url (format
                 "https://youtube.com/%s=%s"
                 (if video-id "watch?v" "playlist?list")
                 (or video-id playlist-id))))
      (async-shell-command (concat
                            "ssh " server " \"" "yt-dlp"
                            " -o '/storage/media/media/chris/extras/yt/"
                            location
                            "/%(title)s.%(ext)s'"
                            " '" url "'" "\""))
      (message (format "%s is downloading to the %s folder on jellyfin" url location))))

  (defun chris/empv-org-play-link (&optional link)
    "Play link in empv from either supplied link or link at point in org-mode"
    (interactive)
    (let ((link (if link
                    link
                  (org-babel-read-link))))
      (empv-play link)))

  (defun chris/dired-empv-play-or-enqueue ()
    "Play file at point in dired"
    (interactive)
    (let* ((file (dired-get-filename)))
      (empv-play-or-enqueue file)))

  :general
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "vo" 'empv-play-or-enqueue
    "vt" 'empv-toggle
    "vv" 'empv-play-video
    "vx" 'empv-chapter-select
    "vy" 'empv-youtube-tabulated
    "vn" 'empv-playlist-next
    "vp" 'empv-playlist-prev
    "vs" 'empv-playlist-select)
  (general-def
    :states 'normal
    :keymaps 'empv-youtube-results-mode-map
    "q" 'kill-this-buffer
    "RET" 'empv-youtube-results-play-or-enqueue-current
    "i" 'empv-youtube-results-inspect
    "d" 'chris/empv-yt-dlp
    "D" 'chris/empv-yt-dlp-jellyfin)
  (general-def
    :states 'normal
    :keymaps 'dired-mode-map
    "vi" 'chris/dired-empv-play-or-enqueue))

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

  (defun chris/elfeed-empv-play ()
    "Play elfeed video or podcast link in empv"
    (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)))
           (final-url (if (string-prefix-p "https://inv.cochrun.xyz/" url)
                          (string-replace "https://inv.cochrun.xyz/" "https://youtube.com/" url)
                        url))
           (title (elfeed-entry-title entry)))
      (empv-play-or-enqueue final-url)
      (message "Playing: %s from %s" title final-url)
      (elfeed-search-untag-all-unread)))

  (defun chris/empv-ytdlp-play-or-enqueue (process signal video)
    "play or enqueue a video after ytdlp is done"
    (when (memq (process-status process) '(exit signal)
                (empv-play-or-enqueue video)
                (shell-command-sentinel process signal))))

  (defun chris/elfeed-ytdlp-empv ()
    "Download the video at point and add to empv"
    (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))
           (output-buffer (generate-new-buffer "*yt-dlp*"))
           (proc (progn
                   (message url)
                   (make-process
                    :name "yt-dlp"
                    :buffer output-buffer
                    :command `("yt-dlp" 
                              "-o" "/home/chris/vids/%(title)s.%(ext)s"
                              ,(cl-coerce url 'string)))
                   (get-buffer-process output-buffer))))
      
      (elfeed-search-untag-all-unread)
      (if (process-live-p proc)
          (set-process-sentinel
           proc `(empv-play-or-enqueue
                   ,(concat "/home/chris/vids/" title ".webm")))
        (message "No process running"))))

  (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)
    (elfeed-search-fetch))

  (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)
    (hide-mode-line-mode +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/efleed-org-view ()
    "Display the url of the entry as an org-mode buffer"
    (interactive)
    (let ((link (elfeed-entry-link elfeed-show-entry)))
      (message link)
      (org-web-tools-read-url-as-org link)))

  (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-empv-play
    "h" 'chris/elfeed-bongo-switch-to-playlist
    "b" 'chris/elfeed-eww-browse
    "o" 'chris/elfeed-org-view
    "d" 'chris/elfeed-ytdlp-empv)
  (general-def 'normal elfeed-show-mode-map
    "o" 'chris/elfeed-org-view
    "b" 'eww))

(use-package elfeed-org
  :after elfeed
  :config
  (setq rmh-elfeed-org-files (list "~/docs/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-mpv-remote-option "--input-ipc-server=/tmp/bongo-mpv.socket"
        bongo-field-separator (propertize " · " 'face 'shadow))

  (define-bongo-backend mpv
                        :constructor 'bongo-start-mpv-player
                        :program-name 'mpv
                        :program-arguments '("--profile=fast")
                        :extra-program-arguments nil
                        :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-mpv)
        emms-player-mpv-parameters '("--quiet" "--really-quiet" "--no-audio-display" "--speed=1"))
  (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)))

  (defun chris/emms-mpv-video-playlist ()
    "Create a playlist for mpv videos."
    (interactive)
    (emms-playlist-new "*emms-videos*"))

  (defun chris/emms-add-video (&optional video)
    "adds the given video to the emms playlist"
    (interactive)
    (if video
      (emms-add-url video)
      (emms-add-url)))

  :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) "kaladin")
      (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 auth-source-pass
  :defer t
  :config (auth-source-pass-enable))

(use-package pass
  :defer t)

(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
  :ensure t)

(use-package ement
  :ensure t
  :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
    "el" 'ement-list-rooms
    "ev" 'ement-view-room
    "es" 'ement-directory-search
    "em" 'ement-send-direct-message
    "en" 'ement-notifications))

(ement-connect :uri-prefix "http://127.0.0.1:8008" :user-id "@chriscochrun:tfcconnection.org" :password "UtK4ik#sRx^GVqr@J3YVZ@#m")

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

(load (concat user-emacs-directory "bible.el"))

;; 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 2
        gcmh-high-cons-threshold (* 128 1024 1024)  ; 128mb
        gcmh-verbose nil))

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