;; 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.plugins
  (:require-macros [app.main.style :as stl])
  (:require
   [app.common.data :as d]
   [app.common.data.macros :as dm]
   [app.common.uri :as u]
   [app.config :as cfg]
   [app.main.data.event :as ev]
   [app.main.data.modal :as modal]
   [app.main.data.plugins :as dp]
   [app.main.refs :as refs]
   [app.main.store :as st]
   [app.main.ui.components.search-bar :refer [search-bar*]]
   [app.main.ui.components.title-bar :refer [title-bar*]]
   [app.main.ui.ds.buttons.button :refer [button*]]
   [app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
   [app.main.ui.ds.foundations.assets.icon :as i]
   [app.main.ui.icons :as deprecated-icon]
   [app.plugins.register :as preg]
   [app.util.avatars :as avatars]
   [app.util.dom :as dom]
   [app.util.globals :as global]
   [app.util.i18n :as i18n :refer [tr]]
   [beicon.v2.core :as rx]
   [cuerdas.core :as str]
   [rumext.v2 :as mf]))

(def ^:private close-icon
  (deprecated-icon/icon-xref :close (stl/css :close-icon)))

(defn- normalize-plugin-url
  "Automatically appens manifest.json if the plugin-uri comes without it."
  [plugin-url]
  (if (str/ends-with? plugin-url "manifest.json")
    plugin-url
    (-> (u/uri plugin-url)
        (update :path (fn [path]
                        (if (str/ends-with? path "/")
                          (str path "manifest.json")
                          (str path "/manifest.json"))))
        (str))))

(defn- icon-url
  "Creates an sanitizes de icon URL to display"
  [host icon]
  (dm/str host
          (if (and (not (str/ends-with? host "/"))
                   (not (str/starts-with? icon "/")))
            "/" "")
          icon))

(mf/defc plugin-entry*
  [{:keys [index manifest user-can-edit on-open-plugin on-remove-plugin]}]

  (let [{:keys [plugin-id host icon name description permissions]} manifest
        plugins-permissions-peek (deref refs/plugins-permissions-peek)
        permissions              (or (get plugins-permissions-peek plugin-id)
                                     permissions)
        is-edition-plugin?       (or (contains? permissions "content:write")
                                     (contains? permissions "library:write"))
        can-open?                (or user-can-edit
                                     (not is-edition-plugin?))

        handle-open-click
        (mf/use-fn
         (mf/deps index manifest on-open-plugin can-open?)
         (fn []
           (when (and can-open? on-open-plugin)
             (on-open-plugin manifest))))

        handle-delete-click
        (mf/use-fn
         (mf/deps index on-remove-plugin)
         (fn []
           (when on-remove-plugin
             (on-remove-plugin index))))]
    [:div {:class (stl/css :plugins-list-element)}
     [:div {:class (stl/css :plugin-icon)}
      [:img {:src (if (some? icon)
                    (icon-url host icon)
                    (avatars/generate {:name name}))}]]
     [:div {:class (stl/css :plugin-description)}
      [:div {:class (stl/css :plugin-title)} name]
      [:div {:class (stl/css :plugin-summary)} (d/nilv description "")]]


     [:> button* {:class (stl/css :open-button)
                  :variant "secondary"
                  :type "button"
                  :on-click handle-open-click
                  :title (when-not can-open? (tr "workspace.plugins.error.need-editor"))
                  :disabled (not can-open?)} (tr "workspace.plugins.button-open")]

     [:> icon-button* {:variant "ghost"
                       :aria-label (tr "workspace.plugins.remove-plugin")
                       :on-click handle-delete-click
                       :icon i/delete}]]))

(mf/defc plugin-management-dialog
  {::mf/register modal/components
   ::mf/register-as :plugin-management}
  []

  (let [plugins-state*  (mf/use-state #(preg/plugins-list))
        plugins-state   (deref plugins-state*)

        plugin-url*     (mf/use-state "")
        plugin-url      (deref plugin-url*)

        input-status*   (mf/use-state nil) ;; :error-url :error-manifest :success
        input-status    (deref input-status*)

        error-url?      (= :error-url input-status)
        error-manifest? (= :error-manifest input-status)
        error?          (or error-url? error-manifest?)

        permissions     (mf/deref refs/permissions)
        user-can-edit?  (get permissions :can-edit)

        fetching-manifest?
        (mf/use-state false)

        on-url-change
        (mf/use-fn
         (fn [value]
           (reset! input-status* nil)
           (reset! plugin-url* value)))

        on-install
        (mf/use-fn
         (mf/deps plugins-state plugin-url)
         (fn []
           (reset! fetching-manifest? true)
           (->> (dp/fetch-manifest (normalize-plugin-url plugin-url))
                (rx/subs!
                 (fn [plugin]
                   (reset! fetching-manifest? false)
                   (if plugin
                     (do
                       (st/emit! (ev/event {::ev/name "install-plugin" :name (:name plugin) :url plugin-url}))
                       (modal/show! :plugin-permissions
                                    {:plugin plugin
                                     :on-accept
                                     #(do
                                        (preg/install-plugin! plugin)
                                        (modal/show! :plugin-management {}))})
                       (reset! input-status* :success)
                       (reset! plugin-url* ""))
                     ;; Cannot get the manifest
                     (reset! input-status* :error-manifest)))
                 (fn [err]
                   (.error js/console err)
                   (reset! fetching-manifest? false)
                   (reset! input-status* :error-url))))))

        on-open-plugin
        (mf/use-fn
         (fn [manifest]
           (st/emit! (ev/event {::ev/name "start-plugin"
                                ::ev/origin "workspace:plugins"
                                :name (:name manifest)
                                :host (:host manifest)}))
           (dp/open-plugin! manifest user-can-edit?)
           (modal/hide!)))

        on-remove-plugin
        (mf/use-fn
         (mf/deps plugins-state)
         (fn [plugin-index]
           (let [plugins-list (preg/plugins-list)
                 plugin (nth plugins-list plugin-index)]
             (st/emit! (ev/event {::ev/name "remove-plugin"
                                  :name (:name plugin)
                                  :host (:host plugin)}))
             (dp/close-plugin! plugin)
             (preg/remove-plugin! plugin)
             (reset! plugins-state* (preg/plugins-list)))))]

    [:div {:class (stl/css :modal-overlay)}
     [:div {:class (stl/css :modal-dialog :plugin-management)}
      [:button {:class (stl/css :close-btn) :on-click modal/hide!} close-icon]
      [:div {:class (stl/css :modal-title)} (tr "workspace.plugins.title")]

      [:div {:class (stl/css :modal-content)}
       [:div {:class (stl/css :top-bar)}
        [:> search-bar* {:on-change on-url-change
                         :on-submit on-install
                         :value plugin-url
                         :placeholder (tr "workspace.plugins.search-placeholder")
                         :class (stl/css-case :input-error error?)}]

        [:button {:class (stl/css :primary-button)
                  :disabled @fetching-manifest?
                  :on-click on-install}
         (tr "workspace.plugins.install")]]

       (when error-url?
         [:div {:class (stl/css-case :info true :error error?)}
          (tr "workspace.plugins.error.url")])

       (when error-manifest?
         [:div {:class (stl/css-case :info true :error error?)}
          (tr "workspace.plugins.error.manifest")])

       (when-not (empty? plugins-state)
         [:> i18n/tr-html*
          {:class (stl/css :discover)
           :on-click #(st/emit! (ev/event {::ev/name "open-plugins-list"}))
           :content (tr "workspace.plugins.discover" cfg/plugins-list-uri)}])

       [:hr]

       (if (empty? plugins-state)
         [:div {:class (stl/css :plugins-empty)}
          [:div {:class (stl/css :plugins-empty-logo)} deprecated-icon/puzzle]
          [:div {:class (stl/css :plugins-empty-text)} (tr "workspace.plugins.empty-plugins")]
          [:a {:class (stl/css :plugins-link)
               :href cfg/plugins-list-uri
               :target "_blank"
               :on-click #(st/emit! (ev/event {::ev/name "open-plugins-list"}))}
           (tr "workspace.plugins.plugin-list-link") deprecated-icon/external-link]]

         [:*
          [:> title-bar* {:collapsable false
                          :title (tr "workspace.plugins.installed-plugins")}]

          [:div {:class (stl/css :plugins-list)}
           (for [[idx manifest] (d/enumerate plugins-state)]
             [:> plugin-entry* {:key (dm/str "plugin-" idx)
                                :index idx
                                :manifest manifest
                                :user-can-edit user-can-edit?
                                :on-open-plugin on-open-plugin
                                :on-remove-plugin on-remove-plugin}])]])]]]))

(mf/defc plugins-permission-list*
  {::mf/private true}
  [{:keys [permissions]}]
  [:div {:class (stl/css :permissions-list)}
   (cond
     (contains? permissions "content:write")
     [:div {:class (stl/css :permissions-list-entry)}
      deprecated-icon/oauth-1
      [:p {:class (stl/css :permissions-list-text)}
       (tr "workspace.plugins.permissions.content-write")]]

     (contains? permissions "content:read")
     [:div {:class (stl/css :permissions-list-entry)}
      deprecated-icon/oauth-1
      [:p {:class (stl/css :permissions-list-text)}
       (tr "workspace.plugins.permissions.content-read")]])

   (cond
     (contains? permissions "user:read")
     [:div {:class (stl/css :permissions-list-entry)}
      deprecated-icon/oauth-2
      [:p {:class (stl/css :permissions-list-text)}
       (tr "workspace.plugins.permissions.user-read")]])

   (cond
     (contains? permissions "library:write")
     [:div {:class (stl/css :permissions-list-entry)}
      deprecated-icon/oauth-3
      [:p {:class (stl/css :permissions-list-text)}
       (tr "workspace.plugins.permissions.library-write")]]

     (contains? permissions "library:read")
     [:div {:class (stl/css :permissions-list-entry)}
      deprecated-icon/oauth-3
      [:p {:class (stl/css :permissions-list-text)}
       (tr "workspace.plugins.permissions.library-read")]])

   (cond
     (contains? permissions "comment:write")
     [:div {:class (stl/css :permissions-list-entry)}
      deprecated-icon/oauth-1
      [:p {:class (stl/css :permissions-list-text)}
       (tr "workspace.plugins.permissions.comment-write")]]

     (contains? permissions "comment:read")
     [:div {:class (stl/css :permissions-list-entry)}
      deprecated-icon/oauth-1
      [:p {:class (stl/css :permissions-list-text)}
       (tr "workspace.plugins.permissions.comment-read")]])

   (cond
     (contains? permissions "allow:downloads")
     [:div {:class (stl/css :permissions-list-entry)}
      deprecated-icon/oauth-1
      [:p {:class (stl/css :permissions-list-text)}
       (tr "workspace.plugins.permissions.allow-download")]])

   (cond
     (contains? permissions "allow:localstorage")
     [:div {:class (stl/css :permissions-list-entry)}
      deprecated-icon/oauth-1
      [:p {:class (stl/css :permissions-list-text)}
       (tr "workspace.plugins.permissions.allow-localstorage")]])

   (cond
     (contains? permissions "clipboard:write")
     [:div {:class (stl/css :permissions-list-entry)}
      deprecated-icon/oauth-1
      [:p {:class (stl/css :permissions-list-text)}
       (tr "workspace.plugins.permissions.clipboard-write")]]

     (contains? permissions "clipboard:read")
     [:div {:class (stl/css :permissions-list-entry)}
      deprecated-icon/oauth-1
      [:p {:class (stl/css :permissions-list-text)}
       (tr "workspace.plugins.permissions.clipboard-read")]])])

(mf/defc plugins-permissions-dialog
  {::mf/register modal/components
   ::mf/register-as :plugin-permissions}
  [{:keys [plugin on-accept on-close]}]

  (let [host
        (:host plugin)

        permissions
        (-> plugin :permissions set)

        on-accept-dialog
        (mf/use-fn
         (mf/deps on-accept)
         (fn [event]
           (dom/prevent-default event)
           (st/emit! (ev/event {::ev/name "allow-plugin-permissions"
                                :host host
                                :permissions (str/join ", " permissions)})
                     (modal/hide))
           (when on-accept (on-accept))))

        on-close-dialog
        (mf/use-fn
         (mf/deps on-close)
         (fn [event]
           (dom/prevent-default event)
           (st/emit! (ev/event {::ev/name "reject-plugin-permissions"
                                :host host
                                :permissions (str/join ", " permissions)})
                     (modal/hide))
           (when on-close (on-close))))]

    (mf/with-effect [on-accept-dialog]
      (.addEventListener ^js global/document "keydown" on-accept-dialog)
      #(.removeEventListener ^js global/document "keydown" on-accept-dialog))

    [:div {:class (stl/css :modal-overlay)}
     [:div {:class (stl/css :modal-dialog :plugin-permissions)}
      [:button {:class (stl/css :close-btn) :on-click on-close-dialog} close-icon]
      [:div {:class (stl/css :modal-title)} (tr "workspace.plugins.permissions.title" (str/upper (:name plugin)))]

      [:div {:class (stl/css :modal-content)}
       [:> plugins-permission-list* {:permissions permissions}]

       (when-not (contains? cfg/plugins-whitelist host)
         [:div {:class (stl/css :permissions-disclaimer)}
          (tr "workspace.plugins.permissions.disclaimer")])]

      [:div {:class (stl/css :modal-footer)}
       [:div {:class (stl/css :action-buttons)}
        [:input
         {:class (stl/css :cancel-button :button-expand)
          :type "button"
          :value (tr "ds.confirm-cancel")
          :on-click on-close-dialog}]

        [:input
         {:class (stl/css :primary-button :button-expand)
          :type "button"
          :value (tr "ds.confirm-allow")
          :on-click on-accept-dialog}]]]]]))


(mf/defc plugins-permissions-updated-dialog
  {::mf/register modal/components
   ::mf/register-as :plugin-permissions-update}
  [{:keys [plugin on-accept on-close]}]

  (let [host
        (:host plugin)

        permissions
        (-> plugin :permissions set)

        on-accept-dialog
        (mf/use-fn
         (mf/deps on-accept)
         (fn [event]
           (dom/prevent-default event)
           (st/emit! (ev/event {::ev/name "allow-plugin-permissions"
                                :host host
                                :permissions (->> permissions (str/join ", "))})
                     (modal/hide))
           (when on-accept (on-accept))))

        on-close-dialog
        (mf/use-fn
         (mf/deps on-close)
         (fn [event]
           (dom/prevent-default event)
           (st/emit! (ev/event {::ev/name "reject-plugin-permissions"
                                :host host
                                :permissions (->> permissions (str/join ", "))})
                     (modal/hide))
           (when on-close (on-close))))]

    (mf/with-effect [on-accept-dialog]
      (.addEventListener ^js global/document "keydown" on-accept-dialog)
      #(.removeEventListener ^js global/document "keydown" on-accept-dialog))

    [:div {:class (stl/css :modal-overlay)}
     [:div {:class (stl/css :modal-dialog :plugin-permissions)}
      [:button {:class (stl/css :close-btn) :on-click on-close-dialog} close-icon]
      [:div {:class (stl/css :modal-title)}
       (tr "workspace.plugins.permissions-update.title" (str/upper (:name plugin)))]

      [:div {:class (stl/css :modal-content)}
       [:div {:class (stl/css :modal-paragraph)}
        (tr "workspace.plugins.permissions-update.warning")]
       [:> plugins-permission-list* {:permissions permissions}]]

      [:div {:class (stl/css :modal-footer)}
       [:div {:class (stl/css :action-buttons)}
        [:input
         {:class (stl/css :cancel-button :button-expand)
          :type "button"
          :value (tr "ds.confirm-cancel")
          :on-click on-close-dialog}]

        [:input
         {:class (stl/css :primary-button :button-expand)
          :type "button"
          :value (tr "ds.confirm-allow")
          :on-click on-accept-dialog}]]]]]))


(mf/defc plugins-try-out-dialog
  {::mf/register modal/components
   ::mf/register-as :plugin-try-out}
  [{:keys [plugin on-accept on-close]}]

  (let [{:keys [icon host name]} plugin

        on-accept-dialog
        (mf/use-fn
         (mf/deps on-accept)
         (fn [event]
           (dom/prevent-default event)
           (st/emit! (ev/event {::ev/name "try-out-accept"})
                     (modal/hide))
           (when on-accept (on-accept))))

        on-close-dialog
        (mf/use-fn
         (mf/deps on-close)
         (fn [event]
           (dom/prevent-default event)
           (st/emit! (ev/event {::ev/name "try-out-cancel"})
                     (modal/hide))
           (when on-close (on-close))))]

    (mf/with-effect [on-accept-dialog]
      (.addEventListener ^js global/document "keydown" on-accept-dialog)
      #(.removeEventListener ^js global/document "keydown" on-accept-dialog))

    [:div {:class (stl/css :modal-overlay)}
     [:div {:class (stl/css :modal-dialog :plugin-try-out)}
      [:button {:class (stl/css :close-btn) :on-click on-close-dialog} close-icon]
      [:div {:class (stl/css :modal-title)}
       [:div {:class (stl/css :plugin-icon)}
        [:img {:src (if (some? icon)
                      (icon-url host icon)
                      (avatars/generate {:name name}))}]]
       (tr "workspace.plugins.try-out.title" (str/upper (:name plugin)))]

      [:div {:class (stl/css :modal-content)}
       [:div {:class (stl/css :modal-message)}
        (tr "workspace.plugins.try-out.message")]]

      [:div {:class (stl/css :modal-footer)}
       [:div {:class (stl/css :action-buttons)}
        [:input
         {:class (stl/css :cancel-button :button-expand)
          :type "button"
          :value (tr "workspace.plugins.try-out.cancel")
          :on-click on-close-dialog}]

        [:input
         {:class (stl/css :primary-button :button-expand)
          :type "button"
          :value (tr "workspace.plugins.try-out.try")
          :on-click on-accept-dialog}]]]]]))
