;;; jao-r2e.el --- List of rss2email subscriptions   -*- lexical-binding: t; -*-

;; Copyright (C) 2025, 2026  Jose Antonio Ortega Ruiz

;; Author: Jose Antonio Ortega Ruiz <mail@jao.io>
;; Keywords: news

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

(require 'multisession)
(require 'jao-url)

(autoload 'View-quit "view")
(autoload 'eww-view-source "eww")
(autoload 'jao-notmuch-subtags "jao-notmuch")

(defvar jao-r2e-command "r2e")
(defvar jao-r2e-confirm-toggle nil)

(defconst jao-r2e--buffer "*r2e*")
(defun jao-r2e--buffer ()
  (with-current-buffer (get-buffer-create jao-r2e--buffer)
    (unless (derived-mode-p 'jao-r2e-mode)
      (jao-r2e-mode))
    (current-buffer)))

(defvar jao-r2e-mode-map
  (let ((map (make-keymap)))
    (suppress-keymap map)
    (define-key map [?q] 'bury-buffer)
    (define-key map [?n] 'next-line)
    (define-key map [?p] 'previous-line)
    (define-key map [?N] 'jao-r2e-next)
    (define-key map [?P] 'jao-r2e-prev)
    (define-key map [?g] 'jao-r2e-list)
    (define-key map [?s] 'jao-r2e-subscribe)
    (define-key map [?t] 'jao-r2e-toggle)
    (define-key map [?D] 'jao-r2e-delete)
    (define-key map [?u] 'jao-r2e-recover)
    (define-key map [?y] 'jao-r2e-kill-url)
    (define-key map [?x] 'jao-r2e-list-subscribed)
    map))

;;;###autoload
(defun jao-r2e-mode ()
  "A very simple mode to show the output of r2e commands."
  (interactive)
  (kill-all-local-variables)
  (buffer-disable-undo)
  (use-local-map jao-r2e-mode-map)
  ;; (setq-local font-lock-defaults '(jao-r2e-font-lock-keywords))
  (setq-local truncate-lines t)
  (setq-local next-line-add-newlines nil)
  (setq major-mode 'jao-r2e-mode)
  (setq mode-name "r2e")
  (read-only-mode 1))

(defun jao-r2e--do (things &optional buffer)
  "Execute a r2e command THINGS in the given BUFFER."
  (let ((b (or buffer (pop-to-buffer (jao-r2e--buffer)))))
    (let ((inhibit-read-only t)
          (cmd (format "%s %s" jao-r2e-command things)))
      (unless buffer
        (with-current-buffer b (delete-region (point-min) (point-max))))
      (with-temp-message (format  "Running: %s ...." cmd)
        (shell-command cmd b))
      (unless buffer (read-only-mode 1)))))

(defconst jao-r2e--feed-rx
  "^\\([0-9]+\\): \\[\\( \\|\\*\\)\\] \\([^ ]+\\) (\\(.+\\) -> \\(.+\\))")

(defun jao-r2e--feed-at-point ()
  (beginning-of-line)
  (when-let* ((m (looking-at jao-r2e--feed-rx)))
    (list (match-string 1)
          (match-string 3)
          (string= "*" (match-string 2))
          (match-string 4)
          (match-string 5))))

(defun jao-r2e ()
  (interactive)
  (pop-to-buffer (jao-r2e--buffer))
  (when (looking-at-p "^$")
    (jao-r2e-list)))

(defun jao-r2e-list ()
  (interactive)
  (jao-r2e--do "list"))

(defun jao-r2e-list-subscribed (arg)
  "Show only subscribed (unsubscribed with arg) feeds."
  (interactive "P")
  (jao-r2e-list)
  (let ((inhibit-read-only t))
    (flush-lines (if arg ".*\\[\\*\\].*" ".*\\[ \\].*"))))

(defun jao-r2e--srx (opp)
  (when-let* ((f (jao-r2e--feed-at-point)))
    (let ((a (if opp (not (caddr f)) (caddr f))))
      (format "^[0-9]+: \\[%s\\] " (if a "\\*" " ")))))

(defun jao-r2e-next (opp)
  "Next feed with the same (or opposite) status."
  (interactive "P")
  (when-let* ((rx (jao-r2e--srx opp)))
    (next-line)
    (when (re-search-forward rx nil t)
      (beginning-of-line))))

(defun jao-r2e-prev (opp)
  "Previous feed with the same (or opposite) status."
  (interactive "P")
  (when-let* ((rx (jao-r2e--srx opp)))
    (when (re-search-backward rx nil t)
      (beginning-of-line))))

(defun jao-r2e-kill-url ()
  "Copy as kill the URL of the feed at point."
  (interactive)
  (let ((url (cadddr (jao-r2e--feed-at-point))))
    (if (not url)
        (error "No feed at point")
      (kill-new url)
      (message "%s" url))))

(defun jao-r2e-toggle ()
  (interactive)
  (let ((f (jao-r2e--feed-at-point)))
    (unless f (error "No feed at point"))
    (let ((p (point))
          (no (car f))
          (name (cadr f))
          (act (if (caddr f) "pause" "unpause")))
      (when (or (not jao-r2e-confirm-toggle)
                (yes-or-no-p (format "%s '%s'? " act name)))
        (with-temp-buffer
          (jao-r2e--do (format "%s %s" act no) (current-buffer)))
        (jao-r2e-list)
        (goto-char p)))))

(define-multisession-variable jao-r2e-deleted-feeds '()
  "List of rss2email feeds deleted at some point.")

(defun jao-r2e--deleted () (multisession-value jao-r2e-deleted-feeds))

(defun jao-r2e-delete ()
  "Delete feed at point. Use `jao-r2e-recover' to undelete."
  (interactive)
  (let ((f (jao-r2e--feed-at-point)))
    (unless f (error "No feed at point"))
    (let ((p (point))
          (no (car f))
          (entry (cdr f)))
      (when (yes-or-no-p (format "Delete feed '%s'" (car entry)))
        (setf (multisession-value jao-r2e-deleted-feeds)
              (cons entry (remove entry (jao-r2e--deleted))))
        (with-temp-buffer
          (jao-r2e--do (format "delete %s" no) (current-buffer)))
        (jao-r2e-list)
        (goto-char p)))))

(defun jao-r2e-recover ()
  (interactive)
  (when (seq-empty-p (jao-r2e--deleted))
    (error "No feeds recoverable at this point."))
  (let* ((entries (jao-r2e--deleted))
         (feed (completing-read "Recover feed: " entries)))
    (when-let* ((ps (assoc feed entries))
                (url (caddr ps))
                (mail (car (last ps)))
                (cat (when (string-match "feeds\\.\\(.+\\)@localhost" mail)
                       (match-string 1 mail))))
      (jao-r2e-subscribe (list url feed) cat t)
      (setf (multisession-value jao-r2e-deleted-feeds) (remove ps entries)))))

(defun jao-r2e--find-url ()
  (save-excursion
    (when (derived-mode-p 'w3m-mode 'eww-mode)
      (if (fboundp 'w3m-view-source) (w3m-view-source) (eww-view-source)))
    (goto-char (point-min))
    (when (re-search-forward
           "type=\"application/\\(?:atom\\|rss\\)\\+xml\" +" nil t)
      (let ((url (save-excursion
                   (when (re-search-forward
                          "href=\"\\([^\n\"]+\\)\"" nil t)
                     (match-string-no-properties 1))))
            (title (when (re-search-forward
                          "\\(?:title=\"\\([^\n\"]+\\)\" +\\)" nil t)
                     (match-string-no-properties 1))))
        (cond ((derived-mode-p 'w3m-view-mode) (w3m-view-source))
              ((string-match-p ".*\\*eww-source\\b.*" (buffer-name))
               (View-quit)))
        (when url (cons url (or title "")))))))

(defun jao-r2e-subscribe (url &optional cat relist)
  "Subscribe to a given RSS URL.  If URL not given, look for it."
  (interactive (list (or (jao-url-around-point)
                         (jao-r2e--find-url)
                         (read-string "Feed URL: "))))
  (let* ((url+title (ensure-list url))
         (url (car url+title))
         (title (cdr url+title)))
    (unless url (error "No feeds found"))
    (let ((url (if (string-match "^feed:" url) (substring url 5) url)))
      (when (y-or-n-p (format "Subscribe to <%s>? " url))
        (let* ((name (read-string "Feed name: " title))
               (cats (cons "prog" (jao-notmuch-subtags "feeds")))
               (cat (completing-read "Category: " cats nil t cat))
               (sfmt (format "%%s add %s '%s' mail+feeds_%s@jao.io" name url cat))
               (subs ))
          (with-temp-message "Subscribing..."
            (shell-command-to-string (format sfmt "r2e"))
            (when (not (string= jao-r2e-command "r2e"))
              (shell-command-to-string (format sfmt jao-r2e-command))))
          (when (y-or-n-p "Retrieve feeds now? ")
            (with-temp-message "Retrieving feed..."
              (shell-command (format "%s run %s" jao-r2e-command name))))
          (when relist (jao-r2e-list)))))))

(provide 'jao-r2e)
;;; jao-r2e.el ends here
