;; 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

(ns app.render
  "The main entry point for UI part needed by the exporter."
  (:require
   [app.common.geom.shapes.bounds :as gsb]
   [app.common.logging :as log]
   [app.common.math :as mth]
   [app.common.schema :as sm]
   [app.common.types.components-list :as ctkl]
   [app.common.uri :as u]
   [app.main.data.fonts :as df]
   [app.main.data.users :as du]
   [app.main.features :as features]
   [app.main.render :as render]
   [app.main.repo :as repo]
   [app.main.store :as st]
   [app.util.dom :as dom]
   [app.util.globals :as glob]
   [beicon.v2.core :as rx]
   [cuerdas.core :as str]
   [garden.core :refer [css]]
   [okulary.core :as l]
   [potok.v2.core :as ptk]
   [rumext.v2 :as mf]))

(log/setup! {:app :info})

(defn- fetch-team
  [& {:keys [file-id]}]
  (ptk/reify ::fetch-team
    ptk/WatchEvent
    (watch [_ _ _]
      (->> (repo/cmd! :get-team {:file-id file-id})
           (rx/mapcat (fn [team]
                        (rx/of (du/set-current-team team)
                               (ptk/data-event ::team-fetched team))))))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; COMPONENTS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(def ^:private ref:objects
  (l/derived :objects st/state))

(mf/defc object-svg
  {::mf/wrap-props false}
  [{:keys [object-id embed]}]
  (let [objects (mf/deref ref:objects)]

    ;; Set the globa CSS to assign the page size, needed for PDF
    ;; exportation process.
    (mf/with-effect [objects]
      (when-let [object (get objects object-id)]
        (let [{:keys [width height]} (gsb/get-object-bounds [objects] object)]
          (dom/set-page-style!
           {:size (str/concat
                   (mth/ceil width) "px "
                   (mth/ceil height) "px")}))))

    (when objects
      [:& render/object-svg
       {:objects objects
        :object-id object-id
        :embed embed}])))

(mf/defc objects-svg
  {::mf/wrap-props false}
  [{:keys [object-ids embed]}]
  (when-let [objects (mf/deref ref:objects)]
    (for [object-id object-ids]
      (let [objects (render/adapt-objects-for-shape objects object-id)]
        [:& render/object-svg
         {:objects objects
          :key (str object-id)
          :object-id object-id
          :embed embed}]))))

(defn- fetch-objects-bundle
  [& {:keys [file-id page-id share-id object-id] :as options}]
  (ptk/reify ::fetch-objects-bundle
    ptk/WatchEvent
    (watch [_ state _]
      (let [features (features/get-team-enabled-features state)]
        (->> (rx/zip
              (repo/cmd! :get-font-variants {:file-id file-id :share-id share-id})
              (repo/cmd! :get-page {:file-id file-id
                                    :page-id page-id
                                    :share-id share-id
                                    :object-id object-id
                                    :features features}))
             (rx/tap (fn [[fonts]]
                       (when (seq fonts)
                         (st/emit! (df/fonts-fetched fonts)))))
             (rx/observe-on :async)
             (rx/map (comp :objects second))
             (rx/map (fn [objects]
                       (let [objects (render/adapt-objects-for-shape objects object-id)]
                         #(assoc % :objects objects)))))))))

(def ^:private schema:render-objects
  [:map {:title "render-objets"}
   [:page-id ::sm/uuid]
   [:file-id ::sm/uuid]
   [:share-id {:optional true} ::sm/uuid]
   [:embed {:optional true} :boolean]
   [:object-id
    [:or
     ::sm/uuid
     ::sm/coll-of-uuid]]])

(def ^:private render-objects-decoder
  (sm/lazy-decoder schema:render-objects
                   sm/default-transformer))

(def ^:private render-objects-validator
  (sm/lazy-validator schema:render-objects))

(defn- render-objects
  [params]
  (let [{:keys [file-id page-id embed share-id object-id] :as params} (render-objects-decoder params)]
    (if-not (render-objects-validator params)
      (do
        (js/console.error "invalid arguments")
        (sm/pretty-explain schema:render-objects params)
        nil)

      (do
        (st/emit! (ptk/reify ::initialize-render-objects
                    ptk/WatchEvent
                    (watch [_ _ stream]
                      (rx/merge
                       (rx/of (fetch-team :file-id file-id))

                       (->> stream
                            (rx/filter (ptk/type? ::team-fetched))
                            (rx/observe-on :async)
                            (rx/map (constantly params))
                            (rx/map fetch-objects-bundle))))))

        (if (uuid? object-id)
          (mf/html
           [:& object-svg
            {:file-id file-id
             :page-id page-id
             :share-id share-id
             :object-id object-id
             :embed embed}])

          (mf/html
           [:& objects-svg
            {:file-id file-id
             :page-id page-id
             :share-id share-id
             :object-ids (into #{} object-id)
             :embed embed}]))))))

;; ---- COMPONENTS SPRITE

(mf/defc components-svg
  {::mf/wrap-props false}
  [{:keys [embed component-id]}]
  (let [file-ref (mf/with-memo [] (l/derived :file st/state))
        state    (mf/use-state {:component-id component-id})]
    (when-let [file (mf/deref file-ref)]
      [:*
       [:style
        (css [[:body
               {:margin 0
                :overflow "hidden"
                :width "100vw"
                :height "100vh"}]

              [:main
               {:overflow "auto"
                :display "flex"
                :justify-content "center"
                :align-items "center"
                :height "calc(100vh - 200px)"}
               [:svg {:width "50%"
                      :height "50%"}]]
              [:.nav
               {:display "flex"
                :margin 0
                :padding "10px"
                :flex-direction "column"
                :flex-wrap "wrap"
                :height "200px"
                :list-style "none"
                :overflow-x "scroll"
                :border-bottom "1px dotted #e6e6e6"}
               [:a {:cursor :pointer
                    :text-overflow "ellipsis"
                    :white-space "nowrap"
                    :overflow "hidden"
                    :text-decoration "underline"}]
               [:li {:display "flex"
                     :width "150px"
                     :padding "5px"
                     :border "0px solid black"}]]])]

       [:ul.nav
        (for [[id data] (ctkl/components (:data file))]
          (let [on-click (fn [event]
                           (dom/prevent-default event)
                           (swap! state assoc :component-id id))]
            [:li {:key (str id)}
             [:a {:on-click on-click} (:name data)]]))]

       [:main
        [:& render/components-svg
         {:data (:data file)
          :embed embed}

         (when-let [component-id (:component-id @state)]
           [:use {:x 0 :y 0 :href (str "#" component-id)}])]]])))

(defn- fetch-components-bundle
  [& {:keys [file-id]}]
  (ptk/reify ::fetch-components-bundle
    ptk/WatchEvent
    (watch [_ state _]
      (let [features (features/get-team-enabled-features state)]
        (->> (repo/cmd! :get-file {:id file-id :features features})
             (rx/map (fn [file] #(assoc % :file file))))))))

(def ^:private schema:render-components
  [:map {:title "render-components"}
   [:file-id ::sm/uuid]
   [:embed {:optional true} :boolean]
   [:component-id {:optional true} ::sm/uuid]])

(def ^:private render-components-decoder
  (sm/lazy-decoder schema:render-components
                   sm/default-transformer))

(def ^:private render-components-validator
  (sm/lazy-validator schema:render-components))

(defn render-components
  [params]
  (let [{:keys [file-id component-id embed] :as params} (render-components-decoder params)]
    (if-not (render-components-validator params)
      (do
        (js/console.error "invalid arguments")
        (sm/pretty-explain schema:render-components params)
        nil)

      (do
        (st/emit! (ptk/reify ::initialize-render-components
                    ptk/WatchEvent
                    (watch [_ _ stream]
                      (rx/merge
                       (rx/of (fetch-team :file-id file-id))

                       (->> stream
                            (rx/filter (ptk/type? ::team-fetched))
                            (rx/observe-on :async)
                            (rx/map (constantly params))
                            (rx/map fetch-components-bundle))))))

        (mf/html
         [:& components-svg
          {:component-id component-id
           :embed embed}])))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SETUP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defonce app-root
  (let [el (dom/get-element "app")]
    (mf/create-root el)))

(declare ^:private render-single-object)
(declare ^:private render-components)
(declare ^:private render-objects)

(defn- parse-params
  [loc]
  (let [href (unchecked-get loc "href")]
    (some-> href u/uri :query u/query-string->map)))

(defn init-ui
  []
  (when-let [params (parse-params glob/location)]
    (when-let [component (case (:route params)
                           "objects"    (render-objects params)
                           "components" (render-components params)
                           nil)]
      (mf/render! app-root component))))

(defn ^:export init
  []
  (st/emit! (features/initialize))
  (init-ui))

(defn reinit
  []
  (init-ui))

(defn ^:dev/after-load after-load
  []
  (reinit))



