(ns auto-ap.views.pages.ledger.profit-and-loss
  (:require [auto-ap.subs :as subs]
            [auto-ap.views.components.layouts :refer [side-bar-layout appearing-side-bar]]
            [auto-ap.views.pages.ledger.table :as ledger-table ]
            [vimsical.re-frame.cofx.inject :as inject]
            [goog.string :as gstring]
            [auto-ap.utils :refer [dollars-0? by ]]
            [auto-ap.forms :as forms]
            [auto-ap.views.pages.ledger.side-bar :refer [ledger-side-bar]]
            [auto-ap.views.components.typeahead :refer [typeahead-v3]]
            [auto-ap.views.components.buttons :as buttons]
            [auto-ap.views.components.switch-field :refer [switch-field]]
            [auto-ap.views.components.modal :as modal]
            [auto-ap.views.utils :refer [date->str date-picker date-picker-friendly bind-field standard pretty dispatch-event local-today ->% ->$ str->date with-user dispatch-value-change query-params multi-field]]
            [cljs-time.core :as t]
            [re-frame.core :as re-frame]
            [react-dom :as react-dom]
            [auto-ap.status :as status]
            [auto-ap.views.pages.data-page :as data-page]
            [vimsical.re-frame.fx.track :as track]
            [reagent.core :as reagent]
            [clojure.set :as set]
            [clojure.string :as str]
            [cljs-time.core :as time]))
(def ranges
  {:sales [40000 49999]
   :cogs [50000 59999]
   :payroll [60000 69999]
   :controllable [70000 79999]
   :fixed-overhead [80000 89999]
   :ownership-controllable [90000 99999]})

(defn and-last-year [[from to]]
  [[from to]
   [(t/minus from (t/years 1)) (t/minus to (t/years 1))]])

(defn in-range? [code]
  (reduce
   (fn [acc [start end]]
     (if (<= start code end)
       (reduced true)
       acc))
   false
   (vals ranges)))



;; SUBS 
(re-frame/reg-sub
 ::locations

 :<- [::forms/form ::form]

 (fn [db]
   (->> db
        :report
        :periods
        (mapcat :accounts)
        (filter (comp in-range? :numeric-code))
        (group-by :location)
        (filter (fn [[k as]]
                  (not (dollars-0?  (reduce + 0 (map :amount as))))))
        (mapcat second)
        (map :location)
        (map not-empty)
        (filter identity)
        (set)
        
        (sort-by (fn [x] (if (= x "HQ" )
                           "ZZZZZZ"
                           x))))))

(re-frame/reg-sub
 ::multi-client?
 :<- [::forms/form ::form]
 (fn [db]
   (> (->> db
           :data
           :clients
           count) 1 )))

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

(re-frame/reg-sub
 ::ledger-list-active?
 (fn [db]
   (-> db ::ledger-list-active?)))


(re-frame/reg-sub
 ::period-accounts
 :<- [::forms/form ::form]
 (fn [db [_ which type only-location]]
   
   (->> (get-in db [:report :periods which :accounts])
        (map #(update % :amount js/parseFloat))
        (filter (fn [{:keys [account-type location numeric-code]}]
                  (and (or (nil? only-location)
                           (= only-location location))
                       (<= (get-in ranges [type 0]) numeric-code (get-in ranges [type 1])))))
        (sort-by :numeric-code))))

(re-frame/reg-sub
 ::uncategorized-accounts
 :<- [::forms/form ::form]
 (fn [db [_ ]]
   
   (->> (get-in db [:report :periods ])
        (mapcat :accounts)
        (map #(update % :amount js/parseFloat))
        (filter (fn [{:keys [account-type location numeric-code]}]
                  (nil? numeric-code))))))

(defn parse-amounts [period]
  (update period :accounts (fn [a]
                      (map #(update % :amount js/parseFloat) a))))


(defn filter-accounts [accounts period [from to] only-location]
  (->> (get accounts period)
       vals
       
       (filter (fn [{:keys [location numeric-code]}]
                 (and (or (nil? only-location)
                          (= only-location location))
                      (<= from numeric-code to))))
       (sort-by :numeric-code)))

(defn aggregate-accounts [accounts]
  (reduce (fnil + 0.0) 0.0 (map :amount accounts)))

(re-frame/reg-sub
 ::all-accounts
 :<- [::forms/form ::form]
 (fn [db [_ which type only-location]]
   (transduce
    (comp
     (map parse-amounts)
     (map #(->> (:accounts %)
                (group-by (juxt :numeric-code :location))
                (map (fn [[k v]]
                       [k 
                        (reduce (fn [a n]
                                  (-> a 
                                      (update :count (fn [z] (+ z (:count n))))
                                      (update :amount (fn [z] (+ z (:amount n))))))
                                (first v)
                                (rest v))]))
                
                (into {}))))

    conj
    []
    (get-in db [:report :periods]))))

(re-frame/reg-event-db
 ::received
 [(forms/in-form ::form)]
 (fn [db [_ data]]
   (-> db (assoc :report (:profit-and-loss data)))))


(re-frame/reg-sub
 ::period-inputs
 (fn [db]
   (-> db ::period-inputs)))

(re-frame/reg-sub
 ::periods
 (fn [db]
   (-> db ::forms/forms ::form :data :periods)))

(re-frame/reg-sub
 ::include-deltas
 (fn [db]
   (-> db ::forms/forms ::form :data :include-deltas)))

;; EVENTS

(re-frame/reg-event-db
 ::ledger-list-closing
 (fn [db]
   (assoc db ::ledger-list-active? false)))

(re-frame/reg-event-fx
 ::params-change
 [with-user (forms/in-form ::form)]
 (fn [{:keys [db user] :as cofx}]
   (cond-> {:graphql {:token user
                      :owns-state {:single ::page}
                      :query-obj {:venia/queries [[:profit-and-loss
                                                   {:client-ids (map :id (:clients (:data db)))
                                                    :periods (mapv (fn [[start end] ] {:start (date->str start standard) :end (date->str end standard)} )
                                                                   (:periods (:data db)))}
                                                   [[:periods [[:accounts [:name :amount :client_id :account-type :id :count :numeric-code :location]]]]]]]} 
                      :on-success [::received]}
            :set-uri-params {:periods (mapv (fn [[start end title]]
                                              [(date->str start standard)
                                               (date->str end standard)])
                                            (:periods (:data db)))
                             :clients (mapv #(select-keys % [:name :id]) (:clients (:data db))) }
            :db (dissoc db :report)})))





(re-frame/reg-event-db
 ::change
 (forms/change-handler ::form
                       (fn  [data field value]
                         (cond
                           (= [:periods] field)
                           [field (mapv (fn [period]
                                          (mapv
                                           (fn [dt]
                                             (str->date dt standard))
                                           period))
                                        value)]

                           (and (= :periods (first field))
                                (= 2 (count field)))
                           [field value] ;;already serialized

                           (= :periods (first field)) [field (str->date value standard) ]

                           :else nil)
                         )))


(defn data-params->query-params [params client-id]
  (when params 
    {:start (:start params 0)
     :sort (:sort params)
     :per-page (:per-page params)
     :vendor-id (:id (:vendor params))
     :client-id client-id
     :from-numeric-code (:from-numeric-code params)
     :to-numeric-code (:to-numeric-code params)
     :location (:location params)
     :date-range (:date-range params)}))

(re-frame/reg-event-fx
 ::ledger-params-change
 [with-user]
 (fn [{:keys [user db]} [_ ledger-params]]
   
   (if (seq ledger-params)
     {:graphql {:token user
                :owns-state {:single [::data-page/page ::ledger]}
                :query-obj {:venia/queries [[:ledger-page
                                             {:filters (data-params->query-params ledger-params
                                                                                  (->> (get-in db [::forms/forms ::form :data])
                                                                                       :clients
                                                                                       first
                                                                                       :id ))}
                                             [[:journal-entries [:id
                                                                 :source
                                                                 :original-entity
                                                                 :note
                                                                 :amount
                                                                 :alternate-description
                                                                 [:vendor
                                                                  [:name :id]]
                                                                 [:client
                                                                  [:name :id]]
                                                                 [:line-items
                                                                  [:id :debit :credit :location :running-balance
                                                                   [:account [:id :name]]]]
                                                                 :date]]
                                              :total
                                              :start
                                              :end]]]} 
                :on-success (fn [result]
                              
                              [::data-page/received ::ledger (set/rename-keys (:ledger-page result)
                                                                              {:journal-entries :data})])}})))

(re-frame/reg-event-fx
 ::investigate-clicked
 (fn [{:keys [db]} [_ location from-numeric-code to-numeric-code which]]
   (let [[from to] (get @(re-frame/subscribe [::periods]) which)]
     {:db (-> db (assoc ::ledger-list-active? true))
      :dispatch [::data-page/additional-params-changed ::ledger {:client-id (:id @(re-frame/subscribe [::subs/client]))
                                                                 :from-numeric-code from-numeric-code
                                                                 :to-numeric-code to-numeric-code
                                                                 :location location
                                                                 :date-range {:start (date->str from standard)
                                                                              :end (date->str to standard)}}]})))

(re-frame/reg-sub
 ::can-submit-period-form
 (fn []
   true))

(def change-period-form (forms/vertical-form {:submit-event [::change-period]
                                                        :change-event [::forms/change ::period-form]
                                                        :can-submit [::can-submit-period-form]
                                                        :id ::period-form}))

(defn change-period-body [idx which]
  (let [{:keys [data active? error id]} @(re-frame/subscribe [::forms/form ::period-form])
        {:keys [form-inline horizontal-field field raw-field error-notification submit-button]} change-period-form]
    (form-inline {}
                 [:<> 
                  (field "From"
                         [date-picker {:class-name "input"
                                       :class "input"
                                       :format-week-number (fn [] "")
                                       :previous-month-button-label ""
                                       :placeholder "mm/dd/yyyy"
                                       :next-month-button-label ""
                                       :next-month-label ""
                                       :type "date"
                                       :field [:period 0]
                                       :popper-props (clj->js {:placement "right"})
                                       }]  )
                  (field "To"
                         [date-picker {:class-name "input"
                                       :class "input"
                                       :format-week-number (fn [] "")
                                       :previous-month-button-label ""
                                       :placeholder "mm/dd/yyyy"
                                       :next-month-button-label ""
                                       :next-month-label ""
                                       :type "date"
                                       :field [:period 1]
                                       :popper-props (clj->js {:placement "right"})
                                       }]  )])))
(re-frame/reg-event-fx
 ::period-change-submitted
 (fn [{:keys [db]} [_ idx [start end] :as z]]
   { :db (-> db (forms/stop-form ::period-form))
    :dispatch-n [[::modal/modal-closed ]
                 [::change [:periods idx ] [start end]]]}))

(re-frame/reg-event-fx
 ::period-removed
 (fn [{:keys [db]} [_ idx which]]
   { :db (-> db (forms/stop-form ::period-form)
             (update-in [::forms/forms ::form :data :periods]
                        (fn [ps]
                          (->> ps
                               (map vector (range) )
                               (filter (fn [[i _]]
                                         (not= i idx)))
                               (map second)
                               (into [])))))
    :dispatch [::modal/modal-closed ]}))

(defn change-period-foot [idx which]
  [:div.buttons
   [:button.button {:class "is-primary"
                    :on-click (dispatch-event [::period-change-submitted idx
                                               (-> @(re-frame/subscribe [::forms/form ::period-form])
                                                   :data
                                                   :period
                                                   (update 0 #(if (instance? goog.date.Date %)
                                                                %
                                                                (str->date % standard)))
                                                   (update 1 #(if (instance? goog.date.Date %)
                                                                %
                                                                (str->date % standard)))
                                                )])}
    "Change period"]
   [:button.button {:class "is-warning"
                    :on-click (dispatch-event [::period-removed idx ])}
    "Delete period"]]
  
  )
(re-frame/reg-event-fx
 ::period-change-requested
 (fn [{:keys [db]} [_ idx which]]
   { :db (-> db
             (forms/start-form ::period-form
                               {:idx idx
                                :period which}))
    :dispatch [::modal/modal-requested {:title (str "Change period " (date->str (nth which 0)) " - " (date->str (nth which 1))) 
                                        :class ""
                                        :body [change-period-body idx which]
                                        :foot [change-period-foot idx which]}]}))

(def groupings
  {:sales [["40000-43999 Food Sales " 40000 43999]
           ["44000-46999 Alcohol Sales" 44000 46999]
           ["47000 Merchandise Sales" 47000 47999]
           ["48000 Other Operating Income" 48000 48999]
           ["49000 Non-Business Income" 49000 49999]]
   :cogs [
          ["50000-54000 Food Costs" 50000 53999]
          ["54000-56000 Alcohol Costs" 54000 55999]
          ["56000 Merchandise Costs" 56000 56999]
          ["57000-60000 Other Costs of Sales" 57000 59999]]
   :payroll [["60000 Payroll - General" 60000 60999]
             ["61000 Payroll - Management" 61000 61999]
             ["62000 Payroll - BOH" 62000 62999]
             ["63000-66000 Payroll - FOH" 63000 65999]
             ["66000-70000 Payroll - Other" 66000 69999]]

   :controllable [["70000 72000 GM Controllable Costs - Ops Related" 70000 71999]
                  ["72000 GM Controllable Costs - Customer Related" 72000 72999]
                  ["73000 GM Controllable Costs - Employee Related" 73000 73999]
                  ["74000 GM Controllable Costs - Building & Equipment Related" 74000 74999]
                  ["75000 GM Controllable Costs - Office & Management Related" 75000 75999]
                  ["76000-80000 GM Controllable Costs - Other" 76000 79999]]

   :fixed-overhead [["80000-82000 Operational Costs" 80000 81999]
                    ["82000 Occupancy Costs" 82000 82999]
                    ["83000 Utility Costs" 83000 83999]
                    ["84000 Equipment Rental" 84000 84999]
                    ["85000-87000 Taxes & Insurance" 85000 86999]
                    ["87000-90000 Other Non-Controllable Costs" 87000 89999]]
   :ownership-controllable [["90000-93000 Research & Entertainment" 90000 92999]
                            ["93000 Bank Charges & Interest" 93000 93999]
                            ["94000-96000 Other Owner Controllable Costs" 94000 95999]
                            ["96000 Depreciation" 96000 96999]
                            ["97000 Taxes" 97000 97999]
                            ["98000 Other Expenses" 98000 98999]]})

(defn percent-of-sales [amount accounts which location]
  (let [sales (aggregate-accounts (filter-accounts accounts which (get ranges :sales) location))]
    (if (not (dollars-0? sales))
      (/ amount
         sales)
      0.0)))

(defn used-accounts [accounts [from to] location]
  (->> accounts
       (mapcat vals)
       (filter #(<= from (:numeric-code %) to))
       (filter #(= location (:location %)))
       (map #(select-keys % [:numeric-code :name]))
       (set)
       (sort-by :numeric-code)))

(defn map-periods [for-every between periods include-deltas]
  (for [[_ i] (map vector periods (range))]
    ^{:key (str "period-" i)}
    [:<>
     (with-meta
       (for-every i)
       {:key i})
     (if (and include-deltas (not= 0 i))
       (with-meta (between i)
         {:key (str "between-" i)}))]))


;; TODO Allow choosing more than one client
;; TODO allow "sandwhiching" clients data
;; TODO do we need squashing locations?

(defn grouping [{:keys [header type groupings location periods all-accounts]}]
  (let [include-deltas @(re-frame/subscribe [::include-deltas])
        multi-client? @(re-frame/subscribe [::multi-client?])]
    [:<>
     (doall 
      (for [[grouping-name from to] groupings
            :let [account-codes (used-accounts all-accounts [from to] location)]
            :when (seq account-codes)]
        ^{:key grouping-name}
        [:<>
         [:tr [:td "---" grouping-name "---"]
          (map-periods
           (fn [i]
             [:<>
              [:td]
              [:td]])
           (fn [i]
             [:td])
           periods
           include-deltas)]
         [:<>
          (for [{:keys [numeric-code name]} account-codes]
            ^{:key numeric-code}
            [:tr
             [:td name]
             (map-periods
              (fn [i]
                (let [amount (get-in all-accounts [i [numeric-code location] :amount] 0.0)]
                  [:<>
                   [:td.has-text-right (if multi-client?
                                         [:span (->$ amount)]
                                         [:a {:on-click (dispatch-event [::investigate-clicked location numeric-code numeric-code i :current])
                                              :disabled (boolean multi-client?)}
                                          (->$ amount)])]
                   [:td.has-text-right (->% (percent-of-sales amount all-accounts i location))]]))
              (fn [i]
                [:td.has-text-right (->$ (- (get-in all-accounts [i [numeric-code location] :amount] 0.0)
                                            (get-in all-accounts [(dec i) [numeric-code location] :amount] 0.0)))])
              periods
              include-deltas)])]

         [:tr
          [:th]
          (map-periods
           (fn [i]
             (let [amount (aggregate-accounts (filter-accounts all-accounts i [from to] location))]
               [:<>
                [:th.has-text-right.total (if multi-client?
                                            [:span (->$ amount)]
                                            [:a {:on-click (dispatch-event [::investigate-clicked location from to i])}
                                             (->$ amount)])]
                [:th.has-text-right.total (->% (percent-of-sales amount all-accounts i location))]]))
           (fn [i]
             [:th.has-text-right.total (->$ (- (aggregate-accounts (filter-accounts all-accounts i [from to] location))
                                               (aggregate-accounts (filter-accounts all-accounts (dec i) [from to] location))))])
           periods
           include-deltas)]]))]))



(defn overall-grouping [type title location]
  (let [all-accounts @(re-frame/subscribe [::all-accounts])
        periods @(re-frame/subscribe [::periods])
        [min-numeric-code max-numeric-code] (ranges type)
        include-deltas @(re-frame/subscribe [::include-deltas])
        multi-client? @(re-frame/subscribe [::multi-client?])]
    [:<>
     [:tr [:th.is-size-5 title]]

     [grouping {:location location
                :groupings (type groupings)
                :periods periods
                :all-accounts all-accounts}]

     [:tr [:th.is-size-5 title]
      (map-periods
       (fn [i]
         (let [amount (aggregate-accounts (filter-accounts all-accounts i [min-numeric-code max-numeric-code] location))]
           [:<>
            [:th.has-text-right (if multi-client?
                                  [:span (->$ amount)]
                                  [:a
                                   {:on-click (dispatch-event [::investigate-clicked location min-numeric-code max-numeric-code i])}
                                   (->$ amount)])]
            [:th.has-text-right (->% (percent-of-sales amount all-accounts i location))]
            ]))
       (fn [i]
         [:th.has-text-right (->$ (- (aggregate-accounts (filter-accounts all-accounts i [min-numeric-code max-numeric-code] location))
                                     (aggregate-accounts (filter-accounts all-accounts (dec i) [min-numeric-code max-numeric-code] location))))])
       periods
       include-deltas)]]))

(defn subtotal [types negs title location]
  (let [all-accounts @(re-frame/subscribe [::all-accounts])
        periods @(re-frame/subscribe [::periods])
        include-deltas @(re-frame/subscribe [::include-deltas])]
    [:tr [:th.is-size-5 title]
     
     (map-periods
      (fn [i]
        (let [amount (aggregate-accounts (mapcat (fn [t]
                                                   (cond->> (filter-accounts all-accounts i (ranges t) location)
                                                     (negs t) (map #(update % :amount -))))
                                                 types))]
          [:<>
           [:td.has-text-right [:span (->$ amount)]]
           [:td.has-text-right (->% (percent-of-sales amount all-accounts i location))]]))
      (fn [i]
        [:td.has-text-right (->$ (- (aggregate-accounts (mapcat (fn [t]
                                                                  (cond->> (filter-accounts all-accounts i (ranges t) location)
                                                                    (negs t) (map #(update % :amount -))))
                                                                types))
                                    (aggregate-accounts (mapcat (fn [t]
                                                                  (cond->> (filter-accounts all-accounts (dec i) (ranges t) location)
                                                                    (negs t) (map #(update % :amount -))))
                                                                types))))])
      periods
      include-deltas)]))

(defn period-header [{:keys [include-deltas periods]}]
  [:<> 
   [:tr
    [:td.has-text-right "Period"]
    (map-periods
     (fn [i]
       [:<>
        [:td.has-text-centered {:colspan 2}
         [:a {:on-click (dispatch-event [::period-change-requested i (get periods i)])}
          (or (get-in periods [i 2])
              (str (date->str (get-in periods [i 0])) " - " (date->str (get-in periods [i 1]))))]]])
     (fn [i]
       [:td.has-text-right ""])
     periods
     include-deltas)]
   [:tr
    [:td.has-text-right ""]
    (map-periods
     (fn [i]
       [:<>
        [:td.has-text-right 
         "Amount"]
        [:td.has-text-right 
         "% Sales"]])
     (fn [i]
       [:td.has-text-right "𝝙"])
     periods
     include-deltas)]])

(defn location-rows [location]
  
  [:<>
   [overall-grouping :sales (str location " Sales") location]
   [overall-grouping :cogs (str location " COGS") location]
   [overall-grouping :payroll (str location " Payroll") location]
   [subtotal [:payroll :cogs] #{} (str location " Prime Costs") location]
   [subtotal [:sales :payroll :cogs] #{:payroll :cogs} (str location " Gross Profits") location]
   [overall-grouping :controllable (str location " Controllable Expenses") location]
   [overall-grouping :fixed-overhead (str location " Fixed Overhead") location]
   [overall-grouping :ownership-controllable (str location " Ownership Controllable") location]
   [subtotal [:controllable :fixed-overhead :ownership-controllable] #{} (str location " Overhead") location]
   [subtotal [:sales :cogs :payroll :controllable :fixed-overhead :ownership-controllable] #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable} (str location " Net Income") location]
   [subtotal [:sales :cogs :payroll :controllable :fixed-overhead :ownership-controllable] #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable} "Net Income" nil]])

(defn location-summary [location include-deltas]
  (let [periods @(re-frame/subscribe [::periods])]
    [:div
     [:h2.title.is-4.mb-4 location " Summary"]
     [:table.table.compact.balance-sheet.mb-6 
      [:tbody 
       [period-header {:include-deltas include-deltas
                       :periods periods}]
       
       
       [subtotal [:sales ] #{} "Sales" location]
       [subtotal [:cogs ] #{} "Cogs" location]
       [subtotal [:payroll ]#{}  "Payroll" location]
       [subtotal [:sales :payroll :cogs] #{:payroll :cogs} "Gross Profits" location]
       [subtotal [:controllable :fixed-overhead :ownership-controllable] #{} "Overhead" location]
       [subtotal [:sales :cogs :payroll :controllable :fixed-overhead :ownership-controllable] #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable} "Net Income" location]]]]))

(re-frame/reg-sub
 ::can-submit
 (fn [db]
   true))

(def pnl-form (forms/vertical-form {:can-submit [::can-submit]
                                    :change-event [::change]
                                    :submit-event [::params-change]
                                    :id ::form}))

(defn report-control-detail [{:keys [active box which]} children]
  (when (and @box
             (= which @active)) 
    (react-dom/createPortal (reagent/as-element
                             [:div.notification.is-light
                              [:a.delete {:on-click (fn [] (reset! active nil))}]
                              children
                              ])
                            @box)))

(defn report-controls [pnl-form]
  (let [!box (reagent/atom nil)
        active (reagent/atom nil)]
    (fn [pnl-form]
      (let [{:keys [form-inline field raw-field error-notification submit-button ]} pnl-form
            {:keys [data report active? error id]} @(re-frame/subscribe [::forms/form ::form])
            {:keys [periods selected-period include-deltas]} data]
        [:div.report-controls
         [:div.level.mb-2
          [:div.level-left
           [:div.level-item
            [buttons/dropdown {:on-click (fn [] (reset! active :clients))}
             [:span (str "Companies"
                         (when-let [clients (:clients data)] 
                           (str " (" (str/join ", " (map :name clients)) ")")))]]
            [report-control-detail {:active active :box !box :which :clients}
             [:div {:style {:width "20em"}}
              [:h4.subtitle "Companies"]
              [raw-field 
               [multi-field {:type "multi-field"
                             :field [:clients]
                             :template [[typeahead-v3 {:entities @(re-frame/subscribe [::subs/clients])
                                                       :entity->text :name
                                                       :type "typeahead-v3"}]]}]]
              ]]]
           [:div.level-item
            [buttons/dropdown {:on-click (fn [] (reset! active :range))}
             [:span (str "Range"
                         (when selected-period
                           (str " (" selected-period ")")))]]
            [report-control-detail {:active active :box !box :which :range}
             [:div 
              [:h4.subtitle "Range"]
              [:div.field.is-grouped
               [:div.control 
                [:div.field.has-addons
                 [:div.control
                  (raw-field 
                   [date-picker-friendly {:placeholder "End date"
                                          :type "date"
                                          :field [:thirteen-periods-end]}])]
                 [:div.control 
                  [:a.button
                   {:class (when (= selected-period "13 periods") "is-active")
                    :on-click (dispatch-event
                               [::forms/change ::form
                                [:periods]
                                (let [today (or (some-> (:thirteen-periods-end data) (str->date  standard))
                                                (local-today))]
                                  (into 
                                   [[(t/plus (t/minus today (t/weeks (* 13 4)))
                                             (t/days 1))
                                     today
                                     "Total"]]
                                   (for [i (range 13)]
                                     [(t/plus (t/minus today (t/weeks (* (inc i) 4)))
                                              (t/days 1))
                                      (t/minus today (t/weeks (* i 4)))])))
                                [:selected-period] "13 periods"])}
                   "13 periods"]]]]

               [:div.control 
                [:div.field.has-addons
                 [:div.control
                  (raw-field 
                   [date-picker-friendly {:placeholder "End date"
                                          :type "date"
                                          :field [:twelve-periods-end]}])
                  ]
                 [:div.control 
                  [:a.button
                   {:class (when (= selected-period "12 months") "is-active")
                    :on-click (dispatch-event
                               [::forms/change ::form
                                [:periods]
                                (let [end-date (or (some-> (:twelve-periods-end data) (str->date standard))
                                                   (local-today))
                                      this-month (t/local-date (t/year end-date)
                                                               (t/month end-date)
                                                               1)
                                      ]
                                  (into 
                                   [[(t/minus this-month (t/months 11))
                                     (t/minus (t/plus this-month (t/months 1))
                                              (t/days 1))
                                     "Total"]]
                                   (for [i (range 12)]
                                     [(t/minus this-month (t/months (- 11 i)))
                                      (t/minus (t/minus this-month (t/months (- 10 i)))
                                               (t/days 1))])))
                                [:selected-period] "12 months"])}
                   "12 months"]]]]

               [:div.control 
                [:a.button
                 {:class (when (= selected-period "Last week") "is-active")
                  :on-click (dispatch-event
                             [::forms/change ::form
                              [:periods]
                              (let [last-sunday (loop [current (local-today)]
                                                  (if (= 7 (t/day-of-week current))
                                                    current
                                                    (recur (t/minus current (t/period :days 1)))))]
                                
                                (and-last-year [(t/minus last-sunday (t/period :days 6)) last-sunday]))
                              [:selected-period] "Last week"])}
                 "Last week"]]
               [:div.control 
                [:a.button
                 {:class (when (= selected-period "Week to date") "is-active")
                  :on-click (dispatch-event
                             [::forms/change ::form
                              [:periods]
                              (and-last-year [(loop [current (local-today)]
                                                (if (= 1 (t/day-of-week current))
                                                  current
                                                  (recur (t/minus current (t/period :days 1)))))
                                              (local-today)])
                              [:selected-period] "Week to date"])}
                 "Week to date"]]
               [:div.control 
                [:a.button
                 {:class (when (= selected-period "Last Month") "is-active")
                  :on-click (dispatch-event

                             [::forms/change ::form
                              [:periods]
                              (and-last-year [(t/minus (t/local-date (t/year (local-today))
                                                                     (t/month (local-today))
                                                                     1)
                                                       (t/period :months 1))
                                              (t/minus (t/local-date (t/year (local-today))
                                                                     (t/month (local-today))
                                                                     1)
                                                       (t/period :days 1))])
                              [:selected-period] "Last Month"])}
                 "Last Month"]]
               [:div.control 
                [:a.button
                 {:class (when (= selected-period "Month to date") "is-active")
                  :on-click (dispatch-event
                             [::forms/change ::form
                              [:periods]
                              (and-last-year [(t/local-date (t/year (local-today))
                                                            (t/month (local-today))
                                                            1)
                                              (local-today)])
                              [:selected-period] "Month to date"]
                             )}
                 "Month to date"]]
               [:div.control 
                [:a.button
                 {:class (when (= selected-period "Year to date") "is-active")
                  :on-click (dispatch-event
                             [::forms/change ::form
                              [:periods]
                              (and-last-year [(t/local-date (t/year (local-today)) 1 1)
                                              (local-today)])
                              [:selected-period] "Year to date"])}
                 "Year to date"]]
               [:div.control 
                [:a.button
                 {:class (when (= selected-period "Full year") "is-active")
                  :on-click (dispatch-event
                             [::forms/change ::form
                              [:periods]
                              (and-last-year [(t/plus (t/minus (local-today) (t/period :years 1))
                                                      (t/period :days 1))
                                              (local-today)])
                              [:selected-period] "Full year"])}
                 "Full year"]]]
              [:div
               [:div.field
                [:label.checkbox
                 (raw-field 
                  [:input {:type "checkbox"
                           :field [:show-advanced?]}])
                 " Show Advanced"]]]
              (when (:show-advanced? data) 
                (doall 
                 (for [[_ i] (map vector periods (range))]
                   ^{:key i}
                   [:div.field.is-grouped
                    [:div.control
                     [:p.help "From"]
                     (raw-field
                      [date-picker-friendly {:type "date"
                                             :field [:periods i 0]}])]

                    [:div.control
                     [:p.help "To"]
                     (raw-field
                      [date-picker-friendly {:type "date"
                                             :field [:periods i 1]}])]])))]]]

           [:div.level-item
            [:div
             [switch-field {:id "include-deltas"
                            :checked (boolean include-deltas)
                            :on-change (fn [e]
                                         (re-frame/dispatch [::forms/change ::form
                                                             [:include-deltas] (.-checked (.-target e))]))
                            :label "Include deltas"
                            :type "checkbox"}]]]]
          [:div.level-right
           [:button.button.is-primary "Run"]]]
         [:div.report-control-detail {:ref (fn [el]
                                             (when-not @!box
                                               (reset! !box el)))}]]))))


(defn profit-and-loss-content []
  (let [user @(re-frame/subscribe [::subs/user])
        status @(re-frame/subscribe [::status/single ::page])
        unresolved-accounts @(re-frame/subscribe [::uncategorized-accounts])
        {:keys [data report active? error id]} @(re-frame/subscribe [::forms/form ::form])
        {:keys [form-inline field raw-field error-notification submit-button ]} pnl-form
        periods (:periods data)]
    (form-inline {}
                 [:div 
                  [status/status-notification {:statuses [[::status/single ::page]]}]
                  [report-controls pnl-form]
                  
                  [status/big-loader status]
                  (when (and (not= :loading (:state status))
                             report)

                    [:div 

                     [:h1.title "Profit and Loss - " (str/join ", " (map :name (:clients data)))]
                     (when (seq unresolved-accounts)
                       [:div.notification.is-warning.is-light
                        "This report does not include " (str/join ", "
                                                                  (map #(str (:count %) " unresolved ledger entries for " (if (str/blank? (:location %))
                                                                                                                            " all locations"
                                                                                                                            (:location %)))
                                                                       unresolved-accounts))])
                     [:<>
                      (for [location @(re-frame/subscribe [::locations])]
                        ^{:key (str location "-summary")}
                        [location-summary location (:include-deltas data)]
                        )]
                     [:h2.title.is-4 {:style {:margin-bottom "1rem"}} "Detail"]
                     [:table.table.compact.balance-sheet
                      [:tbody 
                       [period-header {:include-deltas (:include-deltas data)
                                       :periods periods}]
                       
                       [:<>
                        (for [location @(re-frame/subscribe [::locations])]
                          ^{:key location}
                          [location-rows location])]]]])])))

(re-frame/reg-event-fx
 ::unmounted-pnl
 (fn [{:keys [db]} _]
   {:dispatch [::data-page/dispose ::ledger]
    ::track/dispose {:id ::ledger-params}}))

(re-frame/reg-event-fx
 ::mounted-pnl
 (fn [{:keys [db]} _]
   (let [qp (query-params)]
     {:db (forms/start-form db ::form {:periods (->> qp
                                                     :periods
                                                     (mapv (fn [period]
                                                                     (mapv
                                                                      (fn [dt]
                                                                        (str->date dt standard))
                                                                      period))))
                                       
                                       :clients (or (:clients qp)
                                                    [(some-> @(re-frame/subscribe [::subs/client]) (select-keys [:name :id]))])
                                       :include-deltas true})
      ::track/register {:id ::ledger-params
                        :subscription [::data-page/params ::ledger]
                        :event-fn (fn [params] [::ledger-params-change params])}})))

(defn ledger-list [_ ]
  [:div [:a.delete.is-pulled-right {:on-click (dispatch-event [::ledger-list-closing])}]
   [:div
    [:h1.title "Ledger entries"]
    [ledger-table/table {:id :ledger
                         :data-page ::ledger}]]])

(defn profit-and-loss-page []
  (reagent/create-class
   {:display-name "profit-and-loss-page"
    :component-did-mount #(re-frame/dispatch [::mounted-pnl])
    :component-will-unmount #(re-frame/dispatch [::unmounted-pnl])
    :reagent-render
    (fn []
      (let [ledger-list-active? @(re-frame/subscribe [::ledger-list-active?])
            user (re-frame/subscribe [::subs/user])]
        (if (not= "manager" (:user/role @user))
          [side-bar-layout
           {:side-bar [ledger-side-bar] 
            :main [profit-and-loss-content]
            :right-side-bar [appearing-side-bar
                             {:visible? ledger-list-active?}
                             [ledger-list]]}]
          [:div "Not authorized"])))}))
