;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC Sucursal en España SL

(ns app.main.ui.workspace.sidebar.options.rows.stroke-row
  (:require-macros [app.main.style :as stl])
  (:require
   [app.common.data :as d]
   [app.common.types.color :as ctc]
   [app.main.data.workspace.colors :as dc]
   [app.main.data.workspace.tokens.application :as dwta]
   [app.main.features :as features]
   [app.main.store :as st]
   [app.main.ui.components.numeric-input :as deprecated-input]
   [app.main.ui.components.reorder-handler :refer [reorder-handler*]]
   [app.main.ui.components.select :refer [select]]
   [app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
   [app.main.ui.ds.controls.select :refer [select*]]
   [app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
   [app.main.ui.hooks :as h]
   [app.main.ui.workspace.sidebar.options.common :as soc]
   [app.main.ui.workspace.sidebar.options.menus.input-wrapper-tokens :refer [numeric-input-wrapper*]]
   [app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row*]]
   [app.util.i18n :as i18n :refer [tr]]
   [rumext.v2 :as mf]))

(mf/defc stroke-row*
  [{:keys [index
           stroke
           title
           show-caps
           on-color-change
           on-reorder
           on-color-detach
           on-remove
           on-stroke-width-change
           per-side-available
           per-side-disabled
           on-stroke-per-side-toggle
           on-stroke-width-side-change
           on-stroke-dash-change
           on-stroke-gap-change
           on-stroke-style-change
           on-stroke-alignment-change
           on-stroke-cap-start-change
           on-stroke-cap-end-change
           on-stroke-cap-switch
           on-toggle-visibility
           disable-drag
           on-focus
           on-blur
           applied-tokens
           on-detach-token
           disable-stroke-style
           select-on-focus
           ids]}]

  (let [hidden?            (:hidden stroke)
        hidden?            (if (nil? hidden?) false hidden?)

        token-numeric-inputs
        (features/use-feature "tokens/numeric-input")

        on-drop
        (mf/use-fn
         (mf/deps on-reorder index)
         (fn [relative-pos data]
           (let [from-pos             (:index data)
                 to-space-between-pos (if (= relative-pos :bot) (inc index) index)]
             (on-reorder from-pos to-space-between-pos))))

        [dprops dref]
        (if (some? on-reorder)
          (h/use-sortable
           :data-type "penpot/stroke-row"
           :on-drop on-drop
           :disabled @disable-drag
           :detect-center? false
           :data {:index index})
          [nil nil])

        stroke-color-token
        (:stroke-color applied-tokens)

        on-color-change-refactor
        (mf/use-fn
         (mf/deps index on-color-change)
         (fn [color]
           (on-color-change index color)))

        on-color-detach
        (mf/use-fn
         (mf/deps index on-color-detach)
         (fn [_ color]
           (on-color-detach index color)))

        on-remove
        (mf/use-fn
         (mf/deps index on-remove)
         #(on-remove index))

        stroke-width (:stroke-width stroke)

        per-side? (and per-side-available
                       (not per-side-disabled)
                       (true? (:stroke-per-side stroke)))

        per-side-expanded* (mf/use-state false)
        per-side-expanded? (deref per-side-expanded*)

        all-sides-equal?
        (mf/with-memo [stroke]
          (let [width  (:stroke-width stroke)
                top    (d/nilv (:stroke-width-top stroke) width)
                right  (d/nilv (:stroke-width-right stroke) width)
                bottom (d/nilv (:stroke-width-bottom stroke) width)
                left   (d/nilv (:stroke-width-left stroke) width)]
            (= top right bottom left)))

        show-multiple-placeholder? (or per-side-expanded? (not all-sides-equal?))

        per-side-toggle-label
        (if per-side-disabled
          (tr "workspace.options.stroke.per-side-disabled")
          (tr "workspace.options.stroke.per-side"))

        on-per-side-toggle
        (mf/use-fn
         (mf/deps per-side? on-stroke-per-side-toggle index)
         (fn []
           (if per-side?
             (swap! per-side-expanded* not)
             (do
               (when on-stroke-per-side-toggle
                 (on-stroke-per-side-toggle index))
               (reset! per-side-expanded* true)))))

        on-width-top-change
        (mf/use-fn
         (mf/deps index on-stroke-width-side-change)
         #(on-stroke-width-side-change index :stroke-width-top %))

        on-width-right-change
        (mf/use-fn
         (mf/deps index on-stroke-width-side-change)
         #(on-stroke-width-side-change index :stroke-width-right %))

        on-width-bottom-change
        (mf/use-fn
         (mf/deps index on-stroke-width-side-change)
         #(on-stroke-width-side-change index :stroke-width-bottom %))

        on-width-left-change
        (mf/use-fn
         (mf/deps index on-stroke-width-side-change)
         #(on-stroke-width-side-change index :stroke-width-left %))

        on-width-change
        (mf/use-fn
         (mf/deps index on-stroke-width-change ids per-side?)
         (fn [value]
           (if per-side?
             (st/emit! (dc/change-stroke-attrs
                        ids
                        {:stroke-width value
                         :stroke-width-top value
                         :stroke-width-right value
                         :stroke-width-bottom value
                         :stroke-width-left value}
                        index))
             (soc/emit-value-or-token
              value
              #(on-stroke-width-change index %)
              ids
              #{:stroke-width}))))

        ;; The SVG renderer defaults dash and gap to `stroke-width + 10` when
        ;; unset. Showing that value as placeholder makes the override obvious.
        default-dash-gap (when (number? stroke-width) (+ 10 stroke-width))

        stroke-gap  (or (:stroke-gap stroke) default-dash-gap)
        stroke-dash (or (:stroke-dash stroke) default-dash-gap)

        on-dash-change
        (mf/use-fn
         (mf/deps index on-stroke-dash-change)
         #(on-stroke-dash-change index %))

        on-gap-change
        (mf/use-fn
         (mf/deps index on-stroke-gap-change)
         #(on-stroke-gap-change index %))

        stroke-alignment (or (:stroke-alignment stroke) :center)

        stroke-alignment-options
        (mf/with-memo [stroke-alignment]
          (d/concat-vec
           (when (= :multiple stroke-alignment)
             [{:value :multiple :label "--"}])
           [{:value :center :label (tr "workspace.options.stroke.center") :id "center" :icon "stroke-center"}
            {:value :inner :label (tr "workspace.options.stroke.inner") :id "inner" :icon "stroke-inside"}
            {:value :outer :label (tr "workspace.options.stroke.outer") :id "outer" :icon "stroke-outside"}]))

        on-alignment-change
        (mf/use-fn
         (mf/deps index on-stroke-alignment-change)
         #(on-stroke-alignment-change index (keyword %)))

        on-token-change
        (mf/use-fn
         (mf/deps ids)
         (fn [_ token]
           (st/emit!
            (dwta/apply-token-from-input {:token token
                                          :attrs #{:stroke-color}
                                          :shape-ids ids
                                          :expand-with-children true}))))

        stroke-style (or (:stroke-style stroke) :solid)

        stroke-style-options
        (mf/with-memo [stroke-style]
          (d/concat-vec
           (when (= :multiple stroke-style)
             [{:value :multiple :label "--"}])
           [{:value :solid :label (tr "workspace.options.stroke.solid") :id "solid" :icon "stroke-solid"}
            {:value :dotted :label (tr "workspace.options.stroke.dotted") :id "dotted" :icon "stroke-dotted"}
            {:value :dashed :label (tr "workspace.options.stroke.dashed") :id "dashed" :icon "stroke-dashed"}
            {:value :mixed :label (tr "workspace.options.stroke.mixed") :id "mixed" :icon "stroke-mixed"}]))

        on-style-change
        (mf/use-fn
         (mf/deps index on-stroke-style-change)
         #(on-stroke-style-change index (keyword %)))

        on-caps-start-change
        (mf/use-fn
         (mf/deps index on-stroke-cap-start-change)
         (fn [cap]
           (let [cap (if (= cap "none")
                       nil
                       (keyword cap))]
             (on-stroke-cap-start-change index cap))))

        on-caps-end-change
        (mf/use-fn
         (mf/deps index on-stroke-cap-end-change)
         (fn [cap]
           (let [cap (if (= cap "none")
                       nil
                       (keyword cap))]
             (on-stroke-cap-end-change index cap))))

        on-detach-token-color
        (mf/use-fn
         (mf/deps on-detach-token)
         (fn [token]
           (on-detach-token token #{:stroke-color})))

        on-detach-token-width
        (mf/use-fn
         (mf/deps on-detach-token)
         (fn [token]
           (on-detach-token token #{:stroke-width})))

        stroke-caps-options
        [{:id "none" :value "none" :label (tr "workspace.options.stroke-cap.none")}
         {:label "" :type :separator :id "separator"}
         {:id "line-arrow" :value :line-arrow :label (tr "workspace.options.stroke-cap.line-arrow-short") :icon i/stroke-arrow}
         {:id "triangle-arrow" :value :triangle-arrow :label (tr "workspace.options.stroke-cap.triangle-arrow-short") :icon i/stroke-triangle}
         {:id "square-marker" :value :square-marker :label (tr "workspace.options.stroke-cap.square-marker-short") :icon i/stroke-rectangle}
         {:id "circle-marker" :value :circle-marker :label (tr "workspace.options.stroke-cap.circle-marker-short") :icon i/stroke-circle}
         {:id "diamond-marker" :value :diamond-marker :label (tr "workspace.options.stroke-cap.diamond-marker-short") :icon i/stroke-diamond}
         {:label "" :type :separator :id "separator"}
         {:id "round" :value :round :label (tr "workspace.options.stroke-cap.round") :icon i/stroke-rounded}
         {:id "square" :value :square :label (tr "workspace.options.stroke-cap.square") :icon i/stroke-squared}]

        on-cap-switch
        (mf/use-fn
         (mf/deps index on-stroke-cap-switch)
         #(on-stroke-cap-switch index))

        on-toggle-visibility
        (mf/use-fn
         (mf/deps index on-toggle-visibility)
         (fn []
           (when on-toggle-visibility
             (on-toggle-visibility index))))]

    [:div {:class (stl/css-case
                   :stroke-data true
                   :hidden hidden?
                   :dnd-over-top (= (:over dprops) :top)
                   :dnd-over-bot (= (:over dprops) :bot))
           :aria-label (str "stroke-row-" index)}

     (when (some? on-reorder)
       [:> reorder-handler* {:ref dref}])

     ;; Stroke Color
     ;; FIXME: memorize stroke color
     [:div {:class (stl/css :stroke-color-actions)}
      [:> color-row* {:color (ctc/stroke->color stroke)
                      :index index
                      :title title
                      :on-change on-color-change-refactor
                      :on-detach on-color-detach
                      :disable-drag disable-drag
                      :applied-token (if (= index 0)
                                       stroke-color-token
                                       nil)
                      :on-detach-token on-detach-token-color
                      :on-token-change on-token-change
                      :on-focus on-focus
                      :origin :stroke-color
                      :select-on-focus select-on-focus
                      :on-blur on-blur}]

      (when (some? on-toggle-visibility)
        [:> icon-button* {:variant "ghost"
                          :aria-label (tr "workspace.options.stroke.toggle-stroke")
                          :on-click on-toggle-visibility
                          :icon (if hidden? "hide" "shown")}])

      [:> icon-button* {:variant "ghost"
                        :aria-label (tr "workspace.options.stroke.remove-stroke")
                        :on-click on-remove
                        :icon i/remove}]]

     ;; Stroke Width, Alignment & Style
     (if token-numeric-inputs
       [:div {:class (stl/css-case :stroke-options-tokens true
                                   :stroke-options-tokens-with-toggle per-side-available)}
        [:> numeric-input-wrapper* {:on-change on-width-change
                                    :on-detach on-detach-token-width
                                    :icon i/stroke-size
                                    :min 0
                                    :on-focus on-focus
                                    :on-blur on-blur
                                    :attr :stroke-width
                                    :class (stl/css :numeric-input-wrapper)
                                    :property (tr "workspace.options.stroke-width")
                                    :applied-token (get applied-tokens :stroke-width)
                                    :placeholder (if show-multiple-placeholder?
                                                   (tr "settings.multiple")
                                                   "--")
                                    :nillable true
                                    :value (if all-sides-equal?
                                             stroke-width
                                             nil)}]
        [:> select* {:default-selected (d/name stroke-alignment)
                     :options stroke-alignment-options
                     :variant "icon-only"
                     :data-testid "stroke.alignment"
                     :disabled (if (= :multiple hidden?) true hidden?)
                     :wrapper-class (stl/css :stroke-align-icon-select)
                     :on-change on-alignment-change}]

        (when-not disable-stroke-style
          [:> select* {:default-selected (d/name stroke-style)
                       :options stroke-style-options
                       :wrapper-class (stl/css :stroke-style-icon-select)
                       :data-testid "stroke.style"
                       :variant "icon-only"
                       :disabled (if (= :multiple hidden?) true hidden?)
                       :dropdown-alignment :right
                       :on-change on-style-change}])
        (when per-side-available
          [:> icon-button* {:variant "ghost"
                            :aria-pressed per-side-expanded?
                            :aria-label per-side-toggle-label
                            :disabled per-side-disabled
                            :on-click on-per-side-toggle
                            :icon i/stroke-extended
                            :data-testid "stroke.per-side-toggle"}])]

       [:div {:class (stl/css :stroke-options)}
        [:div {:class (stl/css :stroke-width-input)
               :title (tr "workspace.options.stroke-width")}
         [:> icon* {:icon-id i/stroke-size
                    :size "s"}]
         [:> deprecated-input/numeric-input* {:value (if show-multiple-placeholder?
                                                       nil
                                                       stroke-width)
                                              :min 0
                                              :placeholder (if show-multiple-placeholder?
                                                             (tr "settings.multiple")
                                                             "--")
                                              :on-change on-width-change
                                              :on-focus on-focus
                                              :select-on-focus select-on-focus
                                              :on-blur on-blur}]]
        [:div {:class (stl/css :stroke-alignment-select)
               :data-testid "stroke.alignment"}
         [:& select {:default-value stroke-alignment
                     :options stroke-alignment-options
                     :disabled hidden?
                     :on-change on-alignment-change}]]

        (when-not disable-stroke-style
          [:div {:class (stl/css-case :stroke-style-select true
                                      :stroke-style-select-compact per-side-available)
                 :data-testid "stroke.style"}
           [:& select {:default-value stroke-style
                       :options stroke-style-options
                       :disabled hidden?
                       :on-change on-style-change}]])
        (when per-side-available
          [:> icon-button* {:variant "ghost"
                            :aria-pressed per-side-expanded?
                            :aria-label per-side-toggle-label
                            :disabled per-side-disabled
                            :on-click on-per-side-toggle
                            :icon i/stroke-extended
                            :data-testid "stroke.per-side-toggle"}])])

     (when per-side-expanded?
       [:div {:class (stl/css :stroke-sides-options)
              :data-testid "stroke.per-side-options"}
        [:div {:class (stl/css :stroke-side-input)
               :title (tr "workspace.options.stroke-width-top")}
         [:> icon* {:icon-id i/stroke-top
                    :size "s"}]
         [:> deprecated-input/numeric-input* {:value (d/nilv (:stroke-width-top stroke) stroke-width)
                                              :min 0
                                              :on-change on-width-top-change
                                              :on-focus on-focus
                                              :select-on-focus select-on-focus
                                              :on-blur on-blur}]]
        [:div {:class (stl/css :stroke-side-input)
               :title (tr "workspace.options.stroke-width-right")}
         [:> icon* {:icon-id i/stroke-right
                    :size "s"}]
         [:> deprecated-input/numeric-input* {:value (d/nilv (:stroke-width-right stroke) stroke-width)
                                              :min 0
                                              :on-change on-width-right-change
                                              :on-focus on-focus
                                              :select-on-focus select-on-focus
                                              :on-blur on-blur}]]
        [:div {:class (stl/css :stroke-side-input)
               :title (tr "workspace.options.stroke-width-bottom")}
         [:> icon* {:icon-id i/stroke-bottom
                    :size "s"}]
         [:> deprecated-input/numeric-input* {:value (d/nilv (:stroke-width-bottom stroke) stroke-width)
                                              :min 0
                                              :on-change on-width-bottom-change
                                              :on-focus on-focus
                                              :select-on-focus select-on-focus
                                              :on-blur on-blur}]]
        [:div {:class (stl/css :stroke-side-input)
               :title (tr "workspace.options.stroke-width-left")}
         [:> icon* {:icon-id i/stroke-left
                    :size "s"}]
         [:> deprecated-input/numeric-input* {:value (d/nilv (:stroke-width-left stroke) stroke-width)
                                              :min 0
                                              :on-change on-width-left-change
                                              :on-focus on-focus
                                              :select-on-focus select-on-focus
                                              :on-blur on-blur}]]])

     ;; Stroke Dash / Gap (only visible for dashed style)
     (when (= stroke-style :dashed)
       [:div {:class (stl/css :stroke-dash-options)
              :data-testid "stroke.dash-options"}
        [:> numeric-input-wrapper* {:on-change on-dash-change
                                    :text-icon "DASH"
                                    :min 0
                                    :on-focus on-focus
                                    :on-blur on-blur
                                    :attr :stroke-dash
                                    :class (stl/css :numeric-input-wrapper)
                                    :property (tr "workspace.options.stroke-dash")
                                    :value stroke-dash}]
        [:> numeric-input-wrapper* {:on-change on-gap-change
                                    :text-icon "GAP"
                                    :min 0
                                    :on-focus on-focus
                                    :on-blur on-blur
                                    :attr :stroke-gap
                                    :tooltip-placement "top-left"
                                    :class (stl/css :numeric-input-wrapper)
                                    :property (tr "workspace.options.stroke-gap")
                                    :value stroke-gap}]])

     ;; Stroke Caps
     (when show-caps
       [:div {:class (stl/css :stroke-caps-options)}
        [:> select* {:default-selected (or (d/name (:stroke-cap-start stroke)) "none")
                     :options stroke-caps-options
                     :data-testid "stroke.cap-start"
                     :disabled hidden?
                     :on-change on-caps-start-change}]
        [:> icon-button* {:variant "secondary"
                          :aria-label (tr "labels.switch")
                          :disabled hidden?
                          :on-click on-cap-switch
                          :icon i/switch}]
        [:> select* {:default-selected (or (d/name (:stroke-cap-end stroke)) "none")
                     :options stroke-caps-options
                     :data-testid "stroke.cap-end"
                     :disabled hidden?
                     :on-change on-caps-end-change}]])]))
