
;; GraphQL demo — parser, executor, and hyperscript integration
(defcomp
  ()
  (~docs/page
    :title "GraphQL"
    (p
      (~tw :tokens "text-lg text-gray-600 mb-2")
      "A pure SX implementation of the "
      (a
        :href "https://spec.graphql.org/"
        :target "_blank"
        (~tw :tokens "text-violet-600 underline")
        "GraphQL query language")
      ". The parser, executor, and serializer are all s-expressions "
      "compiled to bytecode by the same kernel that runs the rest of the site.")
    (p
      (~tw :tokens "text-gray-600 mb-6")
      "GraphQL operations map directly to the existing "
      (code "defquery")
      "/"
      (code "defaction")
      " system. Queries become IO suspension calls, mutations become actions, "
      "and field selection is projection over result dicts. The hyperscript "
      "integration adds "
      (code "fetch gql { ... }")
      " as a native command.")
    (~docs/section
      :title "Parser"
      :id "parser"
      (p
        (~tw :tokens "text-gray-600 mb-3")
        "The tokenizer and recursive-descent parser live in "
        (code "lib/graphql.sx")
        ". Feed it any GraphQL source and get an SX AST back.")
      (~docs/code :src "(gql-parse \"{ user(id: 1) { name email } }\")")
      (p (~tw :tokens "text-gray-500 text-sm mb-3") "Result:")
      (~docs/code
        :src (str
          "(gql-doc\n"
          "  (gql-query nil () ()\n"
          "    ((gql-field \"user\" ((\"id\" 1)) ()\n"
          "       ((gql-field \"name\" () () ())\n"
          "        (gql-field \"email\" () () ()))))))"))
      (p
        (~tw :tokens "text-gray-600 mt-3")
        "Every GraphQL concept has an SX node type: "
        (code "gql-query")
        ", "
        (code "gql-mutation")
        ", "
        (code "gql-field")
        ", "
        (code "gql-fragment")
        ", "
        (code "gql-var")
        ", "
        (code "gql-directive")
        ". "
        "Accessors like "
        (code "gql-field-name")
        " and "
        (code "gql-op-selections")
        " make the AST easy to walk."))
    (~docs/section
      :title "Variables and Operations"
      :id "variables"
      (p
        (~tw :tokens "text-gray-600 mb-3")
        "Named operations, variable definitions with types and defaults, "
        "and variable references all parse to structured AST nodes.")
      (~docs/code
        :src "query GetUser($id: ID!, $limit: Int = 10) {\n  user(id: $id) {\n    name\n    posts(limit: $limit) { title }\n  }\n}")
      (p
        (~tw :tokens "text-gray-500 text-sm mt-3 mb-3")
        "Variable definitions become "
        (code "(gql-var-def name type default)")
        " nodes. "
        "References become "
        (code "(gql-var name)")
        " nodes that the executor substitutes "
        "from a provided bindings dict."))
    (~docs/section
      :title "Fragments"
      :id "fragments"
      (p
        (~tw :tokens "text-gray-600 mb-3")
        "Fragment definitions, fragment spreads, and inline fragments:")
      (~docs/code
        :src "query {\n  user {\n    ...UserFields\n    ... on Admin { role }\n  }\n}\n\nfragment UserFields on User {\n  name\n  email\n}")
      (p
        (~tw :tokens "text-gray-600 mt-3")
        "The executor collects fragment definitions from the document "
        "and resolves spreads during projection. Inline fragments apply "
        "unconditionally (type checking is available via "
        (code "spec/types.sx")
        ")."))
    (~docs/section
      :title "Executor"
      :id "executor"
      (p
        (~tw :tokens "text-gray-600 mb-3")
        "The executor in "
        (code "lib/graphql-exec.sx")
        " walks the parsed AST and dispatches each root field through a resolver. "
        "The default resolver uses "
        (code "perform io-gql-resolve")
        " to dispatch via IO suspension, exactly like "
        (code "defquery")
        ".")
      (~docs/code
        :src "(let ((doc (gql-parse \"{ user(id: 1) { name email } }\"))\n      (resolver (fn (field-name args op-type)\n                  (if (= field-name \"user\")\n                    {:name \"Alice\" :email \"alice@test.com\" :age 30}\n                    nil))))\n  (gql-execute doc {} resolver))")
      (p (~tw :tokens "text-gray-500 text-sm mb-3") "Result:")
      (~docs/code
        :src "{:data {:user {:email \"alice@test.com\" :name \"Alice\"}}}")
      (p
        (~tw :tokens "text-gray-600 mt-3")
        "Notice "
        (code ":age")
        " is absent from the result. "
        "The executor projects each resolver result down to only the fields "
        "the query requested. Nested selections, list results, and aliased "
        "fields all work."))
    (~docs/section
      :title "Aliases"
      :id "aliases"
      (p
        (~tw :tokens "text-gray-600 mb-3")
        "Query the same field multiple times with different arguments:")
      (~docs/code
        :src "{\n  me: user(id: 1) { name }\n  them: user(id: 2) { name }\n}")
      (p
        (~tw :tokens "text-gray-600 mt-3")
        "Each aliased field calls the resolver independently. "
        "Results are keyed by the alias name: "
        (code "{:data {:me {:name ...} :them {:name ...}}}")
        "."))
    (~docs/section
      :title "Serializer"
      :id "serializer"
      (p
        (~tw :tokens "text-gray-600 mb-3")
        "The serializer converts an AST back to GraphQL source. "
        "Useful for logging, debugging, and wire format.")
      (~docs/code
        :src "(gql-serialize (gql-parse \"query GetUser($id: ID!) { user(id: $id) { name } }\"))")
      (p
        (~tw :tokens "text-gray-500 text-sm mt-3")
        "Round-trips cleanly. Parse, transform the AST, serialize back."))
    (~docs/section
      :title "Hyperscript: fetch gql"
      :id "hyperscript"
      (p
        (~tw :tokens "text-gray-600 mb-3")
        "The "
        (code "fetch gql")
        " command embeds GraphQL queries directly in hyperscript. "
        "Write queries inline "
        (em "in the HTML attribute")
        ", compiled to bytecode "
        "alongside the rest of the hyperscript.")
      (~docs/code
        :src "<!-- shorthand query -->\n<button _=\"on click fetch gql { user(id: 1) { name } } then put it.data.user.name into #result\">\n  Load User\n</button>\n\n<!-- with explicit endpoint -->\n<button _=\"on click fetch gql { posts { title } } from '/api/graphql' then ...\">\n  Load Posts\n</button>\n\n<!-- mutation -->\n<button _=\"on click fetch gql mutation { deletePost(id: 42) { success } } then ...\">\n  Delete\n</button>")
      (p
        (~tw :tokens "text-gray-600 mt-3 mb-3")
        "The parser collects the GraphQL body between braces, preserving nested "
        "structure. At runtime, "
        (code "hs-fetch-gql")
        " sends a standard "
        (code "{ \"query\": \"...\", \"variables\": {} }")
        " JSON POST to the endpoint (defaults to "
        (code "/graphql")
        ").")
      (p
        (~tw :tokens "text-gray-600")
        "The compilation pipeline: hyperscript source "
        (span (~tw :tokens "text-gray-400") " → ")
        "HS tokenizer "
        (span (~tw :tokens "text-gray-400") " → ")
        "HS parser ("
        (code "fetch-gql")
        " AST) "
        (span (~tw :tokens "text-gray-400") " → ")
        "HS compiler ("
        (code "hs-fetch-gql")
        " call) "
        (span (~tw :tokens "text-gray-400") " → ")
        "bytecode. The GraphQL source string is a constant in the compiled output."))
    (~docs/section
      :title "GraphQL → SX Mapping"
      :id "mapping"
      (p
        (~tw :tokens "text-gray-600 mb-3")
        "Every GraphQL concept has a direct SX equivalent:")
      (table
        (~tw :tokens "w-full text-sm border-collapse mb-4")
        (thead
          (tr
            (~tw :tokens "border-b border-gray-200")
            (th
              (~tw :tokens "text-left py-2 pr-4 text-gray-500 font-medium")
              "GraphQL")
            (th
              (~tw :tokens "text-left py-2 pr-4 text-gray-500 font-medium")
              "SX")
            (th
              (~tw :tokens "text-left py-2 text-gray-500 font-medium")
              "Mechanism")))
        (tbody
          (tr
            (~tw :tokens "border-b border-gray-100")
            (td (~tw :tokens "py-2 pr-4") "Query")
            (td (~tw :tokens "py-2 pr-4") (code "defquery"))
            (td (~tw :tokens "py-2") "IO suspension"))
          (tr
            (~tw :tokens "border-b border-gray-100")
            (td (~tw :tokens "py-2 pr-4") "Mutation")
            (td (~tw :tokens "py-2 pr-4") (code "defaction"))
            (td (~tw :tokens "py-2") "IO suspension"))
          (tr
            (~tw :tokens "border-b border-gray-100")
            (td (~tw :tokens "py-2 pr-4") "Subscription")
            (td (~tw :tokens "py-2 pr-4") "SSE + signals")
            (td (~tw :tokens "py-2") "Reactive islands"))
          (tr
            (~tw :tokens "border-b border-gray-100")
            (td (~tw :tokens "py-2 pr-4") "Fragment")
            (td (~tw :tokens "py-2 pr-4") (code "defcomp"))
            (td (~tw :tokens "py-2") "Component composition"))
          (tr
            (~tw :tokens "border-b border-gray-100")
            (td (~tw :tokens "py-2 pr-4") "Schema")
            (td (~tw :tokens "py-2 pr-4") (code "spec/types.sx"))
            (td (~tw :tokens "py-2") "Gradual type system"))
          (tr
            (~tw :tokens "border-b border-gray-100")
            (td (~tw :tokens "py-2 pr-4") "Resolver")
            (td (~tw :tokens "py-2 pr-4") (code "perform"))
            (td (~tw :tokens "py-2") "CEK IO suspension"))
          (tr
            (td (~tw :tokens "py-2 pr-4") "Field selection")
            (td (~tw :tokens "py-2 pr-4") (code "gql-project"))
            (td (~tw :tokens "py-2") "Dict projection")))))
    (~docs/section
      :title "Live Parse"
      :id "live-parse"
      (p
        (~tw :tokens "text-gray-600 mb-3")
        "Type a GraphQL query and see the SX AST. "
        "Parsed by the same "
        (code "lib/graphql.sx")
        " that compiles to bytecode.")
      (div
        (~tw :tokens "flex flex-col gap-3")
        (textarea
          :id "gql-input"
          :name "source"
          :rows "5"
          (~tw
            :tokens "w-full font-mono text-sm p-3 border border-gray-300 rounded-lg focus:border-violet-500 focus:ring-1 focus:ring-violet-500 outline-none")
          :placeholder "{ user(id: 1) { name email } }"
          "{\n  user(id: 1) {\n    name\n    email\n  }\n}")
        (button
          (~tw
            :tokens "self-start px-4 py-2 bg-violet-600 text-white rounded-lg hover:bg-violet-700 transition-colors cursor-pointer")
          :id "gql-parse-btn"
          "Parse")
        (div
          :id "gql-output"
          (~tw
            :tokens "min-h-24 p-3 bg-gray-50 rounded-lg border border-gray-200 font-mono text-sm text-gray-700 whitespace-pre-wrap")
          (span (~tw :tokens "text-gray-400") "AST will appear here")))
      (script
        "document.getElementById('gql-parse-btn').addEventListener('click', function() { var src = document.getElementById('gql-input').value; fetch('/sx/(applications.(graphql.(api.parse-demo)))?source=' + encodeURIComponent(src)).then(function(r) { return r.text(); }).then(function(html) { document.getElementById('gql-output').innerHTML = html; }); });"))))
