(ns frontend.handler.paste
  (:require [frontend.state :as state]
            [frontend.db :as db]
            [frontend.format.block :as block]
            [logseq.graph-parser.util :as gp-util]
            [logseq.graph-parser.mldoc :as gp-mldoc]
            [logseq.graph-parser.block :as gp-block]
            [clojure.string :as string]
            [frontend.util :as util]
            [frontend.handler.editor :as editor-handler]
            [frontend.extensions.html-parser :as html-parser]
            [goog.object :as gobj]
            [frontend.mobile.util :as mobile-util]
            [frontend.util.thingatpt :as thingatpt]
            ["/frontend/utils" :as utils]
            [frontend.commands :as commands]
            [cljs.core.match :refer [match]]
            [logseq.graph-parser.text :as text]
            [frontend.handler.notification :as notification]
            [frontend.util.text :as text-util]
            [frontend.format.mldoc :as mldoc]
            [lambdaisland.glogi :as log]))

(defn- paste-text-parseable
  [format text]
  (when-let [editing-block (state/get-edit-block)]
    (let [page-id (:db/id (:block/page editing-block))
          blocks (block/extract-blocks
                  (mldoc/->edn text (gp-mldoc/default-config format)) text true format)
          blocks' (gp-block/with-parent-and-left page-id blocks)]
      (editor-handler/paste-blocks blocks' {}))))

(defn- paste-segmented-text
  [format text]
  (let [paragraphs (string/split text #"(?:\r?\n){2,}")
        updated-paragraphs
        (string/join "\n"
                     (mapv (fn [p] (->> (string/trim p)
                                        ((fn [p]
                                           (if (util/safe-re-find (if (= format :org)
                                                                    #"\s*\*+\s+"
                                                                    #"\s*-\s+") p)
                                             p
                                             (str (if (= format :org) "* " "- ") p))))))
                           paragraphs))]
    (paste-text-parseable format updated-paragraphs)))

(defn- wrap-macro-url
  [url]
  (cond
    (boolean (text-util/get-matched-video url))
    (util/format "{{video %s}}" url)

    (string/includes? url "twitter.com")
    (util/format "{{twitter %s}}" url)

    :else
    (do
      (notification/show! (util/format "No macro is available for %s" url) :warning)
      nil)))

(defn- paste-copied-blocks-or-text
  [text e html]
  (util/stop e)
  (let [copied-blocks (state/get-copied-blocks)
        input (state/get-input)
        text (string/replace text "\r\n" "\n") ;; Fix for Windows platform
        internal-paste? (and
                         (seq (:copy/blocks copied-blocks))
                         ;; not copied from the external clipboard
                         (= text (:copy/content copied-blocks)))]
    (if internal-paste?
      (let [blocks (:copy/blocks copied-blocks)]
        (when (seq blocks)
          (editor-handler/paste-blocks blocks {})))
      (let [{:keys [value]} (editor-handler/get-selection-and-format)]
        (cond
          (and (or (gp-util/url? text)
                   (and value (gp-util/url? (string/trim value))))
               (not (string/blank? (util/get-selected-text))))
          (editor-handler/html-link-format! text)

          (and (text/block-ref? text)
               (editor-handler/wrapped-by? input "((" "))"))
          (commands/simple-insert! (state/get-edit-input-id) (text/get-block-ref text) nil)

          :else
          ;; from external
          (let [format (or (db/get-page-format (state/get-current-page)) :markdown)
                html-text (let [result (when-not (string/blank? html)
                                         (try
                                           (html-parser/convert format html)
                                           (catch :default e
                                             (log/error :exception e)
                                             nil)))]
                            (if (string/blank? result) nil result))
                text (or html-text text)
                input-id (state/get-edit-input-id)
                replace-text-f (fn []
                                 (commands/delete-selection! input-id)
                                 (commands/simple-insert! input-id text nil))]
            (match [format
                    (nil? (util/safe-re-find #"(?m)^\s*(?:[-+*]|#+)\s+" text))
                    (nil? (util/safe-re-find #"(?m)^\s*\*+\s+" text))
                    (nil? (util/safe-re-find #"(?:\r?\n){2,}" text))]
              [:markdown false _ _]
              (paste-text-parseable format text)

              [:org _ false _]
              (paste-text-parseable format text)

              [:markdown true _ false]
              (paste-segmented-text format text)

              [:markdown true _ true]
              (replace-text-f)

              [:org _ true false]
              (paste-segmented-text format text)

              [:org _ true true]
              (replace-text-f))))))))

(defn paste-text-in-one-block-at-point
  []
  (utils/getClipText
   (fn [clipboard-data]
     (when-let [_ (state/get-input)]
       (let [data (or (when (gp-util/url? clipboard-data)
                        (wrap-macro-url clipboard-data))
                      clipboard-data)]
         (editor-handler/insert data true))))
   (fn [error]
     (js/console.error error))))

(defn- paste-text-or-blocks-aux
  [input e text html]
  (if (or (thingatpt/markdown-src-at-point input)
          (thingatpt/org-admonition&src-at-point input))
    (when-not (mobile-util/native-ios?)
      (util/stop e)
      (paste-text-in-one-block-at-point))
    (paste-copied-blocks-or-text text e html)))

(defn editor-on-paste!
  ([id]
   (editor-on-paste! id false))
  ([id raw-paste?]
   (fn [e]
     (state/set-state! :editor/on-paste? true)
     (let [input (state/get-input)]
       (if raw-paste?
        (utils/getClipText
         (fn [clipboard-data]
           (when-let [_ (state/get-input)]
             (let [text (or (when (gp-util/url? clipboard-data)
                              (wrap-macro-url clipboard-data))
                            clipboard-data)]
               (paste-text-or-blocks-aux input e text nil))))
         (fn [error]
           (js/console.error error)))
        (let [clipboard-data (gobj/get e "clipboardData")
              html (when-not raw-paste? (.getData clipboard-data "text/html"))
              text (.getData clipboard-data "text")]
          (if-not (string/blank? text)
            (paste-text-or-blocks-aux input e text html)
            (when id
              (let [_handled
                    (let [clipboard-data (gobj/get e "clipboardData")
                          files (.-files clipboard-data)]
                      (when-let [file (first files)]
                        (when-let [block (state/get-edit-block)]
                          (editor-handler/upload-asset id #js[file] (:block/format block)
                                                       editor-handler/*asset-uploading? true))))]
                (util/stop e))))))))))
