Ported from frontend/src/metabase-lib/types/utils/isa.js

(ns metabase.lib.types.isa
  (:refer-clojure :exclude [isa? any? boolean? number? string? integer?])
  (:require
   [medley.core :as m]
   [metabase.lib.types.constants :as lib.types.constants]
   [metabase.lib.util :as lib.util]
   [metabase.types]))
(comment metabase.types/keep-me)

Decide if _column is a subtype of the type denoted by the keyword type-kw. Both effective and semantic types are taken into account.

(defn ^:export isa?
  [{:keys [effective-type base-type semantic-type] :as _column} type-kw]
  (or (clojure.core/isa? (or effective-type base-type) type-kw)
      (clojure.core/isa? semantic-type type-kw)))

Returns if column is of category category. The possible categories are the keys in [[metabase.lib.types.constants/type-hierarchies]].

(defn ^:export field-type?
  [category column]
  (let [type-definition (lib.types.constants/type-hierarchies category)
        column          (cond-> column
                          (and (map? column)
                               (not (:effective-type column)))
                          (assoc :effective-type (:base-type column)))]
    (cond
      (nil? column) false
      ;; check field types
      (some (fn [[type-type types]]
              (and (#{:effective-type :semantic-type} type-type)
                   (some #(clojure.core/isa? (type-type column) %) types)))
            type-definition)
      true
      ;; recursively check if it's not an excluded type
      (some #(field-type? % column) (:exclude type-definition))
      false
      ;; recursively check if it's an included type
      (some #(field-type? % column) (:include type-definition))
      true
      :else false)))

Return the category column belongs to. The possible categories are the keys in [[metabase.lib.types.constants/type-hierarchies]].

(defn ^:export field-type
  [column]
  (m/find-first #(field-type? % column)
                [::lib.types.constants/temporal
                 ::lib.types.constants/location
                 ::lib.types.constants/coordinate
                 ::lib.types.constants/foreign_key
                 ::lib.types.constants/primary_key
                 ::lib.types.constants/boolean
                 ::lib.types.constants/string
                 ::lib.types.constants/string_like
                 ::lib.types.constants/number]))

Is column of a temporal type?

(defn ^:export temporal?
  [column]
  (field-type? ::lib.types.constants/temporal column))

Is column of a numeric type?

(defn ^:export numeric?
  [column]
  (field-type? ::lib.types.constants/number column))

Is column of a boolean type?

(defn ^:export boolean?
  [column]
  (field-type? ::lib.types.constants/boolean column))

Is column of a string type?

(defn ^:export string?
  [column]
  (field-type? ::lib.types.constants/string column))

Is column of a temporal type?

(defn ^:export string-like?
  [column]
  (field-type? ::lib.types.constants/string_like column))

Is column of a temporal type?

(defn ^:export string-or-string-like?
  [column]
  (or (string? column) (string-like? column)))

Is column of a summable type?

(defn ^:export summable?
  [column]
  (field-type? ::lib.types.constants/summable column))

Is column of a scope type?

(defn ^:export scope?
  [column]
  (field-type? ::lib.types.constants/scope column))

Is column of a categorical type?

(defn ^:export category?
  [column]
  (field-type? ::lib.types.constants/category column))

Is column of a location type?

(defn ^:export location?
  [column]
  (field-type? ::lib.types.constants/location column))

Is column a description?

(defn ^:export description?
  [column]
  (clojure.core/isa? (:semantic-type column) :type/Description))

Is column a dimension?

(defn ^:export dimension?
  [column]
  (and column
       (not= (:lib/source column) :source/aggregations)
       (not (description? column))))

Is column a metric?

(defn ^:export metric?
  [column]
  (and (not= (:lib/source column) :source/breakouts)
       (summable? column)))

Is column a foreign-key?

(defn ^:export foreign-key?
  [column]
  (clojure.core/isa? (:semantic-type column) :type/FK))

Is column a primary-key?

(defn ^:export primary-key?
  [column]
  (clojure.core/isa? (:semantic-type column) :type/PK))

Is column an entity name?

(defn ^:export entity-name?
  [column]
  (clojure.core/isa? (:semantic-type column) :type/Name))

Is column a title column?

(defn ^:export title?
  [column]
  (clojure.core/isa? (:semantic-type column) :type/Title))

Is column a serialized JSON column?

(defn ^:export json?
  [column]
  (clojure.core/isa? (:semantic-type column) :type/SerializedJSON))

Is column a serialized XML column?

(defn ^:export xml?
  [column]
  (clojure.core/isa? (:semantic-type column) :type/XML))

Is column serialized structured data? (eg. JSON, XML)

(defn ^:export structured?
  [column]
  (clojure.core/isa? (:semantic-type column) :type/Structured))

Is this _column whatever (including nil)?

(defn ^:export any?
  [_column]
  true)

Is column a numneric base type?

(defn ^:export numeric-base-type?
  [column]
  (clojure.core/isa? (:effective-type column) :type/Number))

Is column a date or datetime?

(defn ^:export date-or-datetime?
  [column]
  (or (clojure.core/isa? (:effective-type column) :type/Date)
      (clojure.core/isa? (:effective-type column) :type/DateTime)))

Is column a date without time?

(defn ^:export date-without-time?
  [column]
  (clojure.core/isa? (:effective-type column) :type/Date))

Is column a creation timestamp column?

(defn ^:export creation-timestamp?
  [column]
  (clojure.core/isa? (:semantic-type column) :type/CreationTimestamp))

Is column a creation date column?

(defn ^:export creation-date?
  [column]
  (clojure.core/isa? (:semantic-type column) :type/CreationDate))

Is column a creation time column?

(defn ^:export creation-time?
  [column]
  (clojure.core/isa? (:semantic-type column) :type/CreationTime))

Is column a number without some other semantic type (like ZIP code)?

ZipCode, ID, etc derive from Number but should not be formatted as numbers

(defn ^:export number?
  [column]
  (and (numeric-base-type? column)
       (let [semantic-type (:semantic-type column)]
         (or (nil? semantic-type)
             ;; this is a precaution, :type/Number is not a semantic type
             (clojure.core/isa? semantic-type :type/Number)))))

Is column a integer column?

(defn ^:export integer?
  [column]
  (field-type? ::lib.types.constants/integer column))

Is column a time?

(defn ^:export time?
  [column]
  (clojure.core/isa? (:effective-type column) :type/Time))

Is column an address?

(defn ^:export address?
  [column]
  (clojure.core/isa? (:semantic-type column) :type/Address))

Is column a city?

(defn ^:export city?
  [column]
  (clojure.core/isa? (:semantic-type column) :type/City))

Is column a state?

(defn ^:export state?
  [column]
  (clojure.core/isa? (:semantic-type column) :type/State))

Is column a zip-code?

(defn ^:export zip-code?
  [column]
  (clojure.core/isa? (:semantic-type column) :type/ZipCode))

Is column a country?

(defn ^:export country?
  [column]
  (clojure.core/isa? (:semantic-type column) :type/Country))

Is column a coordinate?

(defn ^:export coordinate?
  [column]
  (clojure.core/isa? (:semantic-type column) :type/Coordinate))

Is column a latitude?

(defn ^:export latitude?
  [column]
  (clojure.core/isa? (:semantic-type column) :type/Latitude))

Is column a longitude?

(defn ^:export longitude?
  [column]
  (clojure.core/isa? (:semantic-type column) :type/Longitude))

Is column a currency?

(defn ^:export currency?
  [column]
  (clojure.core/isa? (:semantic-type column) :type/Currency))

Is column a comment?

(defn ^:export comment?
  [column]
  (clojure.core/isa? (:semantic-type column) :type/Comment))

Is column an ID?

(defn ^:export id?
  [column]
  (or (clojure.core/isa? (:semantic-type column) :type/FK)
      (clojure.core/isa? (:semantic-type column) :type/PK)))

Is column a URL?

(defn ^:export URL?
  [column]
  (clojure.core/isa? (:semantic-type column) :type/URL))

Is column an email?

(defn ^:export email?
  [column]
  (clojure.core/isa? (:semantic-type column) :type/Email))

Is column an avatar URL?

(defn ^:export avatar-URL?
  [column]
  (clojure.core/isa? (:semantic-type column) :type/AvatarURL))

Is column an image URL?

(defn ^:export image-URL?
  [column]
  (clojure.core/isa? (:semantic-type column) :type/ImageURL))

Does the collection columns contain both a latitude and a longitude column?

(defn ^:export has-latitude-and-longitude?
  [columns]
  (every? #(some % columns) [latitude? longitude?]))

Return a prdicate for checking if a column is a primary key.

(defn ^:export primary-key-pred
  [table-id]
  (fn primary-key-pred-for-table-id [column]
    (let [pk? (primary-key? column)]
      ;; comment from isa.js:
      ;; > FIXME: columns of nested questions at this moment miss table_id value
      ;; > which makes it impossible to match them with their tables that are nested cards
      (if (lib.util/legacy-string-table-id->card-id table-id)
        pk?
        (and pk? (= (:table-id column) table-id))))))

Is this column one that we should show a search widget for (to search its values) in the QB filter UI? If so, we can give it a has-field-values value of :search.

TODO -- This stuff should probably use the constants in [[metabase.lib.types.constants]], however this logic isn't supposed to include things with semantic type = Category which the ::string constant define there includes.

(defn searchable?
  [{:keys [base-type effective-type]}]
  ;; For the time being we will consider something to be "searchable" if it's a text Field since the `starts-with`
  ;; filter that powers the search queries (see [[metabase.api.field/search-values]]) doesn't work on anything else
  (let [column-type (or effective-type base-type)]
    (or (clojure.core/isa? column-type :type/Text)
        (clojure.core/isa? column-type :type/TextLike))))

Given two CLJS :metadata/columns returns true if src-column is a valid source to use for filtering dst-column.

That's the case if both are from the same family (strings, numbers, temporal) or if the src-column [[isa?]] subtype of dst-column.

(defn valid-filter-for?
  [src-column dst-column]
  (or
   (and (string? src-column)   (string? dst-column))
   (and (number? src-column)   (number? dst-column))
   (and (temporal? src-column) (temporal? dst-column))
   (clojure.core/isa? (:base-type src-column) (:base-type dst-column))))