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

(ns app.main.ui.workspace.sidebar.options
  (: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.geom.shapes :as gsh]
   [app.common.types.shape.layout :as ctl]
   [app.main.data.helpers :as dsh]
   [app.main.data.workspace :as udw]
   [app.main.data.workspace.common :as dwc]
   [app.main.features :as features]
   [app.main.refs :as refs]
   [app.main.store :as st]
   [app.main.ui.context :as ctx]
   [app.main.ui.ds.layout.tab-switcher :refer [tab-switcher*]]
   [app.main.ui.inspect.right-sidebar :as hrs]
   [app.main.ui.workspace.sidebar.debug-shape-info :refer [debug-shape-info*]]
   [app.main.ui.workspace.sidebar.options.drawing :as drawing]
   [app.main.ui.workspace.sidebar.options.menus.align :refer [align-options*]]
   [app.main.ui.workspace.sidebar.options.menus.bool :refer [bool-options*]]
   [app.main.ui.workspace.sidebar.options.menus.component :refer [component-menu*]]
   [app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell]
   [app.main.ui.workspace.sidebar.options.menus.interactions :refer [interactions-menu*]]
   [app.main.ui.workspace.sidebar.options.menus.layout-container :as layout-container]
   [app.main.ui.workspace.sidebar.options.page :as page]
   [app.main.ui.workspace.sidebar.options.shapes.bool :as bool]
   [app.main.ui.workspace.sidebar.options.shapes.circle :as circle]
   [app.main.ui.workspace.sidebar.options.shapes.frame :as frame]
   [app.main.ui.workspace.sidebar.options.shapes.group :as group]
   [app.main.ui.workspace.sidebar.options.shapes.multiple :as multiple]
   [app.main.ui.workspace.sidebar.options.shapes.path :as path]
   [app.main.ui.workspace.sidebar.options.shapes.rect :as rect]
   [app.main.ui.workspace.sidebar.options.shapes.svg-raw :as svg-raw]
   [app.main.ui.workspace.sidebar.options.shapes.text :as text]
   [app.util.debug :as dbg]
   [app.util.i18n :as i18n :refer [tr]]
   [okulary.core :as l]
   [rumext.v2 :as mf]))

;; --- Options

(mf/defc single-shape-options*
  {::mf/private true}
  [{:keys [shape page-id file-id libraries] :rest props}]
  (let [shape-type (dm/get-prop shape :type)
        shape-id   (dm/get-prop shape :id)

        wasm-modifiers (mf/deref refs/workspace-wasm-modifiers)
        modifiers  (mf/deref refs/workspace-modifiers)

        shape
        (if (features/active-feature? @st/state "render-wasm/v1")
          (let [wasm-modifiers (into {} wasm-modifiers)]
            (gsh/apply-transform shape (get wasm-modifiers shape-id)))
          (gsh/transform-shape shape (dm/get-in modifiers [shape-id :modifiers])))

        props      (mf/spread-props props {:shape shape :file-id file-id :page-id page-id :libraries libraries})]

    (case shape-type
      :frame   [:> frame/options* props]
      :group   [:> group/options* props]
      :text    [:> text/options* {:shape shape :file-id file-id :page-id page-id :libraries libraries}]
      :rect    [:> rect/options* {:shape shape :file-id file-id :page-id page-id}]
      :circle  [:> circle/options* {:shape shape :file-id file-id :page-id page-id}]
      :path    [:> path/options* {:shape shape :file-id file-id :page-id page-id}]
      :svg-raw [:> svg-raw/options* {:shape shape :file-id file-id :page-id page-id}]
      :bool    [:> bool/options* {:shape shape :file-id file-id :page-id page-id}]
      nil)))

(mf/defc shape-options*
  {::mf/wrap [#(mf/throttle % 200)]
   ::mf/private true}
  [{:keys [shapes shapes-with-children selected page-id file-id libraries]}]
  (if (= 1 (count selected))
    [:> single-shape-options*
     {:page-id page-id
      :file-id file-id
      :libraries libraries
      :shape (first shapes)
      :shapes-with-children shapes-with-children}]
    [:> multiple/options*
     {:shapes-with-children shapes-with-children
      :shapes shapes
      :page-id page-id
      :file-id file-id
      :libraries libraries}]))

(mf/defc specialized-panel*
  {::mf/private true}
  [{:keys [panel]}]
  (when (= (:type panel) :component-swap)
    [:> component-menu* {:shapes (:shapes panel) :is-swap-opened true}]))

(mf/defc design-menu*
  {::mf/private true}
  [{:keys [selected objects page-id file-id shapes]}]
  (let [sp-panel (mf/deref refs/specialized-panel)
        drawing  (mf/deref refs/workspace-drawing)
        edition  (mf/deref refs/selected-edition)

        files
        (mf/deref refs/files)

        libraries
        (mf/with-memo [files file-id]
          (refs/select-libraries files file-id))

        edit-grid?
        (mf/with-memo [objects edition]
          (ctl/grid-layout? objects edition))

        grid-edition
        (mf/deref refs/workspace-grid-edition)

        selected-cells
        (->> (dm/get-in grid-edition [edition :selected])
             (map #(dm/get-in objects [edition :layout-grid-cells %])))

        shapes-with-children*
        (mf/use-state nil)

        _ (mf/use-effect
           (mf/deps selected objects shapes)
           (fn []
             (reset! shapes-with-children* nil)
             (let [result
                   (loop [queue   (into #queue [] selected)
                          visited selected]
                     (if-let [id (peek queue)]
                       (let [shape    (get objects id)
                             children (:shapes shape)]
                         (if (seq children)
                           (let [new-children (remove visited children)]
                             (recur (into (pop queue) new-children)
                                    (into visited new-children)))
                           (recur (pop queue) visited)))
                       (sequence (keep (d/getf objects)) visited)))]
               (reset! shapes-with-children* result))))

        shapes-with-children
        (deref shapes-with-children*)

        total-selected
        (count selected)]

    [:div {:class (stl/css :element-options :design-options)}
     [:> align-options* {:shapes shapes
                         :objects objects}]
     [:> bool-options* {:total-selected total-selected
                        :shapes shapes
                        :shapes-with-children shapes-with-children}]

     (cond
       (and edit-grid? (d/not-empty? selected-cells))
       [:> grid-cell/options*
        {:shape-id (-> (get objects edition)
                       :id)
         :cells selected-cells}]

       edit-grid?
       [:& layout-container/grid-layout-edition
        {:ids [edition]
         :values (get objects edition)}]

       (some? sp-panel)
       [:> specialized-panel* {:panel sp-panel}]

       (d/not-empty? drawing)
       [:> drawing/drawing-options*
        {:drawing-state drawing}]

       (zero? total-selected)
       [:> page/options*]

       :else
       [:> shape-options*
        {:shapes shapes
         :shapes-with-children shapes-with-children
         :page-id page-id
         :file-id file-id
         :selected selected
         :libraries libraries}])]))

(mf/defc inspect-tab*
  {::mf/private true}
  [{:keys [objects shapes] :as props}]
  (let [frame
        (cfh/get-frame objects (first shapes))

        props
        (mf/spread-props props
                         {:frame frame
                          :from :workspace})]

    [:> hrs/right-sidebar* props]))

(defn- generate-options-tabs
  []
  (cond-> [{:label (tr "workspace.options.design")
            :id "design"}
           {:label (tr "workspace.options.prototype")
            :id "prototype"}
           {:label (tr "workspace.options.inspect")
            :id "inspect"}]
    (dbg/enabled? :shape-panel)
    (conj {:label "Debug"
           :id "debug"})))

(defn- on-option-tab-change
  [mode]
  (let [mode (keyword mode)]
    (st/emit! (udw/set-options-mode mode))
    (if (= mode :inspect)
      (st/emit! :interrupt (dwc/set-workspace-read-only true))
      (st/emit! :interrupt (dwc/set-workspace-read-only false)))))

(mf/defc options-content*
  {::mf/private true}
  [{:keys [objects selected page-id file-id on-change-section on-expand]}]
  (let [permissions
        (mf/use-ctx ctx/permissions)
        render-context-lost? (mf/deref refs/render-context-lost?)

        options-mode
        (mf/deref refs/options-mode-global)

        ;; dbg/state is an okulary atom; deref'ing it makes this component
        ;; re-render when debug options change (e.g. :shape-panel toggle)
        ;; so the tabs list is regenerated reactively without a page reload.
        dbg-state
        (mf/deref dbg/state)

        shapes
        (mf/with-memo [selected objects]
          (sequence (keep (d/getf objects)) selected))

        options-tabs
        (generate-options-tabs)]

    (mf/use-effect
     (mf/deps dbg-state)
     (fn []
       (if (contains? dbg-state :shape-panel)
         ;; shape-panel was just enabled: select the debug tab
         (on-option-tab-change "debug")
         ;; shape-panel was just disabled: if debug tab is active, go back to design
         (when (= options-mode :debug)
           (on-option-tab-change "design")))))

    [:div {:class (stl/css :tool-window)}
     (if (and (:can-edit permissions) (not render-context-lost?))
       [:> tab-switcher* {:tabs options-tabs
                          :on-change on-option-tab-change
                          :selected (name options-mode)
                          :class (stl/css :options-tab-switcher)}
        (case options-mode
          :prototype
          [:div {:class (stl/css :element-options :interaction-options)}
           [:> interactions-menu* {:shape (first shapes)}]]

          :inspect
          [:div {:class (stl/css :element-options :inspect-options)}
           [:> inspect-tab* {:page-id page-id
                             :file-id file-id
                             :objects objects
                             :selected selected
                             :shapes shapes
                             :on-change-section on-change-section
                             :on-expand on-expand}]]

          :design
          [:> design-menu* {:selected selected
                            :objects objects
                            :page-id page-id
                            :file-id file-id
                            :shapes shapes}]

          :debug
          [:> debug-shape-info*])]

       [:div {:class (stl/css :element-options :inspect-options :read-only)}
        [:> inspect-tab* {:page-id page-id
                          :file-id file-id
                          :objects objects
                          :selected selected
                          :shapes shapes
                          :on-change-section on-change-section
                          :on-expand on-expand}]])]))

(defn- make-page-objects-ref
  [file-id page-id]
  (l/derived #(dsh/lookup-page-objects % file-id page-id) st/state))

(mf/defc options-toolbox*
  {::mf/memo true}
  [{:keys [page-id file-id section selected on-change-section on-expand]}]
  (let [objects-ref
        (mf/with-memo [page-id file-id]
          (make-page-objects-ref file-id page-id))

        objects
        (mf/deref objects-ref)]

    [:> options-content* {:objects objects
                          :selected selected
                          :file-id file-id
                          :page-id page-id
                          :section section
                          :on-change-section on-change-section
                          :on-expand on-expand}]))
