;; ==========================================================================
;; boot.sx — Browser boot, mount, hydrate, script processing
;;
;; Handles the browser startup lifecycle:
;; 1. CSS tracking init
;; 2. Style dictionary loading (from <script type="text/sx-styles">)
;; 3. Component script processing (from <script type="text/sx">)
;; 4. Hydration of [data-sx] elements
;; 5. Engine element processing
;;
;; Also provides the public mounting/hydration API:
;;   mount, hydrate, update, render-component
;;
;; Depends on:
;;   cssx.sx          — load-style-dict
;;   orchestration.sx — process-elements, engine-init
;;   adapter-dom.sx   — render-to-dom
;;   render.sx        — shared registries
;; ==========================================================================


;; --------------------------------------------------------------------------
;; Head element hoisting (full version)
;; --------------------------------------------------------------------------
;; Moves <meta>, <title>, <link rel=canonical>, <script type=application/ld+json>
;; from rendered content to <head>, deduplicating as needed.

(define HEAD_HOIST_SELECTOR
  "meta, title, link[rel='canonical'], script[type='application/ld+json']")

(define hoist-head-elements-full
  (fn (root)
    (let ((els (dom-query-all root HEAD_HOIST_SELECTOR)))
      (for-each
        (fn (el)
          (let ((tag (lower (dom-tag-name el))))
            (cond
              ;; <title> — replace document title
              (= tag "title")
                (do
                  (set-document-title (dom-text-content el))
                  (dom-remove-child (dom-parent el) el))

              ;; <meta> — deduplicate by name or property
              (= tag "meta")
                (do
                  (let ((name (dom-get-attr el "name"))
                        (prop (dom-get-attr el "property")))
                    (when name
                      (remove-head-element (str "meta[name=\"" name "\"]")))
                    (when prop
                      (remove-head-element (str "meta[property=\"" prop "\"]"))))
                  (dom-remove-child (dom-parent el) el)
                  (dom-append-to-head el))

              ;; <link rel=canonical> — deduplicate
              (and (= tag "link")
                   (= (dom-get-attr el "rel") "canonical"))
                (do
                  (remove-head-element "link[rel=\"canonical\"]")
                  (dom-remove-child (dom-parent el) el)
                  (dom-append-to-head el))

              ;; Everything else (ld+json, etc.) — just move
              :else
                (do
                  (dom-remove-child (dom-parent el) el)
                  (dom-append-to-head el)))))
        els))))


;; --------------------------------------------------------------------------
;; Mount — render SX source into a DOM element
;; --------------------------------------------------------------------------

(define sx-mount
  (fn (target source extra-env)
    ;; Render SX source string into target element.
    ;; target: Element or CSS selector string
    ;; source: SX source string
    ;; extra-env: optional extra bindings dict
    (let ((el (resolve-mount-target target)))
      (when el
        (let ((node (sx-render-with-env source extra-env)))
          (dom-set-text-content el "")
          (dom-append el node)
          ;; Hoist head elements from rendered content
          (hoist-head-elements-full el)
          ;; Process sx- attributes and hydrate
          (process-elements el)
          (sx-hydrate-elements el))))))


;; --------------------------------------------------------------------------
;; Hydrate — render all [data-sx] elements
;; --------------------------------------------------------------------------

(define sx-hydrate-elements
  (fn (root)
    ;; Find all [data-sx] elements within root and render them.
    (let ((els (dom-query-all (or root (dom-body)) "[data-sx]")))
      (for-each
        (fn (el)
          (when (not (is-processed? el "hydrated"))
            (mark-processed! el "hydrated")
            (sx-update-element el nil)))
        els))))


;; --------------------------------------------------------------------------
;; Update — re-render a [data-sx] element with new env data
;; --------------------------------------------------------------------------

(define sx-update-element
  (fn (el new-env)
    ;; Re-render a [data-sx] element.
    ;; Reads source from data-sx attr, base env from data-sx-env attr.
    (let ((target (resolve-mount-target el)))
      (when target
        (let ((source (dom-get-attr target "data-sx")))
          (when source
            (let ((base-env (parse-env-attr target))
                  (env (merge-envs base-env new-env)))
              (let ((node (sx-render-with-env source env)))
                (dom-set-text-content target "")
                (dom-append target node)
                ;; Update stored env if new-env provided
                (when new-env
                  (store-env-attr target base-env new-env))))))))))


;; --------------------------------------------------------------------------
;; Render component — build synthetic call from kwargs dict
;; --------------------------------------------------------------------------

(define sx-render-component
  (fn (name kwargs extra-env)
    ;; Render a named component with keyword args.
    ;; name: component name (with or without ~ prefix)
    ;; kwargs: dict of param-name → value
    ;; extra-env: optional extra env bindings
    (let ((full-name (if (starts-with? name "~") name (str "~" name))))
      (let ((env (get-render-env extra-env))
            (comp (env-get env full-name)))
        (if (not (component? comp))
          (error (str "Unknown component: " full-name))
          ;; Build synthetic call expression
          (let ((call-expr (list (make-symbol full-name))))
            (for-each
              (fn (k)
                (append! call-expr (make-keyword (to-kebab k)))
                (append! call-expr (dict-get kwargs k)))
              (keys kwargs))
            (render-to-dom call-expr env nil)))))))


;; --------------------------------------------------------------------------
;; Script processing — <script type="text/sx">
;; --------------------------------------------------------------------------

(define process-sx-scripts
  (fn (root)
    ;; Process all <script type="text/sx"> tags.
    ;; - data-components + data-hash → localStorage cache
    ;; - data-mount="<selector>" → render into target
    ;; - Default: load as components
    (let ((scripts (query-sx-scripts root)))
      (for-each
        (fn (s)
          (when (not (is-processed? s "script"))
            (mark-processed! s "script")
            (let ((text (dom-text-content s)))
              (cond
                ;; Component definitions
                (dom-has-attr? s "data-components")
                  (process-component-script s text)

                ;; Empty script — skip
                (or (nil? text) (empty? (trim text)))
                  nil

                ;; Mount directive
                (dom-has-attr? s "data-mount")
                  (let ((mount-sel (dom-get-attr s "data-mount"))
                        (target (dom-query mount-sel)))
                    (when target
                      (sx-mount target text nil)))

                ;; Default: load as components
                :else
                  (sx-load-components text)))))
        scripts))))


;; --------------------------------------------------------------------------
;; Component script with caching
;; --------------------------------------------------------------------------

(define process-component-script
  (fn (script text)
    ;; Handle <script type="text/sx" data-components data-hash="...">
    (let ((hash (dom-get-attr script "data-hash")))
      (if (nil? hash)
        ;; Legacy: no hash — just load inline
        (when (and text (not (empty? (trim text))))
          (sx-load-components text))
        ;; Hash-based caching
        (let ((has-inline (and text (not (empty? (trim text))))))
          (let ((cached-hash (local-storage-get "sx-components-hash")))
            (if (= cached-hash hash)
              ;; Cache hit
              (if has-inline
                ;; Server sent full source (cookie stale) — update cache
                (do
                  (local-storage-set "sx-components-hash" hash)
                  (local-storage-set "sx-components-src" text)
                  (sx-load-components text)
                  (log-info "components: downloaded (cookie stale)"))
                ;; Server omitted source — load from cache
                (let ((cached (local-storage-get "sx-components-src")))
                  (if cached
                    (do
                      (sx-load-components cached)
                      (log-info (str "components: cached (" hash ")")))
                    ;; Cache entry missing — clear cookie and reload
                    (do
                      (clear-sx-comp-cookie)
                      (browser-reload)))))
              ;; Cache miss — hash mismatch
              (if has-inline
                ;; Server sent full source — cache it
                (do
                  (local-storage-set "sx-components-hash" hash)
                  (local-storage-set "sx-components-src" text)
                  (sx-load-components text)
                  (log-info (str "components: downloaded (" hash ")")))
                ;; Server omitted but cache stale — clear and reload
                (do
                  (local-storage-remove "sx-components-hash")
                  (local-storage-remove "sx-components-src")
                  (clear-sx-comp-cookie)
                  (browser-reload)))))
          (set-sx-comp-cookie hash))))))


;; --------------------------------------------------------------------------
;; Style dictionary initialization
;; --------------------------------------------------------------------------

(define init-style-dict
  (fn ()
    ;; Process <script type="text/sx-styles"> tags with caching.
    (let ((scripts (query-style-scripts)))
      (for-each
        (fn (s)
          (when (not (is-processed? s "styles"))
            (mark-processed! s "styles")
            (let ((text (dom-text-content s))
                  (hash (dom-get-attr s "data-hash")))
              (if (nil? hash)
                ;; No hash — just parse inline
                (when (and text (not (empty? (trim text))))
                  (parse-and-load-style-dict text))
                ;; Hash-based caching
                (let ((has-inline (and text (not (empty? (trim text))))))
                  (let ((cached-hash (local-storage-get "sx-styles-hash")))
                    (if (= cached-hash hash)
                      ;; Cache hit
                      (if has-inline
                        (do
                          (local-storage-set "sx-styles-src" text)
                          (parse-and-load-style-dict text)
                          (log-info "styles: downloaded (cookie stale)"))
                        (let ((cached (local-storage-get "sx-styles-src")))
                          (if cached
                            (do
                              (parse-and-load-style-dict cached)
                              (log-info (str "styles: cached (" hash ")")))
                            (do
                              (clear-sx-styles-cookie)
                              (browser-reload)))))
                      ;; Cache miss
                      (if has-inline
                        (do
                          (local-storage-set "sx-styles-hash" hash)
                          (local-storage-set "sx-styles-src" text)
                          (parse-and-load-style-dict text)
                          (log-info (str "styles: downloaded (" hash ")")))
                        (do
                          (local-storage-remove "sx-styles-hash")
                          (local-storage-remove "sx-styles-src")
                          (clear-sx-styles-cookie)
                          (browser-reload)))))
                  (set-sx-styles-cookie hash))))))
        scripts))))


;; --------------------------------------------------------------------------
;; Full boot sequence
;; --------------------------------------------------------------------------

(define boot-init
  (fn ()
    ;; Full browser initialization:
    ;; 1. CSS tracking
    ;; 2. Style dictionary
    ;; 3. Process scripts (components + mounts)
    ;; 4. Hydrate [data-sx] elements
    ;; 5. Process engine elements
    (do
      (init-css-tracking)
      (init-style-dict)
      (process-sx-scripts nil)
      (sx-hydrate-elements nil)
      (process-elements nil))))


;; --------------------------------------------------------------------------
;; Platform interface — Boot
;; --------------------------------------------------------------------------
;;
;; From orchestration.sx:
;;   process-elements, init-css-tracking
;;
;; From cssx.sx:
;;   load-style-dict
;;
;; === DOM / Render ===
;;   (resolve-mount-target target) → Element (string → querySelector, else identity)
;;   (sx-render-with-env source extra-env) → DOM node (parse + render with componentEnv + extra)
;;   (get-render-env extra-env)    → merged component env + extra
;;   (merge-envs base new)         → merged env dict
;;   (render-to-dom expr env ns)   → DOM node
;;   (sx-load-components text)     → void (parse + eval into componentEnv)
;;
;; === DOM queries ===
;;   (dom-query sel)               → Element or nil
;;   (dom-query-all root sel)      → list of Elements
;;   (dom-body)                    → document.body
;;   (dom-get-attr el name)        → string or nil
;;   (dom-has-attr? el name)       → boolean
;;   (dom-text-content el)         → string
;;   (dom-set-text-content el s)   → void
;;   (dom-append el child)         → void
;;   (dom-remove-child parent el)  → void
;;   (dom-parent el)               → Element
;;   (dom-append-to-head el)       → void
;;   (dom-tag-name el)             → string
;;
;; === Head hoisting ===
;;   (set-document-title s)        → void (document.title = s)
;;   (remove-head-element sel)     → void (remove matching element from <head>)
;;
;; === Script queries ===
;;   (query-sx-scripts root)       → list of <script type="text/sx"> elements
;;   (query-style-scripts)         → list of <script type="text/sx-styles"> elements
;;
;; === localStorage ===
;;   (local-storage-get key)       → string or nil
;;   (local-storage-set key val)   → void
;;   (local-storage-remove key)    → void
;;
;; === Cookies ===
;;   (set-sx-comp-cookie hash)     → void
;;   (clear-sx-comp-cookie)        → void
;;   (set-sx-styles-cookie hash)   → void
;;   (clear-sx-styles-cookie)      → void
;;
;; === Env ===
;;   (parse-env-attr el)           → dict (parse data-sx-env JSON attr)
;;   (store-env-attr el base new)  → void (merge and store back as JSON)
;;   (to-kebab s)                  → string (underscore → kebab-case)
;;
;; === Logging ===
;;   (log-info msg)                → void (console.log with prefix)
;;   (log-parse-error label text err) → void (diagnostic parse error)
;;
;; === JSON parsing ===
;;   (parse-and-load-style-dict text) → void (JSON.parse + load-style-dict)
;;
;; === Processing markers ===
;;   (mark-processed! el key)      → void
;;   (is-processed? el key)        → boolean
;; --------------------------------------------------------------------------
