;; 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.dashboard
  (:require-macros [app.main.style :as stl])
  (:require
   [app.common.data.macros :as dm]
   [app.config :as cf]
   [app.main.data.dashboard :as dd]
   [app.main.data.dashboard.shortcuts :as sc]
   [app.main.data.event :as ev]
   [app.main.data.modal :as modal]
   [app.main.data.nitrate :as dnt]
   [app.main.data.notifications :as notif]
   [app.main.data.plugins :as dp]
   [app.main.data.profile :as dprof]
   [app.main.data.project :as dpj]
   [app.main.refs :as refs]
   [app.main.router :as rt]
   [app.main.store :as st]
   [app.main.ui.components.progress :refer [progress-notification-widget*]]
   [app.main.ui.context :as ctx]
   [app.main.ui.dashboard.deleted :refer [deleted-section*]]
   [app.main.ui.dashboard.files :refer [files-section*]]
   [app.main.ui.dashboard.fonts :refer [fonts-page* font-providers-page*]]
   [app.main.ui.dashboard.import]
   [app.main.ui.dashboard.layout-toggle :as lt]
   [app.main.ui.dashboard.libraries :refer [libraries-page*]]
   [app.main.ui.dashboard.projects :refer [projects-section*]]
   [app.main.ui.dashboard.search :refer [search-page*]]
   [app.main.ui.dashboard.sidebar :refer [sidebar*]]
   [app.main.ui.dashboard.team :refer [team-settings-page* team-members-page* team-invitations-page* webhooks-page*]]
   [app.main.ui.dashboard.templates :refer [templates-section*]]
   [app.main.ui.hooks :as hooks]
   [app.main.ui.modal :refer [modal-container*]]
   [app.main.ui.workspace.plugins]
   [app.plugins.register :as preg]
   [app.util.dom :as dom]
   [app.util.i18n :refer [tr]]
   [app.util.keyboard :as kbd]
   [app.util.object :as obj]
   [app.util.session-state :as ss]
   [app.util.storage :as storage]
   [beicon.v2.core :as rx]
   [cuerdas.core :as str]
   [goog.events :as events]
   [okulary.core :as l]
   [rumext.v2 :as mf]))

(mf/defc dashboard-content*
  {::mf/private true}
  [{:keys [team projects project section search-term profile default-project layout on-layout-change]}]
  (let [container       (mf/use-ref)
        content-width   (mf/use-state 0)

        project-id      (:id project)
        team-id         (:id team)

        permissions     (:permissions team)

        default-project-id
        (get default-project :id)

        on-resize
        (mf/use-fn
         (fn [_]
           (let [dom   (mf/ref-val container)
                 width (obj/get dom "clientWidth")]
             (reset! content-width width))))

        clear-selected-fn
        (mf/use-fn
         #(st/emit! (dd/clear-selected-files)))

        show-templates?
        (and (contains? cf/flags :dashboard-templates-section)
             (:can-edit permissions))

        show-deleted? (:can-edit permissions)

        section (if (and (not show-deleted?) (= section :dashboard-deleted))
                  :dashboard-recent
                  section)]

    (mf/with-effect []
      (let [key1 (events/listen js/window "resize" on-resize)]
        #(events/unlistenByKey key1)))

    (mf/use-effect on-resize)

    [:div {:class (stl/css :dashboard-content)
           :on-click clear-selected-fn
           :ref container}

     [:> progress-notification-widget*]

     (case section
       :dashboard-recent
       (when (seq projects)
         [:*
          [:> projects-section* {:team team
                                 :projects projects
                                 :profile profile
                                 :layout layout
                                 :on-layout-change on-layout-change}]

          (when ^boolean show-templates?
            [:> templates-section* {:profile profile
                                    :project-id project-id
                                    :team-id team-id
                                    :default-project-id default-project-id
                                    :content-width @content-width}])])

       :dashboard-fonts
       [:> fonts-page* {:team team}]

       :dashboard-font-providers
       [:> font-providers-page* {:team team}]

       :dashboard-files
       (when project
         [:*
          [:> files-section* {:team team
                              :project project
                              :layout layout
                              :on-layout-change on-layout-change}]
          (when ^boolean show-templates?
            [:> templates-section* {:profile profile
                                    :team-id team-id
                                    :project-id project-id
                                    :default-project-id default-project-id
                                    :content-width @content-width}])])

       :dashboard-search
       [:> search-page* {:team team
                         :search-term search-term}]

       :dashboard-libraries
       [:> libraries-page* {:team team
                            :default-project default-project}]

       :dashboard-members
       [:> team-members-page* {:team team :profile profile}]

       :dashboard-invitations
       [:> team-invitations-page* {:team team :profile profile}]

       :dashboard-webhooks
       [:> webhooks-page* {:team team}]

       :dashboard-settings
       [:> team-settings-page* {:team team :profile profile}]

       :dashboard-deleted
       [:> deleted-section* {:team team
                             :projects projects
                             :profile profile
                             :layout layout
                             :on-layout-change on-layout-change}]

       nil)]))

(def ref:dashboard-initialized
  (l/derived :team-initialized st/state))

(defn use-plugin-register
  [plugin-url team-id project-id]

  (let [navegate-file!
        (fn [plugin {:keys [project-id id data]}]
          (st/emit!
           (dp/delay-open-plugin plugin)
           (rt/nav :workspace
                   {:page-id (dm/get-in data [:pages 0])
                    :project-id project-id
                    :file-id id
                    :team-id team-id})))

        create-file!
        (fn [plugin]
          (st/emit!
           (modal/hide)
           (let [data
                 (with-meta
                   {:project-id project-id
                    :name (dm/str (tr "dashboard.plugins.try-plugin") (:name plugin))}
                   {:on-success (partial navegate-file! plugin)})]
             (-> (dd/create-file data)
                 (with-meta {::ev/origin "plugin-try-out"})))))

        open-try-out-dialog
        (fn [plugin]
          (modal/show
           :plugin-try-out
           {:plugin plugin
            :on-accept #(create-file! plugin)
            :on-close modal/hide!}))

        open-permissions-dialog
        (fn [plugin]
          (modal/show!
           :plugin-permissions
           {:plugin plugin
            :on-accept
            #(do (preg/install-plugin! plugin)
                 (st/emit! (modal/hide)
                           (rt/nav :dashboard-recent {:team-id team-id})
                           (open-try-out-dialog plugin)))
            :on-close
            #(st/emit! (modal/hide)
                       (rt/nav :dashboard-recent {:team-id team-id}))}))]

    (mf/with-layout-effect
      [plugin-url team-id project-id]
      (when (and plugin-url project-id)
        (->> (dp/fetch-manifest plugin-url)
             (rx/subs!
              (fn [plugin]
                (if plugin
                  (do
                    (st/emit! (ev/event {::ev/name "install-plugin" :name (:name plugin) :url plugin-url}))
                    (open-permissions-dialog plugin))
                  (st/emit! (notif/error (tr "dashboard.plugins.parse-error")))))
              (fn [_]
                (st/emit! (notif/error (tr "dashboard.plugins.bad-url"))))))
        (binding [storage/*sync* true]
          (swap! storage/session dissoc :plugin-url))))))

(defn use-templates-import
  [can-edit? template project]
  (let [project-id (get project :id)
        team-id    (get project :team-id)]
    (mf/with-layout-effect [can-edit? template project-id team-id]
      (when (and (some? template)
                 (some? project-id)
                 (some? team-id))
        (if can-edit?
          (let [valid-url?    (str/ends-with? template ".penpot")

                ;; Backwards compatibility, ideally the template should be only the .penpot file name, not the full url
                template-name (if (str/starts-with? template "http")
                                (subs template (count cf/templates-uri))
                                template)

                template-url  (str "/github/penpot-files/" template-name)
                on-import     #(st/emit! (dpj/fetch-files project-id)
                                         (dd/fetch-recent-files team-id)
                                         (dd/fetch-projects team-id)
                                         (dd/clear-selected-files)
                                         (ev/event {::ev/name "install-template-from-link-finished"
                                                    :name template-name
                                                    :url template-url}))]
            (if valid-url?
              (st/emit!
               (ev/event {::ev/name "install-template-from-link" :name template-name :url template-url})
               (modal/show
                {:type :import
                 :project-id project-id
                 :entries [{:name template-name :uri template-url}]
                 :on-finish-import on-import}))
              (st/emit! (notif/error (tr "dashboard.import.bad-url")))))
          (st/emit! (notif/error (tr "dashboard.import.no-perms"))))

        (binding [storage/*sync* true]
          (swap! storage/session dissoc :template))))))

(defn- use-nitrate-entry-popup
  [onboarding-viewed? nitrate-onboarding-viewed?]
  (let [nitrate-popup-pending? (dnt/nitrate-entry-popup-pending?)]
    (mf/with-effect [nitrate-popup-pending? onboarding-viewed? nitrate-onboarding-viewed?]
      (when nitrate-popup-pending?
        (dnt/consume-nitrate-entry-popup!)
        (st/emit! (dprof/update-profile-props
                   (cond-> {}
                     (not (or nitrate-onboarding-viewed? onboarding-viewed?))
                     (assoc :nitrate-onboarding-viewed false)

                     (not onboarding-viewed?)
                     (assoc :onboarding-viewed true
                            :release-notes-viewed (:main cf/version))))
                  (dnt/show-nitrate-popup :nitrate-form))))))

(defn- use-pending-action
  "Consumes a pending dashboard action from session storage and resumes it"
  [pending-action-id]
  (mf/with-effect [pending-action-id]
    (when (some? pending-action-id)
      (dom/replace-history-state!
       (dom/remove-query-param (rt/get-current-href) :pending-action-id))
      (when-let [action (ss/consume-pending-action! (str pending-action-id))]
        (case (:type action)
          :add-team-to-organization
          (st/emit! (dnt/add-team-to-organization {:team-id         (:team-id action)
                                                   :organization-id (:organization-id action)
                                                   :skip-audit?     true}))
          nil)))))

(mf/defc dashboard*
  [{:keys [profile project-id team-id search-term plugin-url template section pending-action-id]}]
  (let [team            (mf/deref refs/team)
        projects        (mf/deref refs/projects)
        props           (get profile :props)

        project         (get projects project-id)
        projects        (mf/with-memo [projects team-id]
                          (->> (vals projects)
                               (filterv #(= team-id (:team-id %)))))

        can-edit?       (dm/get-in team [:permissions :can-edit])
        template        (or template (:template storage/session))
        plugin-url      (or plugin-url (:plugin-url storage/session))

        default-project
        (mf/with-memo [projects]
          (->> projects
               (filter :is-default)
               (first)))

        layout*         (hooks/use-persisted-state lt/layout-key lt/default-layout)
        layout          (deref layout*)

        on-layout-change
        (mf/use-fn
         (fn [value]
           (reset! layout* (keyword value))))]

    (hooks/use-shortcuts ::dashboard sc/shortcuts-dashboard :dashboard)

    (mf/with-effect [team-id]
      (st/emit! (dd/initialize team-id))
      (fn []
        (st/emit! (dd/finalize team-id))))

    (mf/with-effect []
      (let [key (events/listen goog/global "keydown"
                               (fn [event]
                                 (when (kbd/enter? event)
                                   (dom/stop-propagation event)
                                   (st/emit! (dd/open-selected-file)))))]
        (fn []
          (events/unlistenByKey key))))

    (use-plugin-register plugin-url team-id (:id default-project))
    (use-templates-import can-edit? template default-project)
    (use-nitrate-entry-popup (:onboarding-viewed props) (:nitrate-onboarding-viewed props))
    (use-pending-action pending-action-id)

    [:& (mf/provider ctx/current-project-id) {:value project-id}
     [:> modal-container*]
     ;; NOTE: dashboard events and other related functions assumes
     ;; that the team is a implicit context variable that is
     ;; available using react context or accessing
     ;; the :current-team-id on the state. We set the key to the
     ;; team-id because we want to completely refresh all the
     ;; components on team change. Many components assumes that the
     ;; team is already set so don't put the team into mf/deps.
     [:main {:class (stl/css :dashboard)
             :key (dm/str (:id team))}
      [:> sidebar* {:team team
                    :projects projects
                    :project project
                    :default-project default-project
                    :profile profile
                    :section section
                    :search-term search-term}]
      [:> dashboard-content* {:projects projects
                              :profile profile
                              :project project
                              :default-project default-project
                              :section section
                              :search-term search-term
                              :team team
                              :layout layout
                              :on-layout-change on-layout-change}]]]))

(mf/defc dashboard-page*
  {::mf/lazy-load true}
  [props]
  [:> dashboard* props])
