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

(require 'cl-lib)
(require 'syd/straight)
(require 'syd/use-package)
(require 'syd/dash)
(require 'syd/general)
(require 'syd/evil)
(eval-when-compile (require 'cl-lib))


;;; Core machinery

(defun syd-parent-mode-list (mode)
  "Return a list of MODE, then MODE's parent, then MODE's grandparent..."
  (cl-loop for m = mode then (get m 'derived-mode-parent)
           while m
           collect m))

(defun syd-handler-alist-name (type)
  (intern (concat "syd-handle-"
                  (symbol-name type)
                  "-alist")))

(defun syd-handler-function-name (type)
  (intern (concat "syd-handle-" (symbol-name type))))

(defmacro syd-define-handler-type
    (handler-type arglist &optional docstring)
  (declare (indent defun))
  (let ((hook-name (syd-handler-alist-name handler-type))
        (fn-name (syd-handler-function-name handler-type)))
    `(progn
       (defvar ,hook-name nil)
       (defun ,fn-name (&rest args)
         ,@(ensure-list docstring)
         (let (r)
           (cl-loop
            for (mode . handle) in ,hook-name
            ;; Test mode's variable to determine if it's active.  Some modes
            ;; lack an associated variable, which is very annoying!  The
            ;; `major-mode' and sledgehammer `minor-mode-list' tests should
            ;; catch these guys.
            ;;
            ;; TODO: Use `syd-parent-mode-list' to support hierarchies of modes.
            when (or (eq mode major-mode)
                     (and (boundp mode)
                          (symbol-value mode)))
            do (setq r (if (commandp handle)
			   (call-interactively handle)
			 (apply handle args)))
            when r return r))))))

(defun syd-handle (modes &rest args)
  (declare (indent defun))
  (cl-loop
   for m in (ensure-list modes)
   do (cl-loop for (type handler) on args by (lambda (xs)
                                               (cdr (cdr xs)))
               do (let ((s (->> (symbol-name type)
                                (string-remove-prefix ":")
                                intern
                                syd-handler-alist-name)))
                    (set s (cons (cons m handler)
                                 (symbol-value s)))))))


;;; Docs handler

(syd-define-handler-type docs (identifier)
  "Return a buffer visiting documentation for IDENTIFIER.")

(defun syd-handle-docs* (identifier)
  (interactive (list (symbol-name (symbol-at-point))))
  (if-let* ((b (save-excursion
		 (save-window-excursion
		   (syd-handle-docs identifier)))))
      (display-buffer b)
    (error "Couldn't find documentation on %S"
	   (substring-no-properties identifier))))


;;; REPL handler

(syd-define-handler-type repl ()
  "Return a buffer.")

(defun syd-handle-repl* (&optional beg end)
  (interactive "r")
  (if-let* ((b (save-window-excursion
		 (syd-handle-repl))))
      (let ((window (get-buffer-window b))
	    (prev-buffer (current-buffer)))
	(popper-select-popup-at-bottom b)
	(when (and beg end)
	  (end-of-buffer)
	  (insert (with-current-buffer prev-buffer
		    (buffer-substring-no-properties beg end)))
	  (with-current-buffer prev-buffer
	    (evil-active-region -1)))
	(unless window
	  (end-of-buffer)
	  (evil-append-line 1)))
    (error "Couldn't open a REPL for %S" major-mode)))


;;; Eval handler

(syd-define-handler-type eval-region (beg end)
  "Eval region.")

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


;;; Load buffer handler

(syd-define-handler-type load-buffer ()
  "Load buffer/file")

(defun syd-handle-load-buffer* ()
  (interactive)
  (syd-handle-load-buffer))


;;; Keybinds

(general-def
  :states '(motion normal)
  "K" #'syd-handle-docs*)

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

(general-def
  :states 'normal
  "gR" #'syd-handle-load-buffer*)

(general-def
  :prefix-map 'syd-leader-open-map
  "r" #'syd-handle-repl*)



(provide 'syd/handle)
