;; ==========================================================================
;; cssx.sx — On-demand CSS style dictionary
;;
;; Resolves keyword atoms (e.g. :flex, :gap-4, :hover:bg-sky-200) into
;; StyleValue objects with content-addressed class names. CSS rules are
;; injected into the document on first use.
;;
;; The style dictionary is loaded from a JSON blob (typically served
;; inline in a <script type="text/sx-styles"> tag) containing:
;;   a — atom → CSS declarations map
;;   v — pseudo-variant → CSS pseudo-selector map
;;   b — responsive breakpoint → media query map
;;   k — keyframe name → @keyframes rule map
;;   p — arbitrary patterns: [[regex, template], ...]
;;   c — child selector prefixes: ["space-x-", "space-y-", ...]
;;
;; Depends on:
;;   render.sx — StyleValue type
;; ==========================================================================


;; --------------------------------------------------------------------------
;; State — populated by load-style-dict
;; --------------------------------------------------------------------------

(define _style-atoms (dict))
(define _pseudo-variants (dict))
(define _responsive-breakpoints (dict))
(define _style-keyframes (dict))
(define _arbitrary-patterns (list))
(define _child-selector-prefixes (list))
(define _style-cache (dict))
(define _injected-styles (dict))


;; --------------------------------------------------------------------------
;; Load style dictionary from parsed JSON data
;; --------------------------------------------------------------------------

(define load-style-dict
  (fn (data)
    (set! _style-atoms (or (get data "a") (dict)))
    (set! _pseudo-variants (or (get data "v") (dict)))
    (set! _responsive-breakpoints (or (get data "b") (dict)))
    (set! _style-keyframes (or (get data "k") (dict)))
    (set! _child-selector-prefixes (or (get data "c") (list)))
    ;; Compile arbitrary patterns from [regex, template] pairs
    (set! _arbitrary-patterns
      (map
        (fn (pair)
          (dict "re" (compile-regex (str "^" (first pair) "$"))
                "tmpl" (nth pair 1)))
        (or (get data "p") (list))))
    ;; Clear cache on reload
    (set! _style-cache (dict))))


;; --------------------------------------------------------------------------
;; Variant splitting
;; --------------------------------------------------------------------------

(define split-variant
  (fn (atom)
    ;; Parse variant prefixes: "sm:hover:bg-sky-200" → ["sm:hover", "bg-sky-200"]
    ;; Returns [variant, base] where variant is nil for no variant.

    ;; Check responsive prefix first
    (let ((result nil))
      (for-each
        (fn (bp)
          (when (nil? result)
            (let ((prefix (str bp ":")))
              (when (starts-with? atom prefix)
                (let ((rest-atom (slice atom (len prefix))))
                  ;; Check for compound variant (sm:hover:...)
                  (let ((inner-match nil))
                    (for-each
                      (fn (pv)
                        (when (nil? inner-match)
                          (let ((inner-prefix (str pv ":")))
                            (when (starts-with? rest-atom inner-prefix)
                              (set! inner-match
                                (list (str bp ":" pv)
                                      (slice rest-atom (len inner-prefix))))))))
                      (keys _pseudo-variants))
                    (set! result
                      (or inner-match (list bp rest-atom)))))))))
        (keys _responsive-breakpoints))

      (when (nil? result)
        ;; Check pseudo variants
        (for-each
          (fn (pv)
            (when (nil? result)
              (let ((prefix (str pv ":")))
                (when (starts-with? atom prefix)
                  (set! result (list pv (slice atom (len prefix))))))))
          (keys _pseudo-variants)))

      (or result (list nil atom)))))


;; --------------------------------------------------------------------------
;; Atom resolution
;; --------------------------------------------------------------------------

(define resolve-atom
  (fn (atom)
    ;; Look up atom → CSS declarations string, or nil
    (let ((decls (dict-get _style-atoms atom)))
      (if (not (nil? decls))
        decls
        ;; Dynamic keyframes: animate-{name}
        (if (starts-with? atom "animate-")
          (let ((kf-name (slice atom 8)))
            (if (dict-has? _style-keyframes kf-name)
              (str "animation-name:" kf-name)
              nil))
          ;; Try arbitrary patterns
          (let ((match-result nil))
            (for-each
              (fn (pat)
                (when (nil? match-result)
                  (let ((m (regex-match (get pat "re") atom)))
                    (when m
                      (set! match-result
                        (regex-replace-groups (get pat "tmpl") m))))))
              _arbitrary-patterns)
            match-result))))))


;; --------------------------------------------------------------------------
;; Child selector detection
;; --------------------------------------------------------------------------

(define is-child-selector-atom?
  (fn (atom)
    (some
      (fn (prefix) (starts-with? atom prefix))
      _child-selector-prefixes)))


;; --------------------------------------------------------------------------
;; FNV-1a 32-bit hash → 6 hex chars
;; --------------------------------------------------------------------------

(define hash-style
  (fn (input)
    ;; FNV-1a 32-bit hash for content-addressed class names
    (fnv1a-hash input)))


;; --------------------------------------------------------------------------
;; Full style resolution pipeline
;; --------------------------------------------------------------------------

(define resolve-style
  (fn (atoms)
    ;; Resolve a list of atom strings into a StyleValue.
    ;; Uses content-addressed caching.
    (let ((key (join "\0" atoms)))
      (let ((cached (dict-get _style-cache key)))
        (if (not (nil? cached))
          cached
          ;; Resolve each atom
          (let ((base-decls (list))
                (media-rules (list))
                (pseudo-rules (list))
                (kf-needed (list)))
            (for-each
              (fn (a)
                (when a
                  (let ((clean (if (starts-with? a ":") (slice a 1) a)))
                    (let ((parts (split-variant clean)))
                      (let ((variant (first parts))
                            (base (nth parts 1))
                            (decls (resolve-atom base)))
                        (when decls
                          ;; Check keyframes
                          (when (starts-with? base "animate-")
                            (let ((kf-name (slice base 8)))
                              (when (dict-has? _style-keyframes kf-name)
                                (append! kf-needed
                                  (list kf-name (dict-get _style-keyframes kf-name))))))

                          (cond
                            (nil? variant)
                              (append! base-decls decls)

                            (dict-has? _responsive-breakpoints variant)
                              (append! media-rules
                                (list (dict-get _responsive-breakpoints variant) decls))

                            (dict-has? _pseudo-variants variant)
                              (append! pseudo-rules
                                (list (dict-get _pseudo-variants variant) decls))

                            ;; Compound variant: "sm:hover"
                            :else
                              (let ((vparts (split variant ":"))
                                    (media-part nil)
                                    (pseudo-part nil))
                                (for-each
                                  (fn (vp)
                                    (cond
                                      (dict-has? _responsive-breakpoints vp)
                                        (set! media-part (dict-get _responsive-breakpoints vp))
                                      (dict-has? _pseudo-variants vp)
                                        (set! pseudo-part (dict-get _pseudo-variants vp))))
                                  vparts)
                                (when media-part
                                  (append! media-rules (list media-part decls)))
                                (when pseudo-part
                                  (append! pseudo-rules (list pseudo-part decls)))
                                (when (and (nil? media-part) (nil? pseudo-part))
                                  (append! base-decls decls))))))))))
              atoms)

            ;; Build hash input
            (let ((hash-input (join ";" base-decls)))
              (for-each
                (fn (mr)
                  (set! hash-input
                    (str hash-input "@" (first mr) "{" (nth mr 1) "}")))
                (chunk-every media-rules 2))
              (for-each
                (fn (pr)
                  (set! hash-input
                    (str hash-input (first pr) "{" (nth pr 1) "}")))
                (chunk-every pseudo-rules 2))
              (for-each
                (fn (kf)
                  (set! hash-input (str hash-input (nth kf 1))))
                (chunk-every kf-needed 2))

              (let ((cn (str "sx-" (hash-style hash-input)))
                    (sv (make-style-value cn
                          (join ";" base-decls)
                          (chunk-every media-rules 2)
                          (chunk-every pseudo-rules 2)
                          (chunk-every kf-needed 2))))
                (dict-set! _style-cache key sv)
                ;; Inject CSS rules
                (inject-style-value sv atoms)
                sv))))))))


;; --------------------------------------------------------------------------
;; Merge multiple StyleValues
;; --------------------------------------------------------------------------

(define merge-style-values
  (fn (styles)
    (if (= (len styles) 1)
      (first styles)
      (let ((all-decls (list))
            (all-media (list))
            (all-pseudo (list))
            (all-kf (list)))
        (for-each
          (fn (sv)
            (when (style-value-declarations sv)
              (append! all-decls (style-value-declarations sv)))
            (set! all-media (concat all-media (style-value-media-rules sv)))
            (set! all-pseudo (concat all-pseudo (style-value-pseudo-rules sv)))
            (set! all-kf (concat all-kf (style-value-keyframes sv))))
          styles)

        (let ((hash-input (join ";" all-decls)))
          (for-each
            (fn (mr)
              (set! hash-input
                (str hash-input "@" (first mr) "{" (nth mr 1) "}")))
            all-media)
          (for-each
            (fn (pr)
              (set! hash-input
                (str hash-input (first pr) "{" (nth pr 1) "}")))
            all-pseudo)
          (for-each
            (fn (kf)
              (set! hash-input (str hash-input (nth kf 1))))
            all-kf)

          (let ((cn (str "sx-" (hash-style hash-input)))
                (merged (make-style-value cn
                          (join ";" all-decls)
                          all-media all-pseudo all-kf)))
            (inject-style-value merged (list))
            merged))))))


;; --------------------------------------------------------------------------
;; Platform interface — CSSX
;; --------------------------------------------------------------------------
;;
;; Hash:
;;   (fnv1a-hash input)             → 6-char hex string (FNV-1a 32-bit)
;;
;; Regex:
;;   (compile-regex pattern)        → compiled regex object
;;   (regex-match re str)           → match array or nil
;;   (regex-replace-groups tmpl match) → string with {0},{1},... replaced
;;
;; StyleValue construction:
;;   (make-style-value cn decls media pseudo kf) → StyleValue object
;;   (style-value-declarations sv)  → declarations string
;;   (style-value-media-rules sv)   → list of [query, decls] pairs
;;   (style-value-pseudo-rules sv)  → list of [selector, decls] pairs
;;   (style-value-keyframes sv)     → list of [name, rule] pairs
;;
;; CSS injection:
;;   (inject-style-value sv atoms)  → void (append CSS rules to <style id="sx-css">)
;; --------------------------------------------------------------------------
