(ns auto-ap.views.utils
  (:require
   [cemerick.url]
   [cljs-time.coerce :as c]
   [cljs-time.core :as t]
   [cljs-time.format :as format]
   [cljs.tools.reader.edn :as edn]
   [clojure.string :as str]
   [goog.crypt.base64 :as base64]
   [re-frame.core :as re-frame]
   [react-transition-group :as react-transition-group]
   #_{:clj-kondo/ignore [:unused-namespace]}
   [react :as react]
   [reagent.core :as r]
   [pako])
  (:import
   (goog.i18n NumberFormat)
   (goog.i18n.NumberFormat Format)))


(def date-regex #"[2]{1}[0-9]{3}-[0-9]{1,2}-[0-9]{1,2}")

(def nff
  (NumberFormat. Format/CURRENCY))

(defn nf
  [num]
  (.format nff (str num)))

(defn ->$ [x]
  (nf x))

(defn- nf%
  [num]
  (.format (doto 
               (NumberFormat. Format/PERCENT)
             (.setMaximumFractionDigits 1)
             (.setMinimumFractionDigits 1))
           (str num)))

(defn ->% [x]
  (nf% x))

(defn ->short$ [x]
  (cond
    (nil? x)
    nil

    (int? x)
    (str x)

    (float? x)
    (.toFixed x 2)

    ))

(defn active-when [active-page f & rest]

  (when (apply f (into [active-page] rest)) " is-active"))

(defn login-url
  ([] (login-url nil))
  ([next]

   (let [client-id "264081895820-0nndcfo3pbtqf30sro82vgq5r27h8736.apps.googleusercontent.com"
         redirect-uri (js/encodeURI (str (.-origin (.-location js/window)) "/api/oauth"
                                                  ))]
     (str "https://accounts.google.com/o/oauth2/auth?access_type=online&client_id=" client-id "&redirect_uri=" redirect-uri "&response_type=code&max_auth_age=0&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile"
          (when next
            (str "&state=" (js/encodeURIComponent next)))))))

(defn dispatch-value-change [event]
  (fn [e]
    (.preventDefault e)
    (re-frame/dispatch (conj event (.. e -target -value)))))


(defn dispatch-event [event]
  (fn [e]
    (when (.-stopPropagation e)
      (.stopPropagation e)
      (.preventDefault e))
    (re-frame/dispatch-sync event)))

(defn dispatch-event-with-propagation [event]
  (fn [_]
    (re-frame/dispatch-sync event)))

(def pretty-long (format/formatter "MM/dd/yyyy HH:mm:ss"))
(def pretty (format/formatter "MM/dd/yyyy"))
(def standard (format/formatter "yyyy-MM-dd"))

(defn date->str
  ([d] (date->str d pretty))
  ([d format]
   (when d
     (format/unparse format d))))

(defn str->date [d f]
  (when d
    (format/parse f d)))

(def css-transition-group
  (r/adapt-react-class react-transition-group/CSSTransition))

(def transition
  (r/adapt-react-class react-transition-group/Transition))

(def transition-group
  (r/adapt-react-class react-transition-group/TransitionGroup))

(def switch-transition
  (r/adapt-react-class react-transition-group/SwitchTransition))

(defn appearing [{:keys [visible? enter-class exit-class timeout]}]
  (let [final-state (r/atom visible?)]
    (fn [{:keys [visible?]} & children]
      [css-transition-group {:in visible? :class-names {:exit exit-class :enter enter-class} :timeout timeout :onEnter (fn [] (reset! final-state true )) :onExited (fn [] (reset! final-state false))} 
       (if (or @final-state visible?)
         (first children)
         [:span])])))

(defn appearing-group []
  (let [children (r/children (r/current-component))]
    (into [transition-group {:exit  true
                             :enter true}
           (for [child children]
             ^{:key (:key (meta child))}
             [transition
              {:timeout 200
               :exit    true
               :in      true #_ (= current-stack- (:key (meta child)))}
              (clj->js (fn [state]
                         (r/as-element 
                           [:div {:style {
                                          :transition "opacity 150ms ease-in-out"
                                          :opacity    (cond
                                                        (= "entered" state)
                                                        1.0

                                                        (= "entering" state)
                                                        0.0

                                                        (= "exiting" state)
                                                        0.0

                                                        (= "exited" state)
                                                        0.0)}}
                            child])))])])))

(defn coerce-date [d]
  (cond (and (string? d)
             (some->> (re-find #"^(\d{4})" d)
                      second
                      (js/parseInt)
                      (#(> % 2000))))
        (try 
          (c/to-date-time (t/to-default-time-zone (t/from-default-time-zone (str->date d standard))))
          (catch js/Error _
            nil))

        (instance? goog.date.DateTime d)
        (c/to-date-time (t/to-default-time-zone (t/from-default-time-zone d)))

        (instance? goog.date.Date d)
        (c/to-date-time d)

        :else
        nil ))

(defn date-picker-internal [params]
  
  (let [[text set-text ]   (react/useState (some-> params :value coerce-date (date->str standard)))
        [value set-value ] (react/useState (some-> params :value coerce-date))
        swap-external-value (fn [new-value]
                              ((:on-change params)
                               (cond (= :text (:output params))
                                     (some-> new-value (date->str standard))

                                     (= :cljs-date (:output params))
                                     new-value

                                     :else
                                     (c/to-date new-value))))]
    (react/useEffect (fn []
                       (let [prop-date (some-> params :value coerce-date)]
                         (when (not (t/= prop-date
                                         value))
                           (set-value prop-date)
                           (if prop-date 
                             (set-text (date->str prop-date standard))
                             (set-text ""))))))
    [:div.field.has-addons 
     [:div.control 
      [:input.input (assoc params
                           :value text
                           :on-change (fn [e]
                                        
                                        (set-text (.. e -target -value))
                                        ;; if it's a perfect match, change it on the spot
                                        ;; especially important for calendar clicking, don't
                                        ;; want to wait for blur
                                        (when (or (re-matches date-regex (.. e -target -value))
                                                  (nil? (.. e -target -value)))
                                          (swap-external-value (some-> (.. e -target -value) coerce-date))))
                           
                           :on-blur (fn []
                                      (swap-external-value (some-> text coerce-date))
                                      (when (:on-blur params)
                                        ((:on-blur params))))
                           :type "date" :placeholder "12/1/2021")]
      ]]))

(defn date-picker []
  [:f> date-picker-internal
   (r/props (r/current-component))])

(defn local-now []
  (t/to-default-time-zone (t/now)))

(defn local-today []
  (t/at-midnight (t/to-default-time-zone (t/now))))

(def with-user
  (re-frame/->interceptor
   :id :with-user
   :before (fn [context]
             (-> context
                 (assoc-in [:coeffects :user] (get-in context [:coeffects :db :user]))))))

(def with-role
  (re-frame/->interceptor
   :id :with-role
   :before (fn [context]
             (-> context
                 (assoc-in [:coeffects :role] (-> (get-in context [:coeffects :db :user])
                                                  (str/split  #"\.")
                                                  second
                                                  (base64/decodeString )
                                                  (#(.parse js/JSON % ))
                                                  (js->clj  :keywordize-keys true)
                                                  :user/role))))))

(def with-is-admin?
  (re-frame/->interceptor
   :id :with-is-admin?
   :before (fn [context]
             (-> context
                 (assoc-in [:coeffects :is-admin?] (= "admin"
                                                      (-> (get-in context [:coeffects :db :user])
                                                          (str/split  #"\.")
                                                          second
                                                          (base64/decodeString )
                                                          (#(.parse js/JSON % ))
                                                          (js->clj  :keywordize-keys true)
                                                          :user/role)))))))

(defn query-params []
  (reduce-kv
   (fn [result k v]
     (assoc result (keyword k) (try (edn/read-string v) (catch js/Error e v))))
   {}
   (:query (cemerick.url/url  (.-location js/window)))))


(defn action-cell-width [cnt]
  (str (inc (* cnt 51)) "px"))

(defn days-until [d]
  (let [today (t/at-midnight (t/now))
        d (t/at-midnight d)
        in (if (t/after? today d)
                 (- (t/in-days (t/interval (t/minus d (t/days 1)) today)))
                 (t/in-days (t/interval today d )))]
    in))

(defn copy-to-clipboard [text]
  (let [el (js/document.createElement "textarea")]

    (set! (.-value el) text)
    (.appendChild js/document.body el)
    (.select el)
    (js/document.execCommand "copy")
    (.removeChild js/document.body el)))

(defn str->int [x]
  (cond
    (nil? x)
    nil

    (and (string? x)
         (str/blank? x))
    nil

    (string? x)
    (js/parseInt x)
    
    :else
    x))

(defn gunzip [b64]
  (let [raw-byte-array (->> b64
                            js/atob
                            (map (fn [z] (.charCodeAt z 0)))
                            clj->js
                            (js/Uint8Array.))]
    (or (edn/read-string (pako/inflate raw-byte-array #js {"to" "string"}))
        nil)))

(defn parse-jwt [jwt]
  (when-let [json (some-> jwt
                          (str/split  #"\.")
                          second
                          base64/decodeString)]
    (let [raw        (js->clj (.parse js/JSON json) :keywordize-keys true)
          gz-clients (or (:gz-clients raw)
                         (get raw "gz-clients"))]
      (cond-> raw
        gz-clients (assoc :user/clients (gunzip gz-clients))))))

(defn coerce-float [f]
  (cond (str/blank?  f)
        nil

        (float? f)
        f

        (and (string? f)
             (not (js/Number.isNaN (js/parseFloat f))))
        (js/parseFloat f)

        :else
        nil))
