(ns auto-ap.ssr.grid-page-helper 
    (:require
     [auto-ap.graphql.utils :refer [extract-client-ids]]
     [auto-ap.logging :as alog]
     [auto-ap.query-params :as query-params]
     [auto-ap.routes.utils
      :refer [wrap-client-redirect-unauthenticated wrap-secure]]
     [auto-ap.ssr-routes :as ssr-routes]
     [auto-ap.ssr.components :as com]
     [auto-ap.ssr.hiccup-helper :as hh]
     [auto-ap.ssr.hx :as hx]
     [auto-ap.ssr.svg :as svg]
     [auto-ap.ssr.ui :refer [base-page]]
     [auto-ap.ssr.utils :refer [html-response main-transformer]]
     [auto-ap.time :as atime]
     [bidi.bidi :as bidi]
     [cemerick.url :as url]
     [clojure.string :as str]
     [hiccup.util :as hu]
     [malli.core :as m]
     [malli.transform :as mt2]
     [taoensso.encore :refer [filter-vals]]
     [clojure.java.io :as io]
     [clojure.data.csv :as csv]))



(defn row* [{:keys [check-box-warning? check-boxes?] :as gridspec} user entity {:keys [flash? delete-after-settle? request class] :as options}]
  (let [cells (if check-boxes?
                [(com/data-grid-cell {:class "relative"} 
                                     (let [cb (com/checkbox {:name "id" :value ((:id-fn gridspec) entity)
                                                             :x-model "selected"})]
                                       (if (and check-box-warning? (check-box-warning? entity)) 
                                         (do 
                                           [:div.bg-yellow-100.absolute.inset-0.flex.items-center.px-4.py-2 
                                            [:div {:class "absolute inset-0 bg-yellow-50 z-0", 

                                                   :style "background-image: linear-gradient(135deg, rgba(0, 0, 0, 0.1) 12.5%, transparent 12.5%, transparent 50%, rgba(0, 0, 0, 0.1) 50%, rgba(0, 0, 0, 0.1) 62.5%, transparent 62.5%, transparent);\n    background-size: 10px 10px;"}]

                                            [:div {:class "z-10"} cb]])
                                         cb)))]
                [])
        cells (->> gridspec
                   :headers
                   (filter (fn [h]
                             ((:render-for h #{:html :csv}) :html)))
                   (filter (fn [h]
                             (if (and (:hide? h)
                                      ((:hide? h) request))
                               nil
                               h)))
                   (mapv (fn [header]
                           (com/data-grid-cell {:class (if-let [show-starting (:show-starting header)]
                                                         (format "hidden %s:table-cell" show-starting)
                                                         (:class header))}
                                               ((:render header) entity))))
                   (into cells))
        cells (conj cells (com/data-grid-right-stack-cell {}
                                                          (into [:form.flex.space-x-2
                                                                 [:input {:type :hidden :name "id" :value ((:id-fn gridspec) entity)}]]
                                                                ((:row-buttons gridspec) request entity))))] ;; TODO double check usage of row buttons user and identity in callers
    (apply com/data-grid-row
           (cond-> {:class (cond-> (or class "")
                             flash? (hh/add-class "live-added group"))
                    :data-id ((:id-fn gridspec) entity)}
             delete-after-settle? 
             (assoc "@htmx:after-settle.camel" "setTimeout(() => $el.remove(), 400)"))
           cells)))

(defn sort-icon [sort key]
  (->> sort
       (filter (comp #(= key %) :sort-key))
       first
       :sort-icon))

(defn sort-by-list [grid-spec sort]
  (if (seq sort)
    (into
     [:div.flex.gap-2.items-center

      "sorted by"]
     (for [{:keys [name sort-icon sort-key]} sort]
       [:div.py-1.px-3.text-sm.rounded.bg-gray-100.dark:bg-gray-600.flex.items-center.gap-2.relative name [:div.h-4.w-4.mr-3 sort-icon]
        [:div {:class "absolute inline-flex items-center justify-center w-6 h-6 text-xs font-bold text-white hover:scale-110 transition-all duration-300 bg-gray-400 border-2 border-white rounded-full -top-2 -right-2 dark:border-gray-900"}
         [:a {:hx-get (str (bidi/path-for ssr-routes/only-routes
                                          (:route grid-spec)) "?remove-sort=" sort-key)
              :href "#"
              :hx-target  (str "#" (:id grid-spec))}
          [:div.h-4.w-4 svg/x]]]]))
    "default sort"))

(defn create-break-table-fn [break-table grid-spec ]
  (let [last (atom nil)]
    (fn [request entity]
      (let [break-table-value (break-table request entity)]
        (when (not= break-table-value @last)
          (reset! last break-table-value)
          (com/data-grid-row {}
                             (com/data-grid-cell {:colspan (cond-> (inc (count (:headers grid-spec)))
                                                             (:check-boxes? grid-spec) inc)}
                                                 [:span.font-bold.text-gray-600.text-lg break-table-value])))))))

(defn sort->query [s]
  (str/join "," (map (fn [k] (format "%s:%s" (:sort-key k) (if (= true (:asc k))
                                                             "asc"
                                                             "desc")))
                     s)))


(defn table* [grid-spec user {{:keys [start per-page flash-id sort]} :parsed-query-params :as request}]
  (alog/info ::TABLE-QP
             :qp (:query-params request)
             :pqp (:parsed-query-params request))
  (let [sort (or  (and (not (string? (:sort (:parsed-query-params request)))) (:sort (:parsed-query-params request)))
                  (:sort (:query-params request)))
        start            (or start 0)
        per-page         (or per-page 25)
        [entities total :as page-results] ((:fetch-page grid-spec)
                                           request)
        request (assoc request :page-results page-results)]

    (com/data-grid-card {:id             (:id grid-spec)
                         :raw? (:raw? grid-spec)
                         :title          [:div.flex.gap-2 (if (string? (:title grid-spec))
                                                            (:title grid-spec)
                                                            ((:title grid-spec) request)) ]
                         :route          (:route grid-spec)
                         :root-params  {:x-data (hx/json {:sort (sort->query sort)})
                                        "x-hx-val:sort" "sort"}
                         :start          start
                         :per-page       per-page
                         :total          total
                         :subtitle       [:div.flex.items-center.gap-2
                                          [:span (format "Total %s: %d, " (:entity-name grid-spec) total)]
                                          (sort-by-list grid-spec sort)]
                         :action-buttons (cond->> ((:action-buttons grid-spec) request)
                                           (:check-boxes? grid-spec) (into [(com/pill {:color :primary
                                                                                       :x-show "selected.length > 0"}
                                                                                      [:div.flex.space-x-2.items-center
                                                                                       [:div

                                                                                        [:span {:x-text "selected.length" :x-show "!all_selected"}]
                                                                                        [:span {:x-show "all_selected"} "All"]
                                                                                        " selected"]
                                                                                       [:div.w-3.h-3
                                                                                        (com/link {"@click" "selected=[]; all_selected=false"}
                                                                                                  svg/x)]])])
                                           (:csv-route grid-spec) (cons (com/a-button {:href (hu/url (bidi/path-for ssr-routes/only-routes (:csv-route grid-spec))
                                                                                                     (dissoc (update (filter-vals #(not (nil? %))
                                                                                                                                  (m/encode (:query-schema grid-spec)
                                                                                                                                            (:query-params request)
                                                                                                                                            main-transformer))
                                                                                                                     "sort" sort->query)
                                                                                                             "selected" "all-selected"))
                                                                                       :color :secondary-light}
                                                                                      [:div.w-4.h-4 svg/download])))
                         :rows           
                         (let [break-table-fn (some-> grid-spec :break-table ( create-break-table-fn grid-spec))]
                           (for [entity entities
                                 row (if-let [break-table-row (when break-table-fn (break-table-fn request entity))]
                                       
                                       [break-table-row (row* grid-spec user entity {:flash? (= flash-id ((:id-fn grid-spec) entity)) :request request})]
                                       [(row* grid-spec user entity {:flash? (= flash-id ((:id-fn grid-spec) entity)) :request request})])]
                             row))
                         :thead-params   {:hx-get     (bidi/path-for ssr-routes/only-routes
                                                                     (:route grid-spec))
                                          :hx-target  (str "#" (:id grid-spec))
                                          :hx-indicator (str "#" (:id grid-spec))
                                          :hx-trigger "sorted once"
                                          :hx-vals    "js:{\"toggle-sort\": event.detail.key || \"\"}"}
                         :headers
                         (conj
                          (->> grid-spec
                               :headers
                               (filter (fn [h]
                                         ((:render-for h #{:html :csv}) :html)))
                               (map
                                (fn [h]
                                  (cond
                                    (and (:hide? h)
                                         ((:hide? h) request))
                                    nil

                                    (:sort-key h)
                                    (com/data-grid-sort-header {:class    (if-let [show-starting (:show-starting h)]
                                                                            (format "hidden %s:table-cell" show-starting)
                                                                            (:class h))
                                                                :sort-key (:sort-key h)}

                                                               [:div.flex.gap-4.items-center
                                                                (:name h)
                                                                [:div.h-6.w-6.text-gray-400.dark:text-gray-500 (sort-icon sort (:sort-key h))]])

                                    :else
                                    (com/data-grid-header {:class    (if-let [show-starting (:show-starting h)]
                                                                       (format "hidden %s:table-cell" show-starting)
                                                                       (:class h))
                                                           :sort-key (:sort-key h)}
                                                          (:name h)))))
                               (filter identity)
                               (into (if (:check-boxes? grid-spec)
                                       [(com/data-grid-checkbox-header {:name "all" :value "all" :x-model "all_selected"})]
                                       [])))
                          (com/data-grid-header {}))})))




(defn wrap-trim-client-ids [handler]
  (fn trim-client-ids [request]
    (let [valid-clients (extract-client-ids (:clients request)
                                            (:client request)
                                            (:client-id (:parsed-query-params request))
                                            (when (:client-code (:parsed-query-params request))
                                              [:client/code (:client-code (:parsed-query-params request))]))
          valid-clients (->> valid-clients
                             (take 20)
                             set)]
      (handler (assoc request :trimmed-clients valid-clients)))))


(defn table-route [grid-spec & {:keys [push-url?] :or { push-url? true}}]
  (cond-> (fn table [{:keys [identity] :as request}]

            (html-response (table*
                            grid-spec
                            identity
                            request)
                           :headers (when push-url?
                                      {"hx-push-url" (str "?" (url/map->query
                                                               (dissoc (if (:query-schema grid-spec)
                                                                         (update (filter-vals #(not (nil? %))
                                                                                              (m/encode (:query-schema grid-spec)
                                                                                                        (:query-params request)
                                                                                                        main-transformer))
                                                                                 "sort" sort->query)
                                                                         {})
                                                                       "selected" "all-selected")))}) ;; TODO seems hacky to special case selected and all-selected here
                           :oob (when-let [oob-render (:oob-render grid-spec)]
                                  (oob-render request))))
    true (wrap-trim-client-ids)
    true (wrap-secure)
    true (wrap-client-redirect-unauthenticated)))

;; TODO oob-render is wonky, requires thinking about accidental side effects. Really only used in a small handful of cases. probably best to just
;; use alpine for those cases, or make it so that date filters issue a change event when they render. for example, when you click on "month",
;; make it so that it rerenders the date range component, along with a hx-trigger change header
(defn csv-route [{:keys [fetch-page headers page->csv-entities]} & {:keys []}]
  (cond-> (fn csv-route [{:keys [identity] :as request}]
  
            (let [page-results (fetch-page (assoc-in request [:query-params :per-page] Long/MAX_VALUE))
                  csv-entities ((or page->csv-entities (fn [[entities]] entities)) page-results)
                  csv-content (with-open [i (java.io.StringWriter.)]
                                (csv/write-csv i
                                               (into [(for [h headers
                                                            :when ((:render-for h #{:html :csv}) :csv)]
                                                        (:name h))]
                                                     (for [e csv-entities ]
                                                       (for [h headers
                                                             :when ((:render-for h #{:html :csv})
                                                                    :csv)]
                                                         ((or (:render-csv h) (comp str (:render h))) e)))))
                                (.toString i))]
  
              {:headers {"Content-Type" "text/csv"}
               :body csv-content}))
    true (wrap-trim-client-ids)
    true (wrap-secure)
    true (wrap-client-redirect-unauthenticated)))

(defn page-route [grid-spec & {:keys []}]
  (cond-> (fn page [{:keys [identity] :as request}]
            (alog/info ::page-route
                       :pqp (:parsed-query-params request)
                       :qp (:query-params request))
            (base-page
             request
             (com/page {:nav              (:nav grid-spec)
                        :page-specific    (when-let [page-specific-nav (:page-specific-nav grid-spec)]
                                            [:div#page-specific-nav (page-specific-nav request)])
                        :client-selection (:client-selection request)
                        :clients          (:clients request)
                        :client           (:client request)
                        :identity         (:identity request)
                        :request request}
                       (apply com/breadcrumbs {} (:breadcrumbs grid-spec))
                       (when (:above-grid grid-spec)
                         ( (:above-grid grid-spec) request))
                       [:div {:x-data (hx/json {:selected [] :all_selected false :type (:entity-name grid-spec)})
                              "x-on:copy" "if (selected.length > 0) {$clipboard(JSON.stringify({'type': type, 'selected': selected}))}"
                              "x-on:client-selected.document" "selected=[]; all_selected=false"
                              "x-bind:hx-vals" "JSON.stringify({selected: $data.selected, 'all-selected': $data.all_selected})"
                              :x-init "$watch('selected', s=> $dispatch('selectedChanged', {selected: s, all_selected: all_selected}) );
                                   $watch('all_selected', a=>$dispatch('selectedChanged', {selected: selected, all_selected: a}))"}

                        (table* grid-spec
                                identity
                                request)])
             (if (string? (:title grid-spec))
               (:title grid-spec)
               ((:title grid-spec) request))))
    true (wrap-trim-client-ids)
    true (wrap-secure)
    true (wrap-client-redirect-unauthenticated)))

(def request-spec (m/schema [:map]))
(def entity-spec (m/schema [:map]))
(def header-spec (m/schema [:map
                            [:key :string]
                            [:name :string]
                            [:header-class {:optional true} [:maybe :string]]
                            [:sort-key {:optional true} :string]
                            [:render {:optional true} [:=> [:cat entity-spec] :any]]
                            [:hide? {:optional true} [:=> [:cat entity-spec] :boolean]]]))
(def grid-spec (m/schema [:map
                          [:id :string]
                          [:nav [:=>
                                 [:cat request-spec]
                                 vector?]]
                          [:page-specific-nav
                           {:optional true
                            :default (fn [request])}
                           [:maybe [:=>
                                    [:cat request-spec]
                                    vector?]]]
                          [:id-fn {:default :db/id
                                   :optional true}
                           [:=>
                            [:cat map?]
                            nat-int?]]
                          [:query-schema :any]
                          [:fetch-page [:=>
                                        [:cat request-spec]
                                        [:cat [:vector entity-spec] :int]]]
                          [:above-grid
                           {:optional true
                            :default (fn [request])}
                           [:=>
                            [:cat request-spec]
                            vector?]]
                          [:oob-render
                           {:optional true
                            :default (fn [request])}
                           [:=>
                            [:cat request-spec]
                            vector?]]
                          [:breadcrumbs [:vector vector?]]
                          [:title [:or :string
                                   [:=> [:cat [:map-of :keyword :any]]
                                    :string]]]
                          [:entity-name :string]
                          [:route :keyword]
                          [:csv-route {:optional true} [:maybe :keyword]]
                          [:action-buttons
                           {:default (fn [request])
                            :optional true}
                           [:=>
                            [:cat request-spec]
                            [:maybe [:vector vector?]]]]
                          [:row-buttons
                           {:default (fn [request entity])
                            :optional true}
                           [:=>
                            [:cat request-spec entity-spec]
                            [:maybe [:vector vector?]]]]
                          [:headers [:vector header-spec]]]))

(defn build [grid-page]
  (when-not (m/validate grid-spec grid-page)
    (throw (ex-info "Could not validate grid"
                    (m/explain grid-spec grid-page))))
  (m/decode grid-spec grid-page (mt2/default-value-transformer {::mt2/add-optional-keys true})))

(defn wrap-apply-sort [handler grid-spec]
  (fn apply-sort [request]
    (handler (update request :query-params
                     (fn [qp]
                       ((comp
                         (query-params/apply-remove-sort)
                         (query-params/apply-toggle-sort grid-spec)
                         (query-params/parse-key :sort #(query-params/parse-sort grid-spec %)))
                        qp))))))

