;; 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.tokens.sets.lists
  (:require-macros [app.main.style :as stl])
  (:require
   [app.common.data.macros :as dm]
   [app.common.types.tokens-lib :as ctob]
   [app.main.data.workspace.tokens.library-edit :as dwtl]
   [app.main.store :as st]
   [app.main.ui.context :as ctx]
   [app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
   [app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
   [app.main.ui.ds.foundations.typography.text :refer [text*]]
   [app.main.ui.hooks :as h]
   [app.main.ui.workspace.tokens.sets.helpers :as sets-helpers]
   [app.util.dom :as dom]
   [app.util.i18n :refer [tr]]
   [app.util.keyboard :as kbd]
   [beicon.v2.core :as rx]
   [cuerdas.core :as str]
   [potok.v2.core :as ptk]
   [rumext.v2 :as mf]))

(defn- on-start-creation
  []
  (st/emit! (dwtl/start-token-set-creation [])))

(mf/defc editing-label*
  {::mf/private true}
  [{:keys [default-value on-cancel on-submit]}]
  (let [on-submit
        (mf/use-fn
         (mf/deps on-cancel on-submit default-value)
         (fn [event]
           (let [value (dom/get-target-val event)]
             (if (or (str/empty? value)
                     (= value default-value))
               (on-cancel)
               (on-submit value)))))

        on-key-down
        (mf/use-fn
         (mf/deps on-submit on-cancel)
         (fn [event]
           (cond
             (kbd/enter? event) (on-submit event)
             (kbd/esc? event) (on-cancel))))]
    [:input
     {:class (stl/css :editing-node)
      :type "text"
      :on-blur on-submit
      :on-key-down on-key-down
      :max-length "256"
      :auto-focus true
      :placeholder (tr "workspace.tokens.set-edit-placeholder")
      :default-value default-value}]))

(mf/defc checkbox*
  [{:keys [checked aria-label on-click disabled]}]
  (let [all?     (true? checked)
        mixed?   (= checked "mixed")
        checked? (or all? mixed?)]

    [:div {:role "checkbox"
           :aria-checked (dm/str checked)
           :disabled disabled
           :title (when disabled (tr "workspace.tokens.no-permisions-set"))
           :tab-index 0
           :class (stl/css-case :checkbox-style true
                                :checkbox-checked-style checked?
                                :checkbox-disabled-checked (and checked? disabled)
                                :checkbox-disabled disabled)
           :on-click (when-not disabled on-click)}

     (when ^boolean checked?
       [:> icon*
        {:aria-label aria-label
         :class (stl/css :check-icon)
         :size "s"
         :icon-id (if mixed? i/remove i/tick)}])]))

(mf/defc inline-add-button*
  []
  (let [can-edit? (mf/use-ctx ctx/can-edit?)]
    (if can-edit?
      [:div {:class (stl/css :empty-sets-wrapper)}
       [:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message)}
        (tr "workspace.tokens.no-sets-yet")]
       [:button {:on-click on-start-creation
                 :class (stl/css :create-set-button)}
        (tr "workspace.tokens.create-one")]]
      [:div {:class (stl/css :empty-sets-wrapper)}
       [:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message)}
        (tr "workspace.tokens.no-sets-yet")]])))

(mf/defc add-button*
  []
  [:> icon-button* {:variant "ghost"
                    :icon i/add
                    :on-click on-start-creation
                    :aria-label (tr "workspace.tokens.add set")}])

(mf/defc sets-tree-set-group*
  {::mf/private true}
  [{:keys [id label is-editing is-active is-selected is-draggable is-collapsed path depth index
           on-toggle on-drop on-start-edition on-reset-edition on-edit-submit on-toggle-collapse]}]

  (let [can-edit?
        (mf/use-ctx ctx/can-edit?)

        label-id
        (str id "-label")

        on-context-menu
        (mf/use-fn
         (mf/deps is-editing id path can-edit?)
         (fn [event]
           (dom/prevent-default event)
           (dom/stop-propagation event)
           (when (and can-edit? (not is-editing))
             (st/emit! (dwtl/assign-token-set-context-menu
                        {:position (dom/get-client-position event)
                         :is-group true
                         :id id
                         :path path})))))

        on-collapse-click
        (mf/use-fn
         (fn [event]
           (dom/prevent-default event)
           (dom/stop-propagation event)
           (on-toggle-collapse path)))

        on-double-click
        (mf/use-fn (mf/deps id) #(on-start-edition id))

        on-checkbox-click
        (mf/use-fn
         (mf/deps on-toggle path can-edit?)
         #(on-toggle path))

        on-edit-submit'
        (mf/use-fn
         (mf/deps path on-edit-submit can-edit?)
         #(on-edit-submit path %))

        on-drop
        (mf/use-fn
         (mf/deps index on-drop)
         (fn [position data]
           (on-drop index position data)))

        [dprops dref]
        (h/use-sortable
         :data-type "penpot/token-set"
         :on-drop on-drop
         :data {:index index
                :is-group true}
         :detect-center? true
         :draggable? (and is-draggable (not is-editing)))]

    [:div {:ref dref
           :data-testid "tokens-set-group-item"
           :style {"--tree-depth" depth}
           :class (stl/css-case :set-item-container true
                                :set-item-group true
                                :selected-set is-selected
                                :dnd-over     (= (:over dprops) :center)
                                :dnd-over-top (= (:over dprops) :top)
                                :dnd-over-bot (= (:over dprops) :bot))
           :on-context-menu on-context-menu}
     [:> icon-button*
      {:class (stl/css :set-item-group-collapse-button)
       :on-click on-collapse-click
       :data-testid "tokens-set-group-collapse"
       :aria-label (tr "labels.collapse")
       :icon (if is-collapsed "arrow-right" "arrow-down")
       :variant "action"}]
     (if is-editing
       [:> editing-label*
        {:default-value label
         :on-cancel on-reset-edition
         :on-submit on-edit-submit'}]
       [:*
        [:div {:class (stl/css :set-name)
               :role "button"
               :title label
               :tab-index 0
               :on-double-click on-double-click
               :id label-id}
         label]
        [:> checkbox*
         {:on-click on-checkbox-click
          :disabled (not can-edit?)
          :checked (case is-active
                     :all true
                     :partial "mixed"
                     :none false)
          :arial-label (tr "workspace.tokens.select-set")}]])]))

(mf/defc sets-tree-set*
  [{:keys [id set label is-editing is-active is-selected is-draggable is-new path depth index
           on-select on-toggle on-drop on-start-edition on-reset-edition on-edit-submit]}]

  (let [can-edit? (mf/use-ctx ctx/can-edit?)

        on-click
        (mf/use-fn
         (mf/deps is-editing on-select id)
         (fn [event]
           (dom/stop-propagation event)
           (when-not is-editing
             (when (fn? on-select)
               (on-select id)))))

        on-context-menu
        (mf/use-fn
         (mf/deps is-editing id path can-edit?)
         (fn [event]
           (dom/prevent-default event)
           (dom/stop-propagation event)
           (when (and can-edit? (not is-editing))
             (st/emit! (dwtl/assign-token-set-context-menu
                        {:position (dom/get-client-position event)
                         :is-group false
                         :id id
                         :path path})))))

        on-double-click
        (mf/use-fn
         (mf/deps id is-new)
         (fn []
           (when-not is-new
             (on-start-edition id))))

        on-checkbox-click
        (mf/use-fn
         (mf/deps set on-toggle)
         (fn [event]
           (dom/stop-propagation event)
           (when (fn? on-toggle)
             (on-toggle (ctob/get-name set)))))

        on-edit-submit'
        (mf/use-fn
         (mf/deps set on-edit-submit)
         #(on-edit-submit set %))

        on-drag
        (mf/use-fn
         (mf/deps path)
         (fn [_]
           (when-not is-selected
             (on-select path))))

        on-drop
        (mf/use-fn
         (mf/deps index on-drop)
         (fn [position data]
           (on-drop index position data)))

        [dprops dref]
        (h/use-sortable
         :data-type "penpot/token-set"
         :on-drag on-drag
         :on-drop on-drop
         :data {:index index
                :is-group false}
         :draggable? (and is-draggable (not is-editing)))

        drop-over
        (get dprops :over)]

    [:div {:ref dref
           :role "button"
           :data-testid "tokens-set-item"
           :id (str "token-set-item-" (str/join "/" path))
           :style {"--tree-depth" depth}
           :class (stl/css-case :set-item-container true
                                :selected-set is-selected
                                :dnd-over     (= drop-over :center)
                                :dnd-over-top (= drop-over :top)
                                :dnd-over-bot (= drop-over :bot))
           :on-click on-click
           :on-double-click on-double-click
           :on-context-menu on-context-menu
           :aria-checked is-active}

     [:> icon*
      {:icon-id i/document
       :class (stl/css-case :icon true
                            :root-icon (not depth))}]
     (if is-editing
       [:> editing-label*
        {:default-value label
         :on-cancel on-reset-edition
         :on-submit on-edit-submit'}]
       [:*
        [:div {:class (stl/css :set-name)}
         label]
        [:> checkbox*
         {:on-click on-checkbox-click
          :disabled (not can-edit?)
          :arial-label (tr "workspace.tokens.select-set")
          :checked is-active}]])]))

(mf/defc token-sets-tree*
  [{:keys [is-draggable
           selected
           is-token-set-group-active
           is-token-set-active
           on-start-edition
           on-reset-edition
           on-edit-submit-set
           on-edit-submit-group
           on-select
           on-toggle-set
           on-toggle-set-group
           tokens-lib
           token-sets
           new-path
           edition-id]}]

  (let [collapsed-paths* (mf/use-state #{})
        collapsed-paths  (deref collapsed-paths*)

        collapsed?
        (mf/use-fn
         (mf/deps collapsed-paths)
         (partial contains? collapsed-paths))

        on-drop
        (mf/use-fn
         (mf/deps collapsed-paths)
         (fn [index position data]
           (let [params {:from-index (:index data)
                         :to-index index
                         :position position
                         :collapsed-paths collapsed-paths}]
             (if (:is-group data)
               (st/emit! (dwtl/drop-token-set-group params))
               (st/emit! (dwtl/drop-token-set params))))))

        on-toggle-collapse
        (mf/use-fn
         (fn [path]
           (swap! collapsed-paths* #(if (contains? % path)
                                      (disj % path)
                                      (conj % path)))))]

    (mf/with-effect []
      (let [sub (rx/subs! (fn [paths']
                            (swap! collapsed-paths* (fn [paths] (apply disj paths paths'))))
                          (->> st/stream
                               (rx/filter (ptk/type? :expand-token-sets))
                               (rx/map deref)
                               (rx/map :paths)))]
        (fn []
          (rx/dispose! sub))))

    (for [{:keys [token-set id index is-new is-group path depth] :as node}
          (ctob/sets-tree-seq token-sets
                              {:skip-children-pred collapsed?
                               :new-at-path new-path})]
      (do
        (cond
          ^boolean is-group
          [:> sets-tree-set-group*
           {:key index
            :id id
            :label (peek path)
            :is-editing (= edition-id id)
            :is-active (is-token-set-group-active path)
            :is-selected false
            :is-draggable is-draggable
            :is-collapsed (collapsed? path)

            :path path
            :depth depth
            :index index

            :on-toggle on-toggle-set-group
            :on-drop on-drop
            :on-start-edition on-start-edition
            :on-reset-edition on-reset-edition
            :on-edit-submit on-edit-submit-group
            :on-toggle-collapse on-toggle-collapse}]

          ^boolean is-new
          [:> sets-tree-set*
           {:key index
            :id id
            :set token-set
            :label ""
            :is-editing true
            :is-active true
            :is-selected true
            :is-draggable false
            :is-new true

            :path path
            :depth depth
            :index index

            :on-drop on-drop
            :on-reset-edition on-reset-edition
            :on-edit-submit (partial sets-helpers/on-create-token-set tokens-lib)}]

          :else
          [:> sets-tree-set*
           {:key index
            :id id
            :set token-set
            :label (peek path)
            :is-editing (= edition-id id)
            :is-active (is-token-set-active (ctob/get-name token-set))
            :is-selected (= selected id)
            :is-draggable is-draggable
            :is-new false

            :path path
            :depth depth
            :index index

            :on-select on-select
            :on-toggle on-toggle-set
            :on-drop on-drop
            :on-start-edition on-start-edition
            :on-reset-edition on-reset-edition
            :on-edit-submit on-edit-submit-set}])))))

(mf/defc controlled-sets-list*
  [{:keys [tokens-lib
           token-sets
           selected
           on-update-token-set
           on-update-token-set-group
           is-token-set-active
           is-token-set-group-active
           on-create-token-set
           on-toggle-token-set
           on-toggle-token-set-group
           on-start-edition
           on-reset-edition
           origin
           on-select
           new-path
           edition-id]}]

  (assert (fn? is-token-set-group-active) "expected a function for `is-token-set-group-active` prop")
  (assert (fn? is-token-set-active) "expected a function for `is-token-set-active` prop")

  (let [theme-modal? (= origin "theme-modal")
        can-edit?    (mf/use-ctx ctx/can-edit?)
        draggable?   (and (not theme-modal?) can-edit?)
        empty-state? (and theme-modal?
                          (empty? token-sets)
                          (not new-path))

        ;; NOTE: on-reset-edition and on-start-edition function can
        ;; come as nil, in this case we need to provide a safe
        ;; fallback for them
        on-reset-edition
        (mf/use-fn
         (mf/deps on-reset-edition)
         (fn [v]
           (when (fn? on-reset-edition)
             (on-reset-edition v))))

        on-start-edition
        (mf/use-fn
         (mf/deps on-start-edition)
         (fn [v]
           (when (fn? on-start-edition)
             (on-start-edition v))))]

    [:div {:class (stl/css :sets-list)}
     (if ^boolean empty-state?
       [:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message-sets)}
        (tr "workspace.tokens.no-sets-create")]

       [:> token-sets-tree*
        {:is-draggable draggable?
         :new-path new-path
         :edition-id edition-id
         :tokens-lib tokens-lib
         :token-sets token-sets
         :selected selected
         :on-select on-select
         :is-token-set-active is-token-set-active
         :is-token-set-group-active is-token-set-group-active
         :on-toggle-set on-toggle-token-set
         :on-toggle-set-group on-toggle-token-set-group
         :on-create-token-set on-create-token-set
         :on-start-edition on-start-edition
         :on-reset-edition on-reset-edition
         :on-edit-submit-set on-update-token-set
         :on-edit-submit-group on-update-token-set-group}])]))
