(ns ^:bb-compatible logseq.db.rules
  "Datalog rules for use with logseq.db.schema")

(def ^:large-vars/data-var rules
  ;; rule "parent" is optimized for child node -> parent node nesting queries
  '[[(parent ?p ?c)
     [?c :block/parent ?p]]
    [(parent ?p ?c)
     [?c :block/parent ?t]
     (parent ?p ?t)]

  ;; rule "child" is optimized for child node -> parent node nesting queries
    [(child ?p ?c)
     [?c :block/parent ?p]]
    [(child ?p ?c)
     [?t :block/parent ?p]
     (child ?t ?c)]

  ;; rule "namespace" is optimized for child node -> node of upper namespace level nesting queries
    [(namespace ?p ?c)
     [?c :block/namespace ?p]]
    [(namespace ?p ?c)
     [?t :block/namespace ?p]
     (namespace ?t ?c)]

    ;; Select rules carefully, as it is critical for performance.
    ;; The rules have different clause order and resolving directions.
    ;; Clause order Reference:
    ;; https://docs.datomic.com/on-prem/query/query-executing.html#clause-order
    ;; Recursive optimization Reference:
    ;; https://stackoverflow.com/questions/42457136/recursive-datalog-queries-for-datomic-really-slow
    ;; Should optimize for query the decendents of a block
    ;; Quote:
    ;; My theory is that your rules are not written in a way that Datalog can optimize for this read pattern - probably resulting in a traversal of all the entities. I suggest to rewrite them as follows:
    ;; [[(ubersymbol ?c ?p)
    ;;   (?c :ml/parent ?p)]
    ;;  [(ubersymbol ?c ?p)
    ;;   ;; we bind a child of the ancestor, instead of a parent of the descendant
    ;;   (?c1 :ml/parent ?p)
    ;;   (ubersymbol ?c ?c1)]]

    ;; This way of writing the ruleset is optimized to find the descendants of some node. The way you originally wrote it is optimized to find the anscestors of some node.

    ;; from https://stackoverflow.com/questions/43784258/find-entities-whose-ref-to-many-attribute-contains-all-elements-of-input
    ;; Quote:
    ;; You're tackling the general problem of 'dynamic conjunction' in Datomic's Datalog.
    ;; Write a dynamic Datalog query which uses 2 negations and 1 disjunction or a recursive rule
    ;; Datalog has no direct way of expressing dynamic conjunction (logical AND / 'for all ...' / set intersection).
    ;; However, you can achieve it in pure Datalog by combining one disjunction
    ;; (logical OR / 'exists ...' / set union) and two negations, i.e
    ;; (For all ?g in ?Gs p(?e,?g)) <=> NOT(Exists ?g in ?Gs, such that NOT(p(?e, ?g)))

    ;; [(matches-all ?e ?a ?vs)
    ;;  [(first ?vs) ?v0]
    ;;  [?e ?a ?v0]
    ;;  (not-join [?e ?vs]
    ;;            [(identity ?vs) [?v ...]]
    ;;            (not-join [?e ?v]
    ;;                      [?e ?a ?v]))]
    ])

(def ^:large-vars/data-var query-dsl-rules
  "Rules used by frontend.db.query-dsl. The symbols ?b and ?p respectively refer
  to block and page. Do not alter them as they are programatically built by the
  query-dsl ns"
  {:page-property
   '[(page-property ?p ?key ?val)
     [?p :block/name]
     [?p :block/properties ?prop]
     [(get ?prop ?key) ?v]
     (or [(= ?v ?val)] [(contains? ?v ?val)])]

   :has-page-property
   '[(has-page-property ?p ?key)
     [?p :block/name]
     [?p :block/properties ?prop]
     [(get ?prop ?key)]]

   :task
   '[(task ?b ?markers)
     [?b :block/marker ?marker]
     [(contains? ?markers ?marker)]]

   :priority
   '[(priority ?b ?priorities)
     [?b :block/priority ?priority]
     [(contains? ?priorities ?priority)]]

   :page-tags
   '[(page-tags ?p ?tags)
     [?p :block/tags ?t]
     [?t :block/name ?tag]
     [(contains? ?tags ?tag)]]

   :all-page-tags
   '[(all-page-tags ?p)
     [_ :block/tags ?p]]

   :between
   '[(between ?b ?start ?end)
     [?b :block/page ?p]
     [?p :block/journal? true]
     [?p :block/journal-day ?d]
     [(>= ?d ?start)]
     [(<= ?d ?end)]]

   :has-property
   '[(has-property ?b ?prop)
     [?b :block/properties ?bp]
     [(missing? $ ?b :block/name)]
     [(get ?bp ?prop)]]

   :block-content
   '[(block-content ?b ?query)
     [?b :block/content ?content]
     [(clojure.string/includes? ?content ?query)]]

   :page
   '[(page ?b ?page-name)
     [?b :block/page ?bp]
     [?bp :block/name ?page-name]]

   :namespace
   '[(namespace ?p ?namespace)
     [?p :block/namespace ?parent]
     [?parent :block/name ?namespace]]

   :property
   '[(property ?b ?key ?val)
     [?b :block/properties ?prop]
     [(missing? $ ?b :block/name)]
     [(get ?prop ?key) ?v]
     (or-join [?v]
              [(= ?v ?val)]
              [(contains? ?v ?val)]
              ;; For integer pages that aren't strings
              (and
               [(str ?val) ?str-val]
               [(contains? ?v ?str-val)]))]

   :page-ref
   '[(page-ref ?b ?page-name)
     [?b :block/path-refs ?bp]
     [?bp :block/name ?page-name]]})
