   (:require [schema.core :as s]
             [clojure.string :as str]
             [medley.core :as medley]
+            [io.aviso.toolchest.macros :refer [cond-let]]
             [io.aviso.toolchest.collections :refer [pretty-print]]
             [clojure.tools.logging :as l]))
 


  :meta
  : The merged meta-data of the endpoint function, with defaults from the enclosing namespace,
    and including :function as the string name of the endpoint."
  [swagger-options swagger-object routing-entry]
  (l/debug "merging" routing-entry)
  (let [path-str (-> routing-entry :path path->str)
        method-str (-> routing-entry :method name)
        pio-constructor (:path-item-object-constructor swagger-options default-path-item-object-constructor)]
    (pio-constructor swagger-options swagger-object routing-entry
       ;; Ignoring :all right now.
       [:paths path-str method-str])

    )
  )

(defn- routing-entry->map
  [[method path _ endpoint-meta]]
  {:method method
   :path   path
   :meta   endpoint-meta})


(defn tap'
  [message data]
  (l/debugf "%s: %s" message (pretty-print data))
  data)


(defn construct-swagger-object
  "Constructs the root Swagger object from the Rook options, swagger options, and the routing table

    (str/join "/")
    (str "/")))

;; Revisit order of parameters in these for ease of ->> threading; should swagger-object always be the last?

(defn default-path-params-injector
  "Identifies each path parameter and adds a value for it to the swagger-object at the path defined by params-key."
  [swagger-options swagger-object routing-entry params-key]
  (let [path-ids (->> routing-entry :path (filter keyword?))
        reducer (fn [so path-id]
                  (update-in so params-key
                             conj {:name     path-id
                                   :in       :path
                                   :required :true}))]
    (reduce reducer swagger-object path-ids)))

(defn default-query-params-injector
  "Identifies each query parameter via the :query-schema metadata and injects it."
  [swagger-options swagger-object routing-entry params-key]
  (if-let [schema (-> routing-entry :meta :query-schema)]
    ;; Assumes that the schema is a map.
    (let [reducer (fn [so [key-id key-schema]]
                    (cond-let
                      ;; Just ignore these, they are a convinience for the caller ("be generous in what you accept").
                      (= s/Any key-id)
                      so

                      [[k required] (cond (s/optional-key? key-id)
                                          [(.k key-id) false]

                                          (s/required-key? key-id)
                                          [(.k key-id) true]

                                          (keyword? key-id)
                                          [key-id true]

                                          :else
                                          (throw (ex-info (format "No idea what to do with query schema key %s." key-id)
                                                          {:routing-entry routing-entry
                                                           :key-id        key-id
                                                           :key-schema    key-schema})))]

                      :else
                      (update-in so params-key
                                 conj {:name     k
                                       :in       :query
                                       :required required})))]
      (reduce reducer swagger-object schema))
    ; no :query-schema
    swagger-object))

(defn schema->swagger-schema
  [swagger-options schema]
  ;; To be done!
  (medley/remove-vals nil?
                      {:required   []
                       :description (-> schema meta :doc)
                       :properties {}}))

(defn to-schema-reference
  "Converts a schema to a schema reference; the schema must be named.  Returns a tuple
  of the possibly updated swagger object and a string reference to the schema within the swagger object
  (as a path string)."
  ;; TODO: return the {:schema {"$ref" ...}} map instead?
  [swagger-options swagger-object schema]
  (cond-let
    [schema-name (s/schema-name schema)
     schema-ns (-> schema meta :ns)]

    (or (nil? schema-name) (nil? schema-ns))
    (throw (ex-info "Unable to create a Swagger schema from an anonymous Prismatic schema."
                    schema))

    ;; Avoid forward slash in the swagger name, as that's problematic.
    [swagger-name (str schema-ns \: schema-name)
     swagger-reference (str "#/definitions/" swagger-name)
     swagger-schema (get-in swagger-object [:definitions swagger-name])]

    (some? swagger-schema)
    [swagger-object swagger-reference]

    [new-schema (schema->swagger-schema swagger-options schema)
     swagger-object' (assoc-in swagger-object [:definitions swagger-name] new-schema)]

    :else
    [swagger-object' swagger-reference]))

(defn default-body-params-injector
  "Uses the :body-schema metadata to identify the body params."
  [swagger-options swagger-object routing-entry paths-key]
  (if-let [schema (-> routing-entry :meta :body-schema)]

    ;; Assumption: it's a map and named
    (let [[swagger-object' schema-reference] (to-schema-reference swagger-options swagger-object schema)]
      (update-in swagger-object' paths-key
                 conj {:name     :request-body
                       :in       :body
                       :required true
                       :schema   {"$ref" schema-reference}}))
    ; no schema:
    swagger-object))

(defn default-path-item-object-injector
  "Injects a PathItemObject based on a routing entry.

  The paths-key is a coordinate into the swagger-object where the PathItemObject should be created.

  Returns the modified swagger-object. "
  [swagger-options swagger-object routing-entry paths-key]
  (let [{endpoint-meta :meta} routing-entry
        swagger-meta (:swagger endpoint-meta)
        description (or (:description swagger-meta)
                        (:doc endpoint-meta))
        summary (:summary swagger-meta)
        path-params-injector (:path-params-injector swagger-options default-path-params-injector)
        query-params-injector (:query-params-injector swagger-options default-query-params-injector)
        body-params-injector (:body-params-injector swagger-options default-body-params-injector)
        params-key (concat paths-key ["parameters"])]
    (as-> swagger-object %
          (assoc-in % paths-key
                    (medley/remove-vals nil?
                                        {:description  description
                                         :summary      summary
                                         :operation-id (:function endpoint-meta)}))
          (path-params-injector swagger-options % routing-entry params-key)
          (query-params-injector swagger-options % routing-entry params-key)
          (body-params-injector swagger-options % routing-entry params-key)
          (update-in % params-key #(sort-by :name %)))))

(defn default-route-injector
  "The default route converter.  The swagger-options may contain an override of this function.

  swagger-options
