(ns frontend.modules.shortcut.config
  (:require [frontend.components.commit :as commit]
            [frontend.extensions.srs.handler :as srs]
            [frontend.extensions.pdf.utils :as pdf-utils]
            [frontend.handler.config :as config-handler]
            [frontend.handler.editor :as editor-handler]
            [frontend.handler.paste :as paste-handler]
            [frontend.handler.history :as history]
            [frontend.handler.page :as page-handler]
            [frontend.handler.route :as route-handler]
            [frontend.handler.journal :as journal-handler]
            [frontend.handler.search :as search-handler]
            [frontend.handler.ui :as ui-handler]
            [frontend.handler.plugin :as plugin-handler]
            [frontend.modules.shortcut.dicts :as dicts]
            [frontend.modules.shortcut.before :as m]
            [frontend.state :as state]
            [frontend.util :refer [mac?] :as util]
            [frontend.commands :as commands]
            [clojure.data :as data]
            [medley.core :as medley]))

;; TODO: Namespace all-default-keyboard-shortcuts keys with `:command` e.g.
;; `:command.date-picker/complete`. They are namespaced in translation but
;; almost everywhere else they are not which could cause needless conflicts
;; with other config keys

;; To add a new entry to this map, first add it here and then
;; a description for it in frontend.modules.shortcut.dicts/all-default-keyboard-shortcuts.
;; :inactive key is for commands that are not active for a given platform or feature condition
(def ^:large-vars/data-var all-default-keyboard-shortcuts
  {:date-picker/complete         {:binding "enter"
                                  :fn      ui-handler/shortcut-complete}

   :date-picker/prev-day         {:binding "left"
                                  :fn      ui-handler/shortcut-prev-day}

   :date-picker/next-day         {:binding "right"
                                  :fn      ui-handler/shortcut-next-day}

   :date-picker/prev-week        {:binding ["up" "ctrl+p"]
                                  :fn      ui-handler/shortcut-prev-week}

   :date-picker/next-week        {:binding ["down" "ctrl+n"]
                                  :fn      ui-handler/shortcut-next-week}

   :pdf/previous-page            {:binding "alt+p"
                                  :fn      pdf-utils/prev-page}

   :pdf/next-page                {:binding "alt+n"
                                  :fn      pdf-utils/next-page}

   :pdf/close                    {:binding "alt+x"
                                  :fn      #(state/set-state! :pdf/current nil)}

   :auto-complete/complete       {:binding "enter"
                                  :fn      ui-handler/auto-complete-complete}

   :auto-complete/prev           {:binding ["up" "ctrl+p"]
                                  :fn      ui-handler/auto-complete-prev}

   :auto-complete/next           {:binding ["down" "ctrl+n"]
                                  :fn      ui-handler/auto-complete-next}

   :auto-complete/shift-complete {:binding "shift+enter"
                                  :fn      ui-handler/auto-complete-shift-complete}

   :auto-complete/open-link      {:binding "mod+o"
                                  :fn      ui-handler/auto-complete-open-link}

   :cards/toggle-answers         {:binding "s"
                                  :fn      srs/toggle-answers}

   :cards/next-card              {:binding "n"
                                  :fn      srs/next-card}

   :cards/forgotten              {:binding "f"
                                  :fn      srs/forgotten}

   :cards/remembered             {:binding "r"
                                  :fn      srs/remembered}

   :cards/recall                 {:binding "t"
                                  :fn      srs/recall}

   :editor/escape-editing        {:binding false
                                  :fn      (fn [_ _]
                                             (editor-handler/escape-editing))}

   :editor/backspace             {:binding "backspace"
                                  :fn      editor-handler/editor-backspace}

   :editor/delete                {:binding "delete"
                                  :fn      editor-handler/editor-delete}

   :editor/new-block             {:binding "enter"
                                  :fn      editor-handler/keydown-new-block-handler}

   :editor/new-line              {:binding "shift+enter"
                                  :fn      editor-handler/keydown-new-line-handler}

   :editor/follow-link           {:binding "mod+o"
                                  :fn      editor-handler/follow-link-under-cursor!}

   :editor/open-link-in-sidebar  {:binding "mod+shift+o"
                                  :fn      editor-handler/open-link-in-sidebar!}

   :editor/bold                  {:binding "mod+b"
                                  :fn      editor-handler/bold-format!}

   :editor/italics               {:binding "mod+i"
                                  :fn      editor-handler/italics-format!}

   :editor/highlight             {:binding "mod+shift+h"
                                  :fn      editor-handler/highlight-format!}

   :editor/strike-through        {:binding "mod+shift+s"
                                  :fn      editor-handler/strike-through-format!}

   :editor/clear-block           {:binding (if mac? "ctrl+l" "alt+l")
                                  :fn      editor-handler/clear-block-content!}

   :editor/kill-line-before      {:binding (if mac? "ctrl+u" "alt+u")
                                  :fn      editor-handler/kill-line-before!}

   :editor/kill-line-after       {:binding (if mac? false "alt+k")
                                  :fn      editor-handler/kill-line-after!}

   :editor/beginning-of-block    {:binding (if mac? false "alt+a")
                                  :fn      editor-handler/beginning-of-block}

   :editor/end-of-block          {:binding (if mac? false "alt+e")
                                  :fn      editor-handler/end-of-block}

   :editor/forward-word          {:binding (if mac? "ctrl+shift+f" "alt+f")
                                  :fn      editor-handler/cursor-forward-word}

   :editor/backward-word         {:binding (if mac? "ctrl+shift+b" "alt+b")
                                  :fn      editor-handler/cursor-backward-word}

   :editor/forward-kill-word     {:binding (if mac? "ctrl+w" "alt+d")
                                  :fn      editor-handler/forward-kill-word}

   :editor/backward-kill-word    {:binding (if mac? false "alt+w")
                                  :fn      editor-handler/backward-kill-word}

   :editor/replace-block-reference-at-point {:binding "mod+shift+r"
                                             :fn      editor-handler/replace-block-reference-with-content-at-point}
   :editor/copy-embed {:binding "mod+e"
                       :fn      editor-handler/copy-current-block-embed}

   :editor/paste-text-in-one-block-at-point {:binding "mod+shift+v"
                                             :fn      (fn [_state e] ((paste-handler/editor-on-paste! nil true) e))}

   :editor/insert-youtube-timestamp         {:binding "mod+shift+y"
                                             :fn      commands/insert-youtube-timestamp}

   :editor/cycle-todo              {:binding "mod+enter"
                                    :fn      editor-handler/cycle-todo!}

   :editor/up                      {:binding ["up" "ctrl+p"]
                                    :fn      (editor-handler/shortcut-up-down :up)}

   :editor/down                    {:binding ["down" "ctrl+n"]
                                    :fn      (editor-handler/shortcut-up-down :down)}

   :editor/left                    {:binding "left"
                                    :fn      (editor-handler/shortcut-left-right :left)}

   :editor/right                   {:binding "right"
                                    :fn      (editor-handler/shortcut-left-right :right)}

   :editor/move-block-up           {:binding (if mac? "mod+shift+up" "alt+shift+up")
                                    :fn      (editor-handler/move-up-down true)}

   :editor/move-block-down         {:binding (if mac? "mod+shift+down" "alt+shift+down")
                                    :fn      (editor-handler/move-up-down false)}

   ;; FIXME: add open edit in non-selection mode
   :editor/open-edit               {:binding "enter"
                                    :fn      (partial editor-handler/open-selected-block! :right)}

   :editor/select-block-up         {:binding "alt+up"
                                    :fn      (editor-handler/on-select-block :up)}

   :editor/select-block-down       {:binding "alt+down"
                                    :fn      (editor-handler/on-select-block :down)}

   :editor/select-up               {:binding "shift+up"
                                    :fn      (editor-handler/shortcut-select-up-down :up)}

   :editor/select-down             {:binding "shift+down"
                                    :fn      (editor-handler/shortcut-select-up-down :down)}

   :editor/delete-selection        {:binding ["backspace" "delete"]
                                    :fn      editor-handler/delete-selection}

   :editor/expand-block-children   {:binding "mod+down"
                                    :fn      editor-handler/expand!}

   :editor/collapse-block-children {:binding "mod+up"
                                    :fn      editor-handler/collapse!}

   :editor/indent                  {:binding "tab"
                                    :fn      (editor-handler/keydown-tab-handler :right)}

   :editor/outdent                 {:binding "shift+tab"
                                    :fn      (editor-handler/keydown-tab-handler :left)}

   :editor/copy                    {:binding "mod+c"
                                    :fn      editor-handler/shortcut-copy}

   :editor/copy-text               {:binding "mod+shift+c"
                                    :fn      editor-handler/shortcut-copy-text}

   :editor/cut                     {:binding "mod+x"
                                    :fn      editor-handler/shortcut-cut}

   :editor/undo                    {:binding "mod+z"
                                    :fn      history/undo!}

   :editor/redo                    {:binding ["shift+mod+z" "mod+y"]
                                    :fn      history/redo!}

   :editor/insert-link             {:binding "mod+l"
                                    :fn      #(editor-handler/html-link-format!)}

   :editor/select-all-blocks       {:binding "mod+shift+a"
                                    :fn      editor-handler/select-all-blocks!}

   :editor/zoom-in                 {:binding (if mac? "mod+." "alt+right")
                                    :fn      editor-handler/zoom-in!}

   :editor/zoom-out                {:binding (if mac? "mod+," "alt+left")
                                    :fn      editor-handler/zoom-out!}

   :ui/toggle-brackets             {:binding "mod+c mod+b"
                                    :fn      config-handler/toggle-ui-show-brackets!}

   :go/search-in-page              {:binding "mod+shift+k"
                                    :fn      #(do
                                                (editor-handler/escape-editing)
                                                (route-handler/go-to-search! :page))}

   :go/search                      {:binding "mod+k"
                                    :fn      #(do
                                                (editor-handler/escape-editing)
                                                (route-handler/go-to-search! :global))}

   :go/journals                    {:binding "g j"
                                    :fn      route-handler/go-to-journals!}

   :go/backward                    {:binding "mod+open-square-bracket"
                                    :fn      (fn [_] (js/window.history.back))}

   :go/forward                     {:binding "mod+close-square-bracket"
                                    :fn      (fn [_] (js/window.history.forward))}

   :search/re-index                {:binding "mod+c mod+s"
                                    :fn      (fn [_] (search-handler/rebuild-indices! true))}

   :sidebar/open-today-page        {:binding (if mac? "mod+shift+j" "alt+shift+j")
                                    :fn      page-handler/open-today-in-sidebar}

   :sidebar/clear                  {:binding "mod+c mod+c"
                                    :fn      #(do
                                                (state/clear-sidebar-blocks!)
                                                (state/hide-right-sidebar!))}

   :misc/copy                      {:binding "mod+c"
                                    :fn      (fn [] (js/document.execCommand "copy"))}

   :command-palette/toggle         {:binding "mod+shift+p"
                                    :fn      #(do
                                                (editor-handler/escape-editing)
                                                (state/toggle! :ui/command-palette-open?))}

   :graph/open                     {:fn      #(do
                                                (editor-handler/escape-editing)
                                                (state/set-state! :ui/open-select :graph-open))
                                    :binding "mod+shift+g"}

   :graph/remove                   {:fn      #(do
                                                (editor-handler/escape-editing)
                                                (state/set-state! :ui/open-select :graph-remove))
                                    :binding false}

   :graph/add                      {:fn (fn [] (route-handler/redirect! {:to :repo-add}))
                                    :binding false}

   :graph/save                     {:fn #(state/pub-event! [:graph/save])
                                    :binding false}

   :command/run                    {:binding "mod+shift+1"
                                    :inactive (not (util/electron?))
                                    :fn      #(do
                                                (editor-handler/escape-editing)
                                                (state/pub-event! [:command/run]))}

   :go/home                        {:binding "g h"
                                    :fn      route-handler/redirect-to-home!}

   :go/all-pages                   {:binding "g a"
                                    :fn      route-handler/redirect-to-all-pages!}

   :go/graph-view                  {:binding "g g"
                                    :fn      route-handler/redirect-to-graph-view!}


   :go/keyboard-shortcuts          {:binding "g s"
                                    :fn      #(route-handler/redirect! {:to :shortcut-setting})}

   :go/tomorrow                    {:binding "g t"
                                    :fn      journal-handler/go-to-tomorrow!}

   :go/next-journal                {:binding "g n"
                                    :fn      journal-handler/go-to-next-journal!}

   :go/prev-journal                {:binding "g p"
                                    :fn      journal-handler/go-to-prev-journal!}

   :go/flashcards                  {:binding "g f"
                                    :fn      (fn []
                                               (if (state/modal-opened?)
                                                 (state/close-modal!)
                                                 (state/pub-event! [:modal/show-cards])))}

   :ui/toggle-document-mode        {:binding "t d"
                                    :fn      state/toggle-document-mode!}

   :ui/toggle-settings              {:binding (if mac? "t s" ["t s" "mod+,"])
                                     :fn      ui-handler/toggle-settings-modal!}

   :ui/toggle-right-sidebar         {:binding "t r"
                                     :fn      ui-handler/toggle-right-sidebar!}

   :ui/toggle-left-sidebar          {:binding "t l"
                                     :fn      state/toggle-left-sidebar!}

   :ui/toggle-help                  {:binding "shift+/"
                                     :fn      ui-handler/toggle-help!}

   :ui/toggle-theme                 {:binding "t t"
                                     :fn      state/toggle-theme!}

   :ui/toggle-contents              {:binding "alt+shift+c"
                                     :fn      ui-handler/toggle-contents!}

   :command/toggle-favorite         {:binding "mod+shift+f"
                                     :fn      page-handler/toggle-favorite!}

   :editor/open-file-in-default-app {:binding "mod+d mod+a"
                                     :inactive (not (util/electron?))
                                     :fn      page-handler/open-file-in-default-app}

   :editor/open-file-in-directory   {:binding "mod+d mod+i"
                                     :inactive (not (util/electron?))
                                     :fn      page-handler/open-file-in-directory}

   :editor/copy-current-file        {:binding false
                                     :inactive (not (util/electron?))
                                     :fn      page-handler/copy-current-file}

   :ui/toggle-wide-mode             {:binding "t w"
                                     :fn      ui-handler/toggle-wide-mode!}

   :ui/select-theme-color           {:binding "t i"
                                     :fn      plugin-handler/show-themes-modal!}

   :ui/goto-plugins                 {:binding "t p"
                                     :inactive (not plugin-handler/lsp-enabled?)
                                     :fn      plugin-handler/goto-plugins-dashboard!}


   :editor/toggle-open-blocks       {:binding "t o"
                                     :fn      editor-handler/toggle-open!}

   :ui/toggle-cards                 {:binding "t c"
                                     :fn      ui-handler/toggle-cards!}

   :git/commit                      {:binding "c"
                                     :fn      commit/show-commit-modal!}})

(let [keyboard-shortcuts
      {::keyboard-shortcuts (set (keys all-default-keyboard-shortcuts))
       ::dicts/keyboard-shortcuts (set (keys dicts/all-default-keyboard-shortcuts))}]
  (assert (= (::keyboard-shortcuts keyboard-shortcuts) (::dicts/keyboard-shortcuts keyboard-shortcuts))
          (str "Keys for keyboard shortcuts must be the same "
               (data/diff (::keyboard-shortcuts keyboard-shortcuts) (::dicts/keyboard-shortcuts keyboard-shortcuts)))))

(defn build-category-map [ks]
  (->> (select-keys all-default-keyboard-shortcuts ks)
       (remove (comp :inactive val))
       (into {})))

(defonce ^:large-vars/data-var config
  (atom
   {:shortcut.handler/date-picker
    (build-category-map [:date-picker/complete
                         :date-picker/prev-day
                         :date-picker/next-day
                         :date-picker/prev-week
                         :date-picker/next-week])

    :shortcut.handler/pdf
    (-> (build-category-map [:pdf/previous-page
                             :pdf/next-page
                             :pdf/close])
        (with-meta {:before m/enable-when-not-editing-mode!}))

    :shortcut.handler/auto-complete
    (build-category-map [:auto-complete/complete
                         :auto-complete/prev
                         :auto-complete/next
                         :auto-complete/shift-complete
                         :auto-complete/open-link])

    :shortcut.handler/cards
    (-> (build-category-map [:cards/toggle-answers
                             :cards/next-card
                             :cards/forgotten
                             :cards/remembered
                             :cards/recall])
        (with-meta {:before m/enable-when-not-editing-mode!}))

    :shortcut.handler/block-editing-only
    (->
     (build-category-map [:editor/escape-editing
                          :editor/backspace
                          :editor/delete
                          :editor/new-block
                          :editor/new-line
                          :editor/follow-link
                          :editor/open-link-in-sidebar
                          :editor/bold
                          :editor/italics
                          :editor/highlight
                          :editor/strike-through
                          :editor/clear-block
                          :editor/kill-line-before
                          :editor/kill-line-after
                          :editor/beginning-of-block
                          :editor/end-of-block
                          :editor/forward-word
                          :editor/backward-word
                          :editor/forward-kill-word
                          :editor/backward-kill-word
                          :editor/replace-block-reference-at-point
                          :editor/copy-embed
                          :editor/paste-text-in-one-block-at-point
                          :editor/insert-youtube-timestamp])
     (with-meta {:before m/enable-when-editing-mode!}))

    :shortcut.handler/editor-global
    (->
     (build-category-map [:command/run
                          :command-palette/toggle
                          :graph/open
                          :graph/remove
                          :graph/add
                          :graph/save
                          :editor/cycle-todo
                          :editor/up
                          :editor/down
                          :editor/left
                          :editor/right
                          :editor/select-up
                          :editor/select-down
                          :editor/move-block-up
                          :editor/move-block-down
                          :editor/open-edit
                          :editor/select-block-up
                          :editor/select-block-down
                          :editor/delete-selection
                          :editor/expand-block-children
                          :editor/collapse-block-children
                          :editor/indent
                          :editor/outdent
                          :editor/copy
                          :editor/copy-text
                          :editor/cut
                          :editor/undo
                          :editor/redo
                          :command/toggle-favorite])
     (with-meta {:before m/enable-when-not-component-editing!}))

    :shortcut.handler/global-prevent-default
    (->
     (build-category-map [:editor/insert-link
                          :editor/select-all-blocks
                          :editor/zoom-in
                          :editor/zoom-out
                          :ui/toggle-brackets
                          :go/search-in-page
                          :go/search
                          :go/backward
                          :go/forward
                          :search/re-index
                          :sidebar/open-today-page
                          :sidebar/clear])
     (with-meta {:before m/prevent-default-behavior}))

    :shortcut.handler/misc
    ;; always overrides the copy due to "mod+c mod+s"
    {:misc/copy              (:misc/copy              all-default-keyboard-shortcuts)}

    :shortcut.handler/global-non-editing-only
    (->
     (build-category-map [:go/home
                          :go/journals
                          :go/all-pages
                          :go/flashcards
                          :go/graph-view
                          :go/keyboard-shortcuts
                          :go/tomorrow
                          :go/next-journal
                          :go/prev-journal
                          :ui/toggle-document-mode
                          :ui/toggle-settings
                          :ui/toggle-right-sidebar
                          :ui/toggle-left-sidebar
                          :ui/toggle-help
                          :ui/toggle-theme
                          :ui/toggle-contents
                          :editor/open-file-in-default-app
                          :editor/open-file-in-directory
                          :editor/copy-current-file
                          :ui/toggle-wide-mode
                          :ui/select-theme-color
                          :ui/goto-plugins
                          :editor/toggle-open-blocks
                          :ui/toggle-cards
                          :git/commit])
     (with-meta {:before m/enable-when-not-editing-mode!}))}))

;; To add a new entry to this map, first add it here and then
;; a description for it in frontend.modules.shortcut.dicts/category
(def ^:large-vars/data-var category*
  "Full list of categories for docs purpose"
  {:shortcut.category/basics
   [:editor/new-block
    :editor/new-line
    :editor/indent
    :editor/outdent
    :editor/select-all-blocks
    :go/search
    :go/search-in-page
    :editor/undo
    :editor/redo
    :editor/copy
    :editor/copy-text
    :editor/cut]

   :shortcut.category/formatting
   [:editor/bold
    :editor/insert-link
    :editor/italics
    :editor/highlight]

   :shortcut.category/navigating
   [:editor/up
    :editor/down
    :editor/left
    :editor/right
    :editor/zoom-in
    :editor/zoom-out
    :editor/collapse-block-children
    :editor/expand-block-children
    :editor/toggle-open-blocks
    :go/backward
    :go/forward
    :go/home
    :go/journals
    :go/all-pages
    :go/graph-view
    :go/flashcards
    :go/tomorrow
    :go/next-journal
    :go/prev-journal
    :go/keyboard-shortcuts]

   :shortcut.category/block-editing
   [:editor/backspace
    :editor/delete
    :editor/indent
    :editor/outdent
    :editor/new-block
    :editor/new-line
    :editor/zoom-in
    :editor/zoom-out
    :editor/cycle-todo
    :editor/follow-link
    :editor/open-link-in-sidebar
    :editor/move-block-up
    :editor/move-block-down
    :editor/escape-editing]

   :shortcut.category/block-command-editing
   [:editor/backspace
    :editor/clear-block
    :editor/kill-line-before
    :editor/kill-line-after
    :editor/beginning-of-block
    :editor/end-of-block
    :editor/forward-word
    :editor/backward-word
    :editor/forward-kill-word
    :editor/backward-kill-word
    :editor/replace-block-reference-at-point
    :editor/copy-embed
    :editor/paste-text-in-one-block-at-point
    :editor/select-up
    :editor/select-down]

   :shortcut.category/block-selection
   [:editor/open-edit
    :editor/select-all-blocks
    :editor/select-block-up
    :editor/select-block-down
    :editor/delete-selection]

   :shortcut.category/toggle
   [:ui/toggle-help
    :editor/toggle-open-blocks
    :ui/toggle-wide-mode
    :ui/toggle-cards
    :ui/toggle-document-mode
    :ui/toggle-brackets
    :ui/toggle-theme
    :ui/toggle-left-sidebar
    :ui/toggle-right-sidebar
    :ui/toggle-settings
    :ui/toggle-contents]

   :shortcut.category/others
   [:pdf/previous-page
    :pdf/next-page
    :pdf/close
    :command/toggle-favorite
    :command/run
    :command-palette/toggle
    :graph/open
    :graph/remove
    :graph/add
    :graph/save
    :sidebar/clear
    :sidebar/open-today-page
    :search/re-index
    :editor/insert-youtube-timestamp
    :editor/open-file-in-default-app
    :editor/open-file-in-directory
    :auto-complete/prev
    :auto-complete/next
    :auto-complete/complete
    :auto-complete/shift-complete
    :auto-complete/open-link
    :date-picker/prev-day
    :date-picker/next-day
    :date-picker/prev-week
    :date-picker/next-week
    :date-picker/complete
    :git/commit]})

(let [category-maps {::category (set (keys category*))
                     ::dicts/category (set (keys dicts/category))}]
  (assert (= (::category category-maps) (::dicts/category category-maps))
          (str "Keys for category maps must be the same "
               (data/diff (::category category-maps) (::dicts/category category-maps)))))

(def category
  "Active list of categories for docs purpose"
  (update-vals
   category*
   (fn [v]
     (vec (remove #(:inactive (get all-default-keyboard-shortcuts %)) v)))))

(defn add-shortcut!
  [handler-id id shortcut-map]
  (swap! config assoc-in [handler-id id] shortcut-map))

(defn remove-shortcut!
  [handler-id id]
  (swap! config medley/dissoc-in [handler-id id]))
