;;; my-openlibrary.el -- openlibrary client -*- lexical-binding: t -*-

;; Copyright (C) 2023 Free Software Foundation.

;; Author: Yuchen Pei <id@ypei.org>
;; Package-Requires: ((emacs "28.2"))

;; This file is part of dotfiles.

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

;; dotfiles 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 Affero General
;; Public License for more details.

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

;;; Commentary:

;; openlibrary client.

;;; Code:


;;; an openlibrary client
(require 'generic-search)
(require 'my-net)

(defvar my-openlibrary-host "https://openlibrary.org")
(defun my-openlibrary-api-book-by-olid (olid)
  (my-url-fetch-json
   (format "%s/api/books?bibkeys=OLID:%s&format=json&jscmd=data"
           my-openlibrary-host olid)))

(defun my-openlibrary-html-book-by-url (url)
  (list
   (cons 'description
         (string-trim
          (dom-texts
           (dom-by-class
            (my-url-fetch-dom url)
            "book-description-content restricted-view"))))))

(defun my-grok-openlibrary (url)
  "grok openlibrary.
title, subtitle, description, subjects, authors (by_statement?), classification, publisher, publish_places, publish_date, subjects, cover"
  (when (string-match
         (concat my-openlibrary-host "/books/\\([^/]+\\)") url)
    (my-grok-openlibrary-make-info
     (append 
      (my-openlibrary-api-book-by-olid (match-string 1 url))
      (my-openlibrary-html-book-by-url url)))))

(defun my-grok-openlibrary-make-info (info)
  (list
   (cons "Title" (alist-get 'title info))
   (cons "Subtitle" (alist-get 'subtitle info))
   (cons "Authors" (string-join
                    (mapcar (lambda (author) (alist-get 'name author))
                            (alist-get 'authors info))
                    ", "))
   (cons "Pages"
         (when (alist-get 'number_of_pages info)
           (number-to-string (alist-get 'number_of_pages info))))
   (cons "OpenLibrary-link" (alist-get 'url info))
   (cons "OpenLibrary-ID" (string-join (alist-get 'openlibrary info) ","))
   (cons "ISBN" (string-join
                 (vconcat
                  (alist-get 'isbn_13
                             (alist-get 'identifiers info))
                  (alist-get 'isbn_10
                             (alist-get 'identifiers info)))
                 ", "))
   (cons "Dewey-Decimal" (alist-get 'dewey_decimal_class info))
   (cons "Subject" (string-join
                    (seq-take
                     (remove-duplicates
                      (mapcar (lambda (subject) (alist-get 'name subject))
                              (alist-get 'subjects info))
                      :test 'string=)
                     20)
                    ", "))
   (cons "Cover" (alist-get 'large (alist-get 'cover info)))
   (cons "Published" (alist-get 'publish_date info))
   (cons "Description" (alist-get 'description info))))

(defun my-openlibrary-api-book-by-isbn (isbn)
  (my-url-fetch-json
   (format
    "%s/api/books?bibkeys=ISBN:%s&format=json&jscmd=data"
    my-openlibrary-host isbn)))

(defun my-grok-openlibrary-isbn (isbn)
  (unless isbn (error "isbn not supplied"))
  (let* ((info-json (alist-get (intern (format "ISBN:%s" isbn))
                               (my-openlibrary-api-book-by-isbn isbn)))
         (url (alist-get 'url info-json))
         (info-html (my-openlibrary-html-book-by-url url)))
    (my-grok-openlibrary-make-info
     (append info-json info-html))))

(defun my-openlibrary-api-search (query)
  (my-url-fetch-json
   (format "%s/search.json?q=%s" my-openlibrary-host query)))

(defun my-openlibrary-format-result (info)
  (format "%s - %s [%s] (%s)"
          (string-join (alist-get 'author_name info) ", ")
          (alist-get 'title info)
          (string-join (alist-get 'isbn info) ",")
          (alist-get 'publish_date info)))

(defun my-openlibrary-action (info)
  (interactive)
  (my-org-create-node
   (my-grok-openlibrary-isbn (elt (alist-get 'isbn info) 0))
   t))

(defun my-openlibrary-show-more-info ()
  (interactive)
  (pp (my-grok-openlibrary-isbn
       (elt
        (alist-get 'isbn (get-text-property (point) 'button-data))
        0))))

(defvar my-openlibrary-button-keymap
  (let ((kmap (make-sparse-keymap)))
    (set-keymap-parent kmap button-map)
    (define-key kmap "p" 'my-openlibrary-show-more-info)
    kmap))

(defun my-openlibrary-search (query)
  (interactive "sQuery: ")
  (generic-search-open
   (alist-get 'docs (my-openlibrary-api-search query))
   (format "openlibrary-query:%s" query)
   `((formatter . my-openlibrary-format-result)
     (default-action . my-openlibrary-action)
     (keymap . ,my-openlibrary-button-keymap))))

(provide 'my-openlibrary)
;;; my-openlibrary.el ends here