(ns metabase.xrays.automagic-dashboards.names
  (:require
   [clojure.string :as str]
   [java-time.api :as t]
   [metabase.legacy-mbql.normalize :as mbql.normalize]
   [metabase.lib.util.match :as lib.util.match]
   [metabase.query-processor.util :as qp.util]
   [metabase.util.date-2 :as u.date]
   [metabase.util.i18n :refer [deferred-tru tru]]
   [metabase.xrays.automagic-dashboards.util :as magic.util]
   [toucan2.core :as t2]))

TODO - rename "minumum" to "minimum". Note that there are internationalization string implications here so make sure to do a thorough find and replace on this.

(def ^:private op->name
  {:sum       (deferred-tru "sum")
   :avg       (deferred-tru "average")
   :min       (deferred-tru "minumum")
   :max       (deferred-tru "maximum")
   :count     (deferred-tru "number")
   :distinct  (deferred-tru "distinct count")
   :stddev    (deferred-tru "standard deviation")
   :cum-count (deferred-tru "cumulative count")
   :cum-sum   (deferred-tru "cumulative sum")})

Return the name of the metric or name by describing it.

(defn metric-name
  [[op & args :as metric]]
  (cond
    (magic.util/adhoc-metric? metric) (-> op qp.util/normalize-token op->name)
    (magic.util/saved-metric? metric) (->> args first (t2/select-one :model/LegacyMetric :id) :name)
    :else                             (second args)))

Join a sequence as [1 2 3 4] to "1, 2, 3 and 4"

(defn- join-enumeration
  [xs]
  (if (next xs)
    (tru "{0} and {1}" (str/join ", " (butlast xs)) (last xs))
    (first xs)))

Return the (display) name of the source of a given root object.

(def ^{:arglists '([root])} source-name
  (comp (some-fn :display_name :name) :source))

Return a description for the metric.

(defn metric->description
  [root aggregation-clause]
  (join-enumeration
   (for [metric (if (sequential? (first aggregation-clause))
                  aggregation-clause
                  [aggregation-clause])]
     (if (magic.util/adhoc-metric? metric)
       (tru "{0} of {1}" (metric-name metric) (or (some->> metric
                                                           second
                                                           (magic.util/->field root)
                                                           :display_name)
                                                  (source-name root)))
       (metric-name metric)))))

Generate a description for the question.

(defn question-description
  [root question]
  (let [aggregations (->> (get-in question [:dataset_query :query :aggregation])
                          (metric->description root))
        dimensions   (->> (get-in question [:dataset_query :query :breakout])
                          (mapcat magic.util/collect-field-references)
                          (map (comp :display_name
                                     (partial magic.util/->field root)))
                          join-enumeration)]
    (if dimensions
      (tru "{0} by {1}" aggregations dimensions)
      aggregations)))
(defmulti
  ^{:private true
    :arglists '([fieldset [op & args]])}
  humanize-filter-value (fn [_ [op & _args]]
                          (qp.util/normalize-token op)))
(def ^:private unit-name (comp {:minute-of-hour  (deferred-tru "minute")
                                :hour-of-day     (deferred-tru "hour")
                                :day-of-week     (deferred-tru "day of week")
                                :day-of-month    (deferred-tru "day of month")
                                :day-of-year     (deferred-tru "day of year")
                                :week-of-year    (deferred-tru "week")
                                :month-of-year   (deferred-tru "month")
                                :quarter-of-year (deferred-tru "quarter")
                                :year            (deferred-tru "year")}
                               qp.util/normalize-token))

Turn a field reference into a field.

(defn item-reference->field
  [root [item-type :as item-reference]]
  (case item-type
    (:field "field") (let [normalized-field-reference (mbql.normalize/normalize item-reference)
                           temporal-unit              (lib.util.match/match-one normalized-field-reference
                                                        [:field _ (opts :guard :temporal-unit)]
                                                        (:temporal-unit opts))
                           {:keys [display_name] :as field-record} (cond-> (->> normalized-field-reference
                                                                                magic.util/collect-field-references
                                                                                first
                                                                                (magic.util/->field root))
                                                                     temporal-unit
                                                                     (assoc :unit temporal-unit))
                           item-name                  (cond->> display_name
                                                        (some-> temporal-unit u.date/extract-units)
                                                        (tru "{0} of {1}" (unit-name temporal-unit)))]
                       (assoc field-record :item-name item-name))
    (:expression "expression") {:item-name (second item-reference)}
    {:item-name "item"}))

Determine the right name to display from an individual humanized item.

(defn item-name
  ([root [field-type potential-name :as field-reference]]
   (case field-type
     (:field "field") (->> field-reference (item-reference->field root) item-name)
     (:expression "expression") potential-name
     "item"))
  ([{:keys [display_name unit] :as _field}]
   (cond->> display_name
     (some-> unit u.date/extract-units) (tru "{0} of {1}" (unit-name unit)))))

Add appropriate pluralization suffixes for integer numbers.

(defn pluralize
  [x]
  ;; the `int` cast here is to fix performance warnings if `*warn-on-reflection*` is enabled
  (case (int (mod x 10))
    1 (tru "{0}st" x)
    2 (tru "{0}nd" x)
    3 (tru "{0}rd" x)
    (tru "{0}th" x)))

Convert a time data type into a human friendly string.

(defn humanize-datetime
  [t-str unit]
  (let [dt (u.date/parse t-str)]
    (case unit
      :second          (tru "at {0}" (t/format "h:mm:ss a, MMMM d, YYYY" dt))
      :minute          (tru "at {0}" (t/format "h:mm a, MMMM d, YYYY" dt))
      :hour            (tru "at {0}" (t/format "h a, MMMM d, YYYY" dt))
      :day             (tru "on {0}" (t/format "MMMM d, YYYY" dt))
      :week            (tru "in {0} week - {1}"
                            (pluralize (u.date/extract dt :week-of-year))
                            (str (u.date/extract dt :year)))
      :month           (tru "in {0}" (t/format "MMMM YYYY" dt))
      :quarter         (tru "in Q{0} - {1}"
                            (u.date/extract dt :quarter-of-year)
                            (str (u.date/extract dt :year)))
      :year            (t/format "YYYY" dt)
      :day-of-week     (t/format "EEEE" dt)
      :hour-of-day     (tru "at {0}" (t/format "h a" dt))
      :month-of-year   (t/format "MMMM" dt)
      :quarter-of-year (tru "Q{0}" (u.date/extract dt :quarter-of-year))
      (:minute-of-hour
       :day-of-month
       :day-of-year
       :week-of-year)  (u.date/extract dt unit))))
(defmethod humanize-filter-value :=
  [root [_ field-reference value]]
  (let [{:keys [item-name effective_type base_type unit]} (item-reference->field root field-reference)]
    (if (isa? (or effective_type base_type) :type/Temporal)
      (tru "{0} is {1}" item-name (humanize-datetime value unit))
      (tru "{0} is {1}" item-name value))))
(defmethod humanize-filter-value :>=
  [root [_ field-reference value]]
  (let [{:keys [item-name effective_type base_type unit]} (item-reference->field root field-reference)]
    (if (isa? (or effective_type base_type) :type/Temporal)
      (tru "{0} is not before {1}" item-name (humanize-datetime value unit))
      (tru "{0} is at least {1}" item-name value))))
(defmethod humanize-filter-value :>
  [root [_ field-reference value]]
  (let [{:keys [item-name effective_type base_type unit]} (item-reference->field root field-reference)]
    (if (isa? (or effective_type base_type) :type/Temporal)
      (tru "{0} is after {1}" item-name (humanize-datetime value unit))
      (tru "{0} is greater than {1}" item-name value))))
(defmethod humanize-filter-value :<=
  [root [_ field-reference value]]
  (let [{:keys [item-name effective_type base_type unit]} (item-reference->field root field-reference)]
    (if (isa? (or effective_type base_type) :type/Temporal)
      (tru "{0} is not after {1}" item-name (humanize-datetime value unit))
      (tru "{0} is no more than {1}" item-name value))))
(defmethod humanize-filter-value :<
  [root [_ field-reference value]]
  (let [{:keys [item-name effective_type base_type unit]} (item-reference->field root field-reference)]
    (if (isa? (or effective_type base_type) :type/Temporal)
      (tru "{0} is before {1}" item-name (humanize-datetime value unit))
      (tru "{0} is less than {1}" item-name value))))
(defmethod humanize-filter-value :between
  [root [_ field-reference min-value max-value]]
  (tru "{0} is between {1} and {2}" (item-name root field-reference) min-value max-value))
(defmethod humanize-filter-value :inside
  [root [_ lat-reference lon-reference lat-max lon-min lat-min lon-max]]
  (tru "{0} is between {1} and {2}; and {3} is between {4} and {5}"
       (item-name root lon-reference) lon-min lon-max
       (item-name root lat-reference) lat-min lat-max))
(defmethod humanize-filter-value :and
  [root [_ & clauses]]
  (->> clauses
       (map (partial humanize-filter-value root))
       join-enumeration))
(defmethod humanize-filter-value :default
  [root [_ field-reference value]]
  (let [{:keys [item-name effective_type base_type unit]} (item-reference->field root field-reference)]
    (if (isa? (or effective_type base_type) :type/Temporal)
      (tru "{0} relates to {1}" item-name (humanize-datetime value unit))
      (tru "{0} relates to {1}" item-name value))))

Return a cell title given a root object and a cell query.

(defn cell-title
  [root cell-query]
  (str/join " " [(if-let [aggregation (get-in root [:entity :dataset_query :query :aggregation])]
                   (metric->description root aggregation)
                   (:full-name root))
                 (tru "where {0}" (humanize-filter-value root cell-query))]))