(ns auto-ap.views.pages.ledger.external-import
  (:require
   [auto-ap.events :as events]
   [auto-ap.forms :as forms]
   [auto-ap.forms.builder :as form-builder]
   [auto-ap.status :as status]
   [auto-ap.views.components :as com]
   [auto-ap.views.components.dropdown
    :refer [drop-down drop-down-contents]]
   [auto-ap.views.components.layouts :refer [side-bar-layout]]
   [auto-ap.views.pages.ledger.side-bar :refer [ledger-side-bar]]
   [auto-ap.views.utils :refer [dispatch-event]]
   [clojure.string :as str]
   [re-frame.core :as re-frame]
   [reagent.core :as r]))

(defn line->id [{:keys [source id client-code]}]
  (str client-code "-"  source "-" id))

(re-frame/reg-sub
 ::request
 :<- [::forms/form ::form]
 (fn [{{lines :line-items} :data}]
   (into []
         (for [[_ lines] (group-by line->id lines)
               :let [{:keys [source client-code date vendor-name note cleared-against] :as line} (first lines)]] 
           {:source source
            :external-id (line->id line) 
            :client-code client-code
            :date date
            :note note
            :cleared-against cleared-against
            :vendor-name vendor-name
            :amount (reduce + 0
                            (->> lines
                                 (map :debit)
                                 (map js/parseFloat)
                                 (map #(if (js/isNaN %) 0 %))))
            :line-items (map (fn [{:keys [debit credit account-identifier location]}]
                               {:account-identifier account-identifier
                                :location (some-> location str/trim)
                                :debit debit
                                :credit credit})
                             lines)}))))


;; EVENTS

(re-frame/reg-event-fx
 ::imported
 (fn [{:keys [db]} [_ result]]
   (let [successful-set (set (map :external-id (:successful (:import-ledger result))))
         error-set (into {} (map (juxt :external-id :error) (:errors (:import-ledger result))))
         existing-set (set (map :external-id (:existing (:import-ledger result))))
         ignored-set (set (map :external-id (:ignored (:import-ledger result))))
         seen-set (atom #{})]
     
     {:db (-> (forms/save-succeeded db ::form )
              (assoc-in [::forms/forms ::form :result] {:errors error-set :success successful-set :existing existing-set :ignored ignored-set})
              (update-in [::forms/forms ::form :data :line-items]
                         (fn [lis]
                           (mapv
                            #(assoc % :status
                                   (cond (successful-set (line->id %))
                                         [:span.icon [:i.fa.fa-check]]

                                         (ignored-set (line->id %))
                                         [:span.icon [:i.fa.fa-minus-circle]]

                                         (existing-set (line->id %))
                                         ""

                                         (and (error-set (line->id %))
                                              (not (@seen-set (line->id %))))
                                         (do 
                                           (swap! seen-set conj (line->id %))
                                           [drop-down {:id [::ledger-import-line (line->id %)  ]
                                                       :is-right? true
                                                       :header [:a.button {:aria-haspopup true
                                                                           :on-click (dispatch-event [::events/toggle-menu [::ledger-import-line (line->id %)]])
                                                                           :tab-index "0"}
                                                                [:span.is-warning.icon {:title (error-set (line->id %))} [:i.fa.fa-exclamation-triangle]]]}
                                            [drop-down-contents
                                             [:div
                                              [:span.dropdown-item (error-set (line->id %)) ]]]])
                                         )
                                   :status-category
                                   (cond (successful-set (line->id %))
                                         :success


                                         (ignored-set (line->id %))
                                         :ignored

                                         (existing-set (line->id %))
                                         :existing

                                         (error-set (line->id %))
                                         :error))
                            lis))))})))

(re-frame/reg-event-fx
 ::importing
 (fn [{:keys [db]} _]
   {:graphql
    {:token      (-> db :user)
     :owns-state {:single ::import}
     :query-obj  {:venia/operation {:operation/type :mutation
                                    :operation/name "ImportLedger"}
                  :venia/queries   [{:query/data [:import-ledger
                                                  {:entries @(re-frame/subscribe [::request])}
                                                  [[:successful [:external-id]]
                                                   [:existing [:external-id]]
                                                   [:ignored [:external-id]]
                                                   [:errors [:external-id :error]]]]}]}
     :on-success [::imported]
     :on-error   [::forms/save-error ::form]}}))

;; COMPONENTS

(defn textarea->table [{:keys [read-only-headings]} & children]
  (let [text-form (r/atom "")
        include-headers (r/atom true)]
    (fn [{:keys [headings value on-change row-filter]}]
      [:div
       (when (> (count value) 2000)
         [:div.notification "Showing first 2000 of " (count value)])
       [:form.form
        (if value
          [:div
           [:a.button {:on-click #(on-change nil)}
            "reset"]
           [:table.table {:style {:width "100%"}}
            [:thead 
             [:tr
              (list 
               (for [[heading-name _] headings]
                 ^{:key heading-name}[:th heading-name]))
              (list 
               (for [[heading-name _] read-only-headings]
                 ^{:key heading-name} [:th heading-name]))]]
            [:tbody
             (list
              (for [[index row] (map vector (range ) (take 2000 value))
                    :when (row-filter row)]
                ^{:key index} [:tr
                               (list 
                                (for [[_ k] headings]
                                  ^{:key k} [:td
                                             [:input.input {:on-change (fn [x]
                                                                         (.preventDefault x)
                                                                         (on-change
                                                                          (assoc-in value [index k] (.. x -target -value))))
                                                            :value (get row k)}]]))
                               (list 
                                (for [[_ k] read-only-headings]
                                  ^{:key k} [:td.expandable
                                             (get row k)]))]))]]
           children]
          [:div 
           
           [:div.field
            [:p.help "Paste manual ledger entries below."]
            [:textarea.textarea {:on-change #(reset! text-form (.. % -target -value))} @text-form]]
           [:div.field
            [:label.checkbox
             [:input {:type "checkbox" :checked @include-headers :on-change #(swap! include-headers (fn [x] (not x)))}
              ]
             " Contains Header Row"]]
           [:button.button.is-medium {:on-click (fn [e]
                                                  (.preventDefault e)
                                                  (on-change
                                                   (persistent! 
                                                    (reduce
                                                     (fn [acc row]
                                                       (let [values (str/split row "\t")]
                                                         (conj! acc
                                                                (loop [row (transient {})
                                                                       [[_ heading] & headings] headings
                                                                       [v & values] values]
                                                                  (if heading
                                                                    (recur 
                                                                     (assoc! row heading v)
                                                                     headings
                                                                     values)
                                                                    (persistent! row))))))
                                                     (transient [])
                                                     (cond->> (str/split @text-form #"\n")
                                                       @include-headers (drop 1))))))}
            "Parse"]
           ])]])))

(def balance-sheet-content
  (with-meta
    (fn []
      (let [status @(re-frame/subscribe [::status/single ::import])
            {:keys [data result]} @(re-frame/subscribe [::forms/form ::form]) ]
        [form-builder/builder {:id           ::form
                               :submit-event [::importing]}
         [:div 
          [:div.level
           [:div.level-left
            [:h1.title "External Import"]]
           
           [:div.level-right
            (when (:line-items data )
              [form-builder/submit-button "Import"])]]
          [status/status-notification {:statuses [[::status/single ::import]]} ]
          (when result
            [:div.notification
             "Imported with "
             (count (:errors result)) " errors, "
             (count (:ignored result)) " ignored, "
             (count (:success result)) " successful."])
          (if (= :loading (:state status ))
            [status/big-loader status]
            [:div 
             [:div.is-clearfix
              [:div.is-pulled-right
               [form-builder/raw-field-v2 {:field :only-show-errors?}
                [com/checkbox {:label "Only show errors"}]]]]
             [:div
              [form-builder/raw-field-v2 {:field :line-items}
               [textarea->table {:headings [["Id" :id]
                                            ["Client" :client-code]
                                            ["Source" :source]
                                            ["Vendor" :vendor-name]
                                            ["Date" :date]
                                            ["Account" :account-identifier]
                                            ["Location" :location]
                                            ["Debit" :debit]
                                            ["Credit" :credit]
                                            ["Note" :note]
                                            ["Cleared against" :cleared-against]]
                                 :read-only-headings
                                 [["status" :status]]

                                 :row-filter
                                 (fn [{:keys [status-category]}]
                                   (if (:only-show-errors? data)
                                     (= :error status-category)
                                     true))}]]]])]]))
    {}))

(re-frame/reg-event-fx
  ::mounted
  (fn [_ _]
    {:dispatch [::forms/start-form ::form]}))

(re-frame/reg-event-fx
  ::unmounted
  (fn [_ _]
    {:dispatch [::forms/form-closing ::form]}))

(defn external-import-page-internal []
  [side-bar-layout
   {:side-bar [ledger-side-bar] 
    :main [balance-sheet-content]}])

(defn external-import-page []
  (r/create-class
    {:display-name           "external-import-page"
     :component-will-unmount #(re-frame/dispatch-sync [::unmounted])
     :component-did-mount    #(re-frame/dispatch [::mounted])
     :reagent-render         external-import-page-internal}))
