(ns sydnix-cli.mangen
  (:require
   [asciidoc.render]
   [asciidoc.types]
   [babashka.fs :as fs]
   [babashka.process :as p]
   [clojure.spec.alpha :as s]
   [clojure.string :as str]
   [sydnix-cli.cli-table :refer [*cli-table]])
  (:import
   [java.io BufferedReader PipedReader PipedWriter]))

(defn- format-p [s]
  (-> s
      (str/replace #"^ +" " ")
      (str/replace #"\n +" "\n")))

(defn- docs-for-command [command-spec]
  (let [command (cons "sydnix" (:cmds command-spec))]
    [:document {:title (str (str/join "-" command)
                            "(1)")
                :author "Madeleine Sydney Ślaga"
                :a {:doctype "manpage"
                    :manmanual "SYDNIX"
                    :mansource "SYDNIX"}}
     [:section {:title "Name"}
      [:p (format "%s - %s"
                  (str/join " " command)
                  (:desc command-spec))]]
     [:section {:title "Synopsis"}
      [:p (format "%s [_option_…]"
                  (str/join " " command))]]
     (concat [:section {:title "Options"}]
             (for [[opt opt-spec] (:spec command-spec)]
               [:description {:described (format "*--%s%s*"
                                                 (name opt)
                                                 (if-let [ref (:ref opt-spec)]
                                                   (str "=" ref)
                                                   ""))}
                [:p (format-p (:desc opt-spec))]]))]))

(defn render-docs-for-command [command-spec]
  (try (asciidoc.render/render (docs-for-command command-spec))
       (catch Exception e
         (prn e)
         (s/explain :asciidoc.types/document command-spec)
         (throw e))))

(defn- find-satisfying [p xs]
  (->> xs (filter p) first))

(defn find-dispatched [dispatch]
  (find-satisfying #(= dispatch (:cmds %)) (deref *cli-table)))

(defn- find-adoc-processor
  "Looks for a suitable AsciiDoc processor.  In order of precedence, it will try
  `$ADOC_PROCESSOR`, then `asciidoctor`."
  [& {:keys [throw?]}]
  (letfn [(suitable [x]
            (when (and x (fs/executable? x))
              x))]
    (or (suitable (System/getenv "ADOC_PROCESSOR"))
        (suitable (fs/which "asciidoctor"))
        (when throw?
          (throw (ex-info "Can't find a suitable AsciiDoc processor" {}))))))

(defn asciidoctor
  "Shell out to AsciiDoctor (`$ADOC_PROCESSOR`, actually) to create ROFF markup
  for man pages."
  [& {:keys [asciidoctor-bin in out err]
      :or {asciidoctor-bin (find-adoc-processor :throw? true)
           in *in*
           out *out*
           err *err*}}]
  (p/shell {:in in :out out :err err}
           asciidoctor-bin "-o" "-" "-b" "manpage" "-"))

(defn with-pipe [with-out with-in]
  (let [out (PipedWriter.)
        in (PipedReader. out)]
    (future (with-out out)
            (.close out))
    (with-in (BufferedReader. in))))

(defn write-man-for-command
  [command-spec & {:keys [out err]
                   :or {out *out*
                        err *err*}}]
  (with-pipe
    (fn [adoc->]
      (binding [*out* adoc->]
        (render-docs-for-command command-spec)))
    (fn [->adoc]
      (asciidoctor :in ->adoc :out out :err err))))
