;; 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.context-menu
  "A workspace specific context menu (mouse right click)."
  (:require-macros [app.main.style :as stl])
  (:require
   [app.common.data :as d]
   [app.common.data.macros :as dm]
   [app.common.files.helpers :as cfh]
   [app.common.transit :as t]
   [app.common.types.component :as ctk]
   [app.common.types.container :as ctn]
   [app.common.types.page :as ctp]
   [app.common.types.shape.layout :as ctl]
   [app.config :as cf]
   [app.main.data.event :as ev]
   [app.main.data.modal :as modal]
   [app.main.data.shortcuts :as scd]
   [app.main.data.workspace :as dw]
   [app.main.data.workspace.guides :as dwg]
   [app.main.data.workspace.interactions :as dwi]
   [app.main.data.workspace.libraries :as dwl]
   [app.main.data.workspace.selection :as dws]
   [app.main.data.workspace.shape-layout :as dwsl]
   [app.main.data.workspace.shapes :as dwsh]
   [app.main.data.workspace.shortcuts :as sc]
   [app.main.data.workspace.variants :as dwv]
   [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.context :as ctx]
   [app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
   [app.main.ui.workspace.sidebar.assets.common :as cmm]
   [app.util.clipboard :as clipboard]
   [app.util.debug :as dbg]
   [app.util.dom :as dom]
   [app.util.i18n :refer [tr] :as i18n]
   [app.util.shape-icon :as usi]
   [app.util.timers :as timers]
   [beicon.v2.core :as rx]
   [okulary.core :as l]
   [rumext.v2 :as mf]))

(def menu-ref
  (l/derived :context-menu refs/workspace-local))

(defn- prevent-default
  [event]
  (dom/prevent-default event)
  (dom/stop-propagation event))

(mf/defc menu-entry*
  {::mf/private true}
  [{:keys [title shortcut shortcut-key on-click on-pointer-enter on-pointer-leave
           on-unmount children is-selected icon disabled value]}]
  (let [cs              (mf/deref refs/custom-shortcuts)
        customized?     (and shortcut-key
                             (let [c (get-in cs [:workspace shortcut-key])]
                               (and c (not= c ""))))

        submenu-ref (mf/use-ref nil)
        hovering?   (mf/use-ref false)

        on-click'
        (mf/use-fn
         (mf/deps on-click)
         (fn [event]
           (st/emit! dw/hide-context-menu)
           (when on-click (on-click event))))

        on-pointer-enter
        (mf/use-fn
         (fn []
           (mf/set-ref-val! hovering? true)
           (let [submenu-node (mf/ref-val submenu-ref)]
             (when (some? submenu-node)
               (dom/set-css-property! submenu-node "display" "block")))
           (when on-pointer-enter (on-pointer-enter))))

        on-pointer-leave
        (mf/use-fn
         (fn []
           (mf/set-ref-val! hovering? false)
           (let [submenu-node (mf/ref-val submenu-ref)]
             (when (some? submenu-node)
               (timers/schedule
                200
                #(when-not (mf/ref-val hovering?)
                   (dom/set-css-property! submenu-node "display" "none")))))
           (when on-pointer-leave (on-pointer-leave))))

        set-dom-node
        (mf/use-fn
         (fn [dom]
           (let [submenu-node (mf/ref-val submenu-ref)]
             (when (and (some? dom) (some? submenu-node))
               (dom/set-css-property! submenu-node "top" (str (.-offsetTop dom) "px"))))))]

    (mf/use-effect
     (mf/deps on-unmount)
     (constantly on-unmount))

    (if icon
      [:li {:class (stl/css :menu-item-icon)
            :disabled disabled
            :data-value value
            :ref set-dom-node
            :on-click on-click'
            :on-pointer-enter on-pointer-enter
            :on-pointer-leave on-pointer-leave}
       [:span
        {:class (stl/css :menu-item-icon-group)}
        (if is-selected
          [:span {:class (stl/css :icon)}
           [:> icon* {:icon-id i/tick :size "s"}]]
          [:span {:class (stl/css :icon)}])
        [:span {:class (stl/css :icon)}
         [:> icon* {:icon-id icon :size "s"}]]]
       [:span {:class (stl/css :menu-item-title)} title]]

      [:li {:class (stl/css :menu-item)
            :disabled disabled
            :ref set-dom-node
            :data-value value
            :on-click on-click'
            :on-pointer-enter on-pointer-enter
            :on-pointer-leave on-pointer-leave}
       [:span {:class (stl/css :menu-item-title)} title]
       (when shortcut
         [:span   {:class (stl/css :shortcut)}
          (for [[idx sc] (d/enumerate (scd/split-sc shortcut))]
            [:span {:key (dm/str shortcut "-" idx)
                    :class (stl/css-case :shortcut-key true
                                         :customized-key customized?)} sc])])

       (when (> (count children) 1)
         [:span {:class (stl/css :icon)}
          [:> icon* {:icon-id i/arrow :size "s"}]])

       (when (> (count children) 1)
         [:ul {:class (stl/css :submenu)
               :ref submenu-ref
               :style {:display "none" :left 250}
               :on-context-menu prevent-default}
          children])])))

(mf/defc menu-separator*
  {::mf/private true}
  []
  [:li {:class (stl/css :separator)}])

(mf/defc context-menu-edit*
  {::mf/private true}
  [{:keys [shapes]}]
  (let [custom-shortcuts (mf/deref refs/custom-shortcuts)
        get-tt           #(sc/get-effective-tooltip % custom-shortcuts)

        multiple?         (> (count shapes) 1)

        do-copy           #(st/emit! (dw/copy-selected))
        do-copy-link      #(st/emit! (dw/copy-link-to-clipboard))
        do-copy-id        #(st/emit! (dw/copy-id-to-clipboard (-> shapes first :id str)))

        do-cut            #(st/emit! (dw/copy-selected)
                                     (dw/delete-selected))
        do-paste          #(st/emit! (dw/paste-from-clipboard))
        do-duplicate      #(st/emit! (dwv/duplicate-or-add-variant))

        enabled-paste-props* (mf/use-state false)

        handle-copy-css
        (mf/use-fn
         #(st/emit! (dw/copy-selected-css)))

        handle-copy-css-nested
        (mf/use-fn
         #(st/emit! (dw/copy-selected-css-nested)))

        handle-copy-props
        (mf/use-fn
         #(st/emit! (dw/copy-selected-props)))

        handle-paste-props
        (mf/use-fn
         #(st/emit! (dw/paste-selected-props)))

        handle-copy-svg
        (mf/use-fn
         #(st/emit! (dw/copy-selected-svg)))

        handle-copy-text
        (mf/use-fn
         #(st/emit! (dw/copy-selected-text)))

        handle-copy-as-image
        (mf/use-fn
         #(st/emit! (dw/copy-as-image)))

        handle-hover-copy-paste
        (mf/use-fn
         (fn []
           (->> (clipboard/from-navigator)
                (rx/mapcat #(.text %))
                (rx/take 1)
                (rx/subs!
                 (fn [data]
                   (try
                     (let [pdata (t/decode-str data)]
                       (reset! enabled-paste-props*
                               (and (dw/paste-data-valid? pdata)
                                    (= :copied-props (:type pdata)))))
                     (catch :default _
                       (reset! enabled-paste-props* false))))
                 (fn []
                   (reset! enabled-paste-props* false))))))]

    [:*
     (when (and (not multiple?)
                (dbg/enabled? :show-ids))
       [:> menu-entry* {:title (tr "workspace.shape.menu.copy-id")
                        :on-click do-copy-id}])
     [:> menu-entry* {:title (tr "workspace.shape.menu.copy")
                      :shortcut (get-tt :copy)
                      :shortcut-key :copy
                      :on-click do-copy}]
     [:> menu-entry* {:title (tr "workspace.shape.menu.copy-link")
                      :shortcut (get-tt :copy-link)
                      :shortcut-key :copy-link
                      :on-click do-copy-link}]
     [:> menu-entry* {:title (tr "workspace.shape.menu.cut")
                      :shortcut (get-tt :cut)
                      :shortcut-key :cut
                      :on-click do-cut}]
     [:> menu-entry* {:title (tr "workspace.shape.menu.paste")
                      :shortcut (get-tt :paste)
                      :shortcut-key :paste
                      :on-click do-paste}]
     [:> menu-entry* {:title (tr "workspace.shape.menu.duplicate")
                      :shortcut (get-tt :duplicate)
                      :shortcut-key :duplicate
                      :on-click do-duplicate}]

     [:> menu-entry* {:title (tr "workspace.shape.menu.copy-paste-as")
                      :on-pointer-enter (when (cf/check-browser? :chrome) handle-hover-copy-paste)}
      [:> menu-entry* {:title (tr "workspace.shape.menu.copy-css")
                       :on-click handle-copy-css}]
      [:> menu-entry* {:title (tr "workspace.shape.menu.copy-css-nested")
                       :on-click handle-copy-css-nested}]
      [:> menu-entry* {:title (tr "workspace.shape.menu.copy-svg")
                       :on-click handle-copy-svg}]

      (when (some cfh/frame-shape? shapes)
        [:> menu-entry* {:title (tr "workspace.shape.menu.copy-as-image")
                         :disabled multiple?
                         :on-click handle-copy-as-image}])

      [:> menu-separator* {}]

      [:> menu-entry* {:title (tr "workspace.shape.menu.copy-text")
                       :on-click handle-copy-text}]

      [:> menu-entry* {:title (tr "workspace.shape.menu.copy-props")
                       :shortcut (get-tt :copy-props)
                       :shortcut-key :copy-props
                       :disabled multiple?
                       :on-click handle-copy-props}]
      [:> menu-entry* {:title (tr "workspace.shape.menu.paste-props")
                       :shortcut (get-tt :paste-props)
                       :shortcut-key :paste-props
                       :disabled (and (cf/check-browser? :chrome) (not @enabled-paste-props*))
                       :on-click handle-paste-props}]]

     [:> menu-separator* {}]]))

(mf/defc context-menu-layer-position*
  {::mf/private true}
  [{:keys [shapes]}]
  (let [custom-shortcuts (mf/deref refs/custom-shortcuts)
        get-tt           #(sc/get-effective-tooltip % custom-shortcuts)

        do-bring-forward  (mf/use-fn #(st/emit! (dw/vertical-order-selected :up)))
        do-bring-to-front (mf/use-fn #(st/emit! (dw/vertical-order-selected :top)))
        do-send-backward  (mf/use-fn #(st/emit! (dw/vertical-order-selected :down)))
        do-send-to-back   (mf/use-fn #(st/emit! (dw/vertical-order-selected :bottom)))

        select-shapes     (fn [id] #(st/emit! (dws/select-shape id)))
        on-pointer-enter  (fn [id] #(st/emit! (dw/highlight-shape id)))
        on-pointer-leave  (fn [id] #(st/emit! (dw/dehighlight-shape id)))
        on-unmount        (fn [id] #(st/emit! (dw/dehighlight-shape id)))

        ;; NOTE: we use deref instead of mf/deref on objects because
        ;; we really don't want rerender on object changes
        hover-ids         (deref refs/current-hover-ids)
        objects           (deref refs/workspace-page-objects)
        hover-objs        (into [] (keep (d/getf objects)) hover-ids)]

    [:*
     (when (> (count hover-objs) 1)
       [:> menu-entry* {:title (tr "workspace.shape.menu.select-layer")}
        (for [object hover-objs]
          [:> menu-entry* {:title (:name object)
                           :key (dm/str (:id object))
                           :is-selected (some #(= object %) shapes)
                           :on-click (select-shapes (:id object))
                           :on-pointer-enter (on-pointer-enter (:id object))
                           :on-pointer-leave (on-pointer-leave (:id object))
                           :on-unmount (on-unmount (:id object))
                           :icon (usi/get-shape-icon object)}])])
     [:> menu-entry* {:title (tr "workspace.shape.menu.forward")
                      :shortcut (get-tt :bring-forward)
                      :shortcut-key :bring-forward
                      :on-click do-bring-forward}]
     [:> menu-entry* {:title (tr "workspace.shape.menu.front")
                      :shortcut (get-tt :bring-front)
                      :shortcut-key :bring-front
                      :on-click do-bring-to-front}]
     [:> menu-entry* {:title (tr "workspace.shape.menu.backward")
                      :shortcut (get-tt :bring-backward)
                      :shortcut-key :bring-backward
                      :on-click do-send-backward}]
     [:> menu-entry* {:title (tr "workspace.shape.menu.back")
                      :shortcut (get-tt :bring-back)
                      :shortcut-key :bring-back
                      :on-click do-send-to-back}]

     [:> menu-separator* {}]]))

(mf/defc context-menu-flip*
  {::mf/private true}
  []
  (let [custom-shortcuts (mf/deref refs/custom-shortcuts)
        get-tt           #(sc/get-effective-tooltip % custom-shortcuts)

        do-flip-vertical #(st/emit! (dw/flip-vertical-selected))
        do-flip-horizontal #(st/emit! (dw/flip-horizontal-selected))]
    [:*
     [:> menu-entry* {:title (tr "workspace.shape.menu.flip-vertical")
                      :shortcut (get-tt :flip-vertical)
                      :shortcut-key :flip-vertical
                      :on-click do-flip-vertical}]

     [:> menu-entry* {:title (tr "workspace.shape.menu.flip-horizontal")
                      :shortcut (get-tt :flip-horizontal)
                      :shortcut-key :flip-horizontal
                      :on-click do-flip-horizontal}]
     [:> menu-separator* {}]]))

(mf/defc context-menu-thumbnail*
  {::mf/private true}
  [{:keys [shapes]}]
  (let [custom-shortcuts (mf/deref refs/custom-shortcuts)
        get-tt           #(sc/get-effective-tooltip % custom-shortcuts)

        single?    (= (count shapes) 1)
        has-frame? (some cfh/frame-shape? shapes)
        do-toggle-thumbnail #(st/emit! (dw/toggle-file-thumbnail-selected))]
    (when (and single? has-frame?)
      [:*
       (if (every? :use-for-thumbnail shapes)
         [:> menu-entry* {:title (tr "workspace.shape.menu.thumbnail-remove")
                          :on-click do-toggle-thumbnail}]
         [:> menu-entry* {:title (tr "workspace.shape.menu.thumbnail-set")
                          :shortcut (get-tt :thumbnail-set)
                          :shortcut-key :thumbnail-set
                          :on-click do-toggle-thumbnail}])
       [:> menu-separator* {}]])))

(mf/defc context-menu-rename*
  {::mf/private true}
  [{:keys [shapes]}]
  (let [custom-shortcuts (mf/deref refs/custom-shortcuts)
        get-tt           #(sc/get-effective-tooltip % custom-shortcuts)

        do-rename #(st/emit! (dw/start-rename-selected))]
    (when (= (count shapes) 1)
      [:*
       [:> menu-separator* {}]
       [:> menu-entry* {:title (tr "workspace.shape.menu.rename")
                        :shortcut (get-tt :rename)
                        :shortcut-key :rename
                        :on-click do-rename}]])))

(mf/defc context-menu-group*
  {::mf/private true}
  [{:keys [shapes]}]
  (let [custom-shortcuts (mf/deref refs/custom-shortcuts)
        get-tt           #(sc/get-effective-tooltip % custom-shortcuts)

        multiple?       (> (count shapes) 1)
        single?         (= (count shapes) 1)

        objects         (deref refs/workspace-page-objects)
        any-in-copy?    (some #(ctn/has-any-copy-parent? objects %) shapes)
        any-is-variant? (some ctk/is-variant? shapes)

        ;; components can't be ungrouped
        has-frame? (->> shapes (d/seek #(and (cfh/frame-shape? %) (not (ctk/instance-head? %)) (not (ctk/is-variant-container? %)))))
        has-group? (->> shapes (d/seek #(and (cfh/group-shape? %) (not (ctk/instance-head? %)) (not (ctk/is-variant-container? %)))))
        has-bool?  (->> shapes (d/seek cfh/bool-shape?))
        has-mask?  (->> shapes (d/seek :masked-group))

        is-group?  (and single? has-group?)
        is-bool?   (and single? has-bool?)

        do-create-group #(st/emit! (dw/group-selected))
        do-remove-group #(st/emit! (dw/ungroup-selected))
        do-mask-group   #(st/emit! (dw/mask-group))
        do-unmask-group #(st/emit! (dw/unmask-group))
        do-create-artboard-from-selection
        #(st/emit! (dwsh/create-artboard-from-selection))]

    [:*
     (when (not (or any-in-copy? any-is-variant?))
       [:*
        (when (or has-bool? has-group? has-mask? has-frame?)
          [:> menu-entry* {:title (tr "workspace.shape.menu.ungroup")
                           :shortcut (get-tt :ungroup)
                           :shortcut-key :ungroup
                           :on-click do-remove-group}])

        [:> menu-entry* {:title (tr "workspace.shape.menu.group")
                         :shortcut (get-tt :group)
                         :shortcut-key :group
                         :on-click do-create-group}]

        (when (or multiple? (and is-group? (not has-mask?)) is-bool?)
          [:> menu-entry* {:title (tr "workspace.shape.menu.mask")
                           :shortcut (get-tt :mask)
                           :shortcut-key :mask
                           :on-click do-mask-group}])

        (when has-mask?
          [:> menu-entry* {:title (tr "workspace.shape.menu.unmask")
                           :shortcut (get-tt :unmask)
                           :shortcut-key :unmask
                           :on-click do-unmask-group}])

        [:> menu-entry* {:title (tr "workspace.shape.menu.create-artboard-from-selection")
                         :shortcut (get-tt :artboard-selection)
                         :shortcut-key :artboard-selection
                         :on-click do-create-artboard-from-selection}]
        [:> menu-separator* {}]])]))

(mf/defc context-focus-mode-menu*
  {::mf/private true}
  []
  (let [custom-shortcuts (mf/deref refs/custom-shortcuts)
        get-tt           #(sc/get-effective-tooltip % custom-shortcuts)

        focus (mf/deref refs/workspace-focus-selected)
        do-toggle-focus-mode #(st/emit! (dw/toggle-focus-mode))]

    [:> menu-entry* {:title (if (empty? focus)
                              (tr "workspace.focus.focus-on")
                              (tr "workspace.focus.focus-off"))
                     :shortcut (get-tt :toggle-focus-mode)
                     :shortcut-key :toggle-focus-mode
                     :on-click do-toggle-focus-mode}]))

(mf/defc context-menu-path*
  {::mf/private true}
  [{:keys [shapes objects disable-flatten disable-booleans]}]
  (let [custom-shortcuts (mf/deref refs/custom-shortcuts)
        get-tt           #(sc/get-effective-tooltip % custom-shortcuts)

        multiple?            (> (count shapes) 1)
        single?              (= (count shapes) 1)

        has-group?           (->> shapes (d/seek cfh/group-shape?))
        has-bool?            (->> shapes (d/seek cfh/bool-shape?))
        has-frame?           (->> shapes (d/seek cfh/frame-shape?))
        has-path?            (->> shapes (d/seek cfh/path-shape?))

        is-group?            (and single? has-group?)
        is-bool?             (and single? has-bool?)
        is-frame?            (and single? has-frame?)

        has-strokes?         (or (->> shapes (d/seek #(seq (:strokes %))))
                                 (when objects
                                   (->> shapes
                                        (d/seek
                                         (fn [shape]
                                           (->> (cfh/get-children-ids objects (:id shape))
                                                (d/seek #(seq (:strokes (get objects %))))))))))

        do-start-editing     (fn [] (timers/schedule #(st/emit! (dw/start-editing-selected))))
        do-transform-to-path #(st/emit! (dw/convert-selected-to-path))
        do-strokes-to-path   #(st/emit! (dw/convert-selected-strokes-to-path))

        make-do-bool
        (fn [bool-type]
          #(cond
             multiple?
             (st/emit! (dw/create-bool bool-type))

             is-group?
             (st/emit! (dw/group-to-bool (-> shapes first :id) bool-type))

             is-bool?
             (st/emit! (dw/change-bool-type (-> shapes first :id) bool-type))))]
    [:*
     (when (and single? (not is-frame?))
       [:> menu-entry* {:title (tr "workspace.shape.menu.edit")
                        :shortcut (get-tt :start-editing)
                        :shortcut-key :start-editing
                        :on-click do-start-editing}])

     (when-not (or disable-flatten has-frame? has-path?)
       [:> menu-entry* {:title (tr "workspace.shape.menu.flatten")
                        :on-click do-transform-to-path}])

     (when (and has-strokes?
                (features/active-feature? @st/state "render-wasm/v1")
                (contains? cf/flags :stroke-path))
       [:> menu-entry* {:title (tr "workspace.shape.menu.stroke-to-path")
                        :on-click do-strokes-to-path}])

     (when (and (not has-frame?)
                (not disable-booleans)
                (or multiple? (and single? (or is-group? is-bool?))))
       [:> menu-entry* {:title (tr "workspace.shape.menu.path")}
        [:> menu-entry* {:title (tr "workspace.shape.menu.union")
                         :shortcut (get-tt :bool-union)
                         :shortcut-key :bool-union
                         :on-click (make-do-bool :union)}]
        [:> menu-entry* {:title (tr "workspace.shape.menu.difference")
                         :shortcut (get-tt :bool-difference)
                         :shortcut-key :bool-difference
                         :on-click (make-do-bool :difference)}]
        [:> menu-entry* {:title (tr "workspace.shape.menu.intersection")
                         :shortcut (get-tt :bool-intersection)
                         :shortcut-key :bool-intersection
                         :on-click (make-do-bool :intersection)}]
        [:> menu-entry* {:title (tr "workspace.shape.menu.exclude")
                         :shortcut (get-tt :bool-exclude)
                         :shortcut-key :bool-exclude
                         :on-click (make-do-bool :exclude)}]

        (when (and single? is-bool? (not disable-flatten))
          [:*
           [:> menu-separator* {}]
           [:> menu-entry* {:title (tr "workspace.shape.menu.flatten")
                            :on-click do-transform-to-path}]])])]))

(mf/defc context-menu-layer-options*
  {::mf/private true}
  [{:keys [shapes]}]
  (let [custom-shortcuts (mf/deref refs/custom-shortcuts)
        get-tt           #(sc/get-effective-tooltip % custom-shortcuts)

        ids (mapv :id shapes)
        do-show-shape #(st/emit! (dw/update-shape-flags ids {:hidden false}))
        do-hide-shape #(st/emit! (dw/update-shape-flags ids {:hidden true}))
        do-lock-shape #(st/emit! (dw/update-shape-flags ids {:blocked true}))
        do-unlock-shape #(st/emit! (dw/update-shape-flags ids {:blocked false}))]
    [:*
     (if (every? :hidden shapes)
       [:> menu-entry* {:title (tr "workspace.shape.menu.show")
                        :shortcut (get-tt :toggle-visibility)
                        :shortcut-key :toggle-visibility
                        :on-click do-show-shape}]
       [:> menu-entry* {:title (tr "workspace.shape.menu.hide")
                        :shortcut (get-tt :toggle-visibility)
                        :shortcut-key :toggle-visibility
                        :on-click do-hide-shape}])

     (if (every? :blocked shapes)
       [:> menu-entry* {:title (tr "workspace.shape.menu.unlock")
                        :shortcut (get-tt :toggle-lock)
                        :shortcut-key :toggle-lock
                        :on-click do-unlock-shape}]
       [:> menu-entry* {:title (tr "workspace.shape.menu.lock")
                        :shortcut (get-tt :toggle-lock)
                        :shortcut-key :toggle-lock
                        :on-click do-lock-shape}])]))

(mf/defc context-menu-prototype*
  {::mf/private true}
  [{:keys [shapes]}]
  (let [flows           (mf/deref refs/workspace-page-flows)
        options-mode    (mf/deref refs/options-mode-global)
        do-add-flow     #(st/emit! (dwi/add-flow-selected-frame))
        do-remove-flow  #(st/emit! (dwi/remove-flow (:id %)))

        prototype?      (= options-mode :prototype)
        single?         (= (count shapes) 1)

        has-frame?      (d/seek cfh/frame-shape? shapes)
        is-frame?       (and single? has-frame?)]

    (when (and prototype? is-frame?)
      (if-let [flow (ctp/get-frame-flow flows (-> shapes first :id))]
        [:> menu-entry* {:title (tr "workspace.shape.menu.delete-flow-start")
                         :on-click (do-remove-flow flow)}]
        [:> menu-entry* {:title (tr "workspace.shape.menu.flow-start")
                         :on-click do-add-flow}]))))

(mf/defc context-menu-layout*
  {::mf/private true}
  [{:keys [shapes]}]
  (let [custom-shortcuts (mf/deref refs/custom-shortcuts)
        get-tt           #(sc/get-effective-tooltip % custom-shortcuts)

        single?      (= (count shapes) 1)
        objects      (deref refs/workspace-page-objects)
        any-in-copy? (some true? (map #(ctn/has-any-copy-parent? objects %) shapes))

        has-flex?
        (and single? (every? ctl/flex-layout? shapes))

        has-grid?
        (and single? (every? ctl/grid-layout? shapes))

        any-is-variant? (some ctk/is-variant? shapes)

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

        on-remove-layout
        (mf/use-fn
         (mf/deps shapes)
         (fn [_event]
           (let [ids (map :id shapes)]
             (st/emit! (dwsl/remove-layout ids)))))]
    [:*
     (when (not any-in-copy?)
       (if (or ^boolean has-flex?
               ^boolean has-grid?)
         [:div
          [:> menu-separator* {}]
          (if has-flex?
            [:> menu-entry* {:title (tr "workspace.shape.menu.remove-flex")
                             :shortcut (get-tt :toggle-layout-flex)
                             :shortcut-key :toggle-layout-flex
                             :on-click on-remove-layout}]
            [:> menu-entry* {:title (tr "workspace.shape.menu.remove-grid")
                             :shortcut (get-tt :toggle-layout-grid)
                             :shortcut-key :toggle-layout-grid
                             :on-click on-remove-layout}])]

         (when (or single? (not any-is-variant?))
           [:div
            [:> menu-separator* {}]
            [:> menu-entry* {:title (tr "workspace.shape.menu.add-flex")
                             :shortcut (get-tt :toggle-layout-flex)
                             :shortcut-key :toggle-layout-flex
                             :value "flex"
                             :on-click on-add-layout}]
            [:> menu-entry* {:title (tr "workspace.shape.menu.add-grid")
                             :shortcut (get-tt :toggle-layout-grid)
                             :shortcut-key :toggle-layout-grid
                             :value "grid"
                             :on-click on-add-layout}]])))]))

(mf/defc context-menu-component*
  {:mf/private true}
  [{:keys [shapes]}]
  (let [custom-shortcuts           (mf/deref refs/custom-shortcuts)
        get-tt                     #(sc/get-effective-tooltip % custom-shortcuts)

        single?                    (= (count shapes) 1)
        objects                    (deref refs/workspace-page-objects)
        can-make-component         (every? true? (map #(ctn/valid-shape-for-component? objects %) shapes))
        components-menu-entries    (cmm/generate-components-menu-entries shapes)
        variant-container?         (and single? (ctk/is-variant-container? (first shapes)))
        all-main?                  (every? ctk/main-instance? shapes)
        any-variant?               (some ctk/is-variant? shapes)
        do-add-component           (mf/use-fn #(st/emit! (dwl/add-component)))
        do-add-multiple-components (mf/use-fn #(st/emit! (dwl/add-multiple-components)))
        do-combine-as-variants     (mf/use-fn #(st/emit!
                                                (dwv/combine-selected-as-variants {:trigger "workspace:context-menu-component"})))
        do-add-variant             (mf/use-fn
                                    (mf/deps shapes)
                                    #(st/emit!
                                      (ev/event {::ev/name "add-new-variant" ::ev/origin "workspace:context-menu-component"})
                                      (dwv/add-new-variant (:id (first shapes)))))]
    [:*
     (when can-make-component ;; We don't want to change the structure of component copies
       [:*
        [:> menu-separator* {}]

        [:> menu-entry* {:title (tr "workspace.shape.menu.create-component")
                         :shortcut (get-tt :create-component-variant)
                         :shortcut-key :create-component-variant
                         :on-click do-add-component}]
        (when (not single?)
          [:> menu-entry* {:title (tr "workspace.shape.menu.create-multiple-components")
                           :on-click do-add-multiple-components}])])

     (when (seq components-menu-entries)
       [:*
        [:> menu-separator*]
        (for [entry (filter some? components-menu-entries)]
          (let [sc-key (:shortcut entry)]
            [:> menu-entry* {:key (:title entry)
                             :title (:title entry)
                             :shortcut (when sc-key (get-tt sc-key))
                             :shortcut-key sc-key
                             :on-click (:action entry)}]))])

     (when variant-container?
       [:*
        [:> menu-separator*]
        [:> menu-entry* {:title (tr "workspace.shape.menu.add-variant")
                         :shortcut (get-tt :create-component-variant)
                         :shortcut-key :create-component-variant
                         :on-click do-add-variant}]])

     (when (and (not single?) all-main? (not any-variant?))
       [:*
        [:> menu-separator*]
        [:> menu-entry* {:title (tr "workspace.shape.menu.combine-as-variants")
                         :on-click do-combine-as-variants}]])]))

(mf/defc context-menu-guides*
  {::mf/props :obj
   ::mf/private true}
  [{:keys [shapes]}]
  (let [frame-ids    (into #{} (comp (filter cfh/frame-shape?) d/xf:map-id) shapes)
        guides       (mf/deref refs/workspace-page-guides)
        has-guides?  (some #(contains? frame-ids (:frame-id %)) (vals guides))

        do-remove-guides
        (mf/use-fn
         (mf/deps frame-ids)
         #(st/emit! (dwg/remove-frame-guides frame-ids)))]

    (when (and (seq frame-ids) has-guides?)
      [:*
       [:> menu-separator* {}]
       [:> menu-entry* {:title (tr "workspace.shape.menu.clear-guides")
                        :on-click do-remove-guides}]])))

(mf/defc context-menu-delete*
  {::mf/private true}
  []
  (let [custom-shortcuts (mf/deref refs/custom-shortcuts)
        get-tt           #(sc/get-effective-tooltip % custom-shortcuts)

        do-delete #(st/emit! (dw/delete-selected))]
    [:*
     [:> menu-separator* {}]
     [:> menu-entry* {:title (tr "workspace.shape.menu.delete")
                      :shortcut (get-tt :delete)
                      :shortcut-key :delete
                      :on-click do-delete}]]))

(mf/defc shape-context-menu*
  {::mf/wrap [mf/memo]
   ::mf/private true}
  [{:keys [mdata]}]
  (let [{:keys [disable-booleans disable-flatten]} mdata
        objects                   (deref refs/workspace-page-objects)
        shape-ids                 (mf/deref refs/selected-shapes)
        shapes                    (map (d/getf objects) shape-ids)
        is-not-variant-container? (->> shapes (d/seek #(not (ctk/is-variant-container? %))))
        props  (mf/props
                {:shapes shapes
                 :objects objects
                 :disable-booleans disable-booleans
                 :disable-flatten disable-flatten})]
    (when-not (empty? shapes)
      [:*
       [:> context-menu-edit* props]
       [:> context-menu-layer-position* props]
       (when is-not-variant-container?
         [:> context-menu-flip* props])
       [:> context-menu-thumbnail* props]
       [:> context-menu-rename* props]
       [:> context-menu-group* props]
       [:> context-focus-mode-menu* props]
       [:> context-menu-path* props]
       [:> context-menu-layer-options* props]
       [:> context-menu-prototype* props]
       (when is-not-variant-container?
         [:> context-menu-layout* props])
       [:> context-menu-component* props]
       [:> context-menu-guides* props]
       [:> context-menu-delete* props]])))

(mf/defc page-item-context-menu*
  {::mf/private true}
  [{:keys [mdata]}]
  (let [page (:page mdata)
        deletable? (:deletable? mdata)
        selected-pages (:selected-pages mdata)
        multi? (> (count selected-pages) 1)
        id (:id page)
        delete-fn #(st/emit! (dw/delete-page id))
        do-delete #(st/emit! (modal/show
                              {:type :confirm
                               :title (tr "modals.delete-page.title")
                               :message (tr "modals.delete-page.body")
                               :on-accept delete-fn}))
        delete-many-fn #(st/emit! (dw/delete-pages selected-pages))
        do-delete-many #(st/emit! (modal/show
                                   {:type :confirm
                                    :title (tr "modals.delete-pages.title")
                                    :message (tr "modals.delete-pages.body")
                                    :on-accept delete-many-fn}))
        do-duplicate #(st/emit!
                       (dw/duplicate-page id)
                       (ev/event {::ev/name "duplicate-page"}))
        do-rename #(st/emit! (dw/start-rename-page-item id))]

    (if multi?
      ;; When several pages are selected, the only available action is
      ;; deleting all of them at once.
      [:> menu-entry* {:title (tr "workspace.assets.delete-pages")
                       :on-click do-delete-many}]
      [:*
       (when deletable?
         [:> menu-entry* {:title (tr "workspace.assets.delete")
                          :on-click do-delete}])

       [:> menu-entry* {:title (tr "workspace.assets.rename")
                        :on-click do-rename}]
       [:> menu-entry* {:title (tr "workspace.assets.duplicate")
                        :on-click do-duplicate}]])))

(mf/defc viewport-context-menu*
  []
  (let [custom-shortcuts (mf/deref refs/custom-shortcuts)
        get-tt           #(sc/get-effective-tooltip % custom-shortcuts)

        focus      (mf/deref refs/workspace-focus-selected)
        read-only? (mf/use-ctx ctx/workspace-read-only?)
        do-paste   #(st/emit! (dw/paste-from-clipboard))
        do-hide-ui #(st/emit! (-> (dw/toggle-layout-flag :hide-ui)
                                  (vary-meta assoc ::ev/origin "workspace-context-menu")))
        do-toggle-focus-mode #(st/emit! (dw/toggle-focus-mode))]
    [:*
     (when-not ^boolean read-only?
       [:> menu-entry* {:title (tr "workspace.shape.menu.paste")
                        :shortcut (get-tt :paste)
                        :shortcut-key :paste
                        :on-click do-paste}])
     [:> menu-entry* {:title (tr "workspace.shape.menu.hide-ui")
                      :shortcut (get-tt :hide-ui)
                      :shortcut-key :hide-ui
                      :on-click do-hide-ui}]

     (when (d/not-empty? focus)
       [:> menu-entry* {:title (tr "workspace.focus.focus-off")
                        :shortcut (get-tt :toggle-focus-mode)
                        :shortcut-key :toggle-focus-mode
                        :on-click do-toggle-focus-mode}])]))

(mf/defc grid-track-context-menu*
  {::mf/private true}
  [{:keys [mdata]}]
  (let [{:keys [type index grid-id]} mdata
        do-delete-track
        (mf/use-fn
         (mf/deps grid-id type index)
         (fn []
           (st/emit! (dwsl/remove-layout-track [grid-id] type index))))

        do-add-track-before
        (mf/use-fn
         (mf/deps grid-id type index)
         (fn []
           (st/emit! (dwsl/add-layout-track [grid-id] type ctl/default-track-value index))))

        do-add-track-after
        (mf/use-fn
         (mf/deps grid-id type index)
         (fn []
           (st/emit! (dwsl/add-layout-track [grid-id] type ctl/default-track-value (inc index)))))

        do-duplicate-track
        (mf/use-fn
         (mf/deps grid-id type index)
         (fn []
           (st/emit! (dwsl/duplicate-layout-track [grid-id] type index))))

        do-delete-track-shapes
        (mf/use-fn
         (mf/deps grid-id type index)
         (fn []
           (st/emit! (dwsl/remove-layout-track [grid-id] type index {:with-shapes? true}))))]

    (if (= type :column)
      [:*
       [:> menu-entry* {:title (tr "workspace.context-menu.grid-track.column.duplicate") :on-click do-duplicate-track}]
       [:> menu-entry* {:title (tr "workspace.context-menu.grid-track.column.add-before") :on-click do-add-track-before}]
       [:> menu-entry* {:title (tr "workspace.context-menu.grid-track.column.add-after") :on-click do-add-track-after}]
       [:> menu-entry* {:title (tr "workspace.context-menu.grid-track.column.delete") :on-click do-delete-track}]
       [:> menu-entry* {:title (tr "workspace.context-menu.grid-track.column.delete-shapes") :on-click do-delete-track-shapes}]]

      [:*
       [:> menu-entry* {:title (tr "workspace.context-menu.grid-track.row.duplicate") :on-click do-duplicate-track}]
       [:> menu-entry* {:title (tr "workspace.context-menu.grid-track.row.add-before") :on-click do-add-track-before}]
       [:> menu-entry* {:title (tr "workspace.context-menu.grid-track.row.add-after") :on-click do-add-track-after}]
       [:> menu-entry* {:title (tr "workspace.context-menu.grid-track.row.delete") :on-click do-delete-track}]
       [:> menu-entry* {:title (tr "workspace.context-menu.grid-track.row.delete-shapes") :on-click do-delete-track-shapes}]])))

(mf/defc grid-cells-context-menu*
  {::mf/private true}
  [{:keys [mdata]}]
  (let [{:keys [grid cells]} mdata

        grid-id (:id grid)
        single? (= (count cells) 1)

        can-merge?
        (mf/use-memo
         (mf/deps cells)
         #(ctl/valid-area-cells? cells))

        can-copy-rows?
        (mf/use-memo
         (mf/deps grid cells)
         #(dwsl/complete-rows? grid cells))

        can-copy-columns?
        (mf/use-memo
         (mf/deps grid cells)
         #(dwsl/complete-columns? grid cells))

        grid-edition-ref
        (mf/use-memo
         (mf/deps grid-id)
         #(refs/workspace-grid-edition-id grid-id))

        grid-edition (mf/deref grid-edition-ref)
        has-copied-tracks? (some? (:copied-tracks grid-edition))

        do-merge-cells
        (mf/use-fn
         (mf/deps grid-id cells)
         (fn []
           (st/emit! (dwsl/merge-cells grid-id (map :id cells)))))

        do-create-board
        (mf/use-fn
         (mf/deps grid-id cells)
         (fn []
           (st/emit! (dwsl/create-cell-board grid-id (map :id cells)))))

        do-copy-rows
        (mf/use-fn
         (mf/deps grid-id)
         (fn []
           (st/emit! (dwsl/copy-grid-tracks grid-id :row))))

        do-copy-columns
        (mf/use-fn
         (mf/deps grid-id)
         (fn []
           (st/emit! (dwsl/copy-grid-tracks grid-id :column))))

        do-paste-tracks
        (mf/use-fn
         (mf/deps grid-id)
         (fn []
           (st/emit! (dwsl/paste-grid-tracks grid-id))))]
    [:*
     (when (not single?)
       [:> menu-entry* {:title (tr "workspace.context-menu.grid-cells.merge")
                        :on-click do-merge-cells
                        :disabled (not can-merge?)}])

     (when single?
       [:> menu-entry* {:title (tr "workspace.context-menu.grid-cells.area")
                        :on-click do-merge-cells}])

     [:> menu-entry* {:title (tr "workspace.context-menu.grid-cells.create-board")
                      :on-click do-create-board
                      :disabled (and (not single?) (not can-merge?))}]

     [:> menu-entry* {:title (tr "workspace.context-menu.grid-cells.copy-rows")
                      :on-click do-copy-rows
                      :disabled (not can-copy-rows?)}]

     [:> menu-entry* {:title (tr "workspace.context-menu.grid-cells.copy-columns")
                      :on-click do-copy-columns
                      :disabled (not can-copy-columns?)}]

     [:> menu-entry* {:title (tr "workspace.context-menu.grid-cells.paste-tracks")
                      :on-click do-paste-tracks
                      :disabled (not has-copied-tracks?)}]]))


(def guide-color-presets
  ["#ff3277" "#4dabf7" "#51cf66" "#fcc419" "#ff922b" "#cc5de8" "#ffffff" "#868e96"])

(mf/defc guide-color-context-menu*
  {::mf/props :obj
   ::mf/private true}
  [{:keys [mdata]}]
  (let [{:keys [guide]} mdata
        guide-id (:id guide)
        current-color (or (:color guide) (first guide-color-presets))

        do-set-color
        (mf/use-fn
         (mf/deps guide-id)
         (fn [event]
           (let [color (dom/get-data (dom/get-current-target event) "color")]
             (st/emit! dw/hide-context-menu
                       (dwg/update-guide-color guide-id color)))))

        do-remove-guide
        (mf/use-fn
         (mf/deps guide)
         (fn []
           (st/emit! dw/hide-context-menu
                     (dwg/remove-guide guide))))]

    [:*
     [:li {:class (stl/css :menu-item :guide-color-label)}
      [:span {:class (stl/css :menu-item-title)}
       (tr "workspace.context-menu.guides.change-color")]]
     [:li {:class (stl/css :guide-color-swatches)}
      (for [color guide-color-presets]
        [:span {:key color
                :class (stl/css-case
                        :guide-color-swatch true
                        :selected (= color current-color))
                :data-color color
                :on-click do-set-color
                :title color
                :style {:background-color color}}])]
     [:> menu-separator* {}]
     [:> menu-entry* {:title (tr "workspace.context-menu.guides.remove")
                      :on-click do-remove-guide}]]))

;; FIXME: optimize because it is rendered always

(mf/defc context-menu*
  []
  (let [mdata        (mf/deref menu-ref)
        top          (- (get-in mdata [:position :y]) 20)
        left         (get-in mdata [:position :x])
        dropdown-ref (mf/use-ref)
        read-only?   (mf/use-ctx ctx/workspace-read-only?)]

    (mf/with-effect [mdata]
      (when-let [dropdown (mf/ref-val dropdown-ref)]
        (let [bounding-rect (dom/get-bounding-rect dropdown)
              window-size (dom/get-window-size)
              delta-x (max (- (+ (:right bounding-rect) 250) (:width window-size)) 0)
              delta-y (max (- (:bottom bounding-rect) (:height window-size)) 0)
              new-style (str "top: " (- top delta-y) "px; "
                             "left: " (- left delta-x) "px;")]
          (when (or (> delta-x 0) (> delta-y 0))
            (.setAttribute ^js dropdown "style" new-style)))))

    [:& dropdown {:show (boolean mdata)
                  :on-close #(st/emit! dw/hide-context-menu)}
     [:div {:class (stl/css :menu-wrapper)
            :ref dropdown-ref
            :style {:top top :left left}
            :data-testid "context-menu"
            :on-context-menu prevent-default}

      [:ul {:class (stl/css :menu)}
       (if ^boolean read-only?
         [:> viewport-context-menu* {:mdata mdata}]
         (case (:kind mdata)
           :shape      [:> shape-context-menu* {:mdata mdata}]
           :page       [:> page-item-context-menu* {:mdata mdata}]
           :grid-track [:> grid-track-context-menu* {:mdata mdata}]
           :grid-cells [:> grid-cells-context-menu* {:mdata mdata}]
           :guide      [:> guide-color-context-menu* {:mdata mdata}]
           [:> viewport-context-menu* {:mdata mdata}]))]]]))
