;;; syd-handle-eval.el -*- lexical-binding: t; -*-

(defvar syd-eval-runners '())

;; Remove ellipsis when printing sexps in message buffer.
(setq eval-expression-print-length nil
      eval-expression-print-level nil)

(set-popup-rule!
  "*eval*"
  :size 0.2)


;; Packages

(use-package quickrun
  :defer t)

(use-package eros
  :hook (emacs-lisp-mode . eros-mode))

;; (with-eval-after-load quickrun
;;   (setq quickrun-focus-p nil)

;;   (set-popup-rule! "^\\*quickrun" :size 0.3 :ttl 0)

;;   (defadvice! +eval--quickrun-fix-evil-visual-region-a ()
;;     "Make `quickrun-replace-region' recognize evil visual selections."
;;     :override #'quickrun--outputter-replace-region
;;     (let ((output (buffer-substring-no-properties (point-min) (point-max))))
;;       (with-current-buffer quickrun--original-buffer
;;         (cl-destructuring-bind (beg . end)
;;             ;; Because `deactivate-mark', the function, was used in
;;             ;; `quickrun--region-command-common' instead of `deactivate-mark',
;;             ;; the variable, the selection is disabled by this point.
;;             (if (bound-and-true-p evil-local-mode)
;;                 (cons evil-visual-beginning evil-visual-end)
;;               (cons (region-beginning) (region-end)))
;;           (delete-region beg end)
;;           (insert output))
;;         (setq quickrun-option-outputter quickrun--original-outputter))))

;;   (defadvice! +eval--quickrun-auto-close-a (&rest _)
;;     "Silently re-create the quickrun popup when re-evaluating."
;;     :before '(quickrun quickrun-region)
;;     (when-let (win (get-buffer-window quickrun--buffer-name))
;;       (let ((inhibit-message t))
;;         (quickrun--kill-running-process)
;;         (message ""))
;;       (delete-window win)))

;;   (add-hook! 'quickrun-after-run-hook
;;     (defun +eval-quickrun-shrink-window-h ()
;;       "Shrink the quickrun output window once code evaluation is complete."
;;       (when-let (win (get-buffer-window quickrun--buffer-name))
;;         (with-selected-window (get-buffer-window quickrun--buffer-name)
;;           (let ((ignore-window-parameters t))
;;             (shrink-window-if-larger-than-buffer)))))
;;     (defun +eval-quickrun-scroll-to-bof-h ()
;;       "Ensures window is scrolled to BOF on invocation."
;;       (when-let (win (get-buffer-window quickrun--buffer-name))
;;         (with-selected-window win
;;           (goto-char (point-min))))))

;;   ;; Display evaluation results in an overlay at the end of the current line. If
;;   ;; the output is more than `+eval-popup-min-lines' (4) lines long, it is
;;   ;; displayed in a popup.
;;   (when (modulep! +overlay)
;;     (defadvice! +eval--show-output-in-overlay-a (fn)
;;       :filter-return #'quickrun--make-sentinel
;;       (lambda (process event)
;;         (funcall fn process event)
;;         (with-current-buffer quickrun--buffer-name
;;           (when (> (buffer-size) 0)
;;             (+eval-display-results
;;              (string-trim (buffer-string))
;;              quickrun--original-buffer)))))

;;     ;; Suppress quickrun's popup window because we're using an overlay instead.
;;     (defadvice! +eval--inhibit-quickrun-popup-a (buf cb)
;;       :override #'quickrun--pop-to-buffer
;;       (setq quickrun--original-buffer (current-buffer))
;;       (save-window-excursion
;;         (with-current-buffer (pop-to-buffer buf)
;;           (setq quickrun-option-outputter #'ignore)
;;           (funcall cb))))

;;     ;; HACK Without this, `+eval--inhibit-quickrun-popup-a' throws a
;;     ;;      window-live-p error because no window exists to be recentered!
;;     (advice-add #'quickrun--recenter :override #'ignore)))



;;;###autoload
(cl-defun syd-eval-region-as-major-mode
    (beg end &key (runner-major-mode major-mode))
  "Evaluate a region between BEG and END and display the output.

Evaluate as in RUNNER-MAJOR-MODE. If RUNNER-MAJOR-MODE is nil, use major-mode
of the buffer instead."
  (if-let* ((runner (alist-get runner-major-mode syd-eval-runners)))
      (funcall runner beg end)
    (and (require 'quickrun nil t)
         (let ((quickrun-option-cmdkey
                (quickrun--command-key
                 (buffer-file-name (buffer-base-buffer)))))
           (quickrun-region beg end)))))

;;;###autoload
(defun syd-eval-region (beg end)
  (interactive "r")
  ;; (message "syd: %s" (pp-to-string
  ;;                     (buffer-substring-no-properties (syd-region-beginning)
  ;;                                                     (syd-region-end))))
  ;; (message "r: %s" (pp-to-string (buffer-substring-no-properties beg end)))
  (syd-eval-region-as-major-mode beg end))

(with-eval-after-load 'evil
  (evil-define-operator syd-eval-operator (beg end)
    "Evaluate selection."
    :move-point nil
    (interactive "<r>")
    (syd-eval-region beg end))

  (general-def
    :states 'normal
    "gR" #'syd-eval-buffer)

  (general-def
    :states '(normal visual)
    "gr" #'syd-eval-operator))

;;;###autoload
(defun set-eval-handler! (modes command)
  "Define a code evaluator for major mode MODES with `quickrun'.

MODES can be list of major mode symbols, or a single one.

1. If MODE is a string and COMMAND is the string, MODE is a file regexp and
   COMMAND is a string key for an entry in `quickrun-file-alist'.
2. If MODE is not a string and COMMAND is a string, MODE is a major-mode symbol
   and COMMAND is a key (for `quickrun--language-alist'), and will be registered
   in `quickrun--major-mode-alist'.
3. If MODE is not a string and COMMAND is an alist, see `quickrun-add-command':
   (quickrun-add-command MODE COMMAND :mode MODE).
4. If MODE is not a string and COMMANd is a symbol, add it to
   `syd-eval-runners', which is used by `syd-eval-region'."
  (declare (indent defun))
  (dolist (mode (ensure-list modes))
    (cond ((symbolp command)
           (push (cons mode command) syd-eval-runners))
          ((stringp command)
           (with-eval-after-load 'quickrun
             (push (cons mode command)
                   (if (stringp mode)
                       quickrun-file-alist
                     quickrun--major-mode-alist))))
          ((listp command)
           (with-eval-after-load 'quickrun
             (quickrun-add-command
               (or (cdr (assq mode quickrun--major-mode-alist))
                   (string-remove-suffix "-mode" (symbol-name mode)))
               command :mode mode))))))

(defvar syd-eval-overlay-max-lines 4
  "The maximum number of lines allowed to be displayed in an eval overlay; any
more and a popup buffer will be used instead.")

;;;###autoload
(defun syd-eval-display-results-in-popup (output)
  "Display OUTPUT in a popup buffer."
  (let ((output-buffer (get-buffer-create "*eval*"))
        (origin (selected-window)))
    (with-current-buffer output-buffer
      (setq-local scroll-margin 0)
      (erase-buffer)
      (insert output)
      (goto-char (point-min))
      ;; (if (fboundp '+word-wrap-mode)
      ;;     (+word-wrap-mode +1)
      ;;   (visual-line-mode +1))
      )
    (when-let* ((win (display-buffer output-buffer)))
      ;; (fit-window-to-buffer
      ;;  win (/ (frame-height) 2)
      ;;  nil (/ (frame-width) 2))
      )
    (select-window origin)
    output-buffer))

;;;###autoload
(defun syd-eval-buffer ()
  "Evaluate the entire buffer."
  (interactive)
  (syd-eval-region (point-min) (point-max)))

;;;###autoload
(defun syd-eval-buffer-or-region ()
  "Evaluate the region if it is active, or the entire buffer, or not."
  (if (use-region-p)
      (call-interactively #'syd-eval-region)
    (call-interactively #'syd-eval-buffer)))

;;;###autoload
(cl-defun syd-eval-display-results-in-overlay (output &key source-buffer)
  "Display OUTPUT in a floating overlay next to the cursor."
  (require 'eros)
  (with-current-buffer (or source-buffer (current-buffer))
    (let* ((this-command #'syd-eval/buffer-or-region)
           (prefix eros-eval-result-prefix)
           (lines (split-string output "\n"))
           (prefixlen (length prefix))
           (len (+ (apply #'max (mapcar #'length lines))
                   prefixlen))
           (next-line? (or (cdr lines)
                           (< (- (window-width)
                                 (save-excursion (goto-char (line-end-position))
                                                 (- (current-column)
                                                    (window-hscroll))))
                              len)))
           (pad (if next-line?
                    (+ (window-hscroll) prefixlen)
                  0))
           eros-overlays-use-font-lock)
      (eros--make-result-overlay
          (concat (make-string (max 0 (- pad prefixlen)) ?\s)
                  prefix
                  (string-join lines (concat hard-newline (make-string pad ?\s))))
        :where (if next-line?
                   (line-beginning-position 2)
                 (line-end-position))
        :duration eros-eval-result-duration
        :format "%s"))))

;;;###autoload
(cl-defun syd-eval-display-results (output &key source-buffer force-popup)
  "Display OUTPUT in an overlay, or if it's too long, a popup buffer."
  (if (or force-popup
          ;; EROS is used to display overlays.  Without it, just use a popup.
          (not (require 'eros nil t))
          (with-temp-buffer
            (insert output)
            (or
             ;; Too tall!
             (<= syd-eval-overlay-max-lines
                 (count-lines (point-min) (point-max)))
             ;; Too wide!
             (<= (window-width)
                 (string-width
                  (buffer-substring (point-min)
                                    (save-excursion
                                      (goto-char (point-min))
                                      (line-end-position))))))))
      (syd-eval-display-results-in-popup output)
    (syd-eval-display-results-in-overlay output :source-buffer source-buffer))
  output)

(provide 'syd-handle-eval)
