;; 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.menus.layout-container
  (:require-macros [app.main.style :as stl])
  (:require
   [app.common.data :as d]
   [app.common.data.macros :as dm]
   [app.common.math :as mth]
   [app.common.types.shape.layout :as ctl]
   [app.config :as cf]
   [app.main.data.event :as-alias ev]
   [app.main.data.workspace :as udw]
   [app.main.data.workspace.grid-layout.editor :as dwge]
   [app.main.data.workspace.shape-layout :as dwsl]
   [app.main.data.workspace.tokens.application :as dwta]
   [app.main.features :as features]
   [app.main.refs :as refs]
   [app.main.store :as st]
   [app.main.ui.components.dropdown :refer [dropdown]]
   [app.main.ui.components.numeric-input :as deprecated-input]
   [app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
   [app.main.ui.components.select :refer [select]]
   [app.main.ui.components.title-bar :refer [title-bar*]]
   [app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
   [app.main.ui.ds.foundations.assets.icon :as i]
   [app.main.ui.formats :as fmt]
   [app.main.ui.hooks :as h]
   [app.main.ui.icons :as deprecated-icon]
   [app.main.ui.workspace.sidebar.options.common :as soc]
   [app.main.ui.workspace.sidebar.options.menus.input-wrapper-tokens :refer [numeric-input-wrapper*]]
   [app.util.dom :as dom]
   [app.util.i18n :as i18n :refer [tr]]
   [app.util.keyboard :as kbd]
   [cuerdas.core :as str]
   [rumext.v2 :as mf]))

(defn- dir-icons-refactor
  [val]
  (case val
    :row            i/grid-row
    :row-reverse    i/row-reverse
    :column         i/column
    :column-reverse i/column-reverse))

;; FLEX COMPONENTS

(def layout-container-flex-attrs
  [:layout                 ;; :flex, :grid in the future
   :layout-flex-dir        ;; :row, :row-reverse, :column, :column-reverse
   :layout-gap-type        ;; :simple, :multiple
   :layout-gap             ;; {:row-gap number , :column-gap number}

   :layout-align-items     ;; :start :end :center :stretch
   :layout-justify-content ;; :start :center :end :space-between :space-around :space-evenly
   :layout-align-content   ;; :start :center :end :space-between :space-around :space-evenly :stretch (by default)
   :layout-wrap-type       ;; :wrap, :nowrap
   :layout-padding-type    ;; :simple, :multiple
   :layout-padding         ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative

   :layout-grid-dir ;; :row :column
   :layout-justify-items
   :layout-grid-columns
   :layout-grid-rows])

(defn get-layout-flex-icon
  [type val ^boolean column?]
  (case type
    :align-items
    (if column?
      (case val
        :start    i/align-items-column-start
        :end      i/align-items-column-end
        :center   i/align-items-column-center)
      (case val
        :start    i/align-items-row-start
        :end      i/align-items-row-end
        :center   i/align-items-row-center))

    :justify-content
    (if column?
      (case val
        :start         i/justify-content-column-start
        :end           i/justify-content-column-end
        :center        i/justify-content-column-center
        :space-around  i/justify-content-column-around
        :space-evenly  i/justify-content-column-evenly
        :space-between i/justify-content-column-between)
      (case val
        :start         i/justify-content-row-start
        :end           i/justify-content-row-end
        :center        i/justify-content-row-center
        :space-around  i/justify-content-row-around
        :space-evenly  i/justify-content-row-evenly
        :space-between i/justify-content-row-between))

    :align-content
    (if column?
      (case val
        :start         i/align-content-column-start
        :end           i/align-content-column-end
        :center        i/align-content-column-center
        :space-around  i/align-content-column-around
        :space-evenly  i/align-content-column-evenly
        :space-between i/align-content-column-between
        :stretch nil)

      (case val
        :start         i/align-content-row-start
        :end           i/align-content-row-end
        :center        i/align-content-row-center
        :space-around  i/align-content-row-around
        :space-evenly  i/align-content-row-evenly
        :space-between i/align-content-row-between
        :stretch nil))

    :align-self
    (if column?
      (case val
        :auto     i/remove
        :start    i/align-self-row-left
        :end      i/align-self-row-right
        :center   i/align-self-row-center)
      (case val
        :auto     i/remove
        :start    i/align-self-column-top
        :end      i/align-self-column-bottom
        :center   i/align-self-column-center))))

(defn get-layout-grid-icon
  [type val ^boolean column?]
  (case type
    :align-items
    (if column?
      (case val
        :auto     i/remove
        :start    i/align-self-row-left
        :end      i/align-self-row-right
        :center   i/align-self-row-center)
      (case val
        :auto     i/remove
        :start    i/align-self-column-top
        :end      i/align-self-column-bottom
        :center   i/align-self-column-center))

    :justify-items
    (if (not column?)
      (case val
        :start         i/align-content-column-start
        :center        i/align-content-column-center
        :end           i/align-content-column-end
        :space-around  i/align-content-column-around
        :space-between i/align-content-column-between
        :stretch       i/align-content-column-stretch)
      (case val
        :start         i/align-content-row-start
        :center        i/align-content-row-center
        :end           i/align-content-row-end
        :space-around  i/align-content-row-around
        :space-between i/align-content-row-between
        :stretch       i/align-content-row-stretch))))

(mf/defc direction-row-flex
  {::mf/props :obj
   ::mf/private true}
  [{:keys [value on-change]}]
  [:& radio-buttons {:class (stl/css :direction-row-flex)
                     :selected (d/name value)
                     :decode-fn keyword
                     :on-change on-change
                     :name "flex-direction"}
   [:& radio-button {:value "row"
                     :id "flex-direction-row"
                     :title "Row"
                     :icon (dir-icons-refactor :row)}]
   [:& radio-button {:value "row-reverse"
                     :id "flex-direction-row-reverse"
                     :title "Row reverse"
                     :icon (dir-icons-refactor :row-reverse)}]
   [:& radio-button {:value "column"
                     :id "flex-direction-column"
                     :title "Column"
                     :icon (dir-icons-refactor :column)}]
   [:& radio-button {:value "column-reverse"
                     :id "flex-direction-column-reverse"
                     :title "Column reverse"
                     :icon (dir-icons-refactor :column-reverse)}]])

(mf/defc wrap-row
  {::mf/props :obj}
  [{:keys [wrap-type on-click]}]
  [:button {:class (stl/css-case :wrap-button true
                                 :selected (= wrap-type :wrap))
            :title (if (= :wrap wrap-type)
                     "No wrap"
                     "Wrap")
            :on-click on-click}
   deprecated-icon/wrap])

(mf/defc align-row
  {::mf/props :obj}
  [{:keys [is-column value on-change]}]
  [:& radio-buttons {:class (stl/css :align-row)
                     :selected (d/name value)
                     :decode-fn keyword
                     :on-change on-change
                     :name "flex-align-items"}
   [:& radio-button {:value "start"
                     :icon  (get-layout-flex-icon :align-items :start is-column)
                     :title "Align items start"
                     :id     "align-items-start"}]
   [:& radio-button {:value "center"
                     :icon  (get-layout-flex-icon :align-items :center is-column)
                     :title "Align items center"
                     :id    "align-items-center"}]
   [:& radio-button {:value "end"
                     :icon  (get-layout-flex-icon :align-items :end is-column)
                     :title "Align items end"
                     :id    "align-items-end"}]])

(mf/defc align-content-row
  {::mf/props :obj}
  [{:keys [is-column value on-change]}]
  [:& radio-buttons {:class (stl/css :align-content-row)
                     :selected (d/name value)
                     :decode-fn keyword
                     :on-change on-change
                     :name "flex-align-content"}
   [:& radio-button {:value "start"
                     :icon  (get-layout-flex-icon :align-content :start is-column)
                     :title "Align content start"
                     :id    "align-content-start"}]
   [:& radio-button {:value "center"
                     :icon  (get-layout-flex-icon :align-content :center is-column)
                     :title "Align content center"
                     :id    "align-content-center"}]
   [:& radio-button {:value "end"
                     :icon  (get-layout-flex-icon :align-content :end is-column)
                     :title "Align content end"
                     :id    "align-content-end"}]
   [:& radio-button {:value "space-between"
                     :icon  (get-layout-flex-icon :align-content :space-between is-column)
                     :title "Align content space-between"
                     :id    "align-content-space-between"}]
   [:& radio-button {:value "space-around"
                     :icon  (get-layout-flex-icon :align-content :space-around is-column)
                     :title "Align content space-around"
                     :id    "align-content-space-around"}]
   [:& radio-button {:value "space-evenly"
                     :icon  (get-layout-flex-icon :align-content :space-evenly is-column)
                     :title "Align content space-evenly"
                     :id    "align-content-space-evenly"}]])

(mf/defc justify-content-row
  {::mf/props :obj}
  [{:keys [is-column justify-content on-change]}]
  [:& radio-buttons {:class (stl/css :justify-content-row)
                     :selected (d/name justify-content)
                     :on-change on-change
                     :name "flex-justify"}
   [:& radio-button {:value "start"
                     :icon  (get-layout-flex-icon :justify-content :start is-column)
                     :title "Justify content start"
                     :id    "justify-content-start"}]
   [:& radio-button {:value "center"
                     :icon  (get-layout-flex-icon :justify-content :center is-column)
                     :title "Justify content center"
                     :id    "justify-content-center"}]
   [:& radio-button {:value "end"
                     :icon  (get-layout-flex-icon :justify-content :end is-column)
                     :title "Justify content end"
                     :id    "justify-content-end"}]
   [:& radio-button {:value "space-between"
                     :icon  (get-layout-flex-icon :justify-content :space-between is-column)
                     :title "Justify content space-between"
                     :id    "justify-content-space-between"}]
   [:& radio-button {:value "space-around"
                     :icon  (get-layout-flex-icon :justify-content :space-around is-column)
                     :title "Justify content space-around"
                     :id    "justify-content-space-around"}]
   [:& radio-button {:value "space-evenly"
                     :icon  (get-layout-flex-icon :justify-content :space-evenly is-column)
                     :title "Justify content space-evenly"
                     :id    "justify-content-space-evenly"}]])

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; PADDING
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defn- select-padding
  ([p]
   (select-padding (= p :p1) (= p :p2) (= p :p3) (= p :p4)))
  ([p1? p2? p3? p4?]
   (st/emit! (udw/set-paddings-selected {:p1 p1? :p2 p2? :p3 p3? :p4 p4?}))))

(defn- on-padding-blur
  [_event]
  (select-padding false false false false))

(mf/defc simple-padding-selection*
  [{:keys [value on-change applied-tokens ids]}]
  (let [token-numeric-inputs
        (features/use-feature "tokens/numeric-input")

        p1 (:p1 value)
        p2 (:p2 value)
        p3 (:p3 value)
        p4 (:p4 value)

        p1 (if (and (not (= :multiple value))
                    (= p1 p3))
             p1
             nil)

        p2 (if (and (not (= :multiple value))
                    (= p2 p4))
             p2
             nil)

        applied-to-p1 (:p1 applied-tokens)
        applied-to-p2 (:p2 applied-tokens)
        applied-to-p3 (:p3 applied-tokens)
        applied-to-p4 (:p4 applied-tokens)

        applied-to-p1 (if (= applied-to-p1 applied-to-p3)
                        applied-to-p1
                        nil)

        applied-to-p2 (if (= applied-to-p2 applied-to-p4)
                        applied-to-p2
                        nil)
        on-change'
        (mf/use-fn
         (mf/deps on-change ids)
         (fn [value attr event]
           (let [on-change-fn #(on-change :simple attr % event)]
             (soc/emit-value-or-token value on-change-fn ids attr))))

        on-detach-token
        (mf/use-fn
         (mf/deps ids)
         (fn [token-name attr]
           (st/emit! (dwta/unapply-token {:token-name token-name
                                          :attributes #{attr}
                                          :shape-ids ids}))))

        on-focus
        (mf/use-fn
         (mf/deps select-padding)
         (fn [attr event]
           (case attr
             :p1 (select-padding true false true false)
             :p2 (select-padding false true false true))

           (dom/select-target event)))

        on-focus-p1
        (mf/use-fn (mf/deps on-focus) #(on-focus :p1))

        on-focus-p2
        (mf/use-fn (mf/deps on-focus) #(on-focus :p2))

        on-p1-change
        (mf/use-fn (mf/deps on-change') #(on-change' % #{:p1 :p3}))

        on-p2-change
        (mf/use-fn (mf/deps on-change') #(on-change' % #{:p2 :p4}))]

    [:div {:class (stl/css :paddings-simple)}
     (if token-numeric-inputs
       [:> numeric-input-wrapper*
        {:on-change on-p1-change
         :on-detach on-detach-token
         :on-blur on-padding-blur
         :on-focus on-focus-p1
         :icon i/padding-top-bottom
         :min 0
         :attr :p1
         :input-type :vertical-padding
         :property (tr "workspace.layout-grid.editor.padding.vertical")
         :nillable true
         :placeholder (if (or (= :multiple applied-to-p1)
                              (= :multiple p1)
                              (nil? p1))
                        (tr "settings.multiple")
                        "--")
         :applied-token applied-to-p1
         :value p1}]

       [:div {:class (stl/css :padding-simple)
              :title (tr "workspace.layout-grid.editor.padding.vertical")}
        [:span {:class (stl/css :icon)}
         deprecated-icon/padding-top-bottom]
        [:> deprecated-input/numeric-input*
         {:class (stl/css :numeric-input)
          :placeholder (tr "settings.multiple")
          :aria-label (tr "workspace.layout-grid.editor.padding.vertical")
          :on-change on-p1-change
          :on-focus on-focus-p1
          :on-blur on-padding-blur
          :is-nillable true
          :min 0
          :value p1}]])

     (if token-numeric-inputs
       [:> numeric-input-wrapper*
        {:on-change on-p2-change
         :on-detach on-detach-token
         :on-blur on-padding-blur
         :on-focus on-focus-p2
         :icon i/padding-left-right
         :min 0
         :attr :p2
         :input-type :horizontal-padding
         :align :right
         :property (tr "workspace.layout-grid.editor.padding.horizontal")
         :nillable true
         :applied-token applied-to-p2
         :placeholder (if (or (= :multiple applied-to-p2)
                              (= :multiple p2)
                              (nil? p2))
                        (tr "settings.multiple")
                        "--")
         :value p2}]

       [:div {:class (stl/css :padding-simple)
              :title (tr "workspace.layout-grid.editor.padding.horizontal")}
        [:span {:class (stl/css :icon)}
         deprecated-icon/padding-left-right]
        [:> deprecated-input/numeric-input*
         {:class (stl/css :numeric-input)
          :placeholder (tr "settings.multiple")
          :aria-label (tr "workspace.layout-grid.editor.padding.horizontal")
          :on-change on-p2-change
          :on-focus on-focus-p2
          :on-blur on-padding-blur
          :min 0
          :is-nillable true
          :value p2}]])]))

(mf/defc multiple-padding-selection*
  [{:keys [value on-change applied-tokens ids]}]
  (let [token-numeric-inputs
        (features/use-feature "tokens/numeric-input")

        p1 (:p1 value)
        p2 (:p2 value)
        p3 (:p3 value)
        p4 (:p4 value)

        applied-to-p1 (:p1 applied-tokens)
        applied-to-p2 (:p2 applied-tokens)
        applied-to-p3 (:p3 applied-tokens)
        applied-to-p4 (:p4 applied-tokens)

        on-change'
        (mf/use-fn
         (mf/deps on-change ids)
         (fn [value attr event]
           (let [on-change-fn #(on-change :multiple attr % event)]
             (soc/emit-value-or-token value on-change-fn ids #{attr}))))

        on-focus
        (mf/use-fn
         (mf/deps select-padding)
         (fn [attr event]
           (select-padding attr)
           (dom/select-target event)))

        on-detach-token
        (mf/use-fn
         (mf/deps ids)
         (fn [token attr]
           (st/emit! (dwta/unapply-token {:token-name token
                                          :attributes #{attr}
                                          :shape-ids ids}))))

        on-p1-change
        (mf/use-fn (mf/deps on-change') #(on-change' % :p1))

        on-p2-change
        (mf/use-fn (mf/deps on-change') #(on-change' % :p2))

        on-p3-change
        (mf/use-fn (mf/deps on-change') #(on-change' % :p3))

        on-p4-change
        (mf/use-fn (mf/deps on-change') #(on-change' % :p4))

        on-focus-p1
        (mf/use-fn (mf/deps on-focus) #(on-focus :p1))

        on-focus-p2
        (mf/use-fn (mf/deps on-focus) #(on-focus :p2))

        on-focus-p3
        (mf/use-fn (mf/deps on-focus) #(on-focus :p3))

        on-focus-p4
        (mf/use-fn (mf/deps on-focus) #(on-focus :p4))]

    [:div {:class (stl/css :paddings-multiple)}
     (if token-numeric-inputs
       [:> numeric-input-wrapper*
        {:on-change on-p1-change
         :on-detach on-detach-token
         :on-blur on-padding-blur
         :on-focus on-focus-p1
         :icon i/padding-top
         :min 0
         :attr :p1
         :input-type :vertical-padding
         :property (tr "workspace.layout-grid.editor.padding.top")
         :placeholder (if (or (= :multiple applied-to-p1)
                              (= :multiple p1))
                        (tr "settings.multiple")
                        "--")
         :applied-token applied-to-p1
         :value p1}]

       [:div {:class (stl/css :padding-multiple)
              :title (tr "workspace.layout-grid.editor.padding.top")}
        [:span {:class (stl/css :icon)}
         deprecated-icon/padding-top]
        [:> deprecated-input/numeric-input*
         {:class (stl/css :numeric-input)
          :placeholder "--"
          :aria-label (tr "workspace.layout-grid.editor.padding.top")
          :data-attr "p1"
          :on-change on-p1-change
          :on-focus on-focus-p1
          :on-blur on-padding-blur
          :min 0
          :value p1}]])

     (if token-numeric-inputs
       [:> numeric-input-wrapper*
        {:on-change on-p2-change
         :on-detach on-detach-token
         :on-blur on-padding-blur
         :on-focus on-focus-p2
         :icon i/padding-right
         :min 0
         :attr :p2
         :input-type :horizontal-padding
         :align :right
         :property (tr "workspace.layout-grid.editor.padding.right")
         :placeholder (if (or (= :multiple applied-to-p2)
                              (= :multiple p2))
                        (tr "settings.multiple")
                        "--")
         :applied-token applied-to-p2
         :value p2}]

       [:div {:class (stl/css :padding-multiple)
              :title (tr "workspace.layout-grid.editor.padding.right")}
        [:span {:class (stl/css :icon)}
         deprecated-icon/padding-right]
        [:> deprecated-input/numeric-input*
         {:class (stl/css :numeric-input)
          :placeholder "--"
          :aria-label (tr "workspace.layout-grid.editor.padding.right")
          :data-attr "p2"
          :on-change on-p2-change
          :on-focus on-focus-p2
          :on-blur on-padding-blur
          :min 0
          :value p2}]])

     (if token-numeric-inputs
       [:> numeric-input-wrapper*
        {:on-change on-p3-change
         :on-detach on-detach-token
         :on-blur on-padding-blur
         :on-focus on-focus-p3
         :icon i/padding-bottom
         :min 0
         :attr :p3
         :input-type :vertical-padding
         :property (tr "workspace.layout-grid.editor.padding.bottom")
         :placeholder (if (or (= :multiple applied-to-p3)
                              (= :multiple p3))
                        (tr "settings.multiple")
                        "--")
         :applied-token applied-to-p3
         :value p3}]

       [:div {:class (stl/css :padding-multiple)
              :title (tr "workspace.layout-grid.editor.padding.bottom")}
        [:span {:class (stl/css :icon)}
         deprecated-icon/padding-bottom]
        [:> deprecated-input/numeric-input*
         {:class (stl/css :numeric-input)
          :placeholder "--"
          :aria-label (tr "workspace.layout-grid.editor.padding.bottom")
          :data-attr "p3"
          :on-change on-p3-change
          :on-focus on-focus-p3
          :on-blur on-padding-blur
          :min 0
          :value p3}]])

     (if token-numeric-inputs
       [:> numeric-input-wrapper*
        {:on-change on-p4-change
         :on-detach on-detach-token
         :on-blur on-padding-blur
         :on-focus on-focus-p4
         :icon i/padding-left
         :min 0
         :align :right
         :attr :p4
         :input-type :horizontal-padding
         :property (tr "workspace.layout-grid.editor.padding.left")
         :placeholder (if (or (= :multiple applied-to-p4)
                              (= :multiple p4))
                        (tr "settings.multiple")
                        "--")
         :applied-token applied-to-p4
         :value p4}]

       [:div {:class (stl/css :padding-multiple)
              :title (tr "workspace.layout-grid.editor.padding.left")}
        [:span {:class (stl/css :icon)}
         deprecated-icon/padding-left]
        [:> deprecated-input/numeric-input*
         {:class (stl/css :numeric-input)
          :placeholder "--"
          :aria-label (tr "workspace.layout-grid.editor.padding.left")
          :data-attr "p4"
          :on-change on-p4-change
          :on-focus on-focus-p4
          :on-blur on-padding-blur
          :min 0
          :value p4}]])]))

(mf/defc padding-section*
  [{:keys [type on-type-change] :as props}]
  (let [on-type-change'
        (mf/use-fn
         (mf/deps on-type-change)
         (fn [event]
           (let [type (-> (dom/get-current-target event)
                          (dom/get-data "type"))
                 type (if (= type "multiple") :simple :multiple)]
             (on-type-change type))))]

    (mf/with-effect []
      ;; on destroy component
      (fn []
        (on-padding-blur nil)))

    [:div {:class (stl/css :padding-group)}
     [:div {:class (stl/css :padding-inputs)}
      (cond
        (= type :simple)
        [:> simple-padding-selection* props]

        (= type :multiple)
        [:> multiple-padding-selection* props])]

     [:button {:class (stl/css-case
                       :padding-toggle true
                       :selected (= type :multiple))
               :title (tr "workspace.layout-grid.editor.padding.expand")
               :aria-label (tr "workspace.layout-grid.editor.padding.expand")
               :data-type (d/name type)
               :on-click on-type-change'}
      deprecated-icon/padding-extended]]))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; GAP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defn- select-gap!
  [value]
  (st/emit! (udw/set-gap-selected value)))

(defn- on-gap-focus
  [type]
  (select-gap! type))

(defn- on-gap-blur
  [_event]
  (select-gap! nil))

(mf/defc gap-section*
  [{:keys [is-column wrap-type on-change value applied-tokens ids]
    :as props}]
  (let [token-numeric-inputs
        (features/use-feature "tokens/numeric-input")

        nowrap? (= :nowrap wrap-type)

        row-gap-disabled?
        (and ^boolean nowrap?
             (not ^boolean is-column))

        col-gap-disabled?
        (and ^boolean nowrap?
             ^boolean is-column)

        on-change'
        (mf/use-fn
         (mf/deps on-change wrap-type ids)
         (fn [value event attr]
           (let [on-change-fn #(on-change (= "nowrap" wrap-type) attr % event)]
             (soc/emit-value-or-token value on-change-fn ids #{attr}))))

        on-detach-token
        (mf/use-fn
         (mf/deps ids)
         (fn [token attr]
           (st/emit! (dwta/unapply-token {:token-name token
                                          :attributes #{attr}
                                          :shape-ids ids}))))

        on-row-gap-change
        (mf/use-fn (mf/deps on-change') #(on-change' %1 %2 :row-gap))

        on-column-gap-change
        (mf/use-fn (mf/deps on-change') #(on-change' %1 %2 :column-gap))

        on-focus-row-gap
        (mf/use-fn (mf/deps on-gap-focus) #(on-gap-focus :row-gap))

        on-focus-column-gap
        (mf/use-fn (mf/deps on-gap-focus) #(on-gap-focus :column-gap))]

    (mf/with-effect []
      ;; on destroy component
      (fn []
        (on-gap-blur nil)))

    [:div {:class (stl/css :gap-group)}

     (if token-numeric-inputs
       [:> numeric-input-wrapper*
        {:on-change on-row-gap-change
         :on-detach on-detach-token
         :on-focus on-focus-row-gap
         :on-blur on-gap-blur
         :icon i/gap-vertical
         :nillable true
         :min 0
         :attr :row-gap
         :property "Row gap"
         :values {:row-gap (:row-gap value)}
         :disabled row-gap-disabled?
         :placeholder (if (or (= :multiple (:row-gap applied-tokens))
                              (= :multiple (:row-gap value)))
                        (tr "settings.multiple")
                        "--")
         :applied-token (:row-gap applied-tokens)
         :value (:row-gap value)}]

       [:div {:class (stl/css-case
                      :row-gap true
                      :disabled row-gap-disabled?)
              :title "Row gap"}
        [:span {:class (stl/css :icon)} deprecated-icon/gap-vertical]
        [:> deprecated-input/numeric-input*
         {:class (stl/css :numeric-input true)
          :no-validate true
          :placeholder "--"
          :data-type "row-gap"
          :data-wrap-type (d/name wrap-type)
          :on-focus on-focus-row-gap
          :on-change on-row-gap-change
          :on-blur on-gap-blur
          :is-nillable true
          :min 0
          :value (:row-gap value)
          :is-disabled row-gap-disabled?}]])

     (if token-numeric-inputs
       [:> numeric-input-wrapper*
        {:on-change on-column-gap-change
         :on-detach on-detach-token
         :on-focus on-focus-column-gap
         :on-blur on-gap-blur
         :icon i/gap-horizontal
         :nillable true
         :min 0
         :attr :column-gap
         :align :right
         :property "Column gap"
         :placeholder (if (or (= :multiple (:column-gap applied-tokens))
                              (= :multiple (:column-gap value)))
                        (tr "settings.multiple")
                        "--")
         :applied-token (:column-gap applied-tokens)
         :value (:column-gap value)
         :disabled col-gap-disabled?}]

       [:div {:class (stl/css-case
                      :column-gap true
                      :disabled col-gap-disabled?)
              :title "Column gap"}
        [:span {:class (stl/css :icon)} deprecated-icon/gap-horizontal]
        [:> deprecated-input/numeric-input*
         {:class (stl/css :numeric-input true)
          :no-validate true
          :placeholder "--"
          :data-type "column-gap"
          :data-wrap-type (d/name wrap-type)
          :on-focus on-focus-column-gap
          :on-change on-column-gap-change
          :on-blur on-gap-blur
          :is-nillable true
          :min 0
          :value (:column-gap value)
          :disabled col-gap-disabled?}]])]))

;; GRID COMPONENTS

(mf/defc direction-row-grid
  {::mf/props :obj}
  [{:keys [value on-change] :as props}]
  [:& radio-buttons {:class (stl/css :direction-row-grid)
                     :selected (d/name value)
                     :decode-fn keyword
                     :on-change on-change
                     :name "grid-direction"}
   [:& radio-button {:value "row"
                     :id "grid-direction-row"
                     :title "Row"
                     :icon (dir-icons-refactor :row)}]
   [:& radio-button {:value "column"
                     :id "grid-direction-column"
                     :title "Column"
                     :icon (dir-icons-refactor :column)}]])

(mf/defc grid-edit-mode
  {::mf/props :obj}
  [{:keys [id]}]
  (let [edition (mf/deref refs/selected-edition)
        active? (= id edition)

        toggle-edit-mode
        (mf/use-fn
         (mf/deps id edition)
         (fn []
           (if-not active?
             (st/emit! (udw/start-edition-mode id))
             (st/emit! :interrupt))))]
    [:button
     {:class (stl/css :edit-mode-btn)
      :alt  "Grid edit mode"
      :on-click toggle-edit-mode}
     (tr "workspace.layout-grid.editor.options.edit-grid")]))

(mf/defc align-grid-row
  {::mf/props :obj
   ::mf/private true}
  [{:keys [is-column value on-change]}]
  (let [type (if ^boolean is-column "column" "row")]
    [:& radio-buttons {:class (stl/css :align-grid-row)
                       :selected (d/name value)
                       :decode-fn keyword
                       :on-change on-change
                       :name (dm/str "flex-align-items-" type)}
     [:& radio-button {:value "start"
                       :icon  (get-layout-grid-icon :align-items :start is-column)
                       :title "Align items start"
                       :id     (dm/str "align-items-start-" type)}]
     [:& radio-button {:value "center"
                       :icon  (get-layout-grid-icon :align-items :center is-column)
                       :title "Align items center"
                       :id    (dm/str "align-items-center-" type)}]
     [:& radio-button {:value "end"
                       :icon  (get-layout-grid-icon :align-items :end is-column)
                       :title "Align items end"
                       :id    (dm/str "align-items-end-" type)}]]))

(mf/defc justify-grid-row
  {::mf/props :obj
   ::mf/private :obj}
  [{:keys [is-column value on-change]}]
  (let [type (if ^boolean is-column "column" "row")]
    [:& radio-buttons {:class (stl/css :justify-grid-row)
                       :selected (d/name value)
                       :on-change on-change
                       :decode-fn keyword
                       :name (dm/str "grid-justify-items-" type)}

     [:& radio-button {:key "justify-item-start"
                       :value "start"
                       :icon (get-layout-grid-icon :justify-items :start is-column)
                       :title "Justify items start"
                       :id (dm/str "justify-items-start-" type)}]

     [:& radio-button {:key "justify-item-center"
                       :value "center"
                       :icon (get-layout-grid-icon :justify-items :center is-column)
                       :title "Justify items center"
                       :id (dm/str "justify-items-center-" type)}]

     [:& radio-button {:key "justify-item-end"
                       :value "end"
                       :icon (get-layout-grid-icon :justify-items :end is-column)
                       :title "Justify items end"
                       :id (dm/str "justify-items-end-" type)}]

     [:& radio-button {:key "justify-item-space-around"
                       :value "space-around"
                       :icon (get-layout-grid-icon :justify-items :space-around is-column)
                       :title "Justify items space-around"
                       :id (dm/str "justify-items-space-around-" type)}]

     [:& radio-button {:key "justify-item-space-between"
                       :value "space-between"
                       :icon (get-layout-grid-icon :justify-items :space-between is-column)
                       :title "Justify items space-between"
                       :id (dm/str "justify-items-space-between-" type)}]

     [:& radio-button {:key "justify-item-stretch"
                       :value "stretch"
                       :icon (get-layout-grid-icon :justify-items :stretch is-column)
                       :title "Justify items stretch"
                       :id (dm/str "justify-items-stretch-" type)}]]))

(defn- manage-values
  [{:keys [type value]}]
  (case type
    :auto "auto"
    :percent (fmt/format-percent (/ value 100))
    :flex    (fmt/format-frs value)
    :fixed   (fmt/format-pixels value)
    value))

(mf/defc grid-track-info
  {::mf/props :obj}
  [{:keys [is-column
           type
           index
           column
           set-column-value
           set-column-type
           remove-element
           reorder-track
           hover-track
           on-select-track]}]

  (let [drop-track
        (mf/use-fn
         (mf/deps type reorder-track index)
         (fn [drop-position data event]
           (reorder-track type (:index data) (if (= :top drop-position) (dec index) index) (not (kbd/mod? event)))))

        pointer-enter
        (mf/use-fn
         (mf/deps type hover-track index)
         (fn []
           (hover-track type index true)))

        pointer-leave
        (mf/use-fn
         (mf/deps type hover-track index)
         (fn []
           (hover-track type index false)))

        handle-select-track
        (mf/use-fn
         (mf/deps on-select-track type index)
         (fn []
           (when on-select-track
             (on-select-track type index))))

        [dprops dref]
        (h/use-sortable
         :data-type "penpot/grid-track"
         :on-drop drop-track
         :data {:is-column is-column
                :index index
                :column column}
         :draggable? true)]

    [:div {:class (stl/css-case :track-info true
                                :dnd-over-top (or (= (:over dprops) :top)
                                                  (= (:over dprops) :center))
                                :dnd-over-bot (= (:over dprops) :bot))
           :ref dref
           :on-pointer-enter pointer-enter
           :on-pointer-leave pointer-leave}

     [:div {:class (stl/css :track-info-container)}
      [:div {:class (stl/css :track-info-dir-icon)
             :on-click handle-select-track}
       (if is-column deprecated-icon/flex-vertical deprecated-icon/flex-horizontal)]

      [:div {:class (stl/css :track-info-value)}
       [:> deprecated-input/numeric-input* {:no-validate true
                                            :value (:value column)
                                            :on-change #(set-column-value type index %)
                                            :placeholder "--"
                                            :min 0
                                            :is-disabled (= :auto (:type column))}]]

      [:div {:class (stl/css :track-info-unit)}
       [:& select {:class (stl/css :track-info-unit-selector)
                   :default-value (:type column)
                   :options [{:value :flex :label "FR"}
                             {:value :auto :label "AUTO"}
                             {:value :fixed :label "PX"}
                             {:value :percent :label "%"}]
                   :on-change #(set-column-type type index %)}]]]

     [:> icon-button* {:variant "ghost"
                       :aria-label (tr "workspace.shape.menu.delete")
                       :on-click remove-element
                       :data-type type
                       :data-index index
                       :icon i/remove}]]))

(mf/defc grid-columns-row
  {::mf/props :obj}
  [{:keys [is-column expanded? column-values toggle add-new-element set-column-value set-column-type
           remove-element reorder-track hover-track on-select-track]}]
  (let [column-num (count column-values)
        direction (if (> column-num 1)
                    (if ^boolean is-column "Columns " "Rows ")
                    (if ^boolean is-column "Column " "Row "))

        track-name (dm/str direction  (if (= column-num 0) " - empty" column-num))
        track-detail (str/join ", " (map manage-values column-values))

        type (if is-column :column :row)
        testid (when (not is-column) "inspect-layout-rows")

        add-track
        #(do
           (when-not expanded? (toggle))
           (add-new-element type ctl/default-track-value))]

    [:div {:class (stl/css :grid-tracks) :data-testid testid}
     [:div {:class (stl/css :grid-track-header)}
      [:button {:class (stl/css :expand-icon) :on-click toggle} deprecated-icon/menu]
      [:div {:class (stl/css :track-title) :on-click toggle}
       [:div {:class (stl/css :track-name) :title track-name} track-name]
       [:div {:class (stl/css :track-detail) :title track-detail} track-detail]]
      [:button {:class (stl/css :add-column) :on-click add-track} deprecated-icon/add]]

     (when expanded?
       [:> h/sortable-container* {}
        [:div {:class (stl/css :grid-tracks-info-container)}
         (for [[index column] (d/enumerate column-values)]
           [:& grid-track-info {:key (dm/str index "-" (d/name type))
                                :type type
                                :is-column is-column
                                :index index
                                :column column
                                :set-column-value set-column-value
                                :set-column-type set-column-type
                                :remove-element remove-element
                                :reorder-track reorder-track
                                :hover-track hover-track
                                :on-select-track on-select-track}])]])]))

;; LAYOUT COMPONENT

(defn- open-flex-help
  [_]
  (st/emit! (dom/open-new-window cf/flex-help-uri)))

(defn- open-grid-help
  [_]
  (st/emit! (dom/open-new-window cf/grid-help-uri)))

(defn- check-layout-container-menu-props
  [old-props new-props]
  (let [o-values (unchecked-get old-props "values")
        n-values (unchecked-get new-props "values")]
    (and (identical? (unchecked-get old-props "ids")
                     (unchecked-get new-props "ids"))
         (identical? (unchecked-get old-props "appliedTokens")
                     (unchecked-get new-props "appliedTokens"))
         (identical? (unchecked-get old-props "multiple")
                     (unchecked-get new-props "multiple"))
         (identical? (get o-values :layout-gap)
                     (get n-values :layout-gap))
         (identical? (get o-values :layout-gap-type)
                     (get n-values :layout-gap-type))
         (identical? (get o-values :layout-padding)
                     (get n-values :layout-padding))
         (identical? (get o-values :layout-padding-type)
                     (get n-values :layout-padding-type))
         (identical? (get o-values :layout-wrap-type)
                     (get n-values :layout-wrap-type))
         (identical? (get o-values :layout-align-items)
                     (get n-values :layout-align-items))
         (identical? (get o-values :layout-flex-dir)
                     (get n-values :layout-flex-dir))
         (identical? (get o-values :layout-justify-content)
                     (get n-values :layout-justify-content))
         (identical? (get o-values :layout-align-content)
                     (get n-values :layout-align-content))
         (identical? (get o-values :layout)
                     (get n-values :layout)))))

(mf/defc layout-container-menu*
  {::mf/wrap [#(mf/memo' % check-layout-container-menu-props)]}
  [{:keys [ids values multiple applied-tokens]}]
  (let [;; Display
        layout-type    (:layout values)
        has-layout?    (some? layout-type)

        show-dropdown* (mf/use-state false)
        show-dropdown? @show-dropdown*

        open*          (mf/use-state #(if layout-type true false))
        open?          (deref open*)

        on-toggle-visibility
        (mf/use-fn #(swap! open* not))

        on-add-layout
        (mf/use-fn
         (fn [event]
           (let [type (-> (dom/get-current-target event)
                          (dom/get-data "type")
                          (keyword))]
             (st/emit! (with-meta (dwsl/create-layout type)
                         {::ev/origin "workspace:sidebar"}))

             (reset! open* true))))

        on-remove-layout
        (mf/use-fn
         (mf/deps ids)
         (fn [_]
           (st/emit! (dwsl/remove-layout ids))
           (reset! open* false)))

        saved-dir (:layout-flex-dir values)
        is-column (or (= :column saved-dir) (= :column-reverse saved-dir))

        ;; Wrap type
        wrap-type (:layout-wrap-type values)

        toggle-wrap
        (mf/use-fn
         (mf/deps wrap-type ids)
         (fn []
           (let [type (if (= wrap-type :wrap)
                        :nowrap
                        :wrap)]
             (st/emit! (dwsl/update-layout ids {:layout-wrap-type type})))))


        ;; Align items
        align-items         (:layout-align-items values)

        set-align-items
        (mf/use-fn
         (mf/deps ids)
         (fn [value]
           (st/emit! (dwsl/update-layout ids {:layout-align-items (keyword value)}))))

        ;; Justify content
        justify-content     (:layout-justify-content values)

        set-justify-content
        (mf/use-fn
         (mf/deps ids)
         (fn [value]
           (st/emit! (dwsl/update-layout ids {:layout-justify-content (keyword value)}))))

        ;; Align content
        align-content         (:layout-align-content values)

        ;; FIXME revisit???
        on-align-content-change
        (mf/use-fn
         (mf/deps ids align-content)
         (fn [value]
           (if (= align-content value)
             (st/emit! (dwsl/update-layout ids {:layout-align-content :stretch}))
             (st/emit! (dwsl/update-layout ids {:layout-align-content (keyword value)})))))

        ;; Gap
        on-gap-change
        (mf/use-fn
         (mf/deps ids)
         (fn [multiple? type val]
           (let [val (mth/finite (d/parse-double val 0) 0)]
             (cond
               ^boolean multiple?
               (st/emit! (dwsl/update-layout ids {:layout-gap {:row-gap val :column-gap val}}))

               (some? type)
               (st/emit! (dwsl/update-layout ids {:layout-gap {type val}}))))))


        ;; Padding
        on-padding-type-change
        (mf/use-fn
         (mf/deps ids)
         (fn [type]
           (st/emit! (dwsl/update-layout ids {:layout-padding-type type}))))

        on-padding-change
        (mf/use-fn
         (mf/deps ids)
         (fn [type prop val]
           (let [val (mth/finite (d/parse-double val 0) 0)]
             (cond
               (and (= type :simple) (or (= prop :p1) (= prop #{:p1 :p3})))
               (st/emit! (dwsl/update-layout ids {:layout-padding {:p1 val :p3 val}}))

               (and (= type :simple) (or (= prop :p2) (= prop #{:p2 :p4})))
               (st/emit! (dwsl/update-layout ids {:layout-padding {:p2 val :p4 val}}))

               (and (= type :multiple) (some? prop))
               (st/emit! (dwsl/update-layout ids {:layout-padding-type :multiple
                                                  :layout-padding {prop val}}))

               (some? prop)
               (st/emit! (dwsl/update-layout ids {:layout-padding {prop val}}))))))

        ;; Grid-direction

        saved-grid-dir (:layout-grid-dir values)

        on-direction-change
        (mf/use-fn
         (mf/deps layout-type ids)
         (fn [dir]
           (if (= :flex layout-type)
             (st/emit! (dwsl/update-layout ids {:layout-flex-dir dir}))
             (st/emit! (dwsl/update-layout ids {:layout-grid-dir dir})))))

        ;; Align grid
        align-items-row    (:layout-align-items values)
        align-items-column (:layout-justify-items values)

        on-column-align-change
        (mf/use-fn
         (mf/deps ids)
         (fn [value]
           (st/emit! (dwsl/update-layout ids {:layout-justify-items value}))))

        on-row-align-change
        (mf/use-fn
         (mf/deps ids)
         (fn [value]
           (st/emit! (dwsl/update-layout ids {:layout-align-items value}))))

        ;; Justify grid
        grid-justify-content-row    (:layout-justify-content values)
        grid-justify-content-column (:layout-align-content values)

        on-column-justify-change
        (mf/use-fn
         (mf/deps ids)
         (fn [value]
           (st/emit! (dwsl/update-layout ids {:layout-align-content value}))))

        on-row-justify-change
        (mf/use-fn
         (mf/deps ids)
         (fn [value]
           (st/emit! (dwsl/update-layout ids {:layout-justify-content value}))))

        on-toggle-dropdown-visibility
        (mf/use-fn #(swap! show-dropdown* not))

        on-hide-dropdown
        (mf/use-fn #(reset! show-dropdown* false))

        add-layout-dropdown
        (mf/html
         [:& dropdown {:show show-dropdown?
                       :on-close on-hide-dropdown}
          [:div {:class (stl/css :layout-options)}
           [:button {:class (stl/css :layout-option)
                     :data-type "flex"
                     :on-click on-add-layout}
            (tr "labels.flex-layout")]
           [:button {:class (stl/css :layout-option)
                     :data-type "grid"
                     :on-click on-add-layout}
            (tr "labels.grid-layout")]]])]

    [:section {:class (stl/css :element-set)
               :aria-label "Layout container section"
               :data-testid "inspect-layout"}
     [:div {:class (stl/css :element-title)}
      [:> title-bar*
       {:collapsable has-layout?
        :collapsed (not open?)
        :on-collapsed on-toggle-visibility
        :title (tr "labels.layout")
        :class (stl/css-case :title-spacing-layout (not has-layout?))}

       (if (and (not multiple) (:layout values))
         [:div {:class (stl/css :title-actions)}
          [:> icon-button* {:variant "ghost"
                            :aria-label (tr "workspace.shape.menu.add-layout")
                            :on-click on-toggle-dropdown-visibility
                            :icon i/menu}]

          add-layout-dropdown

          (when has-layout?
            [:> icon-button* {:variant "ghost"
                              :aria-label (tr "workspace.shape.menu.remove-layout")
                              :on-click on-remove-layout
                              :icon i/remove}])]

         [:div {:class (stl/css :title-actions)}
          [:> icon-button* {:variant "ghost"
                            :aria-label (tr "workspace.shape.menu.add-layout")
                            :on-click on-toggle-dropdown-visibility
                            :icon i/add}]

          add-layout-dropdown

          (when has-layout?
            [:> icon-button* {:variant "ghost"
                              :aria-label (tr "workspace.shape.menu.delete")
                              :on-click on-remove-layout
                              :icon i/remove}])])]]

     (when (and ^boolean open?
                ^boolean has-layout?
                (not= :multiple layout-type))
       (case layout-type
         :flex
         [:div  {:class (stl/css :flex-layout-menu)}
          [:div {:class (stl/css :first-row)}
           [:& align-row {:is-column is-column
                          :value align-items
                          :on-change set-align-items}]

           [:& direction-row-flex {:on-change on-direction-change
                                   :value saved-dir}]

           [:& wrap-row {:wrap-type wrap-type
                         :on-click toggle-wrap}]]

          [:div {:class (stl/css :second-row :help-button-wrapper)}
           [:& justify-content-row {:is-column is-column
                                    :justify-content justify-content
                                    :on-change set-justify-content}]

           [:> icon-button* {:variant "ghost"
                             :aria-label (tr "labels.help-center")
                             :on-click open-flex-help
                             :icon i/help}]]
          (when (= :wrap wrap-type)
            [:div {:class (stl/css :third-row)}
             [:& align-content-row {:is-column is-column
                                    :value align-content
                                    :on-change on-align-content-change}]])
          [:div {:class (stl/css :forth-row)}
           [:> gap-section* {:is-column is-column
                             :wrap-type wrap-type
                             :on-change on-gap-change
                             :ids ids
                             :applied-tokens applied-tokens
                             :value (:layout-gap values)}]
           [:> padding-section* {:value (:layout-padding values)
                                 :type (:layout-padding-type values)
                                 :on-type-change on-padding-type-change
                                 :ids ids
                                 :applied-tokens applied-tokens
                                 :on-change on-padding-change}]]]

         :grid
         [:div {:class (stl/css :grid-layout-menu)}
          (when (= 1 (count ids))
            [:div {:class (stl/css :edit-grid-wrapper)}
             [:& grid-edit-mode {:id (first ids)}]
             [:> icon-button* {:variant "ghost"
                               :aria-label (tr "labels.help-center")
                               :on-click open-grid-help
                               :icon i/help}]])

          [:div {:class (stl/css :first-row)}
           [:div {:class (stl/css :direction-edit)}
            [:div {:class (stl/css :direction)}
             [:& direction-row-grid {:value saved-grid-dir
                                     :on-change on-direction-change}]]]

           [:& align-grid-row {:is-column false
                               :value align-items-row
                               :on-change on-row-align-change}]
           [:& align-grid-row {:is-column true
                               :value align-items-column
                               :on-change on-column-align-change}]]

          [:div {:class (stl/css :row :grid-layout-align)}
           [:& justify-grid-row {:is-column true
                                 :value grid-justify-content-column
                                 :on-change on-column-justify-change}]
           [:& justify-grid-row {:is-column false
                                 :value grid-justify-content-row
                                 :on-change on-row-justify-change}]]

          [:div {:class (stl/css :gap-row)}
           [:> gap-section* {:on-change on-gap-change
                             :ids ids
                             :applied-tokens applied-tokens
                             :value (:layout-gap values)}]]
          [:div {:class (stl/css :padding-row)}
           [:> padding-section* {:value (:layout-padding values)
                                 :type (:layout-padding-type values)
                                 :ids ids
                                 :applied-tokens applied-tokens
                                 :on-type-change on-padding-type-change
                                 :on-change on-padding-change}]]]

         nil))]))

(mf/defc grid-layout-edition
  {::mf/memo #{:ids :values :applied-tokens}}
  [{:keys [ids values applied-tokens]}]
  (let [;; Gap
        saved-grid-dir (:layout-grid-dir values)

        on-direction-change
        (mf/use-fn
         (mf/deps ids)
         (fn [dir]
           (st/emit! (dwsl/update-layout ids {:layout-grid-dir dir}))))

        on-gap-change
        (mf/use-fn
         (mf/deps ids)
         (fn [multiple? type val]
           (let [val (mth/finite (d/parse-double val 0) 0)]
             (if multiple?
               (st/emit! (dwsl/update-layout ids {:layout-gap {:row-gap val :column-gap val}}))
               (st/emit! (dwsl/update-layout ids {:layout-gap {type val}}))))))

        ;; Padding
        on-padding-type-change
        (mf/use-fn
         (mf/deps ids)
         (fn [type]
           (st/emit! (dwsl/update-layout ids {:layout-padding-type type}))))

        on-padding-change
        (fn [type prop val]
          (let [val (mth/finite (d/parse-double val 0) 0)]
            (cond
              (and (= type :simple) (= prop :p1))
              (st/emit! (dwsl/update-layout ids {:layout-padding {:p1 val :p3 val}}))

              (and (= type :simple) (= prop :p2))
              (st/emit! (dwsl/update-layout ids {:layout-padding {:p2 val :p4 val}}))

              :else
              (st/emit! (dwsl/update-layout ids {:layout-padding {prop val}})))))

        ;; Align grid
        align-items-row    (:layout-align-items values)
        align-items-column (:layout-justify-items values)

        on-column-align-change
        (mf/use-fn
         (mf/deps ids)
         (fn [value]
           (st/emit! (dwsl/update-layout ids {:layout-justify-items value}))))

        on-row-align-change
        (mf/use-fn
         (mf/deps ids)
         (fn [value]
           (st/emit! (dwsl/update-layout ids {:layout-align-items value}))))

        ;; Justify grid
        grid-justify-content-row    (:layout-justify-content values)
        grid-justify-content-column (:layout-align-content values)

        on-column-justify-change
        (mf/use-fn
         (mf/deps ids)
         (fn [value]
           (st/emit! (dwsl/update-layout ids {:layout-align-content value}))))

        on-row-justify-change
        (mf/use-fn
         (mf/deps ids)
         (fn [value]
           (st/emit! (dwsl/update-layout ids {:layout-justify-content value}))))

        columns-open?    (mf/use-state false)
        rows-open?       (mf/use-state false)

        column-values    (:layout-grid-columns values)
        rows-values      (:layout-grid-rows values)

        toggle-columns-open
        (mf/use-fn #(swap! columns-open? not))

        toggle-rows-open
        (mf/use-fn #(swap! rows-open? not))

        add-new-element
        (mf/use-fn
         (mf/deps ids)
         (fn [type value]
           (st/emit! (dwsl/add-layout-track ids type value))))

        remove-element
        (mf/use-fn
         (mf/deps ids)
         (fn [event]
           (let [type (-> (dom/get-current-target event)
                          (dom/get-data "type")
                          (d/read-string))
                 index (-> (dom/get-current-target event)
                           (dom/get-data "index")
                           (d/parse-integer))]
             (st/emit! (dwsl/remove-layout-track ids type index)))))

        reorder-track
        (mf/use-fn
         (mf/deps ids)
         (fn [type from-index to-index move-content?]
           (st/emit! (dwsl/reorder-layout-track ids type from-index to-index move-content?))))

        hover-track
        (mf/use-fn
         (mf/deps ids)
         (fn [type index hover?]
           (st/emit! (dwsl/hover-layout-track ids type index hover?))))

        handle-select-track
        (mf/use-fn
         (mf/deps ids)
         (fn [type index]
           (st/emit! (dwge/select-track-cells (first ids) type index))))

        set-column-value
        (mf/use-fn
         (mf/deps ids)
         (fn [type index value]
           (st/emit! (dwsl/change-layout-track ids type index {:value value}))))

        set-column-type
        (mf/use-fn
         (mf/deps ids)
         (fn [type index track-type]
           (let [value (case track-type
                         :auto nil
                         :flex 1
                         :percent 20
                         :fixed 100)]
             (st/emit! (dwsl/change-layout-track ids type index {:value value
                                                                 :type track-type})))))
        handle-locate-grid
        (mf/use-fn
         (mf/deps ids)
         (fn []
           (st/emit! (dwge/locate-board (first ids)))))]

    [:div {:class (stl/css :grid-layout-menu)}
     [:div {:class (stl/css :grid-first-row)}
      [:div {:class (stl/css :grid-layout-menu-title)} "GRID LAYOUT"]
      [:> icon-button* {:variant "ghost"
                        :class (stl/css :help-button)
                        :aria-label (tr "labels.help-center")
                        :on-click open-grid-help
                        :icon i/help}]
      [:button {:class (stl/css :exit-btn)
                :on-click #(st/emit! (udw/clear-edition-mode))}
       (tr "workspace.layout-grid.editor.options.exit")]]

     [:div {:class (stl/css :row :first-row)}
      [:div {:class (stl/css :direction-edit)}
       [:div {:class (stl/css :direction)}
        [:& direction-row-grid {:value saved-grid-dir
                                :on-change on-direction-change}]]]

      [:& align-grid-row {:is-column false
                          :value align-items-row
                          :on-change on-row-align-change}]

      [:& align-grid-row {:is-column true
                          :value align-items-column
                          :on-change on-column-align-change}]]

     [:div {:class (stl/css :row :grid-layout-align)}
      [:& justify-grid-row {:is-column true
                            :value grid-justify-content-column
                            :on-change on-column-justify-change}]
      [:& justify-grid-row {:is-column false
                            :value grid-justify-content-row
                            :on-change on-row-justify-change}]

      [:> icon-button* {:variant "ghost"
                        :class (stl/css :locate-button)
                        :aria-label (tr "workspace.layout-grid.editor.top-bar.locate.tooltip")
                        :on-click handle-locate-grid
                        :icon i/locate}]]

     [:div {:class (stl/css :gap-row)}
      [:> gap-section* {:on-change on-gap-change
                        :ids ids
                        :applied-tokens applied-tokens
                        :value (:layout-gap values)}]]

     [:div {:class (stl/css :padding-row :padding-section)}
      [:> padding-section* {:value (:layout-padding values)
                            :type (:layout-padding-type values)
                            :on-type-change on-padding-type-change
                            :on-change on-padding-change}]]

     [:div {:class (stl/css :grid-tracks-row)}
      [:& grid-columns-row {:is-column true
                            :expanded? @columns-open?
                            :toggle toggle-columns-open
                            :column-values column-values
                            :add-new-element add-new-element
                            :set-column-value set-column-value
                            :set-column-type set-column-type
                            :remove-element remove-element
                            :reorder-track reorder-track
                            :hover-track hover-track
                            :on-select-track handle-select-track}]

      [:& grid-columns-row {:is-column false
                            :expanded? @rows-open?
                            :toggle toggle-rows-open
                            :column-values rows-values
                            :add-new-element add-new-element
                            :set-column-value set-column-value
                            :set-column-type set-column-type
                            :remove-element remove-element
                            :reorder-track reorder-track
                            :hover-track hover-track
                            :on-select-track handle-select-track}]]]))
