;;; wear-inbox.el --- Fetch voice notes from watch to org inbox  -*- lexical-binding: t; -*-

(require 'request)
(require 'json)
(require 'cl-lib)
(require 'org)

(defgroup wear-inbox nil
  "Fetch voice notes from watch to org inbox."
  :group 'applications)

(defcustom wear-inbox-server "http://localhost:3002"
  "URL of the wear-inbox server (127.0.0.1 avoids DNS timeouts)."
  :type 'string
  :group 'wear-inbox)

(defcustom wear-inbox-username "admin"
  "Username for authentication."
  :type 'string
  :group 'wear-inbox)

(defcustom wear-inbox-password ""
  "Password for authentication."
  :type 'string
  :group 'wear-inbox)

(defcustom wear-inbox-inbox-file "~/org/inbox.org"
  "Path to your org inbox file."
  :type 'string
  :group 'wear-inbox)

(defvar wear-inbox--cookies nil
  "Internal session storage.")

(defun wear-inbox--id-exists-p (id)
  "Return non-nil if ID already exists in the current buffer."
  (save-excursion
    (goto-char (point-min))
    (re-search-forward (concat ":ID:[ \t]+" (regexp-quote id)) nil t)))

(defun wear-inbox-fetch ()
  "Fetch notes and delete them in a single bulk request to prevent timeouts."
  (interactive)
  ;; Initialize cookie jar
  (setq wear-inbox--cookies (if (fboundp 'request-cookie-jar) (request-cookie-jar) nil))

  (message "Connecting to Wear Inbox...")

  ;; 1. Login (Synchronous)
  (request (concat wear-inbox-server "/api/login")
           :type "POST"
           :data (json-encode `(("username" . ,wear-inbox-username)
                                ("password" . ,wear-inbox-password)))
           :headers '(("Content-Type" . "application/json"))
           :cookie-jar wear-inbox--cookies
           :sync t)

  ;; 2. Fetch and Import (Synchronous)
  (let* ((resp (request (concat wear-inbox-server "/api/notes")
                        :cookie-jar wear-inbox--cookies
                        :sync t
                        :parser 'json-read))
         (notes (request-response-data resp))
         (imported-ids [])) ; Vector for JSON array compatibility

    (if (or (not notes) (equal notes []))
        (message "No new notes to import.")

      ;; Process the local file
      (with-current-buffer (find-file-noselect (expand-file-name wear-inbox-inbox-file))
        (org-with-wide-buffer
         (cl-loop for note across notes do
                  (let ((id (alist-get 'id note))
                        (content (alist-get 'content note))
                        (created (alist-get 'created note))
                        (audio (alist-get 'audio note)))
                    ;; Check for duplicates locally
                    (unless (wear-inbox--id-exists-p id)
                      ;; Download audio file if present
                      (when audio
                        (let ((audio-dir (concat (file-name-directory
                                                   (expand-file-name wear-inbox-inbox-file))
                                                  "audio/")))
                          (make-directory audio-dir t)
                          (condition-case nil
                              (url-copy-file (concat wear-inbox-server "/audio/" audio)
                                             (concat audio-dir audio) t)
                            (error (message "Failed to download audio: %s" audio)))))
                      (goto-char (point-max))
                      (let ((audio-prop (if audio (format "   :AUDIO: audio/%s\n" audio) ""))
                            (audio-link (if audio (format "   [[file:audio/%s][Play audio]]\n" audio) "")))
                        (insert (format "\n** TODO %s\n   :PROPERTIES:\n   :ID: %s\n   :CREATED: %s\n%s   :END:\n%s"
                                        content id created audio-prop audio-link)))
                      ;; Collect ID to delete from server later
                      (setq imported-ids (vconcat imported-ids (vector id)))))))
        (save-buffer))

      ;; 3. Single Bulk Delete (Asynchronous with JSON parser)
      (if (> (length imported-ids) 0)
          (progn
            (request (concat wear-inbox-server "/api/notes")
                     :type "DELETE"
                     :data (json-encode `(("ids" . ,imported-ids)))
                     :headers '(("Content-Type" . "application/json"))
                     :cookie-jar wear-inbox--cookies
                     :parser 'json-read ; Fixes the "Wrong type argument: listp" error
                     :success (cl-function (lambda (&key data &allow-other-keys)
                                             (message "Done! Imported and cleaned %d note(s) from server."
                                                      (alist-get 'deletedCount data)))))
            (message "Imported %d note(s). Cleaning server..." (length imported-ids)))
        (message "All notes on server were already in your local inbox.")))))

(defun wear-inbox--open-file (path)
  "Open PATH with the system default application."
  (cl-ecase system-type
    ((gnu gnu/linux) (call-process "xdg-open" nil 0 nil path))
    (darwin (call-process "open" nil 0 nil path))
    (windows-nt (w32-shell-execute "open" path))))

(defun wear-inbox-play-audio ()
  "Play the audio file referenced by :AUDIO: property at point."
  (interactive)
  (let ((audio (org-entry-get (point) "AUDIO")))
    (if audio
        (let ((path (expand-file-name audio
                                       (file-name-directory
                                        (expand-file-name wear-inbox-inbox-file)))))
          (if (file-exists-p path)
              (wear-inbox--open-file path)
            (message "Audio file not found: %s" path)))
      (message "No :AUDIO: property found at this heading"))))

(provide 'wear-inbox)
;;; wear-inbox.el ends here
