;; sx-swarm — SX-based Container Orchestration
;; Plan: Docker Swarm management where stack definitions, service configs,
;; and deployment logic are SX s-expressions manipulated by macros.

(defcomp ()
  (~docs/page :title "sx-swarm: Container Orchestration in SX"

    (~docs/section :title "Vision" :id "vision"
      (p "Replace docker-compose.yml and Docker Swarm stack files with SX. "
         "Service definitions are defcomps. Environment configs are macros that expand "
         "differently per target (dev, staging, production). Deployments are SX pipelines "
         "executed by sx-ci. The entire infrastructure is the same language as the application.")
      (p "YAML is a data format pretending to be a configuration language. "
         "It has no functions, no macros, no composition, no type checking. "
         "SX has all of these. A service definition is a value. A deployment is a function. "
         "An environment override is a macro."))

    (~docs/section :title "Why Not YAML" :id "why-not-yaml"
      (p "Docker Compose and Swarm stack files share a fundamental problem: "
         "they're static data with ad-hoc templating bolted on. "
         "Variable substitution (${VAR}), extension fields (x-), YAML anchors (&/*) — "
         "all workarounds for not having a real language.")
      (ul (~tw :tokens "space-y-2 text-stone-600 list-disc pl-5")
        (li "No functions — can't abstract repeated patterns")
        (li "No macros — can't generate config at definition time")
        (li "No conditionals — can't branch on environment")
        (li "No types — a typo in an environment variable is a runtime error")
        (li "No composition — merging files is fragile and order-dependent")
        (li "No verification — can't check constraints before deploy"))
      (p "SX solves all of these because it's a programming language, not a data format."))

    (~docs/section :title "Service Definitions" :id "services"
      (p "Services defined as SX, with macros for common patterns:")
      (highlight ";; Base service macro — shared defaults
(defmacro defservice (name &rest body)
  `(define ,name
     (merge-service
       {:restart-policy {:condition :on-failure :max-attempts 3}
        :logging {:driver :json-file :options {:max-size \"10m\"}}
        :networks (list \"internal\")}
       (dict ,@body))))

;; A concrete service
(defservice blog-service
  :image \"rose-ash/blog:latest\"
  :ports (list \"8001:8000\")
  :environment (env-for :blog)
  :volumes (list \"./blog:/app/blog:ro\")
  :depends-on (list :postgres :redis)
  :deploy {:replicas 2
           :update-config {:parallelism 1 :delay \"10s\"}})" "lisp")
      (p "The " (code "defservice") " macro injects shared defaults. "
         "Every service gets restart policy, logging, and network config for free. "
         "Override any field by specifying it in the body."))

    (~docs/section :title "Environment Macros" :id "environments"
      (p "Environment-specific config via macros, not file merging:")
      (highlight ";; Environment macro — expands differently per target
(defmacro env-for (service)
  (let ((base (service-env-base service))
        (target (current-deploy-target)))
    (case target
      :dev (merge base
        {:DEBUG \"true\"
         :DATABASE_URL (str \"postgresql://\" service \"_dev/\" service)})
      :staging (merge base
        {:DATABASE_URL (from-secret (str service \"-db-staging\"))})
      :production (merge base
        {:DATABASE_URL (from-secret (str service \"-db-prod\"))
         :SENTRY_DSN (from-secret \"sentry-dsn\")}))))

;; Volume mounts differ per environment
(defmacro dev-volumes (service)
  `(list ,(str \"./\" service \":/app/\" service)
         ,(str \"./shared:/app/shared\")
         \"./dev.sh:/app/dev.sh:ro\"))

;; Production: no bind mounts, just named volumes
(defmacro prod-volumes (service)
  `(list ,(str service \"-data:/data\")))" "lisp")
      (p "No more docker-compose.yml + docker-compose.dev.yml + docker-compose.prod.yml. "
         "One definition, macros handle the rest."))

    (~docs/section :title "Stack Composition" :id "composition"
      (p "Stacks compose like functions:")
      (highlight ";; Infrastructure services shared across all stacks
(define infra-services
  (list
    (defservice postgres
      :image \"postgres:16\"
      :volumes (list \"pg-data:/var/lib/postgresql/data\")
      :environment {:POSTGRES_PASSWORD (from-secret \"pg-pass\")})
    (defservice redis
      :image \"redis:7-alpine\"
      :volumes (list \"redis-data:/data\"))
    (defservice pgbouncer
      :image \"edoburu/pgbouncer\"
      :depends-on (list :postgres))))

;; Full stack = infra + app services
(define rose-ash-stack
  (stack :name \"rose-ash\"
    :services (concat infra-services app-services)
    :networks (list
      (network :name \"internal\" :driver :overlay :internal true)
      (network :name \"public\" :driver :overlay))
    :volumes (list
      (volume :name \"pg-data\" :driver :local)
      (volume :name \"redis-data\" :driver :local))))" "lisp"))

    (~docs/section :title "Deploy as SX Pipeline" :id "deploy"
      (p "Deployment is an sx-ci pipeline, not a shell script:")
      (highlight "(define deploy-pipeline
  (pipeline :name \"deploy\"
    (stage :build
      (map (fn (svc)
        (docker-build
          :context (str \"./\" (get svc \"name\"))
          :tag (str \"rose-ash/\" (get svc \"name\") \":\" (git-sha))))
        (changed-services)))
    (stage :push
      (map (fn (svc)
        (docker-push (service-image svc)))
        (changed-services)))
    (stage :deploy
      (swarm-deploy rose-ash-stack
        :target (current-deploy-target)
        :rolling true
        :health-check-interval \"5s\"))))" "lisp"))

    (~docs/section :title "Swarm Operations as IO" :id "swarm-ops"
      (p "Swarm management exposed as SX IO primitives:")
      (highlight ";; boundary.sx additions
(io swarm-deploy    (stack &key target rolling) dict)
(io swarm-status    (&key stack service) list)
(io swarm-scale     (service replicas) dict)
(io swarm-rollback  (service) dict)
(io swarm-logs      (service &key since tail follow) list)
(io swarm-inspect   (service) dict)
(io swarm-nodes     () list)
(io swarm-secrets   (&key create delete) dict)
(io docker-build    (&key context tag dockerfile) dict)
(io docker-push     (image) dict)" "lisp")
      (p "These compose with sx-ci and the forge — push to forge triggers CI, "
         "CI runs tests, tests pass, deploy pipeline executes, swarm updates."))

    (~docs/section :title "Health and Monitoring" :id "monitoring"
      (p "Service health as live SX components:")
      (ul (~tw :tokens "space-y-2 text-stone-600 list-disc pl-5")
        (li (strong "Dashboard") " — defcomp rendering swarm-status, auto-refreshing via sx-get polling")
        (li (strong "Log viewer") " — swarm-logs streamed via SSE, rendered as SX")
        (li (strong "Alerts") " — SX predicates on service state, notifications via sx-activity")
        (li (strong "Rollback") " — one-click rollback via swarm-rollback IO primitive"))
      (p "The monitoring dashboard is an SX page like any other. "
         "No Grafana, no separate monitoring stack. Same language, same renderer, same platform."))

    (~docs/section :title "Implementation Path" :id "implementation"
      (ol (~tw :tokens "space-y-3 text-stone-600 list-decimal pl-5")
        (li (strong "Phase 1: Stack definition") " — SX data structures for services, networks, volumes. "
            "Compiler to docker-compose.yml / docker stack deploy format.")
        (li (strong "Phase 2: Environment macros") " — defmacro for env-for, dev-volumes, prod-volumes. "
            "Single source file, multiple targets.")
        (li (strong "Phase 3: Deploy pipeline") " — sx-ci integration. Build, push, deploy as SX stages.")
        (li (strong "Phase 4: Swarm IO") " — boundary primitives wrapping Docker API. "
            "Dashboard and log viewer components.")
        (li (strong "Phase 5: Native orchestration") " — replace Docker Swarm entirely. "
            "SX-native container scheduling, networking, service discovery. "
            "The ultimate goal: no Docker, no Swarm — just SX managing containers directly."))
      (p "Phase 1-3 are pragmatic — compile SX to existing Docker tooling. "
         "Phase 5 is ambitious — replace Docker tooling with SX. "
         "The architecture supports both because the abstraction boundary is clean."))))
