;;; syd-lang-clojure.el -*- lexical-binding: t; -*-

(use-package clojure-mode
  :mode (("\\.\\(clj\\|cljd\\|dtm\\|edn\\|lpy\\)\\'" . clojure-mode)
         ("\\.cljc\\'" . clojurec-mode)
         ("\\.cljs\\'" . clojurescript-mode)
         ("\\(?:build\\|profile\\)\\.boot\\'" . clojure-mode))
  :interpreter (("bb" . clojure-mode)
                ("nbb" . clojurescript-mode))
  :config
  (add-hook 'clojure-mode-hook #'syd-lisp-mode)
  (dolist (c '(?- ?_ ?? ?! ?+ ?* ?/ ?: ?> ?< ?= ?&))
    (modify-syntax-entry c "w" clojure-mode-syntax-table)))

(use-package cider
  :after clojure-mode)

(defun syd-clojure-open-repl (&optional arg type)
  "Open a Cider REPL for clojure and return the buffer."
  (interactive "P")
  ;; TODO: Better error handling
  ;; type is `clj' for clojure and `cljs' for clojurescript
  ;; ... with no type specified, assume `clj'.
  (let ((type (or type 'clj)))
    (if-let* ((buffer (cider-current-repl type)))
        (pop-to-buffer buffer)
      (let ((process (cond ((eq type 'clj) (cider-jack-in-clj arg))
                           ((eq type 'cljs) (cider-jack-in-cljs arg)))))
        (message "Starting CIDER server for the first time...")
        (while (and (process-live-p process)
                    (not (cider-current-repl type)))
          (sit-for 1))
        (message "Starting CIDER server for the first time...done")
        (pop-to-buffer (cider-current-repl type))))))

(defun syd-clojure-open-cljs-repl (&optional arg)
  (interactive "P")
  (syd-clojure-open-repl arg 'cljs))

(use-package cider-mode
  :straight nil
  ;; :after clojure-mode
  :hook (clojure-mode-local-vars . cider-mode)
  :init
  (with-eval-after-load 'clojure-mode
    (set-repl-handler! '(clojure-mode clojurec-mode)
      #'syd-clojure-open-repl :persist t)
    (set-repl-handler! 'clojurescript-mode
      #'syd-clojure-open-cljs-repl :persist t)
    (set-eval-handler! '(clojure-mode
                         cider-clojure-interaction-mode
                         clojurec-mode clojurescript-mode)
      #'cider-eval-region))
  :custom ((cider-show-error-buffer nil)
           ;; Download Java source archives for local Javadoc and jump-to-def
           ;; functionality.
           (cider-download-java-sources t))
  :general
  ;; DEPRECATED: Remove once a `map!' equivalent is implemented.
  (:keymaps 'cider-repl-mode-map
   :states '(normal insert)
   "C-k" #'cider-repl-backward-input
   "C-j" #'cider-repl-forward-input
   "C-s" #'consult-history)
  ;; DEPRECATED: Remove once a `map!' equivalent is implemented.
  (:keymaps '(cider-repl-mode-map clojure-mode-map)
   :states '(normal visual motion emacs insert)
   :major-modes t
   :prefix syd-localleader-key
   :non-normal-prefix syd-alt-localleader-key
   "\"" #'cider-jack-in-cljs
   "'" #'cider-jack-in-clj
   "c" #'cider-connect-clj
   "C" #'cider-connect-cljs
   "r l" #'cider-load-buffer
   "r n" #'cider-repl-set-ns
   "r r" #'cider-ns-refresh
   "r R" #'cider-restart
   "r q" #'cider-quit
   "d d" #'cider-debug-defun-at-point
   "M-:" #'cider-read-and-eval
   "h d" #'cider-doc
   "h c" #'cider-cheatsheet
   "h j" #'cider-javadoc
   "h n" #'cider-browse-ns
   "T n" #'cider-test-run-ns-tests
   "T r" #'cider-test-rerun-failed-tests
   "T R" #'cider-test-rerun-test
   "x" #'cider-scratch)
  (:keymaps 'cider-repl-mode-map
   :states 'insert
   "S-<return>" #'cider-repl-newline-and-indent)
  :config
  (add-hook 'cider-mode-hook #'eldoc-mode)
  (add-hook 'cider-repl-mode-hook #'syd-lisp-mode)
  (set-popup-rules!
    `(("^\\*cider-error\\*" :ignore t)
      ("^\\*cider-repl" :quit nil :ttl nil)
      ("^\\*cider-repl-history" :vslot 2 :ttl nil)
      (,(rx bol "*cider-nrepl-middleware") :slot 3 :quit t :ttl 0)
      (,(rx bol "*cider-test-report") :vslot 3 :quit t :ttl 0 :height 0.4)
      (,(rx bol "*cider-cheatsheet*")
       :width ,(lambda (win)
                 (with-selected-window win
                   (enlarge-window (- (min 45 (* 0.2 (frame-width)))
                                      (window-width))
                                   t)))
       :side right :vslot -8 :quit t :select t)
      (,(rx bol "*cider-ns-browser")
       :side right :vslot -8 :quit t :select t)
      (,(rx bol "*cider-doc*")
       :slot 2 :vslot -8 :quit t :select t)))
  (with-eval-after-load 'cider-scratch
    (add-to-list 'display-buffer-alist
                 `(,(regexp-quote cider-scratch-buffer-name)
                   (,#'display-buffer-same-window))))
  ;; DEPRECATED: Remove once syd-strategies is working.
  (syd-add-hook '(clojure-mode-hook
                  clojurescript-mode-hook
                  cider-repl-mode-hook
                  cider-clojure-interaction-mode-hook)
    (defun syd-clojure-set-handlers-h ()
      (setq-local syd-lookup-documentation-handlers
                  (list #'cider-doc))))

  ;; Correctly indent some common macros.
  (put-clojure-indent 'match 1)

  (with-eval-after-load 'consult
    (add-to-list 'consult-mode-histories
                 '(cider-repl-mode cider-repl-input-history nil cider-repl-bol-mark))))

;; Give different pairs of delimiters different colours.
(use-package rainbow-delimiters
  :hook (clojure-mode . rainbow-delimiters-mode))

(provide 'syd-lang-clojure)
