(ns asciidoc.types
  (:require
   [clojure.spec.alpha :as s]
   [spec-dict.main :refer [->opt dict]]))

(defn- make-block-arguments [req opt]
  (let [args (dict req
                   (->opt opt)
                   ^:opt {:a map?})]
    (if (empty? req)
      (s/? (s/and map? args))
      (s/& (s/? map?) args))))

(defn- make-block
  [& {:keys [name required-args optional-args content-spec]
      :or {required-args {}
           optional-args {}
           content-spec (s/* ::block)}}]
  (s/and seqable?
         (s/cat :context (s/and keyword? #{name})
                :arguments (make-block-arguments required-args optional-args)
                :content content-spec)
         ;; Set arguments to {} if none were provided.
         (s/conformer #(if (contains? % :arguments)
                         %
                         (assoc % :arguments {})))))

(s/def ::document
  (make-block :name :document
              :required-args {:title string?}
              :optional-args {:author string?
                              :version string?}))

(s/def ::p
  (make-block :name :p
              :content-spec (s/* string?)))

(s/def ::description
  (make-block :name :description
              :required-opts {:described string?}))

(s/def ::section
  (make-block :name :section
              :required-args {:title string?}))

(s/def ::<>
  (make-block :name :<>
              :content (s/coll-of ::block)))

(s/def ::block
  (s/and (s/or :document ::document
               :section ::section
               :description ::description
               :<> ::<>
               :p ::p)
         ;; `s/or` provides tagging that `make-block` already does.
         (s/conformer #(nth % 1))))
