(ns frontend.db
  (:require [frontend.namespaces :refer-macros [import-vars]]
            [frontend.db.conn :as conn]
            [frontend.db.utils :as db-utils]
            [frontend.db.model]
            [frontend.db.react]
            [frontend.db.query-custom]
            [frontend.db.query-react]
            [frontend.util :as util]
            [datascript.core :as d]
            [frontend.state :as state]
            [promesa.core :as p]
            [frontend.db-schema :as db-schema]
            [frontend.db.default :as default-db]
            [clojure.core.async :as async]
            [frontend.idb :as idb]))

(import-vars
 [frontend.db.conn
  ;; TODO: remove later
  conns
  get-repo-path
  datascript-db
  datascript-files-db
  remove-db!
  remove-files-db!
  get-conn
  get-files-conn
  me-tx
  remove-conn!]

 [frontend.db.utils
  date->int db->json db->edn-str db->string get-max-tx-id get-tx-id
  group-by-page seq-flatten sort-by-pos
  string->db with-repo

  entity pull pull-many transact! get-key-value]

 [frontend.db.model
  add-properties! block-and-children-transform blocks-count blocks-count-cache clean-export!  cloned? delete-blocks
  delete-file! delete-file-blocks! delete-file-pages! delete-file-tx delete-files delete-pages-by-files
  filter-only-public-pages-and-blocks get-all-block-contents get-all-tagged-pages
  get-all-templates get-block-and-children get-block-and-children-no-cache get-block-by-uuid get-block-children
  get-block-children-ids get-block-content get-block-file get-block-immediate-children get-block-page
  get-block-page-end-pos get-block-parent get-block-parents get-block-referenced-blocks get-block-refs-count
  get-blocks-by-priority get-blocks-contents get-collapsed-blocks get-custom-css
  get-date-scheduled-or-deadlines get-db-type get-empty-pages get-file get-file-after-blocks get-file-after-blocks-meta
  get-file-blocks get-file-contents get-file-last-modified-at get-file-no-sub get-file-page get-file-page-id
  get-file-pages get-files get-files-blocks get-files-full get-files-that-referenced-page get-journals-length
  get-latest-journals get-marker-blocks get-matched-blocks get-page get-page-alias get-page-alias-names get-page-blocks get-page-linked-refs-refed-pages
  get-page-blocks-count get-page-blocks-no-cache get-page-file get-page-format get-page-name get-page-properties
  get-page-properties-content get-page-referenced-blocks get-page-referenced-pages get-page-unlinked-references
  get-pages get-pages-relation get-pages-that-mentioned-page get-public-pages get-tag-pages
  journal-page? local-native-fs? mark-repo-as-cloned! page-alias-set page-blocks-transform pull-block
  set-file-last-modified-at! transact-files-db! with-block-refs-count get-modified-pages page-empty? get-alias-source-page
  set-file-content!]

 [frontend.db.react
  get-current-marker get-current-page get-current-priority get-handler-keys set-key-value
  transact-react! remove-key! remove-q! remove-query-component! add-q! add-query-component! clear-query-state!
  clear-query-state-without-refs-and-embeds! get-block-blocks-cache-atom get-page-blocks-cache-atom kv q
  query-state query-components query-entity-in-component remove-custom-query! set-new-result! sub-key-value]

 [frontend.db.query-custom
  custom-query]

 [frontend.db.query-react
  react-query custom-query-result-transform]
 )

;; persisting DBs between page reloads
(defn persist! [repo]
  (let [file-key (datascript-files-db repo)
        non-file-key (datascript-db repo)
        files-conn (get-files-conn repo)
        file-db (when files-conn (d/db files-conn))
        non-file-conn (get-conn repo false)
        non-file-db (when non-file-conn (d/db non-file-conn))
        file-db-str (if file-db (db->string file-db) "")
        non-file-db-str (if non-file-db (db->string non-file-db) "")]
    (p/let [_ (idb/set-batch! [{:key file-key :value file-db-str}
                               {:key non-file-key :value non-file-db-str}])]
      (state/set-last-persist-transact-id! repo true (get-max-tx-id file-db))
      (state/set-last-persist-transact-id! repo false (get-max-tx-id non-file-db)))))

(defonce persistent-jobs (atom {}))

(defn clear-repo-persistent-job!
  [repo]
  (when-let [old-job (get @persistent-jobs repo)]
    (js/clearTimeout old-job)))

(defn- persist-if-idle!
  [repo]
  (clear-repo-persistent-job! repo)
  (let [job (js/setTimeout
             (fn []
               (if (and (state/input-idle? repo)
                        (state/db-idle? repo))
                 (do
                   (persist! repo)
                   ;; (state/set-db-persisted! repo true)
)
                 (persist-if-idle! repo)))
             3000)]
    (swap! persistent-jobs assoc repo job)))

;; only save when user's idle

;; TODO: pass as a parameter
(defonce *sync-search-indice-f (atom nil))
(defn- repo-listen-to-tx!
  [repo conn files-db?]
  (d/listen! conn :persistence
             (fn [tx-report]
               (when-not (util/electron?)
                (let [tx-id (get-tx-id tx-report)]
                  (state/set-last-transact-time! repo (util/time-ms))
                  ;; (state/persist-transaction! repo files-db? tx-id (:tx-data tx-report))
                  (persist-if-idle! repo)))

               ;; rebuild search indices
               (when-not files-db?
                 (let [data (:tx-data tx-report)
                       datoms (filter
                               (fn [datom]
                                 (contains? #{:page/name :block/content} (:a datom)))
                               data)]
                   (when-let [f @*sync-search-indice-f]
                     (f datoms)))))))

(defn- listen-and-persist!
  [repo]
  (when-let [conn (get-files-conn repo)]
    (repo-listen-to-tx! repo conn true))
  (when-let [conn (get-conn repo false)]
    (repo-listen-to-tx! repo conn false)))

(defn start-db-conn!
  ([me repo]
   (start-db-conn! me repo {}))
  ([me repo option]
   (conn/start! me repo
                (assoc option
                       :listen-handler listen-and-persist!))))

(defn restore!
  [{:keys [repos] :as me} restore-config-handler]
  (let [logged? (:name me)]
    (doall
     (for [{:keys [url]} repos]
       (let [repo url
             db-name (datascript-files-db repo)
             db-conn (d/create-conn db-schema/files-db-schema)]
         (swap! conns assoc db-name db-conn)
         (p/let [stored (idb/get-item db-name)
                 _ (when stored
                     (let [stored-db (string->db stored)
                           attached-db (d/db-with stored-db
                                                  [(me-tx stored-db me)])]
                       (conn/reset-conn! db-conn attached-db)))
                 db-name (datascript-db repo)
                 db-conn (d/create-conn db-schema/schema)
                 _ (d/transact! db-conn [{:schema/version db-schema/version}])
                 _ (swap! conns assoc db-name db-conn)
                 stored (idb/get-item db-name)
                 _ (if stored
                     (let [stored-db (string->db stored)
                           attached-db (d/db-with stored-db (concat
                                                             [(me-tx stored-db me)]
                                                             default-db/built-in-pages))]
                       (conn/reset-conn! db-conn attached-db))
                     (when logged?
                       (d/transact! db-conn [(me-tx (d/db db-conn) me)])))]
           (restore-config-handler repo)
           (listen-and-persist! repo)))))))

(defn run-batch-txs!
  []
  (let [chan (state/get-db-batch-txs-chan)]
    (async/go-loop []
      (let [f (async/<! chan)]
        (f))
      (recur))
    chan))
