(ns frontend.components.property.config
  (:require [clojure.string :as string]
            [frontend.components.dnd :as dnd]
            [frontend.components.icon :as icon-component]
            [frontend.components.property.default-value :as pdv]
            [frontend.components.property.value :as pv]
            [frontend.components.select :as select]
            [frontend.config :as config]
            [frontend.db :as db]
            [frontend.db-mixins :as db-mixins]
            [frontend.db.async :as db-async]
            [frontend.db.model :as model]
            [frontend.handler.common.developer :as dev-common-handler]
            [frontend.handler.db-based.page :as db-page-handler]
            [frontend.handler.db-based.property :as db-property-handler]
            [frontend.handler.property :as property-handler]
            [frontend.handler.route :as route-handler]
            [frontend.state :as state]
            [frontend.ui :as ui]
            [frontend.util :as util]
            [goog.dom :as gdom]
            [logseq.db :as ldb]
            [logseq.db.common.order :as db-order]
            [logseq.db.frontend.property :as db-property]
            [logseq.db.frontend.property.type :as db-property-type]
            [logseq.outliner.core :as outliner-core]
            [logseq.shui.hooks :as hooks]
            [logseq.shui.popup.core :as shui-popup]
            [logseq.shui.ui :as shui]
            [promesa.core :as p]
            [rum.core :as rum]))

(defn- re-init-commands!
  "Update commands after task status and priority's closed values has been changed"
  [property]
  (when (contains? #{:logseq.property/status :logseq.property/priority} (:db/ident property))
    (state/pub-event! [:init/commands])))

(defn- <upsert-closed-value!
  "Create new closed value and returns its block UUID."
  [property item]
  (p/do!
   (db-property-handler/upsert-closed-value! (:db/ident property) item)
   (re-init-commands! property)))

(defn- loop-focusable-elements!
  ([^js cnt] (loop-focusable-elements! cnt
                                       ".ui__button:not([disabled]), .ui__input, .ui__textarea"))
  ([^js cnt selectors]
   (when-let [els (some-> cnt (.querySelectorAll selectors) (seq))]
     (let [active js/document.activeElement
           current-idx (.indexOf els active)
           total-len (count els)
           to-idx (cond
                    (or (= -1 current-idx)
                        (= total-len (inc current-idx)))
                    0
                    :else
                    (inc current-idx))]
       (some-> els (nth to-idx) (.focus))))))

(defn- set-property-description!
  [property description]
  (if-let [ent (:logseq.property/description property)]
    (db/transact! (state/get-current-repo)
                  [(outliner-core/block-with-updated-at
                    {:db/id (:db/id ent) :block/title description})]
                  {:outliner-op :save-block})
    (when-not (string/blank? description)
      (db-property-handler/set-block-property!
       (:db/id property)
       :logseq.property/description description))))

(defn- <create-class-if-not-exists!
  [value]
  (when (string? value)
    (let [page-name (string/trim value)]
      (when-not (string/blank? page-name)
        (p/let [page (db-page-handler/<create-class! page-name {:redirect? false})]
          (:block/uuid page))))))

(rum/defc class-select
  [property {:keys [multiple-choices? disabled? default-open? no-class? on-hide]
             :or {multiple-choices? true}}]
  (let [*ref (rum/use-ref nil)
        schema-classes (:logseq.property/classes property)]
    [:div.flex.flex-1.col-span-3
     (let [content-fn
           (fn [{:keys [id]}]
             (let [toggle-fn #(do
                                (when (fn? on-hide) (on-hide))
                                (shui/popup-hide! id))
                   classes (model/get-all-readable-classes (state/get-current-repo) {:except-root-class? true})
                   options (map (fn [class]
                                  {:label (:block/title class)
                                   :value (:block/uuid class)})
                                classes)
                   options (if no-class?
                             (cons {:label "Skip choosing tag"
                                    :value :no-tag}
                                   options)
                             options)
                   opts {:items options
                         :input-default-placeholder (if multiple-choices? "Choose tags" "Choose tag")
                         :dropdown? false
                         :close-modal? false
                         :multiple-choices? multiple-choices?
                         :selected-choices (map :block/uuid schema-classes)
                         :extract-fn :label
                         :extract-chosen-fn :value
                         :show-new-when-not-exact-match? true
                         :input-opts {:on-key-down
                                      (fn [e]
                                        (case (util/ekey e)
                                          "Escape"
                                          (do
                                            (util/stop e)
                                            (toggle-fn))
                                          nil))}
                         :on-chosen (fn [value select?]
                                      (if (= value :no-tag)
                                        (toggle-fn)
                                        (p/let [result (<create-class-if-not-exists! value)
                                                value' (or result value)
                                                tx-data [[(if select? :db/add :db/retract) (:db/id property) :logseq.property/classes [:block/uuid value']]]
                                                _ (db/transact! (state/get-current-repo) tx-data {:outliner-op :update-property})]
                                          (when-not multiple-choices? (toggle-fn)))))}]

               (select/select opts)))]

       (if default-open?
         (content-fn nil)
         [:div.flex.flex-1.cursor-pointer
          {:ref *ref
           :on-click (if disabled?
                       (constantly nil)
                       #(shui/popup-show! (.-target %) content-fn {:id :ls-node-tags-sub-pane}))}
          (if (seq schema-classes)
            [:div.flex.flex-1.flex-row.items-center.flex-wrap.gap-2
             {:class "max-w-[300px]"}
             (for [class schema-classes]
               [:a.text-sm (str "#" (:block/title class))])
             [:span.opacity-60.pl-1.top-1.relative.hover:opacity-80.active:opacity-60
              (shui/tabler-icon "edit")]]
            (pv/property-empty-btn-value property))]))]))

(rum/defc name-edit-pane
  [property {:keys [set-sub-open! disabled?]}]
  (let [*form-data (rum/use-ref {:icon (:logseq.property/icon property)
                                 :title (or (:block/title property) "")
                                 :description (or (db-property/property-value-content (:logseq.property/description property)) "")})
        [form-data, set-form-data!] (rum/use-state (rum/deref *form-data))
        [saving?, set-saving!] (rum/use-state false)
        *el (rum/use-ref nil)
        *input-ref (rum/use-ref nil)
        title (util/trim-safe (:title form-data))
        description (util/trim-safe (:description form-data))]

    (hooks/use-effect!
     (fn []
       (js/setTimeout #(some-> (rum/deref *el) (.focus)) 32))
     [])

    [:div.ls-property-name-edit-pane.outline-none
     {:on-key-down (fn [^js e] (when (= "Tab" (.-key e))
                                 (loop-focusable-elements! (rum/deref *el))))
      :tab-index -1
      :ref *el}
     [:div.flex.items-center.input-wrap
      (icon-component/icon-picker (:icon form-data)
                                  {:on-chosen (fn [_e icon] (set-form-data! (assoc form-data :icon icon)))
                                   :popup-opts {:align "start"}
                                   :del-btn? (boolean (:icon form-data))
                                   :empty-label "?"})
      (shui/input {:ref *input-ref
                   :size "sm"
                   :default-value title
                   :placeholder "name"
                   :disabled disabled?
                   :on-key-down (fn [e]
                                  (when (contains? #{"ArrowLeft" "ArrowRight"} (util/ekey e))
                                    (util/stop-propagation e)))
                   :on-change (fn [^js e] (set-form-data! (assoc form-data :title (util/trim-safe (util/evalue e)))))})]
     [:div.pt-2 (shui/textarea {:placeholder "description" :default-value description
                                :disabled disabled? :on-change (fn [^js e] (set-form-data! (assoc form-data :description (util/trim-safe (util/evalue e)))))})]

     (let [dirty? (not= (rum/deref *form-data) form-data)]
       [:div.pt-2.flex.justify-end
        (shui/button {:size "sm" :disabled (or saving? (not dirty?))
                      :variant (if dirty? :default :secondary)
                      :on-click (fn []
                                  (set-saving! true)
                                  (-> [(db-property-handler/upsert-property!
                                        (:db/ident property)
                                        {}
                                        {:property-name title
                                         :properties {:logseq.property/icon (:icon form-data)}})
                                       (when (not= description (:description (rum/deref *form-data)))
                                         (set-property-description! property description))]
                                      (p/all)
                                      (p/then #(set-sub-open! false))
                                      (p/catch #(shui/toast! (str %) :error))
                                      (p/finally #(set-saving! false))))}
                     "Save")])]))

(rum/defc choice-base-edit-form
  [own-property block owner-block]
  (let [create? (:create? block)
        uuid (:block/uuid block)
        *form-data (rum/use-ref
                    {:value (or (str (db-property/closed-value-content block)) "")
                     :icon (:logseq.property/icon block)
                     :description (or (db-property/property-value-content (:logseq.property/description block)) "")})
        [form-data, set-form-data!] (rum/use-state (rum/deref *form-data))
        *input-ref (rum/use-ref nil)]

    (hooks/use-effect!
     (fn []
       (when create?
         (js/setTimeout #(some-> (rum/deref *input-ref) (.focus)) 60)))
     [])

    [:div.ls-base-edit-form
     [:div.flex.items-center.input-wrap
      (icon-component/icon-picker
       (:icon form-data)
       {:on-chosen (fn [_e icon] (set-form-data! (assoc form-data :icon icon)))
        :empty-label "?"
        :del-btn? (boolean (:icon form-data))
        :popup-opts {:align "start"}})

      (shui/input {:ref *input-ref :size "sm"
                   :default-value (:value form-data)
                   :on-change (fn [^js e] (set-form-data! (assoc form-data :value (util/trim-safe (util/evalue e)))))
                   :placeholder "title"})]
     [:div.pt-2 (shui/textarea
                 {:placeholder "description" :default-value (:description form-data)
                  :on-change (fn [^js e] (set-form-data! (assoc form-data :description (util/trim-safe (util/evalue e)))))})]
     [:div.pt-2.flex.justify-end
      (let [dirty? (not= (rum/deref *form-data) form-data)]
        (shui/button {:size "sm"
                      :disabled (not dirty?)
                      :on-click (fn []
                                  (-> (<upsert-closed-value! own-property
                                                             (cond-> form-data
                                                               uuid
                                                               (assoc :id uuid)
                                                               (ldb/class? owner-block)
                                                               (assoc :scoped-class-id (:db/id owner-block))))
                                      (p/then #(shui/popup-hide!))
                                      (p/catch #(shui/toast! (str %) :error))))
                      :variant (if dirty? :default :secondary)}
                     "Save"))]]))

(defn restore-root-highlight-item!
  [id]
  (js/setTimeout
   #(some-> (gdom/getElement id) (.focus)) 32))

(rum/defc dropdown-editor-menuitem
  [{:keys [id icon title desc submenu-content item-props sub-content-props disabled? toggle-checked? on-toggle-checked-change checkbox?]}]
  (let [submenu-content (when-not disabled? submenu-content)
        item-props' (if (and disabled? (:on-select item-props))
                      (assoc item-props :on-select (fn [] nil))
                      item-props)
        [sub-open? set-sub-open!] (rum/use-state false)
        toggle? (boolean? toggle-checked?)
        id1 (str (or id (random-uuid)))
        id2 (str "d2-" id1)
        or-close-menu-sub! (fn []
                             (when (and (not (shui-popup/get-popup :ls-icon-picker))
                                        (not (shui-popup/get-popup :ls-base-edit-form))
                                        (not (shui-popup/get-popup :ls-node-tags-sub-pane)))
                               (set-sub-open! false)
                               (restore-root-highlight-item! id1)))
        wrap-menuitem (if submenu-content
                        #(shui/dropdown-menu-sub
                          {:open sub-open?
                           :on-open-change (fn [v] (if v (set-sub-open! true) (or-close-menu-sub!)))}
                          (shui/dropdown-menu-sub-trigger (merge {:id id1} item-props') %)
                          (shui/dropdown-menu-portal
                           (shui/dropdown-menu-sub-content
                            (merge {:hideWhenDetached true
                                    :onEscapeKeyDown or-close-menu-sub!} sub-content-props)
                            (if (fn? submenu-content)
                              (submenu-content {:set-sub-open! set-sub-open! :id id1}) submenu-content))))
                        #(shui/dropdown-menu-item
                          (merge {:on-select (fn []
                                               (when toggle?
                                                 (some-> (gdom/getElement id2) (.click))))
                                  :id id1}
                                 item-props') %))]
    (wrap-menuitem
     [:div.inner-wrap.cursor-pointer
      {:class (util/classnames [{:disabled disabled?}])}
      [:div.property-setting-title
       (some-> icon (name) (shui/tabler-icon {:size 14
                                              :style {:margin-top "-1"}}))
       [:span {:title title} title]]
      (cond
        (fn? desc)
        (desc)
        (boolean? toggle-checked?)
        [:span.scale-90.flex.items-center
         (let [f (if checkbox? shui/checkbox shui/switch)]
           (f {:id id2 :size "sm" :checked toggle-checked?
               :disabled disabled? :on-click #(util/stop-propagation %)
               :on-checked-change (or on-toggle-checked-change identity)}))]
        :else
        [:label [:span desc]
         (when disabled? (shui/tabler-icon "forbid-2" {:size 15}))])])))

(rum/defc choice-item-content < rum/reactive db-mixins/query
  [property block {:keys [disabled? owner-block]}]
  (let [delete-choice! (fn []
                         (p/do!
                          (db-property-handler/delete-closed-value! (:db/id property) (:db/id block))
                          (re-init-commands! property)))
        update-icon! (fn [icon]
                       (property-handler/set-block-property!
                        (:block/uuid block) :logseq.property/icon
                        (select-keys icon [:id :type :color])))
        icon (:logseq.property/icon block)
        value (db-property/closed-value-content block)
        owner-class? (ldb/class? owner-block)
        owner-block' (when (and owner-class? (:db/id owner-block))
                       (db/sub-block (:db/id owner-block)))
        excluded-ids (set (keep :db/id (:logseq.property/choice-exclusions owner-block')))
        global-choice? (empty? (:logseq.property/choice-classes block))]
    [:li
     (shui/button {:size :sm :variant :ghost :title "Drag && Drop to reorder"}
                  (shui/tabler-icon "grip-vertical" {:size 14}))
     (icon-component/icon-picker icon {:on-chosen (fn [_e icon] (update-icon! icon))
                                       :popup-opts {:align "start"}
                                       :del-btn? (boolean icon)
                                       :empty-label "?"
                                       :button-opts {:title "Set Icon"}})
     [:strong {:on-click (fn [^js e]
                           (shui/popup-show! (.-target e)
                                             (fn [] (choice-base-edit-form property block owner-block))
                                             {:id :ls-base-edit-form
                                              :align "start"}))}
      value]
     (shui/dropdown-menu
      (shui/dropdown-menu-trigger
       {:as-child true
        :disabled disabled?}
       (shui/button
        {:size :sm :variant :ghost
         :title "More settings"}
        (shui/tabler-icon "dots" {:size 16})))
      (shui/dropdown-menu-content
       ;; default choice
       (let [property-type (:logseq.property/type property)
             property (db/sub-block (:db/id property))
             default-type? (contains? #{:default :number} property-type)
             default-value (when default-type? (:logseq.property/default-value property))
             default-value? (= (:db/id default-value) (:db/id block))]
         (when default-type?
           (shui/dropdown-menu-item
            {:key "default value"
             :on-click #(let [value (if default-value? nil (:db/id block))]
                          (db-property-handler/set-block-property! (:db/ident property) :logseq.property/default-value
                                                                   value))}
            (shui/checkbox {:id "default value"
                            :size :sm
                            :title "Set as default choice"
                            :class "mr-1 opacity-50 hover:opacity-100"
                            :checked default-value?})
            "Set as default choice")))

       (when (and owner-class? owner-block' global-choice?)
         (let [excluded? (contains? excluded-ids (:db/id block))
               tag-title (:block/title owner-block')
               toggle-exclusion! (fn []
                                   (if excluded?
                                     (db-property-handler/delete-property-value! (:db/id owner-block) :logseq.property/choice-exclusions (:db/id block))
                                     (db-property-handler/set-block-property! (:db/id owner-block) :logseq.property/choice-exclusions (:db/id block))))]
           (shui/dropdown-menu-item
            {:key "exclude for tag"
             :on-click toggle-exclusion!}
            (shui/checkbox {:id "exclude for tag"
                            :size :sm
                            :title "Hide choice for this tag"
                            :class "mr-1 opacity-50 hover:opacity-100"
                            :checked excluded?})
            (str "Hide for #" tag-title))))

       (when-not (and owner-class? global-choice?)
         (shui/dropdown-menu-item
          {:key "delete"
           :class "del"
           :on-click delete-choice!}
          (ui/icon "x" {:class "scale-90 pr-1 opacity-80"})
          "Delete"))))]))

(rum/defc add-existing-values
  [property values {:keys [toggle-fn]}]
  [:div.flex.flex-col.gap-1.w-64.p-4.overflow-y-auto
   {:class "max-h-[50dvh]"}
   [:div "Existing values:"]
   [:ol
    (for [value values]
      [:li (:label value)])]
   (shui/button
    {:on-click (fn []
                 (p/let [_ (db-property-handler/add-existing-values-to-closed-values! (:db/id property)
                                                                                      (map (fn [{:keys [value]}]
                                                                                             (:block/uuid value)) values))]
                   (toggle-fn)))}
    "Add choices")])

(rum/defcs choices-sub-pane < rum/reactive db-mixins/query
  (rum/local false ::show-hidden?)
  [state property {:keys [disabled? owner-block] :as opts}]
  (let [*show-hidden? (::show-hidden? state)
        values (:property/closed-values property)
        choices (->> values
                     (keep (fn [value]
                             (db/sub-block (:db/id value)))))
        scoped-choices (db-property/scoped-closed-values property owner-block {:values choices})

        excluded-ids (set (keep :db/id (:logseq.property/choice-exclusions owner-block)))
        hidden-choices (filter (fn [block]
                                 (and (empty? (:logseq.property/choice-classes block))
                                      (contains? excluded-ids (:db/id block))))
                               scoped-choices)
        visible-choices (remove (fn [block]
                                  (and (empty? (:logseq.property/choice-classes block))
                                       (contains? excluded-ids (:db/id block))))
                                scoped-choices)
        list-choices (if @*show-hidden?
                       (concat visible-choices hidden-choices)
                       visible-choices)
        choice-items (map
                      (fn [block]
                        (let [id (:block/uuid block)]
                          {:id (str id)
                           :value id
                           :content (choice-item-content property block
                                                         (assoc opts :owner-block owner-block))}))
                      list-choices)]

    [:div.ls-property-dropdown.ls-property-choices-sub-pane
     (when (seq scoped-choices)
       [:<>
        (when (and (seq hidden-choices) (ldb/class? owner-block))
          (shui/button
           {:size :sm
            :variant :ghost
            :class "text-muted-foreground"
            :on-click (fn []
                        (swap! *show-hidden? not))}
           (if @*show-hidden? "Hide hidden choices" "Show hidden choices")))
        [:ul.choices-list
         (dnd/items choice-items
                    {:sort-by-inner-element? false
                     :on-drag-end (fn [_ {:keys [active-id over-id direction]}]
                                    (let [move-down? (= direction :down)
                                          over (db/entity [:block/uuid (uuid over-id)])
                                          active (db/entity [:block/uuid (uuid active-id)])
                                          over-order (:block/order over)
                                          new-order (if move-down?
                                                      (let [next-order (db-order/get-next-order (db/get-db) property (:db/id over))]
                                                        (db-order/gen-key over-order next-order))
                                                      (let [prev-order (db-order/get-prev-order (db/get-db) property (:db/id over))]
                                                        (db-order/gen-key prev-order over-order)))]

                                      (db/transact! (state/get-current-repo)
                                                    [{:db/id (:db/id active)
                                                      :block/order new-order}
                                                     (outliner-core/block-with-updated-at
                                                      {:db/id (:db/id property)})]
                                                    {:outliner-op :save-block})))})]
        (shui/dropdown-menu-separator)])

     ;; add choice
     (when-not disabled?
       (dropdown-editor-menuitem
        {:icon :plus :title "Add choice"
         :item-props {:on-click
                      (fn [^js e]
                        (p/let [values (db-async/<get-property-values (:db/ident property) {})
                                existing-values (seq (:property/closed-values property))
                                values' (if (seq existing-values)
                                          (let [existing-ids (set (map :db/id existing-values))
                                                existing-titles (set (map db-property/property-value-content existing-values))]
                                            (remove (fn [{:keys [label value]}]
                                                      (or (existing-ids (:db/id value))
                                                          (existing-titles label)
                                                          (string/blank? label))) values))
                                          (remove (fn [{:keys [label _value]}]
                                                    (string/blank? label))
                                                  values))]
                          (p/do!
                           (when (seq values')
                             (db-async/<get-blocks (state/get-current-repo) (map (fn [{:keys [value]}] (:db/id value)) values)))
                           (shui/popup-show! (.-target e)
                                             (fn [{:keys [id]}]
                                               (let [opts {:toggle-fn (fn [] (shui/popup-hide! id))}]
                                                 (if (seq values')
                                                   (add-existing-values property values' opts)
                                                   (choice-base-edit-form property
                                                                          {:create? true}
                                                                          owner-block))))
                                             {:id :ls-base-edit-form
                                              :align "start"}))))}}))]))

(rum/defc checkbox-state-mapping
  [choices]
  (let [select-cp (fn [opts]
                    (shui/select
                     opts
                     (shui/select-trigger
                      {:class "h-8"}
                      (shui/select-value {:placeholder "Select a choice"}))
                     (shui/select-content
                      (map (fn [choice]
                             (shui/select-item {:key (str (:db/id choice))
                                                :value (:db/id choice)} (:block/title choice))) choices))))
        checked-choice (some (fn [choice] (when (true? (:logseq.property/choice-checkbox-state choice)) choice)) choices)
        unchecked-choice (some (fn [choice] (when (false? (:logseq.property/choice-checkbox-state choice)) choice)) choices)]
    [:div.flex.flex-col.gap-4.text-sm.p-2
     [:div.flex.flex-col.gap-2
      [:div "Map unchecked to"]
      (select-cp
       (cond->
        {:on-value-change
         (fn [value]
           (p/do!
            (db-property-handler/set-block-property! value :logseq.property/choice-checkbox-state false)
            (when unchecked-choice
              (db-property-handler/remove-block-property! (:db/id unchecked-choice) :logseq.property/choice-checkbox-state))))}
         unchecked-choice
         (assoc :default-value (:db/id unchecked-choice))))

      [:div.mt-2 "Map checked to"]
      (select-cp
       (cond->
        {:on-value-change
         (fn [value]
           (p/do!
            (db-property-handler/set-block-property! value :logseq.property/choice-checkbox-state true)
            (when checked-choice
              (db-property-handler/remove-block-property! (:db/id checked-choice) :logseq.property/choice-checkbox-state))))}
         checked-choice
         (assoc :default-value (:db/id checked-choice))))]]))

(def position-labels
  {:properties {:icon :layout-distribute-horizontal :title "Block properties"}
   :block-left {:icon :layout-align-right :title "Beginning of the block"}
   :block-right {:icon :layout-align-left :title "End of the block"}
   :block-below {:icon :layout-align-top :title "Below the block"}})

(rum/defc ui-position-sub-pane
  [property {:keys [id set-sub-open! _ui-position]}]
  (let [handle-select! (fn [^js e]
                         (when-let [v (some-> (.-target e) (.-dataset) (.-value))]
                           (db-property-handler/set-block-property!
                            (:db/id property)
                            :logseq.property/ui-position
                            (keyword v))
                           (set-sub-open! false)
                           (restore-root-highlight-item! id)))
        item-props {:on-select handle-select!}]
    [:div.ls-property-dropdown.ls-property-ui-position-sub-pane
     (for [[k v] position-labels]
       (let [item-props (assoc item-props :data-value k)]
         (dropdown-editor-menuitem
          (assoc v :item-props item-props))))]))

(defn property-type-label
  [property-type]
  (case property-type
    :default
    "Text"
    :datetime
    "DateTime"
    ((comp string/capitalize name) property-type)))

(defn- handle-delete-property!
  [block property & {:keys [class? class-schema?]}]
  (let [class? (or class? (ldb/class? block))
        remove! #(if (and class? class-schema?)
                   (db-property-handler/class-remove-property! (:db/id block) (:db/id property))
                   (property-handler/remove-block-property! (:block/uuid block) (:db/ident property)))]
    (if (and class? class-schema?)
      (-> (shui/dialog-confirm!
           [:p (str "Are you sure you want to delete the property from this tag?")]
           {:id :delete-property-from-class
            :data-reminder :ok})
          (p/then remove!))
      (-> (shui/dialog-confirm!
           "Are you sure you want to delete the property from this node?"
           {:id :delete-property-from-node
            :data-reminder :ok})
          (p/then remove!)))))

(rum/defc property-type-sub-pane
  [property {:keys [id set-sub-open! _position]}]
  (let [handle-select! (fn [^js e]
                         (when-let [v (some-> (.-target e) (.-dataset) (.-value))]
                           (p/do!
                            (db-property-handler/upsert-property!
                             (:db/ident property)
                             {:logseq.property/type (keyword v)}
                             {})
                            (set-sub-open! false)
                            (restore-root-highlight-item! id))))
        item-props {:on-select handle-select!}
        schema-types (->> db-property-type/user-built-in-property-types
                          (map (fn [type]
                                 {:label (property-type-label type)
                                  :value type})))]
    [:div.ls-property-dropdown.ls-property-type-sub-pane
     (for [{:keys [label value]} schema-types]
       (let [option {:id label
                     :title label
                     :desc ""
                     :item-props (assoc item-props :data-value value)}]
         (dropdown-editor-menuitem option)))]))

(rum/defc default-value-subitem
  [property]
  (let [property-type (:logseq.property/type property)
        option (if (= :checkbox property-type)
                 (let [default-value (:logseq.property/scalar-default-value property)]
                   {:icon :settings-2
                    :title "Default value"
                    :toggle-checked? (boolean default-value)
                    :checkbox? true
                    :on-toggle-checked-change (fn []
                                                (db-property-handler/set-block-property! (:block/uuid property) :logseq.property/scalar-default-value (not default-value)))})
                 (let [default-value (:logseq.property/default-value property)]
                   {:icon :settings-2 :title "Default value"
                    :desc (if default-value (db-property/property-value-content default-value) "Set value")
                    :submenu-content (fn [] (pdv/default-value-config property))}))]
    (dropdown-editor-menuitem (assoc option :disabled? config/publishing?))))

(defn ^:large-vars/cleanup-todo property-dropdown-options
  "property: block entity"
  [property owner-block values {:keys [class-schema? debug? with-title? more-options]
                                :or {with-title? true}}]
  (let [title (:block/title property)
        property-type (:logseq.property/type property)
        property-type-label' (some-> property-type (property-type-label))
        enable-closed-values? (contains? db-property-type/closed-value-property-types
                                         (or property-type :default))
        icon (:logseq.property/icon property)
        icon (when icon [:span.float-left.w-4.h-4.overflow-hidden.leading-4.relative
                         (icon-component/icon icon {:size 15})])
        built-in? (ldb/built-in? property)
        disabled? (or built-in? config/publishing?)
        class-schema? (and (ldb/class? owner-block) class-schema?)
        special-built-in-prop? (contains? #{:block/title :block/tags :block/created-at :block/updated-at} (:db/ident property))]
    (->>
     [(when with-title?
        [:h3.font-medium.px-2.py-2.opacity-80.flex.items-center.gap-1
         "Configure property"])
      (when-not special-built-in-prop?
        (dropdown-editor-menuitem {:icon :pencil :title "Property name" :desc [:span.flex.items-center.gap-1 icon title]
                                   :submenu-content (fn [ops] (name-edit-pane property (assoc ops :disabled? disabled?)))}))
      (let [disabled?' (or disabled? (and property-type (seq values)))]
        (dropdown-editor-menuitem {:icon :letter-t
                                   :title "Property type"
                                   :desc (if disabled?'
                                           (ui/tooltip
                                            [:span (str property-type-label')]
                                            [:div.w-96
                                             "The type of this property is locked once you start using it. This is to make sure all your existing information stays correct if the property type is changed later. To unlock, all uses of a property must be deleted."])
                                           (str property-type-label'))
                                   :disabled? disabled?'
                                   :submenu-content (fn [ops]
                                                      (property-type-sub-pane property ops))}))

      (when (and (= property-type :node)
                 (not (contains? #{:logseq.property.class/extends} (:db/ident property))))
        (dropdown-editor-menuitem {:icon :hash
                                   :disabled? disabled?
                                   :title "Specify node tags"
                                   :desc ""
                                   :submenu-content (fn [_ops]
                                                      [:div.px-4
                                                       (class-select property {:default-open? false})])}))

      (when (and (contains? db-property-type/default-value-ref-property-types property-type)
                 (not (db-property/many? property))
                 (not (and enable-closed-values?
                           (seq (:property/closed-values property))))
                 (not= :logseq.property/enable-history? (:db/ident property)))
        (default-value-subitem property))

      (when enable-closed-values?
        (let [values (:property/closed-values property)]
          (dropdown-editor-menuitem {:icon :list :title "Available choices"
                                     :desc (when (seq values) (str (count values) " choices"))
                                     :submenu-content (fn []
                                                        (choices-sub-pane property
                                                                          {:disabled? config/publishing?
                                                                           :owner-block owner-block
                                                                           :class-schema? class-schema?}))})))

      (when enable-closed-values?
        (let [values (:property/closed-values property)]
          (when (>= (count values) 2)
            (dropdown-editor-menuitem
             {:icon :checkbox
              :title "Checkbox state mapping"
              :disabled? config/publishing?
              :submenu-content (fn []
                                 (checkbox-state-mapping values))}))))

      (when (and (contains? db-property-type/cardinality-property-types property-type) (not disabled?))
        (let [many? (db-property/many? property)]
          (dropdown-editor-menuitem {:icon :checks :title "Multiple values"
                                     :toggle-checked? many?
                                     :on-toggle-checked-change
                                     (fn []
                                       (let [update-cardinality-fn #(db-property-handler/upsert-property! (:db/ident property)
                                                                                                          {:db/cardinality (if many? :one :many)}
                                                                                                          {})]
                                      ;; Only show dialog for existing values as it can be reversed for unused properties
                                         (if (and (seq values) (not many?))
                                           (-> (shui/dialog-confirm!
                                                "This action cannot be undone. Do you want to change this property to have multiple values?")
                                               (p/then update-cardinality-fn))
                                           (update-cardinality-fn))))})))

      (when (and (not= :logseq.property/enable-history? (:db/ident property))
                 (not special-built-in-prop?))
        (let [property-type (:logseq.property/type property)
              group' (->> [(when (and (not (contains? #{:logseq.property.class/extends :logseq.property.class/properties} (:db/ident property)))
                                      (contains? #{:default :number :date :checkbox :node} property-type)
                                      (not
                                       (and (= :default property-type)
                                            (empty? (:property/closed-values property))
                                            (contains? #{nil :properties} (:logseq.property/ui-position property)))))
                             (let [position (:logseq.property/ui-position property)]
                               (dropdown-editor-menuitem {:icon :float-left :title "UI position" :desc (some->> position (get position-labels) (:title))
                                                          :item-props {:class "ui__position-trigger-item"}
                                                          :disabled? config/publishing?
                                                          :submenu-content (fn [ops] (ui-position-sub-pane property (assoc ops :ui-position position)))})))

                           (when (not (contains? #{:logseq.property.class/extends :logseq.property.class/properties} (:db/ident property)))
                             (dropdown-editor-menuitem {:icon :eye-off :title "Hide by default" :toggle-checked? (boolean (:logseq.property/hide? property))
                                                        :disabled? config/publishing?
                                                        :on-toggle-checked-change #(db-property-handler/set-block-property! (:db/id property)
                                                                                                                            :logseq.property/hide?
                                                                                                                            %)}))
                           (when (not (contains? #{:logseq.property.class/extends :logseq.property.class/properties} (:db/ident property)))
                             (dropdown-editor-menuitem
                              {:icon :eye-off :title "Hide empty value"
                               :toggle-checked? (boolean (:logseq.property/hide-empty-value property))
                               :disabled? config/publishing?
                               :on-toggle-checked-change (fn []
                                                           (db-property-handler/set-block-property! (:db/id property)
                                                                                                    :logseq.property/hide-empty-value
                                                                                                    (not (:logseq.property/hide-empty-value property))))}))]
                          (remove nil?))]
          (when (> (count group') 0)
            (cons (shui/dropdown-menu-separator) group'))))

      (when (and owner-block (not special-built-in-prop?))
        [:<>
         (shui/dropdown-menu-separator)
         (dropdown-editor-menuitem
          {:icon :share-3 :title "Go to this property" :desc ""
           :item-props {:class "opacity-90 focus:opacity-100"
                        :on-select (fn []
                                     (shui/popup-hide-all!)
                                     (route-handler/redirect-to-page! (:block/uuid property)))}})])
      (when (and enable-closed-values? owner-block)
        (let [values (:property/closed-values property)]
          (when (>= (count values) 2)
            (let [checked? (contains?
                            (set (map :db/id (:logseq.property/checkbox-display-properties owner-block)))
                            (:db/id property))]
              (dropdown-editor-menuitem
               {:icon :checkbox
                :title (if class-schema? "Show as checkbox on tagged nodes" "Show as checkbox on node")
                :disabled? config/publishing?
                :desc (when owner-block
                        (shui/switch
                         {:id "show as checkbox" :size "sm"
                          :checked checked?
                          :on-click util/stop-propagation
                          :on-checked-change
                          (fn [value]
                            (if value
                              (db-property-handler/set-block-property! (:db/id owner-block) :logseq.property/checkbox-display-properties (:db/id property))
                              (db-property-handler/delete-property-value! (:db/id owner-block) :logseq.property/checkbox-display-properties (:db/id property))))}))})))))

      (when (and owner-block
                ;; Any property should be removable from Tag Properties
                 (if class-schema?
                   (contains? (set (map :db/id (:logseq.property.class/properties owner-block))) (:db/id property))
                   (not (contains? #{:logseq.property.class/extends :logseq.property.class/properties} (:db/ident property)))))

        (dropdown-editor-menuitem
         {:id :delete-property :icon :x
          :title (if class-schema? "Delete property from tag" "Delete property from node")
          :desc "" :disabled? false
          :item-props {:class "opacity-60 focus:!text-red-rx-09 focus:opacity-100"
                       :on-select (fn [^js e]
                                    (util/stop e)
                                    (-> (p/do!
                                         (handle-delete-property! owner-block property {:class-schema? class-schema?})
                                         (shui/popup-hide-all!))
                                        (p/catch (fn [] (restore-root-highlight-item! :delete-property)))))}}))
      (when debug?
        [:<>
         (shui/dropdown-menu-separator)
         (dropdown-editor-menuitem
          {:icon :bug :title (str (:db/ident property)) :desc "" :disabled? false
           :item-props {:class "opacity-60 focus:opacity-100 focus:!text-red-rx-08"
                        :on-select (fn []
                                     (dev-common-handler/show-entity-data (:db/id property))
                                     (shui/popup-hide!))}})])]
     (concat more-options)
     vec)))

(rum/defcs property-dropdown < rum/reactive db-mixins/query
  {:init (fn [state]
           (let [*values (atom :loading)
                 property (first (:rum/args state))
                 ident (:db/ident property)]
             (p/let [result (db-async/<get-property-values ident)]
               (reset! *values result))
             (assoc state ::values *values)))}
  [state property* owner-block opts]
  (let [property (db/sub-block (:db/id property*))
        owner-block (when (:db/id owner-block) (db/sub-block (:db/id owner-block)))
        values (rum/react (::values state))]
    (when-not (= :loading values)
      (vec (cons :<> (property-dropdown-options property owner-block values opts))))))
