;; -*- lexical-binding: t; -*-

;; IDE features

;; Corfu-mode provides inline autocompletion
(use-package corfu
  :straight (:files (:defaults "extensions/corfu-echo.el"))
  :init
  (global-corfu-mode)
  (corfu-echo-mode)
  :config
  (defun corfu-move-to-minibuffer ()
    (interactive)
    (when completion-in-region--data
      (let ((completion-extra-properties corfu--extra)
            completion-cycle-threshold completion-cycling)
        (apply #'consult-completion-in-region completion-in-region--data))))
  (defun corfu-enable-in-minibuffer ()
    "Enable Corfu in the minibuffer if `completion-at-point' is bound."
    (when (where-is-internal #'completion-at-point (list (current-local-map)))
      (setq-local corfu-echo-delay nil ;; Disable automatic echo and popup
                  corfu-popupinfo-delay nil)
      (corfu-mode 1)))
  (add-hook 'minibuffer-setup-hook #'corfu-enable-in-minibuffer)
  :general
  (corfu-map "S-SPC" #'corfu-insert-separator
             "M-m" #'corfu-move-to-minibuffer)
  :custom-face
  (corfu-echo ((t :inherit default))))

;; Quick file overview for supported modes
(use-package imenu
  :straight (:type built-in)
  :general
  (leader-map "m" #'imenu))

;; Find definition/references
(use-package xref
  :straight (:type built-in)
  :general
  (normal "M-," #'xref-pop-marker-stack)
  (normal "M-r" #'xref-find-references)
  :custom
  (xref-prompt-for-identifier nil))

(use-package eglot
  :commands (eglot)
  :config
  (add-to-list 'eglot-server-programs
               '(js-web-mode . ("typescript-language-server" "--stdio")))
  (add-to-list 'eglot-server-programs
               `(html-web-mode . ,(eglot-alternatives
                                   '(("vscode-html-language-server" "--stdio")
                                     ("html-languageserver" "--stdio")))))
  (defvar eglot-java-java-agent nil
    "Java agent JVM arg for eglot JDTLS.")
  ;; Custom eglot java server for deeper customization
  (defclass eglot-java-server (eglot-lsp-server) ()
    :documentation "Eglot integration with JDTLS.")
  (defun eglot-java-contact (&optional interactive)
    `(eglot-java-server .
      ("jdtls" ,(if (s-blank? eglot-java-java-agent)
                    ""
                  (format "--jvm-arg=-javaagent:%s" eglot-java-java-agent))
       :initializationOptions
       (:extendedClientCapabilities (:classFileContentsSupport t)
        :settings
        (:java
         (:home "/Library/Java/JavaVirtualMachines/amazon-corretto-17.jdk/Contents/Home"
          :configuration
          (:runtimes [(:name "JavaSE-11"
                       :path "/Library/Java/JavaVirtualMachines/amazon-corretto-11.jdk")
                      (:name "JavaSE-17"
                       :path "/Library/Java/JavaVirtualMachines/amazon-corretto-17.jdk"
                       :default t)])))))))
  (add-to-list 'eglot-server-programs '(java-mode . eglot-java-contact))
  ;; Fix JDTLS's weird handling of workspaceEdit
  (cl-defmethod eglot-execute-command
    ((_server eglot-java-server) (_cmd (eql java.apply.workspaceEdit)) arguments)
    "Eclipse JDT breaks spec and replies with edits as arguments."
    (mapc #'eglot--apply-workspace-edit arguments))
  ;; Support jdtls' ability to jump into class files
  (define-advice file-relative-name
      (:before-until (filename &optional directory) jdt-uri)
    (when (string-match-p "\\`jdt://" filename)
      filename))
  (define-hash-table-test 'jdt-file-to-uri-hash 'equal 'sxhash-equal)
  (defvar eglot-java--jdt-file-to-uri (make-hash-table :test 'jdt-file-to-uri-hash)
    "Internal variable to map temporary filepaths to JDT URIs.")
  (define-advice eglot--path-to-uri
      (:before-until (path) jdt-path)
    (gethash (file-truename path) eglot-java--jdt-file-to-uri))
  (defun jdt-class-file-name-handler (operation &rest args)
    "File name handler for jdtls' `jdt://' URIs."
    (cond
     ((eq operation 'temporary-file-directory) temporary-file-directory)
     ((member operation '(file-remote-p
                          file-name-case-insensitive-p
                          make-auto-save-file-name
                          find-file-backup-name)) nil)
     (t (let* ((uri (car args))
               (uri-hash (secure-hash 'md5 uri))
               (filename (save-match-data
                           (string-match "jdt://contents/\\(.*?\\)/\\(.*?\\)\.class\\?" uri)
                           (format "%s.java"
                                   (replace-regexp-in-string "/" "." (match-string 2 uri) t t))))
               (temp-dir (expand-file-name "eglot-jdtls" temporary-file-directory))
               (uri-temp-dir (expand-file-name uri-hash temp-dir))
               (filepath (concat (file-name-as-directory uri-temp-dir) filename))
               (metadata-path (format "%s.%s.metadata"
                                      (file-name-directory filepath)
                                      (file-name-base filepath))))
          (unless (gethash (file-truename filepath) eglot-java--jdt-file-to-uri)
            (puthash (file-truename filepath) uri eglot-java--jdt-file-to-uri))
          (unless (or (file-readable-p filepath) (not (eglot-current-server)))
            (let ((contents
                   (jsonrpc-request (eglot-current-server)
                                    :java/classFileContents
                                    (list :uri uri))))
              (unless (file-directory-p uri-temp-dir) (make-directory uri-temp-dir t))
              (with-temp-file filepath (insert contents))
              (with-temp-file metadata-path (insert uri))))
          (cond
           ((eq operation 'get-file-buffer) (get-buffer filename))
           ((member operation '(expand-file-name
                                directory-file-name
                                file-truename
                                file-name-directory
                                file-name-nondirectory)) (apply operation filepath (cdr args)))
           ((eq operation 'insert-file-contents)
            (seq-let (uri visit beg end replace) args
              (let ((content (with-temp-buffer
                               (insert-file-contents filepath nil beg end replace)
                               (buffer-substring (point-min) (point-max)))))
                (insert content)
                (when visit
                  (set-visited-file-name filepath t)
                  (set-buffer-modified-p nil)
                  (read-only-mode))
                (list uri (length content)))))
           (t (let ((inhibit-file-name-handlers
                     (cons 'jdt-class-file-name-handler
                           (and (eq inhibit-file-name-operation operation)
                                inhibit-file-name-handlers)))
                    (inhibit-file-name-operation operation))
                (apply operation args))))))))
  (setq eglot-extend-to-xref t)
  (add-to-list 'file-name-handler-alist '("\\`jdt://" . jdt-class-file-name-handler))
  (add-to-list 'auto-mode-alist '("\\`jdt://" . java-mode))
  ;; Java build command
  (defun eglot-java-maven-build ()
    (interactive)
    (if-let ((pom-path (locate-dominating-file default-directory "pom.xml")))
        (let ((pom-uri (eglot--path-to-uri pom-path)))
          (jsonrpc-notify
           (eglot--current-server-or-lose)
           :java/projectConfigurationUpdate
           (list :uri pom-uri))
          (jsonrpc-notify
           (eglot--current-server-or-lose)
           :java/buildWorkspace
           '((:json-false))))
      (user-error "This doesn't appear to be a Maven project, could not find pom.xml.")))
  :init
  (defvar eglot-prefix-map (make-sparse-keymap)
    "Prefix keymap for eglot commands.")
  (general-def eglot-prefix-map
    "a" #'eglot-code-actions
    "r" #'eglot-reconnect
    "s" #'eglot-shutdown
    "R" #'eglot-rename
    "f" #'eglot-format-buffer
    "i" #'eglot-code-action-organize-imports
    "h" #'eldoc
    "e" #'flymake-show-buffer-diagnostics
    "E" #'flymake-show-project-diagnostics)
  :general
  (eglot-mode-map "C-c l" `(,eglot-prefix-map :which-key "eglot"))
  :hook
  (java-mode . eglot-ensure)
  (rust-mode . eglot-ensure)
  (js-mode . eglot-ensure)
  (typescript-mode . eglot-ensure)
  (python-mode . eglot-ensure)
  (js-web-mode . eglot-ensure)
  (html-web-mode . eglot-ensure)
  (scala-mode . eglot-ensure)
  (c-mode . eglot-ensure)
  :custom
  (eglot-confirm-server-initiated-edits nil))

;; Some compilation-mode conveniences
(use-package compile
  :straight (:type built-in)
  :commands compile
  :config
  (defun postprocess-compilation-buffer ()
    (goto-char compilation-filter-start)
    (when (looking-at "\033c")
      (delete-region (point-min) (match-end 0)))
    (ansi-color-apply-on-region (point) (point-max)))
  (add-hook 'compilation-filter-hook 'postprocess-compilation-buffer))

;; Code formatting library
(use-package apheleia
  :straight (apheleia :host github :repo "raxod502/apheleia")
  :general
  ("C-c f" #'apheleia-format-buffer))

;; Debugger interface
(use-package realgud
  :defer t
  :config

(provide 'init-ide)