(ns metabase.lib.common
  (:require
   [metabase.lib.dispatch :as lib.dispatch]
   [metabase.lib.hierarchy :as lib.hierarchy]
   [metabase.lib.ident :as lib.ident]
   [metabase.lib.options :as lib.options]
   [metabase.lib.ref :as lib.ref]
   [metabase.lib.schema.common :as schema.common]
   [metabase.util :as u]
   [metabase.util.malli :as mu])
  #?(:cljs (:require-macros [metabase.lib.common])))
(comment lib.options/keep-me
         mu/keep-me)
(mu/defn external-op :- [:maybe ::schema.common/external-op]
  "Convert the internal operator `clause` to the external format."
  [[operator options :as clause]]
  (when clause
    {:lib/type :lib/external-op
     :operator (cond-> operator
                 (keyword? operator) name)
     :options  options
     :args     (subvec clause 2)}))

Ensures that clause arguments are properly unwrapped

(defmulti ->op-arg
  {:arglists '([x])}
  lib.dispatch/dispatch-value
  :hierarchy lib.hierarchy/hierarchy)
(defmethod ->op-arg :default
  [x]
  (if (and (vector? x)
           (keyword? (first x)))
    ;; MBQL clause
    (mapv ->op-arg x)
    ;; Something else - just return it
    x))
(defmethod ->op-arg :dispatch-type/sequential
  [xs]
  (mapv ->op-arg xs))
(defmethod ->op-arg :dispatch-type/regex
  [regex]
  (u/regex->str regex))
(defmethod ->op-arg :metadata/column
  [field-metadata]
  (lib.ref/ref field-metadata))
(defmethod ->op-arg :metadata/metric
  [metric-def]
  (lib.ref/ref metric-def))
(defmethod ->op-arg :metadata/segment
  [segment-def]
  (lib.ref/ref segment-def))
(defmethod ->op-arg :lib/external-op
  [{:keys [operator options args] :or {options {}}}]
  (->op-arg (lib.options/ensure-uuid (into [(keyword operator) options]
                                           (map ->op-arg)
                                           args))))

Given an MBQL clause, ensure that it has an :ident in its options.

(defn ensure-ident
  [clause]
  (lib.options/update-options clause update :ident #(or % (lib.ident/random-ident))))

Given two clauses, preserve the original's :ident on replacement.

(defn preserve-ident-of
  [replacement original]
  (if-let [ident (lib.options/ident original)]
    (lib.options/update-options replacement assoc :ident ident)
    replacement))

Impl for [[defop]].

(defn defop-create
  [op-name args]
  (into [op-name {:lib/uuid (str (random-uuid))}]
        (map ->op-arg)
        args))

Defines a clause creating function with given args. Calling the clause without query and stage produces a fn that can be resolved later.

#?(:clj
   (defmacro defop
     [op-name & argvecs]
     {:pre [(symbol? op-name)
            (every? vector? argvecs) (every? #(every? symbol? %) argvecs)
            (every? #(not-any? #{'query 'stage-number} %) argvecs)]}
     `(mu/defn ~op-name :- ~(keyword "mbql.clause" (name op-name))
        ~(format "Create a standalone clause of type `%s`." (name op-name))
        ~@(for [argvec argvecs
                :let [arglist-expr (if (contains? (set argvec) '&)
                                     (cons `list* (remove #{'&} argvec))
                                     argvec)]]
            `([~@argvec]
              (defop-create ~(keyword op-name) ~arglist-expr))))))