(ns auto-ap.ssr.pos.sales-summaries
  (:require
   [auto-ap.datomic
    :refer [apply-pagination apply-sort-3 conn merge-query pull-many
            query2]]
   [auto-ap.datomic.accounts :as d-accounts]
   [auto-ap.datomic.sales-summaries :refer [total-credits total-debits]]
   [auto-ap.graphql.utils :refer [extract-client-ids]]
   [auto-ap.query-params :refer [wrap-copy-qp-pqp]]
   [auto-ap.client-routes :as client-routes]
   [auto-ap.routes.pos.sales-summaries :as route]
   [auto-ap.ssr-routes :as ssr-routes]
   [auto-ap.ssr.common-handlers :refer [add-new-entity-handler]]
   [auto-ap.ssr.components :as com]
   [auto-ap.ssr.components.link-dropdown :refer [link-dropdown]]
   [auto-ap.ssr.components.multi-modal :as mm]
   [auto-ap.ssr.form-cursor :as fc]
   [auto-ap.ssr.grid-page-helper :as helper :refer [wrap-apply-sort]]
   [auto-ap.ssr.hx :as hx]
   [auto-ap.ssr.pos.common
    :refer [date-range-field*]]
   [auto-ap.ssr.svg :as svg]
   [auto-ap.ssr.utils
    :refer [apply-middleware-to-all-handlers clj-date-schema
            default-grid-fields-schema entity-id html-response money
            strip temp-id wrap-merge-prior-hx wrap-schema-enforce]]
   [auto-ap.time :as atime]
   [bidi.bidi :as bidi]
   [clj-time.coerce :as c]
   [clojure.string :as str]
   [datomic.api :as dc]
   [hiccup.util :as hu]
   [iol-ion.query :refer [dollars= dollars-0?]]
   [malli.core :as mc]
   [malli.util :as mut]))

(def query-schema (mc/schema
                   [:maybe
                    (into [:map {:date-range [:date-range :start-date :end-date]}

                           [:start-date {:optional true}
                            [:maybe clj-date-schema]]
                           [:end-date {:optional true}
                            [:maybe clj-date-schema]]]
                          default-grid-fields-schema)]))

(defn filters [request]
  [:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms"
          "hx-get" (bidi/path-for ssr-routes/only-routes
                                  ::route/table)
          "hx-target" "#entity-table"
          "hx-indicator" "#entity-table"}

   [:fieldset.space-y-6
    (date-range-field* request)]])

(def default-read '[:db/id
                    *
                    [:sales-summary/date :xform clj-time.coerce/from-date]
                    {:sales-summary/client [:client/code :client/name :db/id]}
                    {:sales-summary/items [{[:ledger-mapped/ledger-side :xform iol-ion.query/ident] [:db/ident]}
                                           :ledger-mapped/account
                                           :ledger-mapped/amount
                                           :sales-summary-item/category
                                           :sales-summary-item/sort-order
                                           :db/id
                                           :sales-summary-item/manual?]}
                    {:journal-entry/original-entity [:db/id]}])

(defn fetch-ids [db request]
  (let [query-params (:query-params request)
        valid-clients (extract-client-ids (:clients request)
                                          (:client request)
                                          (:client-id query-params)
                                          (when (:client-code query-params)
                                            [:client/code (:client-code query-params)]))
        query (cond-> {:query {:find []
                               :in '[$ [?client ...]]
                               :where '[[?e :sales-summary/client ?client]]}
                       :args [db valid-clients]}
                (or (:start-date query-params)
                    (:end-date query-params))
                (merge-query {:query '{:where [[?e :sales-summary/date ?d]]}})

                (:start-date query-params)
                (merge-query {:query '{:in [?start-date]
                                       :where [[(>= ?d ?start-date)]]}
                              :args [(-> query-params :start-date c/to-date)]})

                (:end-date query-params)
                (merge-query {:query '{:in [?end-date]
                                       :where [[(< ?d ?end-date)]]}
                              :args [(-> query-params :end-date c/to-date)]})

                true
                (merge-query {:query {:find ['?sort-default '?e]
                                      :where ['[?e :sales-summary/date ?sort-default]]}}))]
    (cond->> (query2 query)
      true (apply-sort-3 query-params)
      true (apply-pagination query-params))))

(defn hydrate-results [ids db _]
  (let [results (->> (pull-many db default-read ids)
                     (group-by :db/id))
        refunds (->> ids
                     (map results)
                     (map first))]
    refunds))

(defn fetch-page [request]
  (let [db (dc/db conn)
        {ids-to-retrieve :ids matching-count :count} (fetch-ids db request)]

    [(->> (hydrate-results ids-to-retrieve db request))
     matching-count]))

(defn sort-items [ss]
  (sort-by (juxt :ledger-mapped/ledger-side :sales-summary-item/sort-order :sales-summary-item/category) ss))

(defn truncate [s max-len]
  (if (> (count s) max-len)
    (str (subs s 0 (- max-len 3)) "...")
    s))

(defn account-typeahead*
  [{:keys [name value client-id]}]
  [:div.flex.flex-col
   (com/typeahead {:name name
                   :placeholder "Search..."
                   :url (hu/url (bidi/path-for ssr-routes/only-routes :account-search)
                                {:client-id client-id
                                 :purpose "invoice"})
                   :value value
                   :content-fn (fn [value]
                                 (:account/name (d-accounts/clientize (dc/pull (dc/db conn) d-accounts/default-read value)
                                                                      client-id)))})])

(defn account-display-cell [{:keys [item field-name-prefix client-id]}]
  (let [account-id (:ledger-mapped/account item)
        account-name (when account-id
                       (:account/name (d-accounts/clientize (dc/pull (dc/db conn) d-accounts/default-read account-id)
                                                            client-id)))]
    [:div.account-cell.flex.items-center.gap-2
     (com/hidden {:name (str field-name-prefix "[ledger-mapped/account]")
                  :value (or account-id "")})
     (if account-id
       [:span.text-sm account-name]
       (com/pill {:color :red} "Missing acct"))
     (com/a-icon-button {:class "p-1"
                         :hx-get (bidi/path-for ssr-routes/only-routes ::route/edit-item-account)
                         :hx-target "closest .account-cell"
                         :hx-swap "outerHTML"
                         :hx-vals (hx/json {:item-index (or (:item-index item) 0)
                                            :client-id client-id
                                            :current-account-id (or account-id "")})}
                        svg/pencil)]))

(defn account-edit-cell [{:keys [field-name-prefix client-id current-account-id]}]
  (let [account-input-name (str field-name-prefix "[ledger-mapped/account]")]
    [:div.account-cell.flex.flex-col.gap-2
     (account-typeahead* {:name account-input-name
                          :value current-account-id
                          :client-id client-id})
     [:div.flex.gap-1
      (com/a-icon-button {:class "p-1"
                          :hx-put (bidi/path-for ssr-routes/only-routes ::route/save-item-account)
                          :hx-target "closest .account-cell"
                          :hx-swap "outerHTML"
                          :hx-include "closest .account-cell"
                          :hx-vals (hx/json {:field-name-prefix field-name-prefix
                                             :client-id client-id})}
                         svg/check)
      (com/a-icon-button {:class "p-1"
                          :hx-get (bidi/path-for ssr-routes/only-routes ::route/cancel-item-account)
                          :hx-target "closest .account-cell"
                          :hx-swap "outerHTML"
                          :hx-vals (hx/json {:field-name-prefix field-name-prefix
                                             :client-id client-id
                                             :current-account-id (or current-account-id "")})}
                         svg/x)]]))

(def grid-page
  (helper/build {:id "entity-table"
                 :id-fn :db/id
                 :nav com/main-aside-nav
                 :fetch-page fetch-page
                 :page-specific-nav filters
                 :query-schema query-schema
                 :row-buttons (fn [_ entity]
                                [(com/icon-button {:hx-get (bidi/path-for ssr-routes/only-routes
                                                                          ::route/edit-wizard
                                                                          :db/id (:db/id entity))}
                                                  svg/pencil)])
                 :oob-render
                 (fn [request]
                   [(assoc-in (date-range-field* request) [1 :hx-swap-oob] true)])
                 :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes
                                                         :company)}
                                "POS"]

                               [:a {:href (bidi/path-for ssr-routes/only-routes
                                                         ::route/page)}
                                "Sales Summaries"]]
                 :title "Sales Summaries"
                 :entity-name "Daily Summary"
                 :route ::route/table
                 :headers [{:key "client"
                            :name "Client"
                            :sort-key "client"
                            :hide? (fn [args]
                                     (= (count (:clients args)) 1))
                            :render #(-> % :sales-summary/client :client/code)}

                           {:key "date"
                            :name "Date"
                            :sort-key "date"
                            :render #(some-> % :sales-summary/date (atime/unparse-local atime/normal-date))}

                           {:key "debits"
                            :name "Debits"
                            :sort-key "debits"
                            :class "w-72 align-top"
                            :render (fn [ss]
                                      (let [items (:sales-summary/items ss)
                                            debit-items (filter #(= :ledger-side/debit (:ledger-mapped/ledger-side %)) (sort-items items))
                                            credit-count (count (filter #(= :ledger-side/credit (:ledger-mapped/ledger-side %)) items))
                                            total-debits (total-debits items)]
                                        [:div.flex.flex-col.h-full
                                         [:ul.flex-grow
                                          (for [si debit-items]
                                            [:li.flex.items-baseline.gap-2.py-0.5.text-sm.text-gray-700
                                             [:span.flex-1.min-w-0.truncate.text-gray-600
                                              (:sales-summary-item/category si)]
                                             (when-not (:ledger-mapped/account si)
                                               [:span.shrink-0 (com/pill {:color :red} "?")])
                                             [:span.shrink-0.font-mono.tabular-nums.text-right.text-gray-900.whitespace-nowrap
                                              (format "$%,.2f" (:ledger-mapped/amount si))]])
                                          (for [_ (range (max 0 (- credit-count (count debit-items))))]
                                            [:li.py-0.5.text-sm " "])]
                                         [:div.border-t-2.border-gray-300.mt-1.pt-1.flex.justify-between.items-baseline
                                          [:span.text-xs.uppercase.tracking-wider.font-semibold.text-gray-500 "Total"]
                                          [:span.font-mono.tabular-nums.font-bold.text-gray-900
                                           (format "$%,.2f" total-debits)]]]))}

                           {:key "credits"
                            :name "Credits"
                            :sort-key "credits"
                            :class "w-72 align-top"
                            :render (fn [ss]
                                      (let [items (:sales-summary/items ss)
                                            credit-items (filter #(= :ledger-side/credit (:ledger-mapped/ledger-side %)) (sort-items items))
                                            debit-count (count (filter #(= :ledger-side/debit (:ledger-mapped/ledger-side %)) items))
                                            total-credits (total-credits items)]
                                        [:div.flex.flex-col.h-full
                                         [:ul.flex-grow
                                          (for [si credit-items]
                                            [:li.flex.items-baseline.gap-2.py-0.5.text-sm.text-gray-700
                                             [:span.flex-1.min-w-0.truncate.text-gray-600
                                              (:sales-summary-item/category si)]
                                             (when-not (:ledger-mapped/account si)
                                               [:span.shrink-0 (com/pill {:color :red} "?")])
                                             [:span.shrink-0.font-mono.tabular-nums.text-right.text-gray-900.whitespace-nowrap
                                              (format "$%,.2f" (:ledger-mapped/amount si))]])
                                          (for [_ (range (max 0 (- debit-count (count credit-items))))]
                                            [:li.py-0.5.text-sm " "])]
                                         [:div.border-t-2.border-gray-300.mt-1.pt-1.flex.justify-between.items-baseline
                                          [:span.text-xs.uppercase.tracking-wider.font-semibold.text-gray-500 "Total"]
                                          [:span.font-mono.tabular-nums.font-bold.text-gray-900
                                           (format "$%,.2f" total-credits)]]]))}

                           {:key "balance"
                            :name "Status"
                            :sort-key "balance"
                            :class "w-28 align-top"
                            :render (fn [ss]
                                      (let [items (:sales-summary/items ss)
                                            total-debits (total-debits items)
                                            total-credits (total-credits items)
                                            delta (- total-debits total-credits)
                                            balanced? (dollars= total-debits total-credits)
                                            missing-account? (some #(not (:ledger-mapped/account %)) items)]
                                        [:div.flex.flex-col.items-center.gap-1.pt-2
                                         (when missing-account?
                                           [:span.inline-block.text-xs.font-semibold.uppercase.tracking-wider.text-amber-800.bg-amber-100.border.border-amber-300.rounded-sm.px-1.5.py-0.5
                                            "Missing acct"])
                                         (if balanced?
                                           (when-not missing-account?
                                             [:span.inline-block.text-xs.font-semibold.uppercase.tracking-wider.text-emerald-800.bg-emerald-100.border.border-emerald-300.rounded-sm.px-1.5.py-0.5
                                              "Balanced"])
                                           [:div.flex.flex-col.items-center
                                            [:span.font-mono.tabular-nums.text-red-700.font-bold.text-sm
                                             (format "$%,.2f" (Math/abs delta))]
                                            [:span.text-xs.uppercase.tracking-wider.text-red-600.font-medium.mt-0.5
                                             (if (> total-debits total-credits) "Debit over" "Credit over")]])]))}

                           {:key "links"
                            :name "Links"
                            :show-starting "lg"
                            :class "w-8"
                            :render (fn [ss]
                                      (let [ledger-entry (:journal-entry/original-entity ss)]
                                        (when (seq ledger-entry)
                                          (link-dropdown
                                           [{:link (hu/url (bidi/path-for client-routes/routes :ledger)
                                                           {:exact-match-id (:db/id (first ledger-entry))})
                                             :color :yellow
                                             :content "Ledger entry"}]))))}]}))

(def row* (partial helper/row* grid-page))
(def table* (partial helper/table* grid-page))

(def edit-schema
  [:map
   [:db/id entity-id]
   [:sales-summary/client [:map [:db/id entity-id]]]
   [:sales-summary/items
    [:vector {:coerce? true}
     [:and
      [:map
       [:db/id [:or entity-id temp-id]]
       [:sales-summary-item/category [:string {:decode/string strip}]]
       [:sales-summary-item/manual? {:default false :decode/arbitrary (fn [x] (cond
                                                                                (boolean? x)
                                                                                x
                                                                                (nil? x)
                                                                                false
                                                                                (str/blank? x)
                                                                                false
                                                                                :else
                                                                                true))} :boolean]
       [:ledger-mapped/account entity-id]
       [:credit {:optional true} [:maybe money]]
       [:debit {:optional true} [:maybe money]]]
      [:fn {:error/message "Must choose one of credit/debit"
            :error/path [:credit]}
       (fn [x]
         (not (and (:credit x)
                   (:debit x))))]]]]])

(defn summary-total-row* [request]
  (let [total-credits (-> request
                          :multi-form-state
                          :step-params
                          :sales-summary/items
                          (total-credits))
        total-debits (-> request
                         :multi-form-state
                         :step-params
                         :sales-summary/items
                         (total-debits))]

    (com/data-grid-row {:id "total-row"
                        :class "bg-slate-50 border-t-2 border-slate-300"}
                       (com/data-grid-cell {})
                       (com/data-grid-cell {:class "text-right"}
                                           [:span.text-xs.uppercase.tracking-wider.font-semibold.text-slate-600
                                            "Total"])
                       (com/data-grid-cell {:class "text-right"}
                                           [:span.font-mono.tabular-nums.font-bold.text-slate-900
                                            (format "$%,.2f" total-debits)])
                       (com/data-grid-cell {:class "text-right"}
                                           [:span.font-mono.tabular-nums.font-bold.text-slate-900
                                            (format "$%,.2f" total-credits)])
                       (com/data-grid-cell {}))))

(defn unbalanced-row* [request]
  (let [total-credits (-> request
                          :multi-form-state
                          :step-params
                          :sales-summary/items
                          (total-credits))
        total-debits (-> request
                         :multi-form-state
                         :step-params
                         :sales-summary/items
                         (total-debits))
        unbalanced? (not (dollars= total-credits total-debits))
        debit-over? (and unbalanced? (> total-debits total-credits))
        credit-over? (and unbalanced? (> total-credits total-debits))]

    (com/data-grid-row {:id "unbalanced-row"
                        :class (when unbalanced? "bg-red-50 border-t border-red-200")}
                       (com/data-grid-cell {})
                       (com/data-grid-cell {:class "text-right"}
                                           (when unbalanced?
                                             [:span.text-xs.uppercase.tracking-wider.font-semibold.text-red-700
                                              "Out of balance"]))
                       (com/data-grid-cell {:class "text-right"}
                                           (when debit-over?
                                             [:span.font-mono.tabular-nums.font-bold.text-red-700
                                              (format "$%,.2f" (- total-debits total-credits))]))
                       (com/data-grid-cell {:class "text-right"}
                                           (when credit-over?
                                             [:span.font-mono.tabular-nums.font-bold.text-red-700
                                              (format "$%,.2f" (- total-credits total-debits))]))
                       (com/data-grid-cell {}))))

(defn summary-total-display [request]
  (let [total-credits (-> request
                          :multi-form-state
                          :step-params
                          :sales-summary/items
                          (total-credits))
        total-debits (-> request
                         :multi-form-state
                         :step-params
                         :sales-summary/items
                         (total-debits))]
    [:div.flex.justify-between.text-sm.py-1.border-t.mt-1
     {:id "total-display"}]
    [:span.font-semibold "Total"]
    [:div.flex.gap-8
     [:span.font-mono (format "$%,.2f" total-debits)]
     [:span.font-mono (format "$%,.2f" total-credits)]]))

(defn unbalanced-display [request]
  (let [total-credits (-> request
                          :multi-form-state
                          :step-params
                          :sales-summary/items
                          (total-credits))
        total-debits (-> request
                         :multi-form-state
                         :step-params
                         :sales-summary/items
                         (total-debits))
        delta (- total-debits total-credits)]
    (when-not (dollars-0? delta)
      [:div.flex.justify-between.text-sm.py-1
       {:id "unbalanced-display"}
       [:span.font-semibold.text-red-600 "Unbalanced"]
       [:div.flex.gap-8
        [:span.font-mono (when (pos? delta) (format "$%,.2f" delta))
         [:span.font-mono (when (neg? delta) (format "$%,.2f" (Math/abs delta)))]]]])))

(defn sales-summary-item-row* [{:keys [value client-id]}]
  (let [manual? (fc/field-value (:sales-summary-item/manual? value))]
    (com/data-grid-row (cond-> {:x-ref "p"
                                :x-data (hx/json {})
                                :class (when manual?
                                         "bg-indigo-50/40 border-l-2 border-indigo-300")}
                         (fc/field-value (:new? value)) (hx/htmx-transition-appear))
                       (fc/with-field :db/id
                         (com/hidden {:name (fc/field-name)
                                      :value (fc/field-value)}))
                       (when manual?
                         (fc/with-field :sales-summary-item/manual?
                           (com/hidden {:name (fc/field-name)
                                        :value true})))
                       (com/data-grid-cell {:class "align-top"}
                                           (fc/with-field :sales-summary-item/category
                                             (if manual?
                                               (com/validated-field {:errors (fc/field-errors)}
                                                                    (com/text-input {:placeholder "Category/Explanation"
                                                                                     :name (fc/field-name)
                                                                                     :value (fc/field-value)}))

                                               (list
                                                (com/hidden {:name (fc/field-name)
                                                             :value (fc/field-value)})
                                                [:span.text-sm.text-gray-700
                                                 (fc/field-value (:sales-summary-item/category value))]))))
                       (com/data-grid-cell {:class "align-top"}
                                           (fc/with-field :ledger-mapped/account
                                             (com/validated-field {:errors (fc/field-errors)}
                                                                  (account-typeahead* {:value (fc/field-value)
                                                                                       :client-id client-id
                                                                                       :name (fc/field-name)}))))
                       (com/data-grid-cell {:class "text-right align-top"}

                                           (if manual?
                                             (fc/with-field :debit
                                               (com/validated-field {:errors (fc/field-errors)}
                                                                    (com/money-input {:class "w-24 text-right font-mono tabular-nums"
                                                                                      :name (fc/field-name)
                                                                                      :value (fc/field-value)})))
                                             (when (= (fc/field-value (:ledger-mapped/ledger-side value))
                                                      :ledger-side/debit)
                                               [:span.font-mono.tabular-nums.text-gray-900.text-sm.whitespace-nowrap
                                                (format "$%,.2f" (fc/field-value (:ledger-mapped/amount value)))])))
                       (com/data-grid-cell {:class "text-right align-top"}

                                           (if manual?
                                             (fc/with-field :credit
                                               (com/validated-field {:errors (fc/field-errors)}
                                                                    (com/money-input {:class "w-24 text-right font-mono tabular-nums"
                                                                                      :name (fc/field-name)
                                                                                      :value (fc/field-value)})))
                                             (when (= (fc/field-value (:ledger-mapped/ledger-side value))
                                                      :ledger-side/credit)
                                               [:span.font-mono.tabular-nums.text-gray-900.text-sm.whitespace-nowrap
                                                (format "$%,.2f" (fc/field-value (:ledger-mapped/amount value)))])))
                       (com/data-grid-cell {:class "align-top"}
                                           (when manual?
                                             (com/a-icon-button {"@click.prevent.stop" "$refs.p.remove()"} svg/x))))))

(defrecord MainStep [linear-wizard]
  mm/ModalWizardStep
  (step-name [_]
    "Main")
  (step-key [_]
    :main)

  (edit-path [_ _]
    [])

  (step-schema [_]
    (mut/select-keys (mm/form-schema linear-wizard) #{:db/id :sales-summary/items}))

  (render-step
    [this {:keys [multi-form-state] :as request}]
    (let [client-id (:db/id (:sales-summary/client (:snapshot multi-form-state)))
          items (:sales-summary/items (:step-params multi-form-state))
          sorted-items (sort-items items)
          indexed-items (map-indexed vector sorted-items)
          debit-items (filter #(= :ledger-side/debit (:ledger-mapped/ledger-side (second %))) indexed-items)
          credit-items (filter #(= :ledger-side/credit (:ledger-mapped/ledger-side (second %))) indexed-items)
          max-rows (max (count debit-items) (count credit-items))
          padded-debits (concat debit-items (repeat (- max-rows (count debit-items)) nil))
          padded-credits (concat credit-items (repeat (- max-rows (count credit-items)) nil))]
      (mm/default-render-step
       linear-wizard this
       :head [:div.p-2 "Edit Summary"]
       :body (mm/default-step-body
              {}
              [:div
               (fc/with-field :db/id
                 (com/hidden {:name (fc/field-name)
                              :value (fc/field-value)}))
               [:div.grid.grid-cols-2.gap-6
                [:div
                 [:div.font-semibold.text-sm.mb-2 "Debits"]
                 [:div.space-y-1
                  (for [[actual-idx item] padded-debits]
                    (if item
                      (let [manual? (:sales-summary-item/manual? item)]
                        (if manual?
                          [:div.flex.items-center.gap-2.text-sm {:x-ref "p" :x-data (hx/json {})}
                           (com/hidden {:name (str "step-params[sales-summary/items][" actual-idx "][db/id]")
                                        :value (:db/id item)})
                           (com/hidden {:name (str "step-params[sales-summary/items][" actual-idx "][sales-summary-item/manual?]")
                                        :value "true"})
                           (fc/start-form-with-prefix [(str "step-params[sales-summary/items][" actual-idx "]")]
                                                      item []
                                                      (fc/with-field :sales-summary-item/category
                                                        (com/text-input {:placeholder "Category"
                                                                         :name (fc/field-name)
                                                                         :value (fc/field-value)
                                                                         :class "w-32 text-sm"})))
                           (account-typeahead* {:name (str "step-params[sales-summary/items][" actual-idx "][ledger-mapped/account]")
                                                :value (:ledger-mapped/account item)
                                                :client-id client-id})
                           (fc/start-form-with-prefix [(str "step-params[sales-summary/items][" actual-idx "]")]
                                                      item []
                                                      (fc/with-field :debit
                                                        (com/money-input {:class "w-24 text-right font-mono tabular-nums"
                                                                          :name (fc/field-name)
                                                                          :value (fc/field-value)})))
                           (com/a-icon-button {"@click.prevent.stop" "$refs.p.remove()"} svg/x)]
                          [:div.flex.items-center.gap-2.text-sm
                           (com/hidden {:name (str "step-params[sales-summary/items][" actual-idx "][db/id]")
                                        :value (:db/id item)})
                           (com/hidden {:name (str "step-params[sales-summary/items][" actual-idx "][sales-summary-item/category]")
                                        :value (:sales-summary-item/category item)})
                           [:span.text-gray-500 (truncate (:sales-summary-item/category item) 30)]
                           (account-display-cell {:item (assoc item :item-index actual-idx)
                                                  :field-name-prefix (str "step-params[sales-summary/items][" actual-idx "]")
                                                  :client-id client-id})
                           [:span.ml-auto.font-mono.tabular-nums.text-gray-900 (format "$%,.2f" (:ledger-mapped/amount item))]]))
                      [:div.h-6]))]
                 [:div.mt-2.border-t.pt-1
                  (summary-total-display request)
                  (unbalanced-display request)]]
                [:div
                 [:div.font-semibold.text-sm.mb-2 "Credits"]
                 [:div.space-y-1
                  (for [[actual-idx item] padded-credits]
                    (if item
                      (let [manual? (:sales-summary-item/manual? item)]
                        (if manual?
                          [:div.flex.items-center.gap-2.text-sm {:x-ref "p" :x-data (hx/json {})}
                           (com/hidden {:name (str "step-params[sales-summary/items][" actual-idx "][db/id]")
                                        :value (:db/id item)})
                           (com/hidden {:name (str "step-params[sales-summary/items][" actual-idx "][sales-summary-item/manual?]")
                                        :value "true"})
                           (fc/start-form-with-prefix [(str "step-params[sales-summary/items][" actual-idx "]")]
                                                      item []
                                                      (fc/with-field :sales-summary-item/category
                                                        (com/text-input {:placeholder "Category"
                                                                         :name (fc/field-name)
                                                                         :value (fc/field-value)
                                                                         :class "w-32 text-sm"})))
                           (account-typeahead* {:name (str "step-params[sales-summary/items][" actual-idx "][ledger-mapped/account]")
                                                :value (:ledger-mapped/account item)
                                                :client-id client-id})
                           (fc/start-form-with-prefix [(str "step-params[sales-summary/items][" actual-idx "]")]
                                                      item []
                                                      (fc/with-field :credit
                                                        (com/money-input {:class "w-24 text-right font-mono tabular-nums"
                                                                          :name (fc/field-name)
                                                                          :value (fc/field-value)})))
                           (com/a-icon-button {"@click.prevent.stop" "$refs.p.remove()"} svg/x)]
                          [:div.flex.items-center.gap-2.text-sm
                           (com/hidden {:name (str "step-params[sales-summary/items][" actual-idx "][db/id]")
                                        :value (:db/id item)})
                           (com/hidden {:name (str "step-params[sales-summary/items][" actual-idx "][sales-summary-item/category]")
                                        :value (:sales-summary-item/category item)})
                           [:span.text-gray-500 (truncate (:sales-summary-item/category item) 30)]
                           (account-display-cell {:item (assoc item :item-index actual-idx)
                                                  :field-name-prefix (str "step-params[sales-summary/items][" actual-idx "]")
                                                  :client-id client-id})
                           [:span.ml-auto.font-mono.tabular-nums.text-gray-900 (format "$%,.2f" (:ledger-mapped/amount item))]]))
                      [:div.h-6]))]
                 [:div.mt-2.border-t.pt-1
                  (summary-total-display request)
                  (unbalanced-display request)]]]
               [:div.mt-4.border-t.pt-2
                (fc/with-field :sales-summary/items
                  (com/data-grid-new-row {:colspan 2
                                          :hx-get (bidi/path-for ssr-routes/only-routes ::route/new-summary-item)
                                          :row-offset 0
                                          :index (count (fc/field-value))
                                          :tr-params {:hx-vals (hx/json {:client-id client-id})}}
                                         "New Summary Item"))]])

       :footer
       (mm/default-step-footer linear-wizard this :validation-route ::route/edit-wizard-navigate)
       :validation-route ::route/edit-wizard-navigate
       :width-height-class "lg:w-[900px] lg:h-[600px]"))))

(defn attach-ledger [i]
  (cond-> i
    (:credit i) (assoc :ledger-mapped/ledger-side :ledger-side/credit
                       :ledger-mapped/amount (:credit i))
    (:debit i) (assoc :ledger-mapped/ledger-side :ledger-side/debit
                      :ledger-mapped/amount (:debit i))
    true (dissoc :credit :debit)
    true (assoc :sales-summary-item/manual? true)))

(defrecord EditWizard [_ current-step]
  mm/LinearModalWizard
  (hydrate-from-request
    [this request]
    this)
  (navigate [this step-key]
    (assoc this :current-step step-key))
  (get-current-step
    [this]
    (mm/get-step this :main))
  (render-wizard [this {:keys [multi-form-state] :as request}]
    (mm/default-render-wizard
     this request
     :form-params
     (-> mm/default-form-props
         (assoc :hx-put
                (str (bidi/path-for ssr-routes/only-routes ::route/edit-wizard-submit))))
     :render-timeline? false))
  (steps [_]
    [:main])
  (get-step [this step-key]
    (let [step-key-result (mc/parse mm/step-key-schema step-key)
          [step-key-type step-key] step-key-result]
      (->MainStep this)))
  (form-schema [_]
    edit-schema)
  (submit [this {:keys [multi-form-state request-method identity] :as request}]
    (let [result (:snapshot multi-form-state)
          transaction [:upsert-sales-summary {:db/id (:db/id result)
                                              :sales-summary/items (map
                                                                    (fn [i]
                                                                      (if (:sales-summary-item/manual? i)
                                                                        (attach-ledger i)
                                                                        {:db/id (:db/id i)
                                                                         :ledger-mapped/account (:ledger-mapped/account i)}))
                                                                    (:sales-summary/items result))}]]
      @(dc/transact conn [transaction])
      (html-response
       (row* identity (dc/pull (dc/db conn) default-read (:db/id result))
             {:flash? true
              :request request})
       :headers (cond-> {"hx-trigger" "modalclose"
                         "hx-retarget" (format "#entity-table tr[data-id=\"%d\"]" (:db/id result))
                         "hx-reswap" "outerHTML"})))))

(def edit-wizard (->EditWizard nil nil))

(defn initial-edit-wizard-state [request]
  (let [entity (dc/pull (dc/db conn) default-read (:db/id (:route-params request)))
        entity (select-keys entity (mut/keys edit-schema))
        entity (update entity :sales-summary/items (comp #(map (fn [x]
                                                                 (if (= :ledger-side/debit (:ledger-mapped/ledger-side x))
                                                                   (assoc x :debit (:ledger-mapped/amount x))
                                                                   (assoc x :credit (:ledger-mapped/amount x))))
                                                               %) sort-items))]

    (mm/->MultiStepFormState entity [] entity)))

(defn edit-item-account [request]
  (let [{:keys [item-index client-id current-account-id]} (:query-params request)
        item-index (if (string? item-index) (Integer/parseInt item-index) item-index)
        field-name-prefix (str "step-params[sales-summary/items][" item-index "]")
        current-account-id (when (and current-account-id (not= current-account-id ""))
                             (if (string? current-account-id)
                               (Long/parseLong current-account-id)
                               current-account-id))
        client-id (if (string? client-id) (Long/parseLong client-id) client-id)]
    (html-response
     (account-edit-cell {:field-name-prefix field-name-prefix
                         :client-id client-id
                         :current-account-id current-account-id}))))

(defn save-item-account [request]
  (let [field-name-prefix (get-in request [:params "field-name-prefix"])
        client-id (get-in request [:params "client-id"])
        account-input-name (str field-name-prefix "[ledger-mapped/account]")
        account-id-str (get-in request [:form-params account-input-name])
        account-id (when (and account-id-str (not= account-id-str ""))
                     (Long/parseLong account-id-str))
        item {:ledger-mapped/account account-id
              :item-index (second (re-find #"\[(\d+)\]" (or field-name-prefix "")))}
        client-id (if (string? client-id) (Long/parseLong client-id) client-id)]
    (html-response
     (account-display-cell {:item item
                            :field-name-prefix field-name-prefix
                            :client-id client-id}))))

(defn cancel-item-account [request]
  (let [{:keys [field-name-prefix client-id current-account-id]} (:query-params request)
        account-id (when (and current-account-id (not= current-account-id ""))
                     (if (string? current-account-id)
                       (Long/parseLong current-account-id)
                       current-account-id))
        item {:ledger-mapped/account account-id
              :item-index (second (re-find #"\[(\d+)\]" (or field-name-prefix "")))}
        client-id (if (string? client-id) (Long/parseLong client-id) client-id)]
    (html-response
     (account-display-cell {:item item
                            :field-name-prefix field-name-prefix
                            :client-id client-id}))))

(def key->handler
  (apply-middleware-to-all-handlers
   (->>
    {::route/page (helper/page-route grid-page)
     ::route/table (helper/table-route grid-page)
     ::route/edit-wizard (-> mm/open-wizard-handler
                             (mm/wrap-wizard edit-wizard)
                             (mm/wrap-init-multi-form-state initial-edit-wizard-state)
                             (wrap-schema-enforce :route-schema [:map [:db/id entity-id]]))
     ::route/edit-wizard-navigate (-> mm/next-handler
                                      (mm/wrap-wizard edit-wizard)
                                      (mm/wrap-decode-multi-form-state))
     ::route/new-summary-item (-> (add-new-entity-handler [:step-params :sales-summary/items]
                                                          (fn render [cursor request]
                                                            (sales-summary-item-row*
                                                             {:value cursor
                                                              :client-id (:client-id (:query-params request))}))
                                                          (fn build-new-row [base _]
                                                            (assoc base :sales-summary-item/manual? true)))
                                  (wrap-schema-enforce :query-schema [:map
                                                                      [:client-id {:optional true}
                                                                       [:maybe entity-id]]]))
     ::route/edit-item-account (-> edit-item-account
                                   (wrap-schema-enforce :query-schema [:map
                                                                       [:item-index nat-int?]
                                                                       [:client-id {:optional true} [:maybe entity-id]]
                                                                       [:current-account-id {:optional true} [:maybe :string]]]))
     ::route/save-item-account save-item-account
     ::route/cancel-item-account cancel-item-account
     ::route/edit-wizard-submit (-> mm/submit-handler
                                    (mm/wrap-wizard edit-wizard)
                                    (mm/wrap-decode-multi-form-state))})
   (fn [h]
     (-> h
         (wrap-copy-qp-pqp)
         (wrap-apply-sort grid-page)
         (wrap-merge-prior-hx)
         (wrap-schema-enforce :query-schema query-schema)
         (wrap-schema-enforce :hx-schema query-schema)))))
