;; lib/host/middleware.sx — Host middleware: composable handler->handler layers
;; for the cross-cutting concerns every write endpoint shares — error trapping
;; (JSON 500), authentication (bearer token -> principal), and authorisation
;; (ACL permit?). Middleware is plain function composition; host/pipeline threads a
;; list onto a handler, FIRST middleware outermost (so it runs first). Auth and
;; permission policy are INJECTED — the token resolver and the resource extractor —
;; so this layer carries no hardcoded policy. Reuses Dream's bearer/error helpers
;; and lib/acl's public acl/permit?.
;; Depends on lib/dream/{auth,error,router}.sx + lib/acl/api.sx + lib/host/handler.sx.

;; Compose a list of middlewares onto a handler (first = outermost).
(define host/pipeline
  (fn (middlewares handler)
    (dr/apply-middlewares middlewares handler)))

;; The authenticated principal attached by host/require-auth.
(define host/principal (fn (req) (dream-principal req)))

;; ── error trapping ─────────────────────────────────────────────────
;; Any error thrown downstream becomes a JSON 500 envelope.
(define host/-on-error
  (fn (req e) (host/error 500 "internal error")))
(define host/wrap-errors (dream-catch-with host/-on-error))

;; ── authentication ─────────────────────────────────────────────────
;; resolve : token -> principal | nil. Missing/invalid token -> JSON 401 with a
;; WWW-Authenticate: Bearer challenge; success attaches :dream-principal so
;; downstream layers (and host/principal) can read it.
(define host/require-auth
  (fn (resolve)
    (fn (next)
      (fn (req)
        (let ((tok (dream-bearer-token req)))
          (let ((principal (if tok (resolve tok) nil)))
            (if (nil? principal)
              (dream-add-header
                (host/error 401 "unauthorized")
                "www-authenticate"
                "Bearer")
              (next (assoc req :dream-principal principal)))))))))

;; ── authorisation ──────────────────────────────────────────────────
;; Gate on ACL: the authed principal must be permitted `action` on the resource
;; computed by res-fn from the request. Denied -> JSON 403. Assumes the ACL fact
;; db was loaded (acl/load!) at startup. Place AFTER host/require-auth.
(define host/require-permission
  (fn (action res-fn)
    (fn (next)
      (fn (req)
        (let ((subject (host/principal req))
              (resource (res-fn req)))
          (if (acl/permit? subject action resource)
            (next req)
            (host/error 403 "forbidden")))))))
