(ns auto-ap.views.components.grid
  (:require
   [auto-ap.subs :as subs]
   [auto-ap.utils :refer [default-pagination-size]]
   [auto-ap.views.pages.data-page :as data-page]
   [auto-ap.views.utils :refer [copy-to-clipboard]]
   [re-frame.core :as re-frame]
   [react :as react]
   [reagent.core :as r]))

(set! *warn-on-infer* true)

(defonce ^js/React.Context grid-context ( react/createContext "default"))
(def  ^js/React.Provider Provider  (. grid-context  -Provider))
#_(println "Provider is"  Provider)
(def  ^js/React.Consumer Consumer  (. grid-context  -Consumer))
#_(println "Consumer is"  Consumer)

(defn toggle-sort-by [params sort-key sort-name]
  (let [[found? sort] (reduce
                       (fn [[found? sort] sort-item]
                         (if (= sort-key (:sort-key sort-item))
                           [true (conj sort
                                       (update sort-item :asc not))]
                           [found? (conj sort sort-item)]))
                       [false []]
                       (:sort params))
        sort (if found?
               sort
               (conj sort {:sort-key sort-key
                           :sort-name sort-name
                           :asc true}))]

    (-> params
        (assoc :sort sort))))

(defn sort-icon [which sort]
  (let [sort-item (first (filter #(= which (:sort-key %)) sort))]
    (cond
      (and sort-item (:asc sort-item))
      [:span.icon
       [:i.fa.fa-sort-up]]

      (and sort-item (not (:asc sort-item)))
      [:span.icon
       [:i.fa.fa-sort-down]]

      :else
      [:span.icon
       [:i.fa.fa-sort]])))

(defn bound [x y z]
  (cond
    (< z x)
    x
    (< y x)
        x
        (> y z)
        z
        :else 
        y))

(defn paginator [{:keys [start per-page end total on-change]}]
  (let [per-page (or per-page default-pagination-size)
        max-buttons 5
        buttons-before (Math/floor (/ max-buttons 2))
        total-pages (Math/max 1 (Math/ceil (/ total per-page)))
        current-page (Math/floor (/ start per-page))
        first-page-button (bound 0 (- current-page buttons-before) (- total-pages max-buttons))
        all-buttons (into [] (for [x (range total-pages)]
                               ^{:key x}
                               [:li
                                [:a.pagination-link {:class   (when (= current-page x)
                                                                "is-current")
                                                     :on-click (fn [_] (on-change {:start (* x per-page)
                                                                                   :per-page per-page}))}
                                 (inc x)]]))


        last-page-button (Math/min total-pages (+ max-buttons first-page-button))

        extended-last-page-button (when (not= last-page-button total-pages)
                                    (list
                                     ^ {:key -1} [:li [:span.pagination-ellipsis "…"]]
                                     ^ {:key -2} (last all-buttons)))

        extended-first-page-button (when (not= first-page-button 0)
                                     (list
                                      ^{:key -1} (first all-buttons)
                                      ^{:key -2} [:li [:span.pagination-ellipsis "…"]]))]
    

    [:nav.pagination {:role "pagination"}
     [:ul.pagination-list
      extended-first-page-button
      (apply list (subvec all-buttons first-page-button last-page-button))
      extended-last-page-button
      [:span.mx-4 (Math/min (inc start) total) "-" end "/" total]
      [:div.select
       [:select {:value per-page :on-change (fn [^js/Event e] (on-change {:start 0
                                                                          :per-page (js/parseInt (.-value ^js/HTMLOptionElement (.-target e )))}))}
        [:option {:value 20 } "20 per page" ]
        [:option {:value 50} "50 per page" ]
        [:option {:value 100} "100 per page" ]
        [:option {:value 200} "200 per page" ]]]]]))

(defn sort-by-list [{:keys [sort on-change]}]
  [:div.field.is-grouped.is-grouped-multiline
   (for [{:keys [sort-key sort-name asc]} sort]
     ^{:key sort-key}
     [:div.control
      [:div.tags.has-addons
       [:div.tag.is-medium [:span.icon (if asc
                                         [:i.fa.fa-sort-up]
                                         [:i.fa.fa-sort-down])]
        [:span sort-name] ]
       [:a.tag.is-medium.is-delete {:on-click (fn []
                                                (on-change {:sort (filter #(not= sort-key (:sort-key %)) sort)}))}]]])])

(defn controls [{:keys [start end count total action-buttons]}]
  (let [children (r/children (r/current-component))]
    [:> Consumer {}
     (fn [consume]
       (let [{:strs [on-params-change params]} (js->clj consume)]
         (r/as-element (into
                        [:div {:style {:margin-bottom "1rem"}}
                         [:div.level
                          (into 
                           [:div.level-left
                            [:div.level-item 
                             [paginator {:start start :end end :count count :total total
                                         :per-page (:per-page params)
                                         :on-change on-params-change}]]
                            [:div.level-item
                             [sort-by-list {:sort (:sort params)  
                                            :on-change on-params-change}]]]
                           (mapv (fn [c]
                                   [:div.level-item c]) children))
                          [:div.level-right action-buttons]]]))))]))

(defn bottom-paginator [{:keys [start end count total action-buttons]}]
  (let [children (r/children (r/current-component))]
    [:> Consumer {}
     (fn [consume]
       (let [{:strs [on-params-change params]} (js->clj consume)]
         (r/as-element (into
                        [:div {:style {:margin-bottom "1rem"}}
                         [:div.level
                          (into 
                           [:div.level-left
                            [:div.level-item 
                             [paginator {:start start :end end :count count :total total
                                         :per-page (:per-page params)
                                         :on-change on-params-change}]]
                            [:div.level-item
                             [sort-by-list {:sort (:sort params)  
                                            :on-change on-params-change}]]])]]))))]))

(defn table [{:keys [fullwidth class style]}]
  
  (into 
   [:table.table.compact.grid {:class (cond-> []
                                        fullwidth (conj "is-fullwidth")
                                        class (into class))
                               :style style}]
   (r/children (r/current-component))))

(defn header []
  (into 
   [:thead]
   (r/children (r/current-component))))

(defn header-cell [{:keys [style class]}]
  (into [:th {:style style
              :class class}]
        (r/children (r/current-component))))

(defn row [{:keys [class id checkable? entity] }]
  (let [children (r/children (r/current-component))
        is-admin? @(re-frame/subscribe [::subs/is-admin?])
        
        ]
    [:> Consumer {}
     (fn [consume]
       (let [on-check-changed (aget consume "on-check-changed")
             check-boxes? (aget consume "check-boxes?")
             checked (aget consume "checked")]
         (apply r/create-element "tr" #js {:className class}
                (when check-boxes?
                  (r/as-element [:th {:style {:width "35px"}}
                                 [:input.checkbox (cond-> {:type "checkbox"
                                                           :checked (if (get checked id)
                                                                      "checked"
                                                                      "")
                                                           :on-change (fn [_ _]
                                                                        (when id 
                                                                          (let [checked (or checked #{})]
                                                                            ;; TODO only map once everything is moved over to data-page
                                                                            (if (map? checked)
                                                                              (if (get checked id)
                                                                                (on-check-changed (dissoc checked id))
                                                                                (on-check-changed (assoc checked id entity)))

                                                                              (if (get checked id)
                                                                                (on-check-changed (disj checked id))
                                                                                (on-check-changed (conj checked id)))))))}
                                                    (boolean? checkable?) (assoc :disabled (not checkable?))) ]]))

                (when is-admin? 
                  (r/as-element 
                   (cond 
                     (not id) [:th {:style {:width "4em"}}]
                     id [:th {:style {:width "4em"}} [:button.button.is-link.is-light
                                                      {:on-click (fn []
                                                                   (copy-to-clipboard id))}
                                                      [:span.icon [:i.fa.fa-files-o]]
                                                      ]])))
                (map r/as-element children))))]))

(defn button-cell []
  (apply r/create-element "td" #js {"style" #js {"overflow" "visible"}}
         (map r/as-element (r/children (r/current-component)))))

(defn cell [params]
  (apply r/create-element "td" #js {:className (:class params) :style (some-> (:style params) clj->js)}
         (map r/as-element (r/children (r/current-component)))))

(defn body []
  (let [children (r/children (r/current-component))
        is-admin? @(re-frame/subscribe [::subs/is-admin?])]
    [:> Consumer {}
     (fn [consume]
       (let [{:strs [column-count status check-boxes?]} (js->clj consume)
             column-count (cond-> column-count
                            check-boxes? inc
                            is-admin? inc)]
         (r/as-element
          (cond (= :loading (:state status))
                ^{:key "loading-body"}
                [:tbody.test
                 (for [i (range default-pagination-size)]
                   ^{:key i}
                  [:tr 
                    (for [x (range column-count)]
                      ^{:key x}
                      [:td #_{:col-span column-count}
                       [:div
                        [:div.ph-item
                         [:div.ph-row
                          [:div.ph-col-12.big]]]]])])]
                
                (= :error (:state status))
                ^{:key "error-body"}
                [:tbody [:tr [:td.has-text-centered {:col-span column-count}
                              "An unexpected error has occured. "
                              (-> status :error first :message)
                              " Please try refreshing the page."]]]
                :else
                (into ^{:key "main-body"}
                      [:tbody]

                      children)))))]))

(defn sortable-header-cell [{:keys [style class sort-key sort-name]}]
  (let [children (r/children (r/current-component))]
    [:> Consumer {}
     (fn [consume]
       (let [{:strs [on-params-change params]} (js->clj consume)]
         (r/as-element (conj (into 
                              [:th {:on-click (fn [_]
                                                (on-params-change
                                                 (toggle-sort-by {:sort (:sort params)} sort-key sort-name)))
                                    :style (assoc style
                                                  :cursor "pointer")
                                    :class class}]
                              children)
                             (sort-icon sort-key (:sort params))))))]))

(defn grid [{:keys [on-params-change on-check-changed checked params status column-count check-boxes? data-page]}]
  (if data-page
    (let [page @(re-frame/subscribe [::data-page/page data-page])]
      (r/create-element Provider
                                 #js {:value #js {:on-params-change (fn [p]
                                                                      (re-frame/dispatch [::data-page/table-params-changed data-page p]))
                                                  :on-check-changed (fn [new]
                                                                      (re-frame/dispatch [::data-page/toggle-check data-page new]))
                                                  :check-boxes? check-boxes?
                                                  :checked (:checked page)
                                                  :params (:params page)
                                                  :status (:status page)
                                                  :column-count column-count}}
                                 (r/as-element
                                  (into 
                                   [:<> ]
                                   (r/children (r/current-component))))))
    (r/create-element Provider
                      #js {:value #js {:on-params-change on-params-change
                                       :on-check-changed on-check-changed
                                       :check-boxes? check-boxes?
                                       :checked checked
                                       :params params
                                       :status status
                                       :column-count column-count}}
                      (r/as-element
                       (into 
                        [:<> ]
                        (r/children (r/current-component)))))))

(defn virtual-paginate [start per-page xs ]
  (take (or per-page default-pagination-size) (drop (or start 0) xs)))

(defn virtual-paginate-controls [start per-page xs ]
  {:start (or start 0) :end (min (+ (or start 0) (or per-page default-pagination-size))
                                 (count xs))
   :total (count xs)})
