#|

src/web/inventory.lisp

Crystal Commerce-style Inventory Management User Interface and helpers.

TODO pass over the results of (make-search-query) such that
:total-qty is the aggregate of:

(select 'ygo-cc-item (where (:= :item_id xxx)))

(reduce #'+
 (mapcar (alexandria:compose #'read-from-string #'cc-sell-price-of)
         (retrieve-dao 'ygo-cc-item :item-id 48883)))

(reduce #'+ (mapcar #'cc-qty-of (retrieve-dao 'ygo-cc-item :item-id 48883)))


|#

(in-package #:cl-deck-builder2.web)

(defun render-results (&key (active "/inventory") (class 'cc-item) (params nil) (tpl #P"inventory/search-results.html"))
  "Mega helper function. Render function for anything that currently queries the databse. We search both PRODUCT-NAME and NAME, as well as querying a list of VARIANTs, with a LIMIT on the number of results and an OFFSET into those search results. You may also specify a DIRECTION, and SORT-BY options.

TODO Major rewrite candidate right here.
TODO Integrate new SEARCH-SESSION object after that's done."
  (let ((direction (or (query-param "direction" params) "desc"))
        (variant (or (query-param "variant" params)
                     (find-dao 'variant-condition :name "Near Mint")))
        (variants (select-variant-condition))
        (limit (or (query-param "limit" params) "10"))
        (name (or
               (query-param "name" params)
               (query-param "product-name" params)))
        (offset (or (query-param "offset" params) "0"))
        (sort-by (or (query-param "sort-by" params) "id")))
    (handler-case
        (ratify:with-parsed-forms
            ((:integer limit)
             (:integer offset)
             (:string direction)
             (:string sort-by))
          ;; NAME can be blank, so...
          (let* ((filtered-cards (make-search-query class params))
                 (length (make-count-query class params))
                 (pages (generate-pages length offset limit)))
            (render-with-env tpl
                             `(:active ,active
                               :cards ,filtered-cards
                               :direction ,direction
                               :last-page ,(car (last pages))
                               :length ,length
                               :limit ,limit
                               :name ,name
                               :offset ,offset
                               :opposite-direction ,(get-opposite-direction direction)
                               :pages ,pages
                               :search-params ,+search-params+
                               :sort-by ,sort-by
                               :total ,(count-dao class)
                               :variant ,variant
                               :variants ,variants))))
      (ratify:combined-error (e)
        (flash-error (format nil "~a/search.error => ~a~%" active e))))))

;; USE REINITIALIZE-INSTANCE to update the existing YGO-SET-ITEM from _PARSED
(defun patch-card (card _parsed)
  (let ((set-id (query-param "set-id" card))
        (variant-id (query-param "variant-id" card)))
    (handler-case
        (ratify-parsing:with-parsed-forms
            ((:integer set-id)
             (:integer variant-id))
          (let ((found (find-or-create-instance 'ygo-set-item :item-id set-id :variant-id variant-id))
                (clean (filter-alist _parsed)))
            (v:info :inventory "PATCH-CARD: ~a" found)
            (if found
                ;; TODO redraw item based on context? Like patching from the inventory list vs patching from the editor?
                ;; (redirect (format nil "/inventory/~d/edit" (mito:object-id found)) 303)
                (progn
                  (apply #'reinitialize-instance found (assoc-utils:alist-plist clean))
                  (update-dao found)
                  (_ "Save"))
                (progn
                  (flash-error (format nil "Error udpating id ~d:~d" set-id variant-id))))))
      (ratify:combined-error (e)
        (flash-error e)))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Inventory Viewer
(defroute ("/inventory" :method :GET) (&key _parsed)
  "Inventory Viewer GET route. Will display index with search results."
  (v:info :inventory "GET /inventory ~a" _parsed)

  ;; (with-logged-in-user
  ;;   (render-results :params _parsed :tpl #P"inventory/index.html"))
  (redirect "/cards"))

#|
(defroute ("/inventory" :method :POST) (&key _parsed)
  "Inventory Viewer search POST route. Will display index with search results."
  (v:info :inventory "POST /inventory ~a" _parsed)

  (with-logged-in-user
    (render-results :params _parsed :tpl #P"inventory/index.html")))

(defroute ("/inventory/search" :method :POST) (&key _parsed)
  "Inventory Viewer search POST route. Will display index with search results."
  (v:info :inventory "POST /inventory/search ~a" _parsed)

  (with-logged-in-user
    (render-results :params _parsed)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Inventory Item Editor
;;
;; TODO PUT?? POST?? GET??

;; New Item
(defroute ("/inventory/new" :method :GET) ()
  "Display the page for creation of a new inventory item"
  (v:info :inventory "GET /inventory/new")

  (with-logged-in-user
    (render-with-env #P"inventory/new.html"
                     `(:active "/inventory"
                       :card ,(make-instance 'cc-item)))))

(defroute ("/inventory/new" :method :POST) (&key _parsed)
  "POST method for the processing the information from page for creation of a new inventory item."
  (v:info :inventory "POST /inventory/new => ~a" _parsed)

  (with-logged-in-user
    (let ((clean (filter-alist _parsed)))
      (let ((new (apply #'cc-create (assoc-utils:alist-plist clean))))
        (when (mito:object-id new)
          (redirect (format nil "/inventory/~d/edit" (mito:object-id new))))))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Inventory Item Importer
(defroute ("/inventory/import" :method :GET) ()
  "Inventory Item Importer. TODO"
  (v:info :inventory "GET /inventory/import")

  (with-logged-in-user
    (render-with-env #P"inventory/import.html"
                     `(:active "/inventory"))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Seems like this one needs to be last. It has something to do with
;; the wildcard :ID parameter. Maybe we ought to add the /edit back?

(defroute ("/inventory/:id/edit" :method :GET) (&key id)
  "Edit Route for Inventory Item.

ARGUMENTS
ID The ID of the CC-ITEM inventory item you wish to edit."
  (v:info :inventory "GET /inventory/~d/edit" id)

  (with-logged-in-user
    (handler-case
        (ratify-parsing:with-parsed-forms
            ((:integer id))
          (render-with-env #P"inventory/edit.html"
                           `(:active "/inventory"
                             :errors ,(flash-gethash :errors)
                             :messages ,(flash-gethash :messages)
                             :card ,(cc-select-by-id id))))
      (ratify:combined-error (e)
        (flash-error e)))))

(defroute ("/inventory/:id/edit" :method :POST) (&key id _parsed)
  "CC-ITEM Update POST route. If the item exists, update it using REINITIALIZE-INSTANCE, after filtering _PARSED through FILTER-ALIST.

TODO Differentiate between CC-ITEM and YGO-CC-ITEM?"
  (v:info :inventory "POST /inventory/~d/edit => ~a" id _parsed)

  (with-logged-in-user
    (handler-case
        (ratify-parsing:with-parsed-forms
            ((:integer id))
          (with-connection (db)
            (let ((found (mito:find-dao 'cc-item :id id))
                  (clean (filter-alist _parsed)))
              (when found
                (apply #'reinitialize-instance found (assoc-utils:alist-plist clean))
                (mito:save-dao found)
                (redirect (format nil "/inventory/~d/edit" (mito:object-id found)) 303)))))
      (ratify:combined-error (e)
(flash-error e)))))

(defroute ("/inventory/:id/delete" :method :DELETE) (&key id)
  "DELETE an Inventory Item specified by ID."
  (v:info :inventory "DELETE /inventory/~d/delete" id)

  (with-logged-in-user
    (handler-case
        (ratify-parsing:with-parsed-forms
            ((:integer id))
          (cc-delete-by-id id))
      (ratify:combined-error (e)
        (flash-error e)))))
|#

(defroute ("/inventory/patch" :method :PATCH) (&key _parsed)
  "YGO-CC-ITEM PATCH route. This appears to be functionally identical to EDIT route for ID, but this is using YGO-CC-ITEM as class. The POST route appears to be used for CC-ITEM object."
  (v:info :inventory "PATCH /inventory/patch => ~a" _parsed)

  (with-logged-in-user
    (alexandria:if-let ((cards (query-param "cards" _parsed)))
      (dolist (card cards)
        (patch-card card _parsed))
      (patch-card _parsed _parsed))))

;; Huh? It works??
(defroute ("/inventory/variants/:id" :method :GET) (&key id)
  "Display the VARIANTs for this inventory item using SELECT-YGO-CC-ITEM-VARIANTS."
  (v:info :inventory "GET /inventory/variants/~d" id)

  (with-logged-in-user
    (handler-case
        (ratify-parsing:with-parsed-forms
            ((:integer id))
          (render-with-env #P"inventory/variant-results.html"
                           (list :active "/inventory"
                                 :id id
                                 :cards (select-ygo-cc-item-variants id))))
      ;; :cards ,filtered-cards
      ;; :variant ,variant
      ;; :variants ,variants
      ;; :direction ,direction
      ;; :length ,length
      ;; :total ,(count-dao class)
      ;; :limit ,limit
      ;; :name ,name
      ;; :offset ,offset
      ;; :opposite-direction ,(get-opposite-direction direction)
      ;; :pages ,(generate-pages length offset limit)
      ;; :search-params ,+search-params+
      ;; :sort-by ,sort-by)
      
      (ratify:combined-error (e)
        (flash-error e)))))
