;;;; Tabs, indentation, and the TAB key
(use-package emacs
  :ensure nil
  :demand t
  :config
  (setq tab-always-indent 'complete)
  (setq tab-first-completion 'word-or-paren-or-punct) ; Emacs 27
  (setq-default tab-width 4
                indent-tabs-mode nil))

;;;; Parentheses (show-paren-mode)
(use-package paren
  :ensure nil
  :hook (prog-mode . show-paren-local-mode)
  :config
  (setq show-paren-style 'mixed)
  (setq show-paren-when-point-in-periphery nil)
  (setq show-paren-when-point-inside-paren nil)
  (setq show-paren-context-when-offscreen 'overlay)) ; Emacs 29

;;;; Eldoc (Emacs live documentation feedback)
(use-package eldoc
  :ensure nil
  :hook (prog-mode . eldoc-mode)
  :config
  (setq eldoc-message-function #'message)) ; don't use mode line for M-x eval-expression, etc.

;;;; Eglot (built-in client for the language server protocol)
(use-package eglot
  :ensure nil
  :demand t ; Not a mistake, we need to load Eglot elisp code before
            ; we open any Python file.
  :functions (eglot-ensure)
  :commands (eglot)
  :bind
  ( :map eglot-mode-map
    ("C-c e r" . eglot-rename)
    ("C-c e o" . eglot-code-action-organize-imports)
    ("C-c e d" . eldoc)
    ("C-c e c" . eglot-code-actions)
    ("C-c e f" . eglot-format)
    ;; Since eglot plugs into flymake anyway
    ("C-c e l" . flymake-show-buffer-diagnostics))
  :config
  (setq eglot-sync-connect nil)
  (setq eglot-autoshutdown t)
  (setq eglot-extend-to-xref t))

;;; Markdown (markdown-mode)
(use-package markdown-mode
  :ensure t
  :defer t
  :config
  (setq markdown-fontify-code-blocks-natively t))

;;; csv-mode
(use-package csv-mode
  :ensure t
  :commands (csv-align-mode))

;;; Flyspell
(use-package flyspell
  :ensure nil
  :bind
  ( :map flyspell-mode-map
    ("C-;" . nil)
    :map flyspell-mouse-map
    ("<mouse-3>" . flyspell-correct-word))
  :config
  (setq flyspell-issue-message-flag nil)
  (setq flyspell-issue-welcome-flag nil)
  (setq ispell-program-name "aspell")
  (setq ispell-dictionary "en_GB"))

;;; Flymake
(use-package flymake
  :ensure nil
  :bind
  ( :map flymake-mode-map
    ("C-c ! s" . flymake-start)
    ("C-c ! l" . flymake-show-buffer-diagnostics)  ; Emacs28
    ("C-c ! L" . flymake-show-project-diagnostics) ; Emacs28
    ("C-c ! n" . flymake-goto-next-error)
    ("C-c ! p" . flymake-goto-prev-error))
  :config
  (setq flymake-fringe-indicator-position 'left-fringe)
  (setq flymake-suppress-zero-counters t)
  (setq flymake-no-changes-timeout nil)
  (setq flymake-start-on-flymake-mode t)
  (setq flymake-start-on-save-buffer t)
  (setq flymake-proc-compilation-prevents-syntax-check t)
  (setq flymake-wrap-around nil)
  (setq flymake-mode-line-format
        '("" flymake-mode-line-exception flymake-mode-line-counters))
  (setq flymake-mode-line-counter-format
        '("" flymake-mode-line-error-counter
          flymake-mode-line-warning-counter
          flymake-mode-line-note-counter ""))
  (setq flymake-show-diagnostics-at-end-of-line nil)) ; Emacs 30

;;; Elisp packaging requirements
(use-package package-lint-flymake
  :ensure t
  :after flymake
  :config
  (add-hook 'flymake-diagnostic-functions #'package-lint-flymake))

;;; General configurations for prose/writing

;;;; `outline' (`outline-mode' and `outline-minor-mode')
(use-package outline
  :ensure nil
  :bind
  ("<f10>" . outline-minor-mode)
  :config
  (setq outline-minor-mode-highlight nil) ; emacs28
  (setq outline-minor-mode-cycle t) ; emacs28
  (setq outline-minor-mode-use-buttons nil) ; emacs29---bless you for the nil option!
  (setq outline-minor-mode-use-margins nil)) ; as above

;;;; `dictionary'
(use-package dictionary
  :ensure nil
  :config
  (setq dictionary-server "dict.org"
        dictionary-default-popup-strategy "lev" ; read doc string
        dictionary-create-buttons nil
        dictionary-use-single-buffer t))

;;; Denote (simple note-taking and file-naming)

;; Read the manual: <https://protesilaos.com/emacs/denote>.  This does
;; not include all the useful features of Denote.  I have a separate
;; private setup for those, as I need to test everything is in order.
(use-package denote
  :ensure t
  :hook
  ;; If you use Markdown or plain text files you want to fontify links
  ;; upon visiting the file (Org renders links as buttons right away).
  ((text-mode . denote-fontify-links-mode-maybe)

   ;; Highlight Denote file names in Dired buffers.  Below is the
   ;; generic approach, which is great if you rename files Denote-style
   ;; in lots of places as I do.
   ;;
   ;; If you only want the `denote-dired-mode' in select directories,
   ;; then modify the variable `denote-dired-directories' and use the
   ;; following instead:
   ;;
   ;;  (dired-mode . denote-dired-mode-in-directories)
   (dired-mode . denote-dired-mode))
  :bind
  ;; Denote DOES NOT define any key bindings.  This is for the user to
  ;; decide.  Here I only have a subset of what Denote offers.
  ( :map global-map
    ("C-c d n" . denote-create-note)
    ("C-c d N" . denote-silo-extras-select-silo-then-command)
    ("C-c d o" . denote-open-or-create)
    ("C-c d O" . denote-silo-extras-open-or-create)
    ("C-c d l" . denote-link-or-create)
    ("C-c d L" . denote-link-after-creating-with-command)
    ;; Note that `denote-rename-file' can work from any context, not
    ;; just Dired buffers.  That is why we bind it here to the
    ;; `global-map'.
    ;;
    ;; Also see `denote-rename-file-using-front-matter' further below.
    ("C-c d r" . denote-rename-file)
    ;; If you intend to use Denote with a variety of file types, it is
    ;; easier to bind the link-related commands to the `global-map', as
    ;; shown here.  Otherwise follow the same pattern for
    ;; `org-mode-map', `markdown-mode-map', and/or `text-mode-map'.
    ("C-c d j" . denote-journal-extras-new-entry)
    ("C-c d s" . denote-sort-dired)
    :map text-mode-map
    ("C-c d B" . denote-backlinks)
    ("C-c d b" . denote-find-backlink)
    ;; Also see `denote-rename-file' further above.
    ("C-c d R" . denote-rename-file-using-front-matter)
    ("C-c d k" . denote-rename-file-keywords)
    :map org-mode-map
    ("C-c d h" . denote-org-extras-link-to-heading)
    ("C-c d d l" . denote-org-extras-dblock-insert-links)
    ("C-c d d b" . denote-org-extras-dblock-insert-backlinks)
    ("C-c d d m" . denote-org-extras-dblock-insert-missing-links)
    ;; Key bindings specifically for Dired.
    :map dired-mode-map
    ("C-c C-d C-i" . denote-dired-link-marked-notes)
    ("C-c C-d C-r" . denote-dired-rename-marked-files)
    ("C-c C-d C-k" . denote-dired-rename-marked-files-with-keywords)
    ("C-c C-d C-A" . denote-dired-rename-marked-files-add-keywords)
    ("C-c C-d C-K" . denote-dired-rename-marked-files-remove-keywords)
    ("C-c C-d C-f" . denote-dired-rename-marked-files-using-front-matter))
  :config
  (require 'denote-silo-extras)
  (require 'denote-journal-extras)
  (require 'denote-org-extras)

  ;; Remember to check the doc strings of those variables.
  (setq denote-directory (expand-file-name "~/Tresors/Documents/diary/notes"))
  (setq denote-infer-keywords t)
  (setq denote-sort-keywords t)
  (setq denote-excluded-directories-regexp "data") ; external data related to headings is stored in these directories (web archives)
  (setq denote-date-format nil)         ; read its doc string
  (setq denote-date-prompt-use-org-read-date t)
  (setq denote-prompts '(title keywords subdirectory signature))

  (setq denote-rename-confirmations nil) ; CAREFUL with this if you are not familiar with Denote!
  (setq denote-save-buffers t)
  (setq denote-rename-buffer-format "[D] %s %t%b")
  ;; Automatically rename Denote buffers when opening them so that
  ;; instead of their long file name they have a literal "[D]"
  ;; followed by the file's title.  Read the doc string of
  ;; `denote-rename-buffer-format' for how to modify this.
  (denote-rename-buffer-mode 1)

  (setq denote-buffer-has-backlinks-string " (<--->)")
  (setq denote-backlinks-show-context t)
  (setq denote-org-store-link-to-heading t)

  ;; Journal settings
  (setq denote-journal-extras-keyword "")

  ;; I use Yasnippet to expand these into a better template.
  (add-to-list 'denote-templates '(reference-note . "reference"))
  (add-to-list 'denote-templates '(morning . "morningpage"))
  (add-to-list 'denote-templates '(emotion . "emotion"))
  (add-to-list 'denote-templates '(insight . "insight"))
  (add-to-list 'denote-templates '(weekly_intentions . "weekint"))
  (add-to-list 'denote-templates '(weekly_report . "weekrpt"))
  (add-to-list 'denote-templates '(sketch . "sketch"))

  ;; Front-matter for Org files
  (setq denote-org-front-matter
        ":PROPERTIES:
:ID: %4$s
:CREATED: %2$s
:END:
#+title:      %1$s
#+filetags:   %3$s
#+date:       %2$s
#+identifier: %4$s
\n"))

(defun vedang/denote-publishing-extras-new-blog-entry (&optional date)
  "Create a new blog entry.

  With optional DATE as a prefix argument, prompt for a date.  If
  `denote-date-prompt-use-org-read-date' is non-nil, use the Org
  date selection module.

  When called from Lisp DATE is a string and has the same format as
  that covered in the documentation of the `denote' function.  It
  is internally processed by `denote-parse-date'."
  (interactive (list (when current-prefix-arg (denote-date-prompt))))
  (let ((internal-date (denote-parse-date date))
        (denote-directory (file-name-as-directory (expand-file-name "published" denote-directory))))
    (denote
     (denote-title-prompt)
     '("draft")
     nil nil date
     ;; See YASnippet
     "fullblog")))

(defun vedang/denote-publishing-extras-new-microblog-entry (&optional date)
  "Create a new microblog entry.
  Set the title of the new entry according to the value of the user option
  `denote-journal-extras-title-format'.

  With optional DATE as a prefix argument, prompt for a date.  If
  `denote-date-prompt-use-org-read-date' is non-nil, use the Org
  date selection module.

  When called from Lisp DATE is a string and has the same format as
  that covered in the documentation of the `denote' function.  It
  is internally processed by `denote-parse-date'."
  (interactive (list (when current-prefix-arg (denote-date-prompt))))
  (let ((internal-date (denote-parse-date date))
        (denote-directory (file-name-as-directory (expand-file-name "published" denote-directory))))
    (denote
     (denote-journal-extras-daily--title-format internal-date)
     '("draft" "microblog")
     nil nil date
     ;; See YASnippet
     "microblog")))

(defun vedang/denote-link-ol-get-id ()
  "Get the CUSTOM_ID of the current entry.

If the entry already has a CUSTOM_ID, return it as-is, else create a new
one.

If we are creating a new ID, add a CREATED property with the current
timestamp as well.

This function is based on `denote-link-ol-get-id', with minor
modifications."
  (interactive)
  (let* ((pos (point))
         (id (org-entry-get pos "CUSTOM_ID"))
         (created (org-entry-get pos "CREATED")))
    (if (and (stringp id) (string-match-p "\\S-" id))
        id
      (setq id (org-id-new "h"))
      (org-entry-put pos "CUSTOM_ID" id))
    (when (not created)
      (setq created (format-time-string (org-time-stamp-format t t) (current-time)))
      (org-entry-put pos "CREATED" created))
    id))

(defun vedang/denote--split-luhman-sig (signature)
  "Split numbers and letters in Luhmann-style SIGNATURE string."
  (replace-regexp-in-string
   "\\([a-zA-Z]+?\\)\\([0-9]\\)" "\\1=\\2"
   (replace-regexp-in-string
    "\\([0-9]+?\\)\\([a-zA-Z]\\)" "\\1=\\2"
    signature)))

(defun vedang/denote--pad-sig (signature)
  "Create a new signature with padded spaces for all components"
  (combine-and-quote-strings
   (mapcar
    (lambda (x)
      (string-pad x 5 32 t))
    (split-string (vedang/denote--split-luhman-sig signature) "=" t))
   "="))

(defun vedang/denote-sort-for-signatures (sig1 sig2)
  "Return non-nil if SIG1 is smaller that SIG2.

  Perform the comparison with `string<'."
  (string< (vedang/denote--pad-sig sig1) (vedang/denote--pad-sig sig2)))

(use-package denote
  :ensure t
  :bind
  ( :map global-map
    ;; Bindings to personal functions (defined above)
    ("C-c d p m" . vedang/denote-publishing-extras-new-microblog-entry)
    ("C-c d p b" . vedang/denote-publishing-extras-new-blog-entry))
  :config
  (setq denote-sort-signature-comparison-function #'vedang/denote-sort-for-signatures))

(use-package consult-denote
  :ensure t
  :bind
  (("C-c d f" . consult-denote-find)
   ("C-c d g" . consult-denote-grep))
  :config
  (consult-denote-mode 1))

(use-package paredit
  :ensure t
  :bind ( :map paredit-mode-map
          ("C-o" . paredit-open-round)
          ("M-D" . paredit-splice-sexp)
          ("C-A-d" . paredit-forward-down)
          ("C-A-u" . paredit-backward-up)
          ;; Unbind things that I don't need
          ("M-s" . nil) ; used for search related keybindings
          ("M-?" . nil)) ; `xref-find-references` uses it.
  :hook ((lisp-data-mode lisp-mode clojure-mode clojure-ts-mode cider-repl-mode inferior-emacs-lisp-mode) . paredit-mode))

(use-package apheleia
  :ensure t
  :demand t
  :config
  (apheleia-global-mode +1)
  (with-eval-after-load 'apheleia-formatters
    (push '(zprint . ("zprint")) apheleia-formatters)))

;;;; Configuration for Python Programming

(use-package python
  :ensure nil
  :ensure-system-package (dasel sqlite3)
;;; Uncomment this if you want Eglot to start automatically. I don't
;;; recommend it because it does not give you time to activate the
;;; appropriate VirtualEnv and get the best of the situation.
  :hook ((python-base-mode . eglot-ensure))
  :config
  (setq python-shell-dedicated 'project)
  ;; Apheleia is an Emacs package for formatting code as you save
  ;; it. Here we are asking Apheleia to use Ruff for formatting our
  ;; Python code.
  (with-eval-after-load 'apheleia
    (setf (alist-get 'python-mode apheleia-mode-alist)
          '(ruff-isort ruff))
    (setf (alist-get 'python-ts-mode apheleia-mode-alist)
          '(ruff-isort ruff)))
  (with-eval-after-load 'eglot
    (require 'vedang-pet)
    ;; The -10 here is a way to define the priority of the function in
    ;; the list of hook functions. We want `pet-mode' to run before
    ;; any other configured hook function.
    (add-hook 'python-base-mode-hook #'pet-mode -10)))

;;;; Configuration for Zig Programming

(use-package zig-mode
  :ensure t
;;; Uncomment this if you want Eglot to start automatically. I don't
;;; recommend it, but that's just me.
  :hook ((zig-mode . eglot-ensure)))

;;; Configuration for Clojure programming
(use-package clojure-mode
  :ensure t)

(use-package cider
  :ensure t
  :after clojure-mode
  :config
  (defun cider-repl-prompt-on-newline (ns)
    "Return a prompt string with newline.
NS is the namespace information passed into the function by cider."
    (concat ns ">\n"))
  (setq cider-repl-prompt-function #'cider-repl-prompt-on-newline))

(use-package clj-refactor
  :ensure t
  :after clojure-mode
  :hook
  ((clojure-mode . clj-refactor-mode))
  :config
  (cljr-add-keybindings-with-prefix "C-c m")
  ;; Don't magically add stuff to the namespace requires form (because
  ;; for big projects this operation is slow) it's easier to do this
  ;; by hand (=add-missing= operation) after you've typed out what you
  ;; wanted to.
  (setq cljr-magic-requires nil))

(use-package clojure-snippets
  :ensure t
  :after clojure-mode)

(use-package jet
  :ensure t
  :config
  (defun json->edn ()
  "Convert the selected region, or entire file, from JSON to EDN."
  (interactive)
  (let ((b (if mark-active (region-beginning) (point-min)))
        (e (if mark-active (region-end) (point-max)))
        (jet (when (executable-find "jet")
               "jet --pretty --keywordize keyword --from json --to edn")))
    (if jet
        (let ((p (point)))
          (shell-command-on-region b e jet (current-buffer) t)
          (goto-char p))
      (user-error "Could not find jet installed")))))

(provide 'unravel-langs)