(ns re-com.tabs
  (:require-macros [re-com.core :refer [handler-fn]])
  (:require [re-com.util     :refer [deref-or-value]]
            [re-com.box      :refer [flex-child-style]]
            [re-com.validate :refer [css-style? vector-of-maps?] :refer-macros [validate-args-macro]]))


;;--------------------------------------------------------------------------------------------------
;; Component: horizontal-tabs
;;--------------------------------------------------------------------------------------------------

(def tabs-args-desc
  [{:name :tabs      :required true                  :type "vector of tabs | atom"    :validate-fn vector-of-maps? :description "one element in the vector for each tab. Typically, each element is a map with :id and :label keys"}
   {:name :model     :required true                  :type "unique-id | atom"                                      :description "the unique identifier of the currently selected tab"}
   {:name :on-change :required true                  :type "unique-id -> nil"         :validate-fn fn?             :description "called when user alters the selection. Passed the unique identifier of the selection"}
   {:name :id-fn     :required false :default :id    :type "tab -> anything"          :validate-fn ifn?            :description [:span "given an element of " [:code ":tabs"] ", returns its unique identifier (aka id)"]}
   {:name :label-fn  :required false :default :label :type "tab -> string | hiccup"   :validate-fn ifn?            :description [:span "given an element of " [:code ":tabs"] ", returns its displayable label"]}
   {:name :style     :required false                 :type "CSS style map"            :validate-fn css-style?      :description "CSS styles to add or override (for each individual tab rather than the container)"}])

(defn horizontal-tabs
  [& {:keys [model tabs on-change id-fn label-fn style]
      :or   {id-fn :id label-fn :label}
      :as   args}]
  {:pre [(validate-args-macro tabs-args-desc args "tabs")]}
  (let [current  (deref-or-value model)
        tabs     (deref-or-value tabs)
        _        (assert (not-empty (filter #(= current (id-fn %)) tabs)) "model not found in tabs vector")]
    [:ul
     {:class "rc-tabs nav nav-tabs noselect"
      :style (flex-child-style "none")}
     (for [t tabs]
       (let [id        (id-fn  t)
             label     (label-fn  t)
             selected? (= id current)]                   ;; must use current instead of @model to avoid reagent warnings
         [:li
          {:class (if selected? "active")
           :key   (str id)}
          [:a
           {:style    (merge {:cursor "pointer"}
                             style)
            :on-click (when on-change (handler-fn (on-change id)))}
           label]]))]))


;;--------------------------------------------------------------------------------------------------
;; Component: horizontal-bar-tabs
;;--------------------------------------------------------------------------------------------------

(defn- bar-tabs
  [& {:keys [model tabs on-change id-fn label-fn style vertical?]}]
  (let [current  (deref-or-value model)
        tabs     (deref-or-value tabs)
        _        (assert (not-empty (filter #(= current (id-fn %)) tabs)) "model not found in tabs vector")]
    [:div
     {:class (str "rc-tabs noselect btn-group" (if vertical? "-vertical"))
      :style (flex-child-style "none")}
     (for [t tabs]
       (let [id        (id-fn  t)
             label     (label-fn  t)
             selected? (= id current)]                    ;; must use current instead of @model to avoid reagent warnings
         [:button
          {:type     "button"
           :key      (str id)
           :class    (str "btn btn-default "  (if selected? "active"))
           :style    style
           :on-click (when on-change (handler-fn (on-change id)))}
          label]))]))


(defn horizontal-bar-tabs
  [& {:keys [model tabs on-change id-fn label-fn style]
      :or   {id-fn :id label-fn :label}
      :as   args}]
  {:pre [(validate-args-macro tabs-args-desc args "tabs")]}
  (bar-tabs
    :model     model
    :tabs      tabs
    :on-change on-change
    :style     style
    :id-fn     id-fn
    :label-fn  label-fn
    :vertical? false))

(defn vertical-bar-tabs
  [& {:keys [model tabs on-change id-fn label-fn style]
      :or   {id-fn :id label-fn :label}
      :as   args}]
  {:pre [(validate-args-macro tabs-args-desc args "tabs")]}
  (bar-tabs
    :model     model
    :tabs      tabs
    :on-change on-change
    :style     style
    :id-fn     id-fn
    :label-fn  label-fn
    :vertical? true))


;;--------------------------------------------------------------------------------------------------
;; Component: pill-tabs
;;--------------------------------------------------------------------------------------------------

(defn- pill-tabs    ;; tabs-like in action
  [& {:keys [model tabs on-change id-fn label-fn style vertical?]}]
  (let [current  (deref-or-value model)
        tabs     (deref-or-value tabs)
        _        (assert (not-empty (filter #(= current (id-fn %)) tabs)) "model not found in tabs vector")]
    [:ul
     {:class (str "rc-tabs noselect nav nav-pills" (when vertical? " nav-stacked"))
      :style (flex-child-style "none")
      :role  "tabslist"}
     (for [t tabs]
       (let [id        (id-fn  t)
             label     (label-fn  t)
             selected? (= id current)]                   ;; must use 'current' instead of @model to avoid reagent warnings
         [:li
          {:class    (if selected? "active" "")
           :key      (str id)}
          [:a
           {:style     (merge {:cursor "pointer"}
                              style)
            :on-click  (when on-change (handler-fn (on-change id)))}
           label]]))]))


(defn horizontal-pill-tabs
  [& {:keys [model tabs on-change id-fn style label-fn]
      :or   {id-fn :id label-fn :label}
      :as   args}]
  {:pre [(validate-args-macro tabs-args-desc args "tabs")]}
  (pill-tabs
    :model     model
    :tabs      tabs
    :on-change on-change
    :style     style
    :id-fn     id-fn
    :label-fn  label-fn
    :vertical? false))


(defn vertical-pill-tabs
  [& {:keys [model tabs on-change id-fn style label-fn]
      :or   {id-fn :id label-fn :label}
      :as   args}]
  {:pre [(validate-args-macro tabs-args-desc args "tabs")]}
  (pill-tabs
    :model     model
    :tabs      tabs
    :on-change on-change
    :style     style
    :id-fn     id-fn
    :label-fn  label-fn
    :vertical? true))
