(ns auto-ap.subs
  (:require [re-frame.core :as re-frame]
            [auto-ap.utils :refer [by]]
            [clojure.string :as str]
            [goog.crypt.base64 :as base64]
            [minisearch :as ms]))
(set! *warn-on-infer* true)

(def MiniSearch (if ms (aget ms "default")
                    nil))

(re-frame/reg-sub
 ::client
 (fn [db]
   (get (:clients db) (:client db))))

(re-frame/reg-sub
 ::clients
 (fn [db]
   (when (:user db)
     (sort-by :name (vals (:clients db))))))

(re-frame/reg-sub
 ::all-accounts
 (fn [db]
   (:accounts db)))

(re-frame/reg-sub
 ::all-accounts-by-id
 (fn [db]
   (by :id (:accounts db))))

(defn clientize-account [account client]
  (let [override (->>
                  (:client-overrides account)
                  (filter (fn [co]
                            (= (:id (:client co)) (:id client))))
                  first)]
    (condp = (:applicability account)
      nil
      (assoc account :name (or (:name override) (:name account)))

      :global
      (assoc account :name (or (:name override) (:name account)))

      :optional
      (when override
        (assoc account :name (or (:name override) (:name account))))

      :customized
      (when override 
        (assoc account :name (or (:name override) (:name account)))))))

(re-frame/reg-sub
 ::accounts
 :<- [::all-accounts]
 :<- [::client]
 (fn [[accounts client] [_ client-override]]
   (transduce
    (comp 
     (map
      #(clientize-account % (or client-override client)))
     (filter identity))
    conj
    []
    accounts)))

(re-frame/reg-sub
 ::accounts-index
 :<- [::accounts]
 (fn [accounts]
   (doto (MiniSearch. #js {:fields #js ["numeric-code" "name" "content"]
                           :storeFields #js ["id" "numeric-code" "name" "content"]})
     (.addAll (clj->js (map (fn [v] {:id (:id v)
                                     :name (:name v)
                                     :numeric-code (:numeric-code v)
                                     :content (str (:numeric-code v) " " (:name v))})
                            accounts))))))




(re-frame/reg-sub
 ::account
 (fn [[_ client]]
   [(re-frame/subscribe [::accounts-by-id client])])
 (fn [[as] [_ _ i]]
   (as i)))

(defn accounts-by-id [accounts client]
  (by :id
      (map
       #(clientize-account % client)
       
       accounts)))

(re-frame/reg-sub
 ::accounts-by-id
 (fn [[_ client-override]]
   [(re-frame/subscribe [::accounts client-override])
    (re-frame/subscribe [::client])])
 (fn [[accounts client] ]
   (accounts-by-id accounts client)))

(re-frame/reg-sub
 ::bank-accounts
 :<- [::clients]
 :<- [::client]
 (fn [[clients client]]
   (if client
     (:bank-accounts client)
     (reduce (fn [result {:keys [bank-accounts]}]
               (into result bank-accounts))
             []
             clients))))

(re-frame/reg-sub
 ::bank-accounts-by-id
 :<- [::bank-accounts]
 (fn [as]
   (by :id as)))

;; Bank accounts only, not including cash
(re-frame/reg-sub
 ::real-bank-accounts 
 :<- [::client]
 (fn [client]
   (->> client :bank-accounts (filter #(= (:type %) :check)) (sort-by :sort-order))))

(re-frame/reg-sub
 ::real-bank-accounts-for-client
 (fn [db [_ {:keys [id]}]]
   (sort-by :sort-order (-> db :clients (get id) :bank-accounts ))))

(re-frame/reg-sub
 ::locations-for-client
 
 (fn [db [_ client]]
   (:locations (get (:clients db) (or client (:client db))))))

(re-frame/reg-sub
 ::forecasted-transactions-for-client
 (fn [db [_ client]]
   (:forecasted-transactions (get (:clients db) (or client (:client db))))))

(re-frame/reg-sub
 ::locations-for-client-or-bank-account
 (fn [db [_ client-id bank-account-id]]
   (let [client (get (:clients db) (or client-id (:client db)))
         client-locations (:locations client)
         bank-account-locations (-> (by :id (:bank-accounts client))
                                    (get bank-account-id)
                                    :locations)]
     (or (seq bank-account-locations) client-locations))))

(re-frame/reg-sub
 ::clients-by-id
 (fn [db]
   (when (:user db)
     (:clients db))))

(re-frame/reg-sub
 ::menu-active?
 (fn [db [_ id]]
   (-> db :menu (get id) :active?)))

(re-frame/reg-sub
 ::menu
 (fn [db]
   (:menu db)))

(re-frame/reg-sub
 ::is-initial-loading?
 (fn [db]
   (:is-initial-loading? db)))

(re-frame/reg-sub
 ::intuit-bank-accounts
 (fn [db]
   (::intuit-bank-accounts db)))

(re-frame/reg-sub
 ::modal-state
 (fn [db [_ id status-from]]
   (if status-from
     (assoc (get (:modal-state db) id)
            :error-message (get-in db [:auto-ap.forms/forms status-from :error])
            :saving? (= (get-in db [:auto-ap.forms/forms status-from :status]) :loading))
     (get (:modal-state db) id))))

(re-frame/reg-sub
 ::token
 (fn [db]
   (:user db)))

(re-frame/reg-sub
 ::vendors
 :<- [::is-admin?]
 :<- [::all-vendors]
 (fn [[is-admin all-vendors]]
   (filter #(or (not (:hidden %))
                is-admin) all-vendors)))

(re-frame/reg-sub
 ::searchable-vendors
 :<- [::is-admin?]
 :<- [::client]
 :<- [::all-vendors]
 (fn [[is-admin client all-vendors]]
   (cond is-admin
         all-vendors

         client
         (filter (fn [{:keys [hidden usage name] :as vendor}]
                   (or (not hidden)
                       (-> (first (filter #(= (:client-id %)
                                              (:id client))
                                          usage))
                           (:count 0)
                           (> 0))))
                 all-vendors)


         :else


         (filter #(not (:hidden %)) all-vendors))))

(re-frame/reg-sub
 ::searchable-vendors-index
 :<- [::searchable-vendors]
 (fn [searchable-vendors]
   (doto (MiniSearch. #js {:fields #js ["name" "content"]
                           :storeFields #js ["id" "name" "content" "usage"]})
     (.addAll (clj->js (map (fn [v] {:id (:id v)
                                     :name (:name v)
                                     :content (:name v)
                                     :usage (:usage v)})
                            searchable-vendors))))))

(re-frame/reg-sub
 ::all-vendors-index
 :<- [::all-vendors]
 (fn [all-vendors]
   (doto (MiniSearch. #js {:fields #js ["name" "content"]
                           :storeFields #js ["id" "name" "content" "usage"]})
     (.addAll (clj->js (map (fn [v] {:id (:id v)
                                     :name (:name v)
                                     :content (:name v)
                                     :usage (:usage v)})
                            all-vendors))))))

(re-frame/reg-sub
 ::all-vendors
 (fn [db]
   (vals (:vendors db))))

(re-frame/reg-sub
 ::vendors-by-id
 (fn [db]
   (:vendors db)))

(re-frame/reg-sub
 ::vendor-default-account
 (fn [db [_ v client]]
   (let [accounts (accounts-by-id (:accounts db) client)
         vendor (if (map? v)
                  v
                  (-> (:vendors db) (get v)))
         client-override (->> (:account-overrides vendor)
                              (filter #(= (:id (:client %)) (:id client))
                                      )
                              first
                              :account
                              :id)
         default-id (:id (:default-account vendor))
         i (or client-override default-id)]
     (accounts i))))

(re-frame/reg-sub
 ::sorted-vendors
 :<- [::vendors]
 (fn [vendors]
   (sort-by :name vendors)))

(re-frame/reg-sub
 ::sorted-all-vendors
 :<- [::all-vendors]
 (fn [all-vendors]
   (sort-by :name all-vendors)))

(re-frame/reg-sub
 ::admin
 (fn [db]
   (:admin db)))

(re-frame/reg-sub
 ::is-admin?
 :<- [::user]
 (fn [user]
   (= "admin" (:user/role user))))

(re-frame/reg-sub
 ::is-power-user?
 :<- [::user]
 (fn [user]
   (or (= "power-user" (:user/role user))
       (= "power_user" (:user/role user)))))

(re-frame/reg-sub
 ::user
 (fn [db]
   (when (:user db)
     (js->clj (.parse js/JSON (base64/decodeString (second (str/split (:user db) #"\.")))) :keywordize-keys true))))

(re-frame/reg-sub
 ::active-page
 (fn [db]
   (:active-page db)))

(re-frame/reg-sub
 ::pending-invoices
 (fn [db]
   (:pending (:invoices db))))


(re-frame/reg-sub
 ::status
 (fn [db]
   (:status db)))

(re-frame/reg-sub
 ::new-invoice-form
 (fn [db]
   (:new-invoice db)))

(re-frame/reg-sub
 ::query-params
 (fn [db]
   (:query-params db)))

(re-frame/reg-sub
 ::page-failure
 (fn [db]
   (when-let [error (-> db :page-failure first)] 
     (if (= 500 (:status error))
       "System error occured. If you are stuck, please notify ben@integreatconsult.com."
       (:message error)))))

(re-frame/reg-sub
 ::yodlee-merchants
 (fn [db]
   (:yodlee-merchants db)))
