;; ---- Page content ----
(defcomp
  ()
  (~docs/page
    :title "Provide / Context / Emit!"
    (p
      (~tw :tokens "text-stone-500 text-sm italic mb-8")
      "Sugar for "
      (code "scope")
      " with a value. "
      (code "provide")
      " creates a named scope "
      "with a value and an accumulator. "
      (code "context")
      " reads the value downward. "
      (code "emit!")
      " appends to the accumulator upward. "
      (code "emitted")
      " retrieves what was emitted. "
      "See "
      (a
        :href "/sx/(geography.(scopes))"
        (~tw :tokens "text-violet-600 hover:underline")
        "scopes")
      " for the unified primitive.")
    (~docs/section
      :title "Four primitives"
      :id "primitives"
      (~docs/subsection
        :title "provide (special form)"
        (p
          (code "provide")
          " creates a named scope with a value and an empty accumulator. "
          "The body expressions execute with the scope active. When the body completes, "
          "the scope is popped.")
        (~docs/code
          :src (highlight
            "(provide name value\n  body...)\n\n;; Example: theme context\n(provide \"theme\" {:primary \"violet\"}\n  (h1 \"Title\")    ;; can read (context \"theme\")\n  (p \"Body\"))     ;; scope active for all children"
            "lisp"))
        (p
          (code "provide")
          " is a special form, not a function — the body is evaluated "
          "inside the scope, not before it."))
      (~docs/subsection
        :title "context"
        (p
          "Reads the value from the nearest enclosing "
          (code "provide")
          " with the given name. "
          "Errors if no provider and no default given.")
        (~docs/code
          :src (highlight
            "(provide \"theme\" {:primary \"violet\" :font \"serif\"}\n  (get (context \"theme\") :primary))  ;; → \"violet\"\n\n;; With default (no error when missing):\n(context \"theme\" {:primary \"stone\"})  ;; → {:primary \"stone\"}"
            "lisp")))
      (~docs/subsection
        :title "emit!"
        (p
          "Appends a value to the nearest enclosing provider's accumulator. "
          "Tolerant: returns nil silently when no provider exists.")
        (~docs/code
          :src (highlight
            "(provide \"scripts\" nil\n  (emit! \"scripts\" \"analytics.js\")\n  (emit! \"scripts\" \"charts.js\")\n  ;; accumulator now has both scripts\n  )\n\n;; Outside any provider — silently does nothing:\n(emit! \"scripts\" \"orphan.js\")  ;; → nil, no error"
            "lisp"))
        (p
          "Tolerance is critical. Spreads emit into "
          (code "\"element-attrs\"")
          " — but a spread might be evaluated in a fragment, a "
          (code "begin")
          " block, or a "
          (code "map")
          " call where no element provider exists. "
          "Tolerant "
          (code "emit!")
          " means these cases silently vanish instead of crashing."))
      (~docs/subsection
        :title "emitted"
        (p
          "Returns the list of values emitted into the nearest provider with the given name. "
          "Empty list if no provider.")
        (~docs/code
          :src (highlight
            "(provide \"scripts\" nil\n  (emit! \"scripts\" \"a.js\")\n  (emit! \"scripts\" \"b.js\")\n  (emitted \"scripts\"))  ;; → (\"a.js\" \"b.js\")"
            "lisp"))))
    (~docs/section
      :title "Two directions, one mechanism"
      :id "directions"
      (p
        (code "provide")
        " serves both downward and upward communication through a single scope.")
      (~docs/table
        :headers (list "Direction" "Read with" "Write with" "Example")
        :rows (list
          (list
            "Downward (scope → child)"
            "context"
            "provide value"
            "Theme, config, locale")
          (list
            "Upward (child → scope)"
            "emitted"
            "emit!"
            "Script collection, spread attrs")))
      (~geography/provide/provide-demo-example
        :demo (~geography/provide/demo-provide-basic)
        :code (highlight
          ";; Downward: theme context\n(provide \"theme\"\n  {:primary \"violet\" :accent \"rose\"}\n  (h1 :style (str \"color:\"\n    (get (context \"theme\") :primary))\n    \"Themed heading\")\n  (p \"inherits theme context\"))"
          "lisp"))
      (~geography/provide/provide-demo-example
        :demo (~geography/provide/demo-emit-collect)
        :code (highlight
          ";; Upward: script accumulation\n(provide \"scripts\" nil\n  (div\n    (emit! \"scripts\" \"analytics.js\")\n    (div\n      (emit! \"scripts\" \"charts.js\")\n      \"chart\"))\n  ;; Collect at the boundary:\n  (for-each (fn (s)\n    (script :src s))\n    (emitted \"scripts\")))"
          "lisp")))
    (~docs/section
      :title "How spreads use provide/emit!"
      :id "spreads"
      (p
        "Every element rendering function wraps its children in a provider scope "
        "named "
        (code "\"element-attrs\"")
        ". When the adapter encounters a spread child, "
        "it emits the spread's attrs into this scope. After all children render, the "
        "element collects and merges the emitted attrs.")
      (~geography/provide/provide-demo-example
        :demo (~geography/provide/demo-spread-mechanism)
        :code (highlight
          ";; Spread = emit! into element-attrs\n(div (make-spread {:class \"card\"})\n  \"hello\")\n\n;; Internally:\n;; 1. div opens provider:\n;;    (provide-push! \"element-attrs\" nil)\n;; 2. spread child emits:\n;;    (emit! \"element-attrs\"\n;;           {:class \"card\"})\n;; 3. div collects + merges:\n;;    (emitted \"element-attrs\")\n;;    → ({:class \"card\"})\n;; 4. (provide-pop! \"element-attrs\")\n;; Result: <div class=\"card\">hello</div>"
          "lisp"))
      (~docs/subsection
        :title "Why this matters"
        (p
          "Before the refactor, every intermediate form in the render pipeline — "
          "fragments, "
          (code "let")
          ", "
          (code "begin")
          ", "
          (code "map")
          ", "
          (code "for-each")
          ", "
          (code "when")
          ", "
          (code "cond")
          ", component children — "
          "needed an explicit "
          (code "(filter (fn (r) (not (spread? r))) ...)")
          " to strip "
          "spread values from rendered output. Over 25 such filters existed across the four adapters.")
        (p
          "With provide/emit!, all of these disappear. Spreads emit into the nearest element's "
          "scope regardless of how many layers of control flow they pass through. Non-element "
          "contexts have no provider, so "
          (code "emit!")
          " is a silent no-op.")))
    (~docs/section
      :title "Nested scoping"
      :id "nesting"
      (p
        "Providers stack. Each "
        (code "provide")
        " pushes onto a per-name stack; "
        "the closest one wins. This gives lexical-style scoping at render time.")
      (~geography/provide/provide-demo-example
        :demo (~geography/provide/demo-nested-provide)
        :code (highlight
          "(provide \"level\" \"outer\"\n  (context \"level\")  ;; → \"outer\"\n  (provide \"level\" \"inner\"\n    (context \"level\"))  ;; → \"inner\"\n  (context \"level\"))  ;; → \"outer\" again"
          "lisp"))
      (p
        "For "
        (code "emit!")
        ", this means emissions go to the "
        (em "nearest")
        " provider. "
        "A spread inside a nested element emits to that element, not an ancestor.")
      (~docs/code
        :src (highlight
          ";; Nested elements = nested providers\n(div                          ;; provider A\n  (span                       ;; provider B\n    (make-spread {:class \"inner\"}))  ;; emits to B\n  (make-spread {:class \"outer\"}))    ;; emits to A\n;; → <div class=\"outer\"><span class=\"inner\"></span></div>"
          "lisp")))
    (~docs/section
      :title "Across all adapters"
      :id "adapters"
      (p
        "The provide/emit! mechanism works identically across all four rendering adapters. "
        "The element rendering pattern is the same; only the output format differs.")
      (~docs/table
        :headers (list "Adapter" "Element render" "Spread dispatch")
        :rows (list
          (list
            "HTML (server)"
            "provide-push! → render children → merge emitted → provide-pop!"
            "(emit! \"element-attrs\" (spread-attrs expr)) → \"\"")
          (list
            "Async (server)"
            "Same pattern, with await on child rendering"
            "Same dispatch")
          (list
            "SX wire (aser)"
            "provide-push! → serialize children → merge emitted as :key attrs → provide-pop!"
            "(emit! \"element-attrs\" (spread-attrs expr)) → nil")
          (list
            "DOM (browser)"
            "provide-push! → reduce children → merge emitted onto DOM element → provide-pop!"
            "emit! + keep value for reactive-spread detection")))
      (~docs/subsection
        :title "DOM adapter: reactive-spread preserved"
        (p
          "In the DOM adapter, spread children inside islands are still checked individually "
          "for signal dependencies. "
          (code "reactive-spread")
          " tracks signal deps and "
          "surgically updates attributes when signals change. The static path uses provide/emit!; "
          "the reactive path wraps it in an effect.")
        (p
          "See the "
          (a
            :href "/sx/(geography.(spreads))"
            (~tw :tokens "text-violet-600 hover:underline")
            "spreads article")
          " for reactive-spread details.")))
    (~docs/section
      :title "Comparison with collect! / collected"
      :id "comparison"
      (~docs/table
        :headers (list "" "provide / emit!" "collect! / collected")
        :rows (list
          (list
            "Scope"
            "Lexical (nearest enclosing provide)"
            "Global (render-wide)")
          (list
            "Deduplication"
            "None — every emit! appends"
            "Automatic (same value skipped)")
          (list
            "Multiple scopes"
            "Yes — nested provides shadow"
            "No — single global bucket per name")
          (list "Downward data" "Yes (context)" "No")
          (list "Used by" "Spreads (element-attrs)" "CSSX rule accumulation")))
      (p
        (code "collect!")
        " remains the right tool for CSS rule accumulation — deduplication "
        "matters there, and rules need to reach the layout root regardless of nesting depth. "
        (code "emit!")
        " is right for spread attrs — no dedup needed, and each element only "
        "wants attrs from its direct children."))
    (~docs/section
      :title "Platform implementation"
      :id "platform"
      (p
        (code "provide")
        " is sugar for "
        (code "scope")
        ". At the platform level, "
        (code "provide-push!")
        " and "
        (code "provide-pop!")
        " are aliases for "
        (code "scope-push!")
        " and "
        (code "scope-pop!")
        ". All operations work on a unified "
        (code "_scope_stacks")
        " data structure.")
      (~docs/table
        :headers (list "Platform primitive" "Purpose")
        :rows (list
          (list
            "scope-push!(name, value)"
            "Push a new scope with value and empty accumulator")
          (list "scope-pop!(name)" "Pop the most recent scope")
          (list
            "context(name, ...default)"
            "Read value from nearest scope (error if missing and no default)")
          (list
            "emit!(name, value)"
            "Append to nearest scope's accumulator (tolerant: no-op if missing)")
          (list "emitted(name)" "Return accumulated values from nearest scope")))
      (p
        (code "provide")
        " is a special form in "
        (a
          :href "/sx/(language.(spec.(explore.evaluator)))"
          (~tw :tokens "font-mono text-violet-600 hover:underline text-sm")
          "eval.sx")
        " — it calls "
        (code "scope-push!")
        ", evaluates the body, "
        "then calls "
        (code "scope-pop!")
        ". See "
        (a
          :href "/sx/(geography.(scopes))"
          (~tw :tokens "text-violet-600 hover:underline")
          "scopes")
        " for the full unified platform.")
      (~docs/note
        (p
          (strong "Spec explorer: ")
          "See the provide/emit! primitives in "
          (a
            :href "/sx/(language.(spec.(explore.boundary)))"
            (~tw :tokens "font-mono text-violet-600 hover:underline text-sm")
            "boundary.sx explorer")
          ". The "
          (code "provide")
          " special form is in "
          (a
            :href "/sx/(language.(spec.(explore.evaluator)))"
            (~tw :tokens "font-mono text-violet-600 hover:underline text-sm")
            "eval.sx explorer")
          ". Element rendering with provide/emit! is visible in "
          (a
            :href "/sx/(language.(spec.(explore.adapter-html)))"
            (~tw :tokens "font-mono text-violet-600 hover:underline text-sm")
            "adapter-html")
          " and "
          (a
            :href "/sx/(language.(spec.(explore.adapter-async)))"
            (~tw :tokens "font-mono text-violet-600 hover:underline text-sm")
            "adapter-async")
          ".")))))
