#|

src/web/construct-decks.lisp

Web Routes For Construct Decks

TODO Pick one: /construct/ or /construct/ ?

Features Requsted:

- DONE Card Names
- DONE Card Prices - I think we'll need to wire this up to the
  YGO-CC-ITEM. That requires rewriting that component to use the new
  YGO-SET instead of the CSV.
- DONE Sort by Price - Honestly having trouble sorting this junk lmao!
- Show only YGO-SET-ITEMS with inventory - see constructed-decks.lisp<models>


|#

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

(defparameter *current-constructed-deck* nil)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun render-construct-deck (id tpl)
  (with-logged-in-user
    (let ((deck (first
                 (select-constructed-decks
                   (sxql:where (:= :id id))))))
      (when deck
        (render-with-env tpl
                         (list :active "/construct"
                               :deck deck
                               :id (mito:object-id deck)
                               :name (cl-deck-builder2.models.constructed-decks::deck-name-of deck)
                               :saved-deck-list (select-constructed-decks)))))))

;; I think this takes a CAR and a CDR pair of YGO-SET ID and VARIANT ID
;; I'm pretty sure it's supposed to be finding YGO-SET-ITEMs though
(defun selected-sets-to-deck-listing (cards)
  (let ((lst '()))
    (with-connection (db)
      (with-transaction
        (dolist (card cards (reverse lst))
          (destructuring-bind (set variant)
              card
            (let* ((set-id (cdr set))
                   (variant-id (cdr variant))
                   (set-item (first (ygo-select-set-item set-id variant-id)))
                   (set (first (ygo-set (sxql:where (:= :id set-id)))))
                   (variant (mito:find-dao 'variant-condition :id variant-id)))
              (with-slots ((passcode-id cl-deck-builder2.models.ygoprodeck.classes::passcode-id))
                  set
                (push (list :deck-set set
                            :condition variant
                            :set-item set-item
                            :card (ygo-card-by-passcode passcode-id))
                      lst)))))))))

;; XXX Where does this go? What does this accomplish?
(defun select-deck-as-plist (id)
  (let ((lst '()))
    (dolist (itm (retrieve-dao 'deck-item :deck-id id) (reverse lst))
      (push (list :deck-item itm) lst))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun default-constructed-deck-is-valid-to-pull ()
  (valid-pull-p
   (deck-to-pull-set-items-qty-as-alist *current-constructed-deck*)
   (deck-to-pull-desired-qty-as-alist *current-constructed-deck*)))

(defun default-constructed-deck-invalid-qtys ()
  (find-any-invalid-qtys
   (deck-to-pull-set-items-qty-as-alist *current-constructed-deck*)
   (deck-to-pull-desired-qty-as-alist *current-constructed-deck*)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun store-combined-deck-as-current (original-deck selected-listing)
  (setf *current-constructed-deck*
        (loop for original in (reverse original-deck)
              for selected in selected-listing
              collect (apply #'make-instance 'constructed-deck-intermediate (append original selected)))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; We have to actually pull the inventory.
;; This is the old code but it's written for YDK-DECK object so maybe I'll write one for CONSTRUCTED-DECK-INTERMEDIATE.
(defun pull-from-inventory (cdi-items)
  "Attempt to find inventory stock for every card in this deck, and if we are able to secure it, construct and insert a new PULLED-DECK into the database."
  (let ((counts (deck-to-pull-desired-qty-as-alist cdi-items)))
    (dolist (row counts)
      (decf (qty-of (car row)) (cdr row))
      ;; Update each one and save - wrapping this in a transaction causes issues with pulling multiple items.
      (save-dao (car row)))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defroute ("/construct" :method :GET) ()
  "Constructed Deck View Main Route. Login Required."
  (v:info :construct "GET /construct")

  (with-logged-in-user
    (render-with-env #P"construct/index.html"
                     (list :active "/construct"
                           :saved-deck-list (select-constructed-decks)))))

(defroute ("/construct/:id/view" :method :GET) (&key id)
  "Constructed Deck View deck by ID Route. Login Required."
  (v:info :construct "GET /construct/~d" id)

  (with-logged-in-user
    (render-construct-deck id #P"construct/index.html")))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defroute ("/construct/ygo-set-item" :method :GET) (&key _parsed)
  "Find the YGO-SET-ITEM associated with the |SET-ID| and |VARIANT-ID|

TODO This needs a rewrite as it currently actually accepts multiple
CARDs and operates on their component |SET-ID| and |VARIANT-ID| which
is really not optimal. Ideally we should be handling this all in bulk
with better routes."
  (v:info :cards "GET /cards/ygo-set-items ~a" _parsed)

  (with-logged-in-user
    (alexandria:if-let ((cards (query-param "cards" _parsed)))
      (dolist (card cards)
        (let ((|set-id| (query-param "set-id" card))
              (|variant-id| (query-param "variant-id" card)))
          (handler-case
              (ratify:with-parsed-forms
                  ((:integer |set-id|)
                   (:integer |variant-id|))
                (return
                  (render-with-env #P"cards/variant-results.html"
                                   (list :id (gensym)
                                         :item (find |variant-id|
                                                     (select-ygo-cc-item-variants |set-id|)
                                                     :key (alexandria:compose #'mito:object-id #'variant-of))))))
            (ratify:combined-error (e)
              (flash-error (format nil "/cards/ygo-set-items => ~d:~d ~a~%" |set-id| |variant-id| e)))))))))


(defroute ("/construct/:id/select-sets" :method :GET) (&key id)
  "Select the YDK-SET for the corresponding PASSCODEs. Use Deck ID from the deck builder."
  (v:info :construct "GET /construct/~d/select-sets" id)

  (with-logged-in-user
    (with-connection (db)
      (let* ((cards (mito:retrieve-dao 'deck-item :deck-id id))
             (sets (mapcar (alexandria:compose #'ygo-card-sets #'deck-passcode-of) cards))
             (variants (select-variant-condition)))
        (render-with-env #P"construct/select-sets.html"
                         (list :sets (reverse sets)
                               :deck-id id
                               :variants variants))))))

(defroute ("/construct/:id/select-sets" :method :POST) (&key id _parsed)
  "Constructed Decks Select Sets POST Route - Attempt to SUBTRACT-DESIRED-FROM-SET-ITEM-QTY-AS-ALIST, and if VALID-PULL-P, call PULL-FROM-INVENTORY. Otherwise, FLASH-ERROR to the user about Insufficient Inventory."
  (v:info :construct "POST /construct/~d/select-sets ~a" id _parsed)

  (with-logged-in-user
    (alexandria:if-let ((cards (query-param "cards" _parsed)))
      (let ((original-deck (select-deck-as-plist id))
            (selected-listing (selected-sets-to-deck-listing cards)))

        ;; Store the current constructed deck template
        (store-combined-deck-as-current original-deck selected-listing)

        ;; See if there are any issues with the current inventory
        (if (default-constructed-deck-is-valid-to-pull)
            (progn
              ;; Decrement the stock from the YGO-SET-ITEM
              (pull-from-inventory *current-constructed-deck*)
              ;; Create a new CONSTRUCTED-DECK  based on *CURRENT-CONSTRUCTED-DECK*
              (cdi-pull-from-inventory *current-constructed-deck*)
              (flash-message (_ "Deck constructed!"))
              (render-with-env #P"construct/constructed-deck-list.html"
                               (list :original-deck original-deck
                                     :deck-id id
                                     :total-price nil
                                     :table *current-constructed-deck*)))
            (let ((err (default-constructed-deck-invalid-qtys)))
              (with-connection (db)
                (flash-error (format nil (_ "Insufficient Inventory: <a href=\"/cards/by-passcode/~a\" target=\"_blank\">~a</a>")
                                     ;; TODO all this nesting again...
                                     (ygo-passcode-of err)
                                     (ygo-passcode-of err))))
              (redirect (format nil "/construct/~d/select-sets" id))))))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defroute ("/construct/:id/deconstruct" :method :POST) (&key id _parsed)
  "Deconstruct deck. That is, increment the QTY of the the contents to of the CONSTRUCTED-DECK-AS-CDI-LIST."
  (v:info :construct "POST /construct/~d/deconstruct ~a" id _parsed)

  (with-logged-in-user
    (return-to-inventory
     (constructed-deck-as-cdi-list id)))

  (_ "Ok!"))

(defroute ("/construct/:id/selected-sets" :method :GET) (&key id _parsed)
  "Query CONSTRUCTED-DECK ID for the YGO-SET-ITEMS it contains. We use CONSTRUCTED-DECK-AS-CDI-LIST again."
  (v:info :construct "GET /construct/~d/selected-sets ~a" id _parsed)

  (with-logged-in-user
    (let ((table (constructed-deck-as-cdi-list id)))
      (render-with-env #P"construct/constructed-deck-list.html"
                       (list :table table)))))

(defroute ("/construct/:id/update" :method :POST) (&key id |name| |sell-price| |sold| _parsed)
  "Update a  CONSTRUCTED-DECK by ID"
  (v:info :construct "POST /construct/~d/update ~a" id _parsed)

  (with-logged-in-user
    (let ((found (find-dao 'constructed-deck :id id)))
      (when found
        (when |name| (setf (deck-name-of found) |name|))
        (when |sell-price| (setf (deck-sell-price-of found) |sell-price|))
        (when (string= |sold| "on") (setf (deck-sold found) 1))
        (update-dao found)
        (flash-message (_ "Deck Updated"))
        (redirect (format nil "/construct/~d/view" id))))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; This one looks like: Get all the cards in a deck id 13 and query all the sets using YGO-CARD-SETS

;; (let* ((cards (retrieve-dao 'deck-item :deck-id 13))
;;        (all-sets (mapcar #'deck-passcode-of cards)))
;;   (mapcar #'cl-deck-builder2.models.ygoprodeck.methods::ygo-card-sets
;;           (reverse all-sets)))

;; (with-connection (db)
;;   (with-transaction
;;     (let ((v (mito:find-dao 'cl-deck-builder2.models.ygoprodeck.fields::variant-condition :name "Near Mint")))
;;       (mapcar (lambda (set)
;;                 (mito:find-dao 'cl-deck-builder2.models.ygoprodeck.classes::ygo-set-item
;;                                :item set
;;                                :variant v))
;;               *))))
