;;; tools/lookup/autoload/docsets.el -*- lexical-binding: t; -*-
;;;###if (modulep! +docsets)

(defvar dash-docs-docsets nil)

;;;###autodef
(defun set-docsets! (modes &rest docsets)
  "Registers a list of DOCSETS for MODES.

MODES can be one major mode, or a list thereof.

DOCSETS can be strings, each representing a dash docset, or a vector with the
structure [DOCSET FORM]. If FORM evaluates to nil, the DOCSET is omitted. If it
is non-nil, (format DOCSET FORM) is used as the docset.

The first element in DOCSETS can be :add or :remove, making it easy for users to
add to or remove default docsets from modes.

DOCSETS can also contain sublists, which will be flattened.

Example:

  (set-docsets! '(js2-mode rjsx-mode) \"JavaScript\"
    [\"React\" (eq major-mode 'rjsx-mode)]
    [\"TypeScript\" (bound-and-true-p tide-mode)])

Used by `+lookup/in-docsets' and `+lookup/documentation'."
  (declare (indent defun))
  (let ((action (if (keywordp (car docsets)) (pop docsets))))
    (dolist (mode (ensure-list modes))
      (let ((hook (intern (format "%s-hook" mode)))
            (fn (intern (format "+lookup-init--%s-%s" (or action "set") mode))))
        (if (null docsets)
            (remove-hook hook fn)
          (fset
           fn (lambda ()
                (make-local-variable 'dash-docs-docsets)
                (unless (memq action '(:add :remove))
                  (setq dash-docs-docset nil))
                (dolist (spec docsets)
                  (cl-destructuring-bind (docset . pred)
                      (cl-typecase spec
                        (string (cons spec nil))
                        (vector (cons (aref spec 0) (aref spec 1)))
                        (otherwise (signal 'wrong-type-arguments (list spec '(vector string)))))
                    (when (or (null pred)
                              (eval pred t))
                      (if (eq action :remove)
                          (setq dash-docs-docsets (delete docset dash-docs-docsets))
                        (cl-pushnew docset dash-docs-docsets)))))))
          (add-hook hook fn 'append))))))

;;;###autoload
(defun +lookup-dash-docsets-backend-fn (identifier)
  "Looks up IDENTIFIER in available Dash docsets, if any are installed.

This backend is meant for `+lookup-documentation-functions'.

Docsets must be installed with one of the following commands:

+ `dash-docs-install-docset'
+ `dash-docs-install-docset-from-file'
+ `dash-docs-install-user-docset'
+ `dash-docs-async-install-docset'
+ `dash-docs-async-install-docset-from-file'

Docsets can be searched directly via `+lookup/in-docsets'."
  (when (require 'dash-docs nil t)
    (when-let (docsets (cl-remove-if-not #'dash-docs-docset-path (dash-docs-buffer-local-docsets)))
      (+lookup/in-docsets nil identifier docsets)
      'deferred)))


;;
;;; Commands

(defun +lookup--consult-search (sync cb)
  (lambda (action)
    (pcase action
      ((pred stringp)
       (when-let (cands (with-current-buffer cb
                          (dash-docs-search action)))
         (funcall sync 'flush)
         (funcall sync cands)))
      (_ (funcall sync action)))))

;;;###autoload
(defun +lookup/in-docsets (arg &optional query docsets)
  "Lookup QUERY in dash DOCSETS.

QUERY is a string and docsets in an array of strings, each a name of a Dash
docset. Requires either helm or ivy.

If prefix ARG is supplied, search all installed installed docsets. They can be
installed with `dash-docs-install-docset'."
  (interactive "P")
  (require 'dash-docs)
  (let ((dash-docs-common-docsets)
        (dash-docs-docsets
         (if arg
             (dash-docs-installed-docsets)
           (cl-remove-if-not #'dash-docs-docset-path (or docsets dash-docs-docsets))))
        (query (doom-thing-at-point-or-region query)))
    (doom-log "Searching docsets %s" dash-docs-docsets)
    (cond ((modulep! :completion vertico)
           (require 'consult)
           (dash-docs-initialize-debugging-buffer)
           (dash-docs-create-buffer-connections)
           (dash-docs-create-common-connections)
           (let* ((sink
                   (thread-first (consult--async-sink)
                     (consult--async-refresh-immediate)
                     (+lookup--consult-search (current-buffer))
                     (consult--async-throttle)))
                  (result
                   (or (consult--read sink
                                      :prompt "Documentation for: "
                                      :category 'dash
                                      :initial query)
                       (user-error "Aborted"))))
             (dash-docs-browse-url (cdr (assoc result (funcall sink nil))))))
          ((modulep! :completion ivy)
           (counsel-dash query))
          ((modulep! :completion helm)
           (helm-dash query))
          ((user-error "No dash backend is installed, enable ivy or helm.")))))

;;;###autoload
(defun +lookup/in-all-docsets (&optional query)
  "TODO"
  (interactive)
  (+lookup/in-docsets t query))
