

(define-library (web page-helpers)
  (export
    special-form-category-map
    extract-define-kwargs
    categorize-special-forms
    build-ref-items-with-href
    build-reference-data
    build-attr-detail
    build-header-detail
    build-event-detail
    build-component-source
    build-bundle-analysis
    build-routing-analysis
    build-affinity-analysis)
  (begin

(define special-form-category-map {:defmacro "Functions & Components" :for-each "Higher-Order Forms" :defpage "Domain Definitions" :let "Binding" :filter "Higher-Order Forms" :shift "Continuations" :and "Control Flow" :set! "Binding" :map-indexed "Higher-Order Forms" :dynamic-wind "Guards" :reduce "Higher-Order Forms" :cond "Control Flow" :defquery "Domain Definitions" :-> "Sequencing & Threading" :let* "Binding" :define "Binding" :reset "Continuations" :case "Control Flow" :do "Sequencing & Threading" :map "Higher-Order Forms" :some "Higher-Order Forms" :letrec "Binding" :if "Control Flow" :quote "Quoting" :every? "Higher-Order Forms" :defhandler "Domain Definitions" :fn "Functions & Components" :defstyle "Domain Definitions" :lambda "Functions & Components" :defaction "Domain Definitions" :or "Control Flow" :defcomp "Functions & Components" :quasiquote "Quoting" :when "Control Flow" :begin "Sequencing & Threading"})

(define
  extract-define-kwargs
  (fn
    ((expr :as list))
    (let
      ((result {}) (items (slice expr 2)) (n (len items)))
      (for-each
        (fn
          ((idx :as number))
          (when
            (and (< (+ idx 1) n) (= (type-of (nth items idx)) "keyword"))
            (let
              ((key (keyword-name (nth items idx)))
                (val (nth items (+ idx 1))))
              (dict-set!
                result
                key
                (if
                  (= (type-of val) "list")
                  (str "(" (join " " (map serialize val)) ")")
                  (str val))))))
        (range 0 n))
      result)))

(define
  categorize-special-forms
  (fn
    ((parsed-exprs :as list))
    (let
      ((categories {}))
      (for-each
        (fn
          (expr)
          (when
            (and
              (= (type-of expr) "list")
              (>= (len expr) 2)
              (= (type-of (first expr)) "symbol")
              (= (symbol-name (first expr)) "define-special-form"))
            (let
              ((name (nth expr 1))
                (kwargs (extract-define-kwargs expr))
                (category
                  (or
                    (get kwargs "category")
                    (get special-form-category-map name)
                    "Other")))
              (when
                (not (has-key? categories category))
                (dict-set! categories category (list)))
              (append! (get categories category) {:doc (or (get kwargs "doc") "") :example (or (get kwargs "example") "") :tail-position (or (get kwargs "tail-position") "") :syntax (or (get kwargs "syntax") "") :name name}))))
        parsed-exprs)
      categories)))

(define
  build-ref-items-with-href
  (fn
    ((items :as list)
      (base-path :as string)
      (detail-keys :as list)
      (n-fields :as number))
    (map
      (fn
        ((item :as list))
        (if
          (= n-fields 3)
          (let
            ((name (nth item 0))
              (field2 (nth item 1))
              (field3 (nth item 2)))
            {:href (if (and field3 (some (fn ((k :as string)) (= k name)) detail-keys)) (str base-path name) nil) :exists field3 :desc field2 :name name})
          (let ((name (nth item 0)) (desc (nth item 1))) {:href (if (some (fn ((k :as string)) (= k name)) detail-keys) (str base-path name) nil) :desc desc :name name})))
      items)))

(define
  build-reference-data
  (fn
    ((slug :as string) (raw-data :as dict) (detail-keys :as list))
    (case
      slug
      "attributes"
      {:req-attrs (build-ref-items-with-href (get raw-data "req-attrs") "/geography/hypermedia/reference/attributes/" detail-keys 3) :beh-attrs (build-ref-items-with-href (get raw-data "beh-attrs") "/geography/hypermedia/reference/attributes/" detail-keys 3) :uniq-attrs (build-ref-items-with-href (get raw-data "uniq-attrs") "/geography/hypermedia/reference/attributes/" detail-keys 3)}
      "headers"
      {:req-headers (build-ref-items-with-href (get raw-data "req-headers") "/geography/hypermedia/reference/headers/" detail-keys 3) :resp-headers (build-ref-items-with-href (get raw-data "resp-headers") "/geography/hypermedia/reference/headers/" detail-keys 3)}
      "events"
      {:events-list (build-ref-items-with-href (get raw-data "events-list") "/geography/hypermedia/reference/events/" detail-keys 2)}
      "js-api"
      {:js-api-list (map (fn ((item :as list)) {:desc (nth item 1) :name (nth item 0)}) (get raw-data "js-api-list"))}
      :else {:req-attrs (build-ref-items-with-href (get raw-data "req-attrs") "/geography/hypermedia/reference/attributes/" detail-keys 3) :beh-attrs (build-ref-items-with-href (get raw-data "beh-attrs") "/geography/hypermedia/reference/attributes/" detail-keys 3) :uniq-attrs (build-ref-items-with-href (get raw-data "uniq-attrs") "/geography/hypermedia/reference/attributes/" detail-keys 3)})))

(define
  build-attr-detail
  (fn ((slug :as string) detail) (if (nil? detail) {:attr-not-found true} {:attr-handler (get detail "handler") :attr-title slug :attr-example (get detail "example") :attr-not-found nil :attr-description (get detail "description") :attr-demo (get detail "demo") :attr-wire-id (if (has-key? detail "handler") (str "ref-wire-" (replace (replace slug ":" "-") "*" "star")) nil)})))

(define
  build-header-detail
  (fn ((slug :as string) detail) (if (nil? detail) {:header-not-found true} {:header-description (get detail "description") :header-demo (get detail "demo") :header-not-found nil :header-title slug :header-example (get detail "example") :header-direction (get detail "direction")})))

(define
  build-event-detail
  (fn ((slug :as string) detail) (if (nil? detail) {:event-not-found true} {:event-example (get detail "example") :event-demo (get detail "demo") :event-description (get detail "description") :event-not-found nil :event-title slug})))

(define
  build-component-source
  (fn
    ((comp-data :as dict))
    (let
      ((comp-type (get comp-data "type"))
        (name (get comp-data "name"))
        (params (get comp-data "params"))
        (has-children (get comp-data "has-children"))
        (body-sx (get comp-data "body-sx"))
        (affinity (get comp-data "affinity")))
      (if
        (= comp-type "not-found")
        (str ";; component " name " not found")
        (let
          ((param-strs (if (empty? params) (if has-children (list "&rest" "children") (list)) (if has-children (append (cons "&key" params) (list "&rest" "children")) (cons "&key" params))))
            (params-sx (str "(" (join " " param-strs) ")"))
            (form-name (if (= comp-type "island") "defisland" "defcomp"))
            (affinity-str
              (if
                (and
                  (= comp-type "component")
                  (not (nil? affinity))
                  (not (= affinity "auto")))
                (str " :affinity " affinity)
                "")))
          (str
            "("
            form-name
            " "
            name
            " "
            params-sx
            affinity-str
            "\n  "
            body-sx
            ")"))))))

(define
  build-bundle-analysis
  (fn
    ((pages-raw :as list)
      (components-raw :as dict)
      (total-components :as number)
      (total-macros :as number)
      (pure-count :as number)
      (io-count :as number))
    (let
      ((pages-data (list)))
      (for-each
        (fn
          ((page :as dict))
          (let
            ((needed-names (get page "needed-names"))
              (n (len needed-names))
              (pct
                (if
                  (> total-components 0)
                  (round (* (/ n total-components) 100))
                  0))
              (savings (- 100 pct))
              (pure-in-page 0)
              (io-in-page 0)
              (page-io-refs (list))
              (comp-details (list)))
            (for-each
              (fn
                ((comp-name :as string))
                (let
                  ((info (get components-raw comp-name)))
                  (when
                    (not (nil? info))
                    (if
                      (get info "is-pure")
                      (set! pure-in-page (+ pure-in-page 1))
                      (do
                        (set! io-in-page (+ io-in-page 1))
                        (for-each
                          (fn
                            ((ref :as string))
                            (when
                              (not
                                (some
                                  (fn ((r :as string)) (= r ref))
                                  page-io-refs))
                              (append! page-io-refs ref)))
                          (or (get info "io-refs") (list)))))
                    (append! comp-details {:io-refs (or (get info "io-refs") (list)) :render-target (get info "render-target") :deps (or (get info "deps") (list)) :source (get info "source") :name comp-name :is-pure (get info "is-pure") :affinity (get info "affinity")}))))
              needed-names)
            (append! pages-data {:pure-in-page pure-in-page :io-refs (len page-io-refs) :direct (get page "direct") :needed n :io-in-page io-in-page :components comp-details :savings savings :pct pct :path (get page "path") :name (get page "name")})))
        pages-raw)
      {:total-macros total-macros :pages pages-data :io-count io-count :pure-count pure-count :total-components total-components})))

(define
  build-routing-analysis
  (fn
    ((pages-raw :as list))
    (let
      ((pages-data (list)) (client-count 0) (server-count 0))
      (for-each
        (fn
          ((page :as dict))
          (let
            ((has-data (not (nil? (get page "data"))))
              (content-src (or (get page "content-src") ""))
              (mode nil)
              (reason ""))
            (cond
              has-data
              (do
                (set! mode "server")
                (set! reason "Has :data expression — needs server IO")
                (set! server-count (+ server-count 1)))
              (empty? content-src)
              (do
                (set! mode "server")
                (set! reason "No content expression")
                (set! server-count (+ server-count 1)))
              :else (do
                (set! mode "client")
                (set! client-count (+ client-count 1))))
            (append! pages-data {:reason reason :mode mode :content-expr (if (> (len content-src) 80) (str (slice content-src 0 80) "...") content-src) :has-data has-data :path (get page "path") :name (get page "name")})))
        pages-raw)
      {:pages pages-data :total-pages (+ client-count server-count) :server-count server-count :client-count client-count})))

(define
  build-affinity-analysis
  (fn ((demo-components :as list) (page-plans :as list)) {:components demo-components :page-plans page-plans}))


)) ;; end define-library

;; Re-export to global namespace for backward compatibility
(import (web page-helpers))
