(ns frontend.components.page
  (:require [rum.core :as rum]
            [frontend.util :as util :refer-macros [profile]]
            [frontend.tools.html-export :as html-export]
            [frontend.handler.file :as file]
            [frontend.handler.page :as page-handler]
            [frontend.handler.ui :as ui-handler]
            [frontend.handler.common :as common-handler]
            [frontend.handler.route :as route-handler]
            [frontend.handler.graph :as graph-handler]
            [frontend.handler.notification :as notification]
            [frontend.handler.editor :as editor-handler]
            [frontend.state :as state]
            [clojure.string :as string]
            [frontend.components.block :as block]
            [frontend.components.editor :as editor]
            [frontend.components.reference :as reference]
            [frontend.components.svg :as svg]
            [frontend.components.export :as export]
            [frontend.extensions.graph-2d :as graph-2d]
            [frontend.ui :as ui]
            [frontend.components.content :as content]
            [frontend.components.project :as project]
            [frontend.config :as config]
            [frontend.db :as db]
            [frontend.db.model :as model]
            [frontend.db.utils :as db-utils]
            [frontend.mixins :as mixins]
            [frontend.db-mixins :as db-mixins]
            [goog.dom :as gdom]
            [goog.object :as gobj]
            [frontend.utf8 :as utf8]
            [frontend.date :as date]
            [frontend.graph :as graph]
            [frontend.format.mldoc :as mldoc]
            [cljs-time.coerce :as tc]
            [cljs-time.core :as t]
            [cljs.pprint :as pprint]
            [frontend.context.i18n :as i18n]
            [reitit.frontend.easy :as rfe]
            [frontend.text :as text]
            [frontend.handler.block :as block-handler]))

(defn- get-page-name
  [state]
  (let [route-match (first (:rum/args state))]
    (get-in route-match [:parameters :path :name])))

(defn- get-blocks
  [repo page-name page-original-name block? block-id]
  (when page-name
    (if block?
      (db/get-block-and-children repo block-id)
      (do
        (page-handler/add-page-to-recent! repo page-original-name)
        (db/get-page-blocks repo page-name)))))

(rum/defc page-blocks-cp < rum/reactive
  db-mixins/query
  [repo page file-path page-name page-original-name encoded-page-name sidebar? journal? block? block-id format]
  (let [raw-page-blocks (get-blocks repo page-name page-original-name block? block-id)
        grouped-blocks-by-file (into {} (for [[k v] (db-utils/group-by-file raw-page-blocks)]
                                          [(:file/path (db-utils/entity (:db/id k))) v]))
        raw-page-blocks (get grouped-blocks-by-file file-path raw-page-blocks)
        page-blocks (block-handler/with-dummy-block raw-page-blocks format
                      (if (empty? raw-page-blocks)
                        (let [content (db/get-file repo file-path)]
                          {:block/page {:db/id (:db/id page)}
                           :block/file {:db/id (:db/id (:page/file page))}
                           :block/meta
                           (let [file-id (:db/id (:page/file page))]
                             {:start-pos (utf8/length (utf8/encode content))
                              :end-pos nil})}))
                      {:journal? journal?
                       :page-name page-name})
        start-level (or (:block/level (first page-blocks)) 1)
        hiccup-config {:id (if block? (str block-id) page-name)
                       :start-level start-level
                       :sidebar? sidebar?
                       :block? block?
                       :editor-box editor/box}
        hiccup-config (common-handler/config-with-document-mode hiccup-config)
        hiccup (block/->hiccup page-blocks hiccup-config {})]
    [:div.page-blocks-inner
     (when (and (seq grouped-blocks-by-file)
                (> (count grouped-blocks-by-file) 1))
       (ui/admonition
        :warning
        [:div.text-sm
         [:p.font-medium "Those pages have the same title, you might want to only keep one file."]
         [:ol
          (for [[file-path blocks] (into (sorted-map) grouped-blocks-by-file)]
            [:li [:a {:key file-path
                      :href (rfe/href :file {:path file-path})} file-path]])]]))

     (rum/with-key
       (content/content page-name
                        {:hiccup   hiccup
                         :sidebar? sidebar?})
       (str encoded-page-name "-hiccup"))]))

(defn contents-page
  [{:page/keys [name original-name file] :as contents}]
  (when-let [repo (state/get-current-repo)]
    (let [format (db/get-page-format name)
          file-path (:file/path file)]
      (page-blocks-cp repo contents file-path name original-name name true false false nil format))))

(defn presentation
  [repo page]
  [:a.opacity-30.hover:opacity-100.page-op
   {:title "Presentation mode (Powered by Reveal.js)"
    :style {:margin-top -2}
    :on-click (fn []
                (state/sidebar-add-block!
                 repo
                 (:db/id page)
                 :page-presentation
                 {:page page}))}
   svg/slideshow])

(rum/defc today-queries < rum/reactive
  [repo today? sidebar?]
  (when (and today? (not sidebar?))
    (let [queries (state/sub [:config repo :default-queries :journals])]
      (when (seq queries)
        [:div#today-queries.mt-10
         (for [{:keys [title] :as query} queries]
           (rum/with-key
             (block/custom-query {:start-level 2
                                  :attr {:class "mt-10"}
                                  :editor-box editor/box} query)
             (str repo "-custom-query-" (:query query))))]))))

(defn- delete-page!
  [page-name]
  (page-handler/delete! page-name
                        (fn []
                          (notification/show! (str "Page " page-name " was deleted successfully!")
                                              :success)))
  (state/close-modal!)
  (route-handler/redirect-to-home!))

(defn delete-page-dialog
  [page-name]
  (fn [close-fn]
    (rum/with-context [[t] i18n/*tongue-context*]
      [:div
       [:div.sm:flex.sm:items-start
        [:div.mx-auto.flex-shrink-0.flex.items-center.justify-center.h-12.w-12.rounded-full.bg-red-100.sm:mx-0.sm:h-10.sm:w-10
         [:svg.h-6.w-6.text-red-600
          {:stroke "currentColor", :view-box "0 0 24 24", :fill "none"}
          [:path
           {:d
            "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
            :stroke-width "2"
            :stroke-linejoin "round"
            :stroke-linecap "round"}]]]
        [:div.mt-3.text-center.sm:mt-0.sm:ml-4.sm:text-left
         [:h3#modal-headline.text-lg.leading-6.font-medium
          (t :page/delete-confirmation)]]]

       [:div.mt-5.sm:mt-4.sm:flex.sm:flex-row-reverse
        [:span.flex.w-full.rounded-md.shadow-sm.sm:ml-3.sm:w-auto
         [:button.inline-flex.justify-center.w-full.rounded-md.border.border-transparent.px-4.py-2.bg-indigo-600.text-base.leading-6.font-medium.text-white.shadow-sm.hover:bg-indigo-500.focus:outline-none.focus:border-indigo-700.focus:shadow-outline-indigo.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5
          {:type "button"
           :on-click (fn []
                       (delete-page! page-name))}
          (t :yes)]]
        [:span.mt-3.flex.w-full.rounded-md.shadow-sm.sm:mt-0.sm:w-auto
         [:button.inline-flex.justify-center.w-full.rounded-md.border.border-gray-300.px-4.py-2.bg-white.text-base.leading-6.font-medium.text-gray-700.shadow-sm.hover:text-gray-500.focus:outline-none.focus:border-blue-300.focus:shadow-outline-blue.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5
          {:type "button"
           :on-click close-fn}
          (t :cancel)]]]])))

(rum/defcs rename-page-dialog-inner <
  (rum/local "" ::input)
  [state title page-name close-fn]
  (let [input (get state ::input)]
    (rum/with-context [[t] i18n/*tongue-context*]
      [:div.w-full.sm:max-w-lg.sm:w-96
       [:div.sm:flex.sm:items-start
        [:div.mt-3.text-center.sm:mt-0.sm:text-left
         [:h3#modal-headline.text-lg.leading-6.font-medium
          (t :page/rename-to title)]]]

       [:input.form-input.block.w-full.sm:text-sm.sm:leading-5.my-2
        {:auto-focus true
         :on-change (fn [e]
                      (reset! input (util/evalue e)))}]

       [:div.mt-5.sm:mt-4.sm:flex.sm:flex-row-reverse
        [:span.flex.w-full.rounded-md.shadow-sm.sm:ml-3.sm:w-auto
         [:button.inline-flex.justify-center.w-full.rounded-md.border.border-transparent.px-4.py-2.bg-indigo-600.text-base.leading-6.font-medium.text-white.shadow-sm.hover:bg-indigo-500.focus:outline-none.focus:border-indigo-700.focus:shadow-outline-indigo.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5
          {:type "button"
           :on-click (fn []
                       (let [value (string/trim @input)]
                         (when-not (string/blank? value)
                           (page-handler/rename! page-name value)
                           (state/close-modal!))))}
          (t :submit)]]
        [:span.mt-3.flex.w-full.rounded-md.shadow-sm.sm:mt-0.sm:w-auto
         [:button.inline-flex.justify-center.w-full.rounded-md.border.border-gray-300.px-4.py-2.bg-white.text-base.leading-6.font-medium.text-gray-700.shadow-sm.hover:text-gray-500.focus:outline-none.focus:border-blue-300.focus:shadow-outline-blue.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5
          {:type "button"
           :on-click close-fn}
          (t :cancel)]]]])))

(defn rename-page-dialog
  [title page-name]
  (fn [close-fn]
    (rename-page-dialog-inner title page-name close-fn)))

(defn tagged-pages
  [repo tag]
  (let [pages (db/get-tag-pages repo tag)]
    (when (seq pages)
      [:div.references.mt-6.flex-1.flex-row
       [:div.content
        (ui/foldable
         [:h2.font-bold.opacity-50 (util/format "Pages tagged with \"%s\"" tag)]
         [:ul.mt-2
          (for [[original-name name] pages]
            [:li {:key (str "tagged-page-" name)}
             [:a {:href (rfe/href :page {:name name})}
              original-name]])] false)]])))

;; A page is just a logical block
(rum/defcs page < rum/reactive
  {:did-mount (fn [state]
                (ui-handler/scroll-and-highlight! state)
                state)
   :did-update (fn [state]
                 (ui-handler/scroll-and-highlight! state)
                 state)}
  [state {:keys [repo] :as option}]
  (let [current-repo (state/sub :git/current-repo)
        repo (or repo current-repo)
        path-page-name (or (get-page-name state)
                           (state/get-current-page))
        page-name (string/lower-case path-page-name)
        marker-page? (util/marker? page-name)
        priority-page? (contains? #{"a" "b" "c"} page-name)
        block? (util/uuid-string? page-name)
        block-id (and block? (uuid page-name))
        format (let [page (if block-id
                            (:page/name (:block/page (db/entity [:block/uuid block-id])))
                            page-name)]
                 (db/get-page-format page))
        journal? (db/journal-page? page-name)
        sidebar? (:sidebar? option)]
    (rum/with-context [[t] i18n/*tongue-context*]
      (cond
        priority-page?
        [:div.page
         [:h1.title
          (t :page/priority (string/upper-case page-name))]
         [:div.ml-2
          (reference/references page-name false true)]]

        marker-page?
        [:div.page
         [:h1.title
          (string/upper-case page-name)]
         [:div.ml-2
          (reference/references page-name true false)]]

        :else
        (let [route-page-name page-name
              page (if block?
                     (->> (:db/id (:block/page (db/entity repo [:block/uuid block-id])))
                          (db/entity repo))
                     (db/entity repo [:page/name page-name]))
              page (if page page (do
                                   (db/transact! repo [{:page/name page-name
                                                        :page/original-name path-page-name}])
                                   (db/entity repo [:page/name page-name])))
              {:keys [title] :as properties} (:page/properties page)
              page-name (:page/name page)
              page-original-name (:page/original-name page)
              title (or title page-original-name page-name)
              file (:page/file page)
              file-path (and (:db/id file) (:file/path (db/entity repo (:db/id file))))
              today? (and
                      journal?
                      (= page-name (string/lower-case (date/journal-name))))
              developer-mode? (state/sub [:ui/developer-mode?])
              published? (= "true" (:published properties))
              public? (= "true" (:public properties))]
          [:div.flex-1.page.relative (if (seq (:page/tags page))
                                       (let [page-names (model/get-page-names-by-ids (map :db/id (:page/tags page)))]
                                         {:data-page-tags (text/build-data-value page-names)})
                                       {})
           [:div.relative
            (when (and (not block?)
                       (not sidebar?)
                       (not config/publishing?))

              (let [contents? (= (string/lower-case (str page-name)) "contents")
                    links (->>
                           [(when-not contents?
                              {:title (t :page/add-to-contents)
                               :options {:on-click (fn [] (page-handler/handle-add-page-to-contents! page-original-name))}})

                            (when-not contents?
                              {:title (t :page/rename)
                               :options {:on-click #(state/set-modal! (rename-page-dialog title page-name))}})

                            (when (and file-path (util/electron?))
                              [{:title   (t :page/open-in-finder)
                                :options {:on-click #(js/window.apis.showItemInFolder file-path)}}
                               {:title (t :page/open-with-default-app)
                                :options {:on-click #(js/window.apis.openPath file-path)}}])

                            (when-not contents?
                              {:title (t :page/delete)
                               :options {:on-click #(state/set-modal! (delete-page-dialog page-name))}})

                            (when (state/get-current-page)
                              {:title (t :export)
                               :options {:on-click #(state/set-modal! export/export-page)}})

                            (when (util/electron?)
                              {:title  (t (if public? :page/make-private :page/make-public))
                               :options {:on-click
                                         (fn []
                                           (page-handler/update-public-attribute!
                                            page-name
                                            (if public? false true))
                                           (state/close-modal!))}})

                            (when file
                              {:title (t :page/re-index)
                               :options {:on-click (fn []
                                                     (file/re-index! file))}})

                            (when developer-mode?
                              {:title "(Dev) Show page data"
                               :options {:on-click (fn []
                                                     (let [page-data (with-out-str (pprint/pprint (db/pull (:db/id page))))]
                                                       (println page-data)
                                                       (notification/show!
                                                        [:div
                                                         [:pre.code page-data]
                                                         [:br]
                                                         (ui/button "Copy to clipboard"
                                                                    :on-click #(.writeText js/navigator.clipboard page-data))]
                                                        :success
                                                        false)))}})]
                           (flatten)
                           (remove nil?))]
                [:div {:style {:position "absolute"
                               :right 0
                               :top 20}}
                 [:div.flex.flex-row
                  [:a.opacity-30.hover:opacity-100.page-op.mr-2
                   {:on-click #(route-handler/go-to-search! :page)}
                   svg/search]
                  (when (not config/mobile?)
                    (presentation repo page))
                  (when (seq links)
                    (ui/dropdown-with-links
                     (fn [{:keys [toggle-fn]}]
                       [:a.opacity-30.hover:opacity-100
                        {:title "More options"
                         :on-click toggle-fn}
                        (svg/vertical-dots {:class (util/hiccup->class "opacity-30.hover:opacity-100.h-5.w-5")})])
                     links
                     {:modal-class (util/hiccup->class
                                    "origin-top-right.absolute.right-0.top-10.mt-2.rounded-md.shadow-lg.whitespace-no-wrap.dropdown-overflow-auto.page-drop-options")
                      :z-index 1}))]]))
            (when (and (not sidebar?)
                       (not block?))
              [:a {:on-click (fn [e]
                               (.preventDefault e)
                               (when (gobj/get e "shiftKey")
                                 (when-let [page (db/pull repo '[*] [:page/name page-name])]
                                   (state/sidebar-add-block!
                                    repo
                                    (:db/id page)
                                    :page
                                    {:page page}))))}
               [:h1.title {:style {:margin-left -2}}
                (if page-original-name
                  (if (and (string/includes? page-original-name "[[")
                           (string/includes? page-original-name "]]"))
                    (let [ast (mldoc/->edn page-original-name (mldoc/default-config format))]
                      (block/markup-element-cp {} (ffirst ast)))
                    page-original-name)
                  (or
                   page-name
                   path-page-name))]])
            [:div
             [:div.content
              (when (and file-path
                         (not sidebar?)
                         (not block?)
                         (not (state/hide-file?))
                         (not config/publishing?))
                [:div.text-sm.ml-1.mb-4.flex-1.inline-flex
                 {:key "page-file"}
                 [:span.opacity-50 {:style {:margin-top 2}} (t :file/file)]
                 [:a.bg-base-2.px-1.ml-1.mr-3 {:style {:border-radius 4
                                                       :word-break    "break-word"}
                                               :href  (rfe/href :file {:path file-path})}
                  file-path]])]

             (when (and repo (not block?))
               (let [alias (db/get-page-alias-names repo page-name)]
                 (when (seq alias)
                   [:div.text-sm.ml-1.mb-4 {:key "page-file"}
                    [:span.opacity-50 "Alias: "]
                    (for [item alias]
                      [:a.ml-1.mr-1 {:href (rfe/href :page {:name item})}
                       item])])))

             (when (and block? (not sidebar?))
               (let [config {:id "block-parent"
                             :block? true}]
                 [:div.mb-4
                  (block/block-parents config repo block-id format)]))

             ;; blocks
             (page-blocks-cp repo page file-path page-name page-original-name page-name sidebar? journal? block? block-id format)]]

           (when-not block?
             (today-queries repo today? sidebar?))

           (tagged-pages repo page-name)

           ;; referenced blocks
           [:div {:key "page-references"}
            (reference/references route-page-name false)]

           [:div {:key "page-unlinked-references"}
            (reference/unlinked-references route-page-name)]])))))

(defonce layout (atom [js/window.outerWidth js/window.outerHeight]))

(defonce graph-ref (atom nil))
(defonce show-journal? (atom false))
(rum/defcs global-graph < rum/reactive
  (mixins/event-mixin
   (fn [state]
     (mixins/listen state js/window "resize"
                    (fn [e]
                      (reset! layout [js/window.outerWidth js/window.outerHeight])))))
  [state]
  (let [theme (state/sub :ui/theme)
        sidebar-open? (state/sub :ui/sidebar-open?)
        [width height] (rum/react layout)
        dark? (= theme "dark")
        graph (graph-handler/build-global-graph theme (rum/react show-journal?))]
    (rum/with-context [[t] i18n/*tongue-context*]
      [:div.relative#global-graph
       (if (seq (:nodes graph))
         (graph-2d/graph
          (graph/build-graph-opts
           graph
           dark?
           {:width (if (and (> width 1280) sidebar-open?)
                     (- width 24 600)
                     (- width 24))
            :height height
            :ref (fn [v] (reset! graph-ref v))
            :ref-atom graph-ref}))
         [:div.ls-center.mt-20
          [:p.opacity-70.font-medium "Empty"]])
       [:div.absolute.top-15.left-5
        [:div.flex.flex-col
         [:a.text-sm.font-medium
          {:on-click (fn [_e]
                       (swap! show-journal? not))}
          (str (t :page/show-journals)
               (if @show-journal? " (ON)"))]]]])))

(rum/defc all-pages < rum/reactive
  ;; {:did-mount (fn [state]
  ;;               (let [current-repo (state/sub :git/current-repo)]
  ;;                 (js/setTimeout #(db/remove-orphaned-pages! current-repo) 0))
  ;;               state)}
  []
  (let [current-repo (state/sub :git/current-repo)]
    (rum/with-context [[t] i18n/*tongue-context*]
      [:div.flex-1
       [:h1.title (t :all-pages)]
       (when current-repo
         (let [pages (page-handler/get-pages-with-modified-at current-repo)]
           [:table.table-auto
            [:thead
             [:tr
              [:th (t :page/name)]
              [:th (t :file/last-modified-at)]]]
            [:tbody
             (for [page pages]
               [:tr {:key page}
                [:td [:a {:on-click (fn [e]
                                      (let [repo (state/get-current-repo)
                                            page (db/pull repo '[*] [:page/name (string/lower-case page)])]
                                        (when (gobj/get e "shiftKey")
                                          (state/sidebar-add-block!
                                           repo
                                           (:db/id page)
                                           :page
                                           {:page page}))))
                          :href (rfe/href :page {:name page})}
                      page]]
                [:td [:span.text-gray-500.text-sm
                      (t :file/no-data)]]])]]))])))

(rum/defcs new < rum/reactive
  (rum/local "" ::title)
  (mixins/event-mixin
   (fn [state]
     (mixins/on-enter state
                      :node (gdom/getElement "page-title")
                      :on-enter (fn []
                                  (let [title @(get state ::title)]
                                    (when-not (string/blank? title)
                                      (page-handler/create! title)))))))
  [state]
  (rum/with-context [[t] i18n/*tongue-context*]
    (let [title (get state ::title)]
      [:div#page-new.flex-1.flex-col {:style {:flex-wrap "wrap"}}
       [:div.mt-10.mb-2 {:style {:font-size "1.5rem"}}
        (t :page/new-title)]
       [:input#page-title.focus:outline-none.ml-1
        {:style {:border "none"
                 :font-size "1.8rem"
                 :max-width 300}
         :auto-focus true
         :auto-complete "off"
         :on-change (fn [e]
                      (reset! title (util/evalue e)))}]])))
