(ns re-com.popover
  (:require-macros [re-com.core :refer [handler-fn]])
  (:require [re-com.util     :refer [get-element-by-id px deref-or-value sum-scroll-offsets]]
            [re-com.box      :refer [h-box v-box flex-child-style flex-flow-style align-style]]
            [re-com.validate :refer [position? position-options-list popover-status-type? popover-status-types-list number-or-string?
                                     string-or-hiccup? string-or-atom? vector-of-maps? css-style? html-attr?] :refer-macros [validate-args-macro]]
            [clojure.string  :as    string]
            [reagent.core    :as    reagent]
            [reagent.ratom   :refer-macros [reaction]]))


(defn point
  [x y]
  (str x "," y " "))


(defn- split-keyword
  "Return the vector of the two keywords formed by splitting another keyword 'kw' on an internal delimiter (usually '-')
   (split-keyword  :above-left  \"-\") => [:above :left]"
  [kw delimiter]
  (let [keywords (string/split (str kw) (re-pattern (str "[" delimiter ":]")))]
    [(keyword (keywords 1)) (keyword (keywords 2))]))


(defn- close-button
  "A button with a big X in it, placed to the right of the popover title"
  [showing? close-callback style]
  ;; Can't use [button] because [button] already uses [popover] which would be a circular dependency.
  [:button
   {:on-click (handler-fn
                (if close-callback
                  (close-callback)
                  (reset! showing? false)))
    :class    "close"
    :style    (merge {:width     "34px"
                      :font-size "26px"
                      :position  "absolute"
                      :top       "4px"
                      :right     "2px"}
                     style)}
   [:i {:class "zmdi zmdi-hc-fw-rc zmdi-close"}]])


(defn- calc-popover-pos
  "Determine values for :left :right :top :bottom CSS styles.
   - pop-orient    What side of the anchor the popover will be attached to. One of :above :below :left :right
   - p-width       The px width of the popover after it has been rendered
   - p-height      The px height of the popover after it has been rendered
   - pop-offset    The number of pixels the popover is offset from it's natural position in relation to the popover-arrow (ugh, hard to explain)
   - arrow-length  The px length of the arrow (from the point to middle of arrow base)
   - arrow-gap     The px distance between the anchor and the arrow tip. Positive numbers push the popover away from the anchor
  "
  [pop-orient p-width p-height pop-offset arrow-length arrow-gap]
  (let [total-offset   (+ arrow-length arrow-gap)
        popover-left   (case pop-orient
                         :left           "initial" ;; TODO: Ultimately remove this (must have NO :left which is in Bootstrap .popover class)
                         :right          (px total-offset)
                         (:above :below) (px (if pop-offset pop-offset (/ p-width 2)) :negative))
        popover-top    (case pop-orient
                         (:left :right)  (px (if pop-offset pop-offset (/ p-height 2)) :negative)
                         :above          "initial"
                         :below          (px total-offset))
        popover-right  (case pop-orient
                         :left           (px total-offset)
                         :right          nil
                         :above          nil
                         :below          nil)
        popover-bottom (case pop-orient
                         :left           nil
                         :right          nil
                         :above          (px total-offset)
                         :below          nil)]
    {:left popover-left :top popover-top :right popover-right :bottom popover-bottom}))


(defn calculate-optimal-position
  "Calculate the optimal :position value that results in the least amount of clipping by the screen edges
  Taken from: https://github.com/Lambda-X/cljs-repl-web/blob/0.3.1/src/cljs/cljs_repl_web/views/utils.cljs#L52
  Thanks to @richiardiandrea and @tomek for this code"
  [[x y]]
  (let [w (.-innerWidth   js/window) ;; Width/height of the browser window viewport including, if rendered, the vertical scrollbar
        h (.-innerHeight  js/window)
        h-threshold-left  (quot w 3)
        h-threshold-cent  (* 2 h-threshold-left)
        h-position        (cond
                            (< x h-threshold-left) "right"
                            (< x h-threshold-cent) "center"
                            :else "left")
        v-threshold       (quot h 2)
        v-position        (if (< y v-threshold) "below" "above")]
    (keyword (str v-position \- h-position))))


(defn calc-element-midpoint
  "Given a node reference, calculate the absolute x and y coordinates of the node's midpoint"
  [node]
  (let [bounding-rect (.getBoundingClientRect node)]
    [(/ (+ (.-right  bounding-rect) (.-left bounding-rect)) 2)    ;; x
     (/ (+ (.-bottom bounding-rect) (.-top  bounding-rect)) 2)])) ;; y


(defn- popover-arrow
  "Render the triangle which connects the popover to the anchor (using SVG)"
  [orientation pop-offset arrow-length arrow-width grey-arrow? no-border? popover-color]
  (let [half-arrow-width (/ arrow-width 2)
        arrow-shape {:left  (str (point 0 0)            (point arrow-length half-arrow-width) (point 0 arrow-width))
                     :right (str (point arrow-length 0) (point 0 half-arrow-width)            (point arrow-length arrow-width))
                     :above (str (point 0 0)            (point half-arrow-width arrow-length) (point arrow-width 0))
                     :below (str (point 0 arrow-length) (point half-arrow-width 0)            (point arrow-width arrow-length))}]
    [:svg {:class "popover-arrow"
           :style {:position "absolute"
                   (case orientation ;; Connect arrow to edge of popover
                     :left  :right
                     :right :left
                     :above :bottom
                     :below :top) (px arrow-length :negative)

                   (case orientation ;; Position the arrow at the top/left, center or bottom/right of the popover
                     (:left  :right) :top
                     (:above :below) :left) (if (nil? pop-offset) "50%" (px pop-offset))

                   (case orientation ;; Adjust the arrow position so it's center is attached to the desired position set above
                     (:left  :right) :margin-top
                     (:above :below) :margin-left) (px half-arrow-width :negative)

                   :width (px (case orientation ;; Arrow is rendered in a rectangle so choose the correct edge length
                                (:left  :right) arrow-length
                                (:above :below) arrow-width))

                   :height (px (case orientation ;; Same as :width comment above
                                 (:left  :right) arrow-width
                                 (:above :below) arrow-length))}}
     [:polyline {:points (arrow-shape orientation)
                 :style {:fill (if popover-color
                                 popover-color
                                 (if grey-arrow? "#f7f7f7" "white"))
                         :stroke (when-not no-border? "rgba(0, 0, 0, .2)")
                         :stroke-width "1"}}]]))


;;--------------------------------------------------------------------------------------------------
;; Component: backdrop
;;--------------------------------------------------------------------------------------------------

(def backdrop-args-desc
  [{:name :opacity  :required false :default 0.0 :type "double | string" :validate-fn number-or-string? :description [:span "opacity of backdrop from:" [:br] "0.0 (transparent) to 1.0 (opaque)"]}
   {:name :on-click :required false              :type "-> nil"          :validate-fn fn?               :description "a function which takes no params and returns nothing. Called when the backdrop is clicked"}])

(defn- backdrop
  "Renders a backdrop dive which fills the entire page and responds to clicks on it. Can also specify how tranparent it should be"
  [& {:keys [opacity on-click] :as args}]
  {:pre [(validate-args-macro backdrop-args-desc args "backdrop")]}
  [:div {:class     "rc-backdrop noselect"
         :style    {:position         "fixed"
                    :left             "0px"
                    :top              "0px"
                    :width            "100%"
                    :height           "100%"
                    :background-color "black"
                    :opacity          (if opacity opacity 0.0)}
         :on-click (handler-fn (on-click))}])


;;--------------------------------------------------------------------------------------------------
;; Component: popover-title
;;--------------------------------------------------------------------------------------------------

(def popover-title-args-desc
  [{:name :showing?       :required true                 :type "boolean atom"                                   :description "an atom. When the value is true, the popover shows."}
   {:name :title          :required false                :type "string | hiccup" :validate-fn string-or-hiccup? :description "describes the title of the popover. Default font size is 18px to make it stand out"}
   {:name :close-button?  :required false  :default true :type "boolean"                                        :description "when true, displays the close button"}
   {:name :close-callback :required false                :type "-> nil"          :validate-fn fn?               :description [:span "a function which takes no params and returns nothing. Called when the close button is pressed. Not required if " [:code ":showing?"] " atom passed in OR " [:code ":close-button?"] " is set to false"]}])

(defn- popover-title
  "Renders a title at the top of a popover with an optional close button on the far right"
  [& {:keys [title showing? close-button? close-callback]
      :as args}]
  {:pre [(validate-args-macro popover-title-args-desc args "popover-title")]}
  (assert (or ((complement nil?) showing?) ((complement nil?) close-callback)) "Must specify either showing? OR close-callback")
  (let [close-button? (if (nil? close-button?) true close-button?)]
    [:h3.popover-title {:style (merge (flex-child-style "inherit")
                                      {:font-size "18px"})}
     [h-box
      :justify  :between
      :align    :center
      :children [title
                 (when close-button? [close-button showing? close-callback])]]]))


;;--------------------------------------------------------------------------------------------------
;; Component: popover-border
;;--------------------------------------------------------------------------------------------------

(defn next-even-integer
  [num]
  (-> num inc (/ 2) int (* 2)))

(defn calc-pop-offset
  [arrow-pos position-offset p-width p-height]
  (case arrow-pos
    :center nil
    :right  (+ 20 position-offset)
    :below  (+ 20 position-offset)
    :left   (if p-width (- (- p-width 25) position-offset) p-width)
    :above  (if p-height (- (- p-height 25) position-offset) p-height)))

(defn popover-clipping
  [node]
  (let [viewport-width  (.-innerWidth   js/window)    ;; Width  (in pixels) of the browser window viewport including, if rendered, the vertical   scrollbar.
        viewport-height (.-innerHeight  js/window)    ;; Height (in pixels) of the browser window viewport including, if rendered, the horizontal scrollbar.
        bounding-rect   (.getBoundingClientRect node)
        left            (.-left   bounding-rect)
        right           (.-right  bounding-rect)
        top             (.-top    bounding-rect)
        bottom          (.-bottom bounding-rect)
        clip-left       (when (< left 0) (- left))
        clip-right      (when (> right viewport-width) (- right viewport-width))
        clip-top        (when (< top 0) (- top))
        clip-bottom     (when (> bottom viewport-height) (- bottom viewport-height))]
    #_(when (or (some? clip-left) (some? clip-right) (some? clip-top) (some? clip-bottom))  ;; Return full clipping details (or nil if not clipped)
        {:left clip-left :right clip-right :top clip-top :bottom clip-bottom})
    (or (some? clip-left) (some? clip-right) (some? clip-top) (some? clip-bottom))))        ;; Return boolean

(def popover-border-args-desc
  [{:name :children        :required true                        :type "vector"           :validate-fn sequential?       :description "a vector of component markups"}
   {:name :position        :required true                        :type "keyword atom"     :validate-fn position?         :description [:span "relative to this anchor. One of " position-options-list]}
   {:name :position-offset :required false                       :type "integer"          :validate-fn number?           :description [:span "px offset of the arrow from its default " [:code ":position"] " along the popover border. Is ignored when " [:code ":position"] " is one of the " [:code ":xxx-center"] " variants. Positive numbers slide the popover toward its center"]}
   {:name :width           :required false                       :type "string"           :validate-fn string?           :description "a CSS style describing the popover width"}
   {:name :height          :required false :default "auto"       :type "string"           :validate-fn string?           :description "a CSS style describing the popover height"}
   {:name :popover-color   :required false :default "white"      :type "string"           :validate-fn string?           :description "fill color of the popover"}
   {:name :arrow-length    :required false :default 11           :type "integer | string" :validate-fn number-or-string? :description "the length in pixels of the arrow (from pointy part to middle of arrow base)"}
   {:name :arrow-width     :required false :default 22           :type "integer | string" :validate-fn number-or-string? :description "the width in pixels of arrow base"}
   {:name :arrow-gap       :required false :default -1           :type "integer"          :validate-fn number?           :description "px gap between the anchor and the arrow tip. Positive numbers push the popover away from the anchor"}
   {:name :padding         :required false                       :type "string"           :validate-fn string?           :description "a CSS style which overrides the inner padding of the popover"}
   {:name :margin-left     :required false                       :type "string"           :validate-fn string?           :description "a CSS style describing the horiztonal offset from anchor after position"}
   {:name :margin-top      :required false                       :type "string"           :validate-fn string?           :description "a CSS style describing the vertical offset from anchor after position"}
   {:name :tooltip-style?  :required false :default false        :type "boolean"                                         :description "setup popover styles for a tooltip"}
   {:name :title           :required false                       :type "string | markup"                                 :description "describes a title"}])

(defn popover-border
  "Renders an element or control along with a Bootstrap popover"
  [& {:keys [children position position-offset width height popover-color arrow-length arrow-width arrow-gap padding margin-left margin-top tooltip-style? title]
      :or {arrow-length 11 arrow-width 22 arrow-gap -1}
      :as args}]
  {:pre [(validate-args-macro popover-border-args-desc args "popover-border")]}
  (let [pop-id                  (gensym "popover-")
        rendered-once           (reagent/atom false)        ;; The initial render is off screen because rendering it in place does not render at final width, and we need to measure it to be able to place it properly
        ready-to-show?          (reagent/atom false)        ;; This is used by the optimal position code to avoid briefly seeing it in its intended position before quickly moving to the optimal position
        p-width                 (reagent/atom 0)
        p-height                (reagent/atom 0)
        pop-offset              (reagent/atom 0)
        found-optimal           (reagent/atom false)
        calc-metrics            (fn [position]
                                  (let [popover-elem            (get-element-by-id pop-id)
                                        [orientation arrow-pos] (split-keyword position "-")
                                        grey-arrow?             (and title (or (= orientation :below) (= arrow-pos :below)))]
                                    (reset! p-width    (if popover-elem (next-even-integer (.-clientWidth  popover-elem)) 0)) ;; next-even-integer required to avoid wiggling popovers (width/height appears to prefer being even and toggles without this call)
                                    (reset! p-height   (if popover-elem (next-even-integer (.-clientHeight popover-elem)) 0))
                                    (reset! pop-offset (calc-pop-offset arrow-pos position-offset @p-width @p-height))
                                    [orientation grey-arrow?]))]
    (reagent/create-class
      {:display-name "popover-border"

       :component-did-mount
       (fn []
         (reset! rendered-once true))

       :component-did-update
       (fn [this]
         (let [pop-border-node (reagent/dom-node this)
               clipped?        (popover-clipping pop-border-node)
               anchor-node     (-> pop-border-node .-parentNode .-parentNode .-parentNode)] ;; Get reference to rc-point-wrapper node
           (when (and clipped? (not @found-optimal))
             (reset! position (calculate-optimal-position (calc-element-midpoint anchor-node)))
             (reset! found-optimal true))
           (calc-metrics @position)
           (reset! ready-to-show? true)))

       :reagent-render
       (fn
         [& {:keys [children position position-offset width height popover-color arrow-length arrow-width arrow-gap padding margin-left margin-top tooltip-style? title]
             :or {arrow-length 11 arrow-width 22 arrow-gap -1}
             :as args}]
         {:pre [(validate-args-macro popover-border-args-desc args "popover-border")]}
         (let [[orientation grey-arrow?] (calc-metrics @position)]
           [:div.popover.fade.in
            {:id pop-id
             :style (merge (if @rendered-once
                             (when pop-id (calc-popover-pos orientation @p-width @p-height @pop-offset arrow-length arrow-gap))
                             {:top "-10000px" :left "-10000px"})

                           (if width  {:width  width})
                           (if height {:height height})
                           (if popover-color {:background-color popover-color})
                           (when tooltip-style?
                             {:border-radius "4px"
                              :box-shadow    "none"
                              :border        "none"})

                           ;; The popover point is zero width, therefore its absolute children will consider this width when deciding their
                           ;; natural size and in particular, how they natually wrap text. The right hand side of the popover is used as a
                           ;; text wrapping point so it will wrap, depending on where the child is positioned. The margin is also taken into
                           ;; consideration for this point so below, we set the margins to negative a lot to prevent
                           ;; this annoying wrapping phenomenon.
                           (case orientation
                             :left                  {:margin-left  "-2000px"}
                             (:right :above :below) {:margin-right "-2000px"})
                           ;; optional override offsets
                           (when margin-left {:margin-left margin-left})
                           (when margin-top  {:margin-top  margin-top})

                           ;; make it visible and turn off Bootstrap max-width and remove Bootstrap padding which adds an internal white border
                           {:display   "block"
                            :opacity   (if @ready-to-show? "1" "0")
                            :max-width "none"
                            :padding   "0px"})}
            [popover-arrow orientation @pop-offset arrow-length arrow-width grey-arrow? tooltip-style? popover-color]
            (when title title)
            (into [:div.popover-content {:style {:padding padding}}] children)]))})))


;;--------------------------------------------------------------------------------------------------
;; Component: popover-content-wrapper
;;--------------------------------------------------------------------------------------------------

(def popover-content-wrapper-args-desc
  [{:name :showing-injected? :required true                         :type "boolean atom"                                    :description [:span "an atom. When the value is true, the popover shows." [:br] [:strong "NOTE: "] "When used as direct " [:code ":popover"] " arg in popover-anchor-wrapper, this arg will be injected automatically by popover-anchor-wrapper. If using your own popover function, you must add this yourself"]}
   {:name :position-injected :required true                         :type "keyword atom"     :validate-fn position?         :description [:span "relative to this anchor. One of " position-options-list [:br] [:strong "NOTE: "] "See above NOTE for " [:code ":showing-injected?"] ". Same applies" ]}
   {:name :position-offset   :required false                        :type "integer"          :validate-fn number?           :description [:span "px offset of the arrow from its default " [:code ":position"] " along the popover border. Is ignored when " [:code ":position"] " is one of the " [:code ":xxx-center"] " variants. Positive numbers slide the popover toward its center"]}
   {:name :no-clip?          :required false  :default false        :type "boolean"                                         :description "when an anchor is in a scrolling region (e.g. scroller component), the popover can sometimes be clipped. By passing true for this parameter, re-com will use a different CSS method to show the popover. This method is slightly inferior because the popover can't track the anchor if it is repositioned"}
   {:name :width             :required false                        :type "string"           :validate-fn string?           :description "a CSS style representing the popover width"}
   {:name :height            :required false                        :type "string"           :validate-fn string?           :description "a CSS style representing the popover height"}
   {:name :backdrop-opacity  :required false  :default 0.0          :type "double | string"  :validate-fn number-or-string? :description "indicates the opacity of the backdrop where 0.0=transparent, 1.0=opaque"}
   {:name :on-cancel         :required false                        :type "-> nil"           :validate-fn fn?               :description "a function which takes no params and returns nothing. Called when the popover is cancelled (e.g. user clicks away)"}
   {:name :title             :required false                        :type "string | hiccup"  :validate-fn string-or-hiccup? :description "describes the title of the popover. The default font size is 18px to make it stand out"}
   {:name :close-button?     :required false  :default true         :type "boolean"                                         :description "when true, displays the close button"}
   {:name :body              :required false                        :type "string | hiccup"  :validate-fn string-or-hiccup? :description "describes the popover body. Must be a single component"}
   {:name :tooltip-style?    :required false  :default false        :type "boolean"                                         :description "setup popover styles for a tooltip"}
   {:name :popover-color     :required false  :default "white"      :type "string"           :validate-fn string?           :description "fill color of the popover"}
   {:name :arrow-length      :required false  :default 11           :type "integer | string" :validate-fn number-or-string? :description "the length in pixels of the arrow (from pointy part to middle of arrow base)"}
   {:name :arrow-width       :required false  :default 22           :type "integer | string" :validate-fn number-or-string? :description "the width in pixels of arrow base"}
   {:name :arrow-gap         :required false  :default -1           :type "integer"          :validate-fn number?           :description "px gap between the anchor and the arrow tip. Positive numbers push the popover away from the anchor"}
   {:name :padding           :required false                        :type "string"           :validate-fn string?           :description "a CSS style which overrides the inner padding of the popover"}
   {:name :style             :required false                        :type "CSS style map"    :validate-fn css-style?        :description "override component style(s) with a style map, only use in case of emergency"}])

(defn popover-content-wrapper
  "Abstracts several components to handle the 90% of cases for general popovers and dialog boxes"
  [& {:keys [showing-injected? position-injected position-offset no-clip? width height backdrop-opacity on-cancel title close-button? body tooltip-style? popover-color arrow-length arrow-width arrow-gap padding style]
      :or {arrow-length 11 arrow-width 22 arrow-gap -1}
      :as args}]
  {:pre [(validate-args-macro popover-content-wrapper-args-desc args "popover-content-wrapper")]}
  (let [left-offset              (reagent/atom 0)
        top-offset               (reagent/atom 0)
        position-no-clip-popover (fn position-no-clip-popover
                                   [this]
                                   (when no-clip?
                                     (let [node               (reagent/dom-node this)
                                           popover-point-node (.-parentNode node)                           ;; Get reference to rc-popover-point node
                                           bounding-rect      (.getBoundingClientRect popover-point-node)]  ;; The modern magical way of getting offsetLeft and offsetTop. Returns this: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIDOMClientRect
                                       (reset! left-offset (.-left bounding-rect))
                                       (reset! top-offset  (.-top  bounding-rect)))))]
    (reagent/create-class
      {:display-name "popover-content-wrapper"

       :component-did-mount
       (fn [this]
         (position-no-clip-popover this))

       :component-did-update
       (fn [this]
         (position-no-clip-popover this))

       :reagent-render
       (fn
         [& {:keys [showing-injected? position-injected position-offset no-clip? width height backdrop-opacity on-cancel title close-button? body tooltip-style? popover-color arrow-length arrow-width arrow-gap padding style]
             :or {arrow-length 11 arrow-width 22 arrow-gap -1}
             :as args}]
         {:pre [(validate-args-macro popover-content-wrapper-args-desc args "popover-content-wrapper")]}

         @position-injected ;; Dereference this atom. Although nothing here needs its value explicitly, the calculation of left-offset and top-offset are affected by it for :no-clip? true
         [:div
          {:class "popover-content-wrapper"
           :style (merge (flex-child-style "inherit")
                         (when no-clip? {:position "fixed"
                                         :left      (px @left-offset)
                                         :top       (px @top-offset)})
                         style)}
          (when (and @showing-injected? on-cancel)
            [backdrop
             :opacity  backdrop-opacity
             :on-click on-cancel])
          [popover-border
           :position        position-injected
           :position-offset position-offset
           :width           width
           :height          height
           :tooltip-style?  tooltip-style?
           :popover-color   popover-color
           :arrow-length    arrow-length
           :arrow-width     arrow-width
           :arrow-gap       arrow-gap
           :padding         padding
           :title           (when title [popover-title
                                         :title          title
                                         :showing?       showing-injected?
                                         :close-button?  close-button?
                                         :close-callback on-cancel])
           :children        [body]]])})))


;;--------------------------------------------------------------------------------------------------
;; Component: popover-anchor-wrapper
;;--------------------------------------------------------------------------------------------------

(def popover-anchor-wrapper-args-desc
  [{:name :showing? :required true                        :type "boolean atom"                                   :description "an atom. When the value is true, the popover shows"}
   {:name :position :required true                        :type "keyword"         :validate-fn position?         :description [:span "relative to this anchor. One of " position-options-list]}
   {:name :anchor   :required true                        :type "string | hiccup" :validate-fn string-or-hiccup? :description "the component the popover is attached to"}
   {:name :popover  :required true                        :type "string | hiccup" :validate-fn string-or-hiccup? :description "the popover body component"}
   {:name :style    :required false                       :type "CSS style map"   :validate-fn css-style?        :description "override component style(s) with a style map, only use in case of emergency"}])

(defn popover-anchor-wrapper
  "Renders an element or control along with a Bootstrap popover"
  [& {:keys [showing? position anchor popover style] :as args}]
  {:pre [(validate-args-macro popover-anchor-wrapper-args-desc args "popover-anchor-wrapper")]}
  (let [external-position (reagent/atom position)
        internal-position (reagent/atom @external-position)
        reset-on-hide     (reaction (when-not @showing? (reset! internal-position @external-position)))]
    (reagent/create-class
      {:display-name "popover-anchor-wrapper"

       :reagent-render
       (fn
         [& {:keys [showing? position anchor popover style] :as args}]
         {:pre [(validate-args-macro popover-anchor-wrapper-args-desc args "popover-anchor-wrapper")]}

         @reset-on-hide ;; Dereference this reaction, otherwise it won't be set up. The reaction is set to run whenever the popover closes
         (when (not= @external-position position) ;; Has position changed externally?
           (reset! external-position position)
           (reset! internal-position @external-position))
         (let [[orientation arrow-pos] (split-keyword @internal-position "-") ;; only need orientation here
               place-anchor-before?    (case orientation (:left :above) false true)
               flex-flow               (case orientation (:left :right) "row" "column")]
           [:div {:class  "rc-popover-anchor-wrapper display-inline-flex"
                  :style (merge (flex-child-style "inherit")
                                style)}
            [:div                                ;; Wrapper around the anchor and the "point"
             {:class "rc-point-wrapper display-inline-flex"
              :style (merge (flex-child-style "auto")
                            (flex-flow-style flex-flow)
                            (align-style :align-items :center))}
             (when place-anchor-before? anchor)
             (when @showing?
               [:div                             ;; The "point" that connects the anchor to the popover
                {:class "rc-popover-point display-inline-flex"
                 :style (merge (flex-child-style "auto")
                               {:position "relative"
                                :z-index  4})}
                (into popover [:showing-injected? showing? :position-injected internal-position])]) ;; NOTE: Inject showing? and position to the popover
             (when-not place-anchor-before? anchor)]]))})))


;;--------------------------------------------------------------------------------------------------
;; Component: popover-tooltip
;;--------------------------------------------------------------------------------------------------

(def popover-tooltip-args-desc
  [{:name :label         :required true                         :type "string | hiccup | atom" :validate-fn string-or-hiccup?    :description "the text (or component) for the tooltip"}
   {:name :showing?      :required true                         :type "boolean atom"                                             :description "an atom. When the value is true, the tooltip shows"}
   {:name :on-cancel     :required false                        :type "-> nil"                 :validate-fn fn?                  :description "a function which takes no params and returns nothing. Called when the popover is cancelled (e.g. user clicks away)"}
   {:name :close-button? :required false :default false         :type "boolean"                                                  :description "when true, displays the close button"}
   {:name :status        :required false                        :type "keyword"                :validate-fn popover-status-type? :description [:span "controls background color of the tooltip. " [:code "nil/omitted"] " for black or one of " popover-status-types-list " (although " [:code ":validating"] " is only used by the input-text component)"]}
   {:name :anchor        :required true                         :type "hiccup"                 :validate-fn string-or-hiccup?    :description "the component the tooltip is attached to"}
   {:name :position      :required false :default :below-center :type "keyword"                :validate-fn position?            :description [:span "relative to this anchor. One of " position-options-list]}
   {:name :no-clip?      :required false :default true          :type "boolean"                                                  :description "when an anchor is in a scrolling region (e.g. scroller component), the popover can sometimes be clipped. When this parameter is true (which is the default), re-com will use a different CSS method to show the popover. This method is slightly inferior because the popover can't track the anchor if it is repositioned"}
   {:name :width         :required false                        :type "string"                 :validate-fn string?              :description "specifies width of the tooltip"}
   {:name :style         :required false                        :type "CSS style map"          :validate-fn css-style?           :description "override component style(s) with a style map, only use in case of emergency"}])

(defn popover-tooltip
  "Renders text as a tooltip in Bootstrap popover style"
  [& {:keys [label showing? on-cancel close-button? status anchor position no-clip? width style]
      :or {no-clip? true}
      :as args}]
  {:pre [(validate-args-macro popover-tooltip-args-desc args "popover-tooltip")]}
  (let [label         (deref-or-value label)
        popover-color (case status
                        :warning "#f57c00"
                        :error   "#d50000"
                        :info    "#333333"
                        :success "#13C200"
                        "black")]
    [popover-anchor-wrapper
     :showing? showing?
     :position (or position :below-center)
     :anchor   anchor
     :style    style
     :popover [popover-content-wrapper
               :no-clip?       no-clip?
               :on-cancel      on-cancel
               :width          width
               :tooltip-style? true
               :popover-color  popover-color
               :padding        "3px 8px"
               :arrow-length   6
               :arrow-width    12
               :arrow-gap      4
               :body           [v-box
                                :style (if (= status :info)
                                         {:color       "white"
                                          :font-size   "14px"
                                          :padding     "4px"}
                                         {:color       "white"
                                          :font-size   "12px"
                                          :font-weight "bold"
                                          :text-align  "center"})
                                :children [label (when close-button?
                                                   [close-button showing? on-cancel {:font-size   "20px"
                                                                                     :color       "white"
                                                                                     :text-shadow "none"
                                                                                     :right       "1px"}])]]]]))
