;; lib/commerce/order.sx — order lifecycle as a durable flow-on-sx flow.
;;
;; The lifecycle (reserve -> await payment -> fulfil) is a Scheme flow running
;; in the flow-on-sx guest (lib/flow). The flow is PURE ORCHESTRATION: it
;; carries only the order-id and enforces step ordering + the suspension at the
;; payment IO boundary. All IO/state lives in SX: the SX driver here services
;; each flow request by appending to the persist ledger (ledger.sx).
;;
;;   reserve  -> SX appends :reserved, resumes  (synchronous host effect)
;;   payment  -> flow stays SUSPENDED until the SumUp webhook resumes it
;;   fulfil   -> SX appends :fulfilled, resumes (synchronous host effect)
;;
;; Durability: the flow's replay log is plain data (flow-store-export), so a
;; suspended order survives a process restart — order-flow-restart! simulates
;; that entirely Scheme-side. Idempotency: order-settle! only resumes a flow
;; still waiting on payment, so a replayed webhook is a no-op at the flow level,
;; and order-pay is idempotent at the ledger level.

;; The flow definition (Scheme source). oid is in scope throughout the begin.
(define
  order-flow-src
  "(defflow order-lifecycle (lambda (oid) (begin (request (quote reserve) oid) (request (quote payment) oid) (request (quote fulfil) oid))))")

;; Build a flow env with the order flow registered. Never returns the env from
;; an eval boundary (the env is large/cyclic — serializing it hangs).
(define
  order-make-env
  (fn
    ()
    (let
      ((env (flow-make-env)))
      (begin (flow-run-in env order-flow-src) env))))

;; --- thin Scheme bridge (string-interpolated flow ops) ---

(define
  order-flow-start
  (fn
    (env oid)
    (flow-run-in env (str "(flow/start order-lifecycle \"" oid "\")"))))

(define
  order-flow-resume
  (fn
    (env id sym)
    (flow-run-in env (str "(flow/resume " id " (quote " sym "))"))))

(define
  order-flow-status
  (fn (env id) (flow-run-in env (str "(flow/status " id ")"))))
(define
  order-flow-result
  (fn (env id) (flow-run-in env (str "(flow/result " id ")"))))

;; The request kind the flow with this id is waiting on, or nil if it is not
;; suspended on a host request (done / cancelled / unknown).
(define
  order-flow-waiting
  (fn
    (env id)
    (let
      ((reqs (flow-run-in env "(flow-host-requests)")))
      (let
        ((mine (filter (fn (r) (= (first r) id)) reqs)))
        (if (empty? mine) nil (nth (first mine) 1))))))

;; Id out of a (flow-suspended id tag) start/resume result.
(define order-susp-id (fn (susp) (nth susp 1)))

;; --- high-level lifecycle (flow + ledger composed) ---

;; Create the order, start the flow, service the reserve step, and leave the
;; flow suspended at payment. Returns the flow id (needed to settle later).
(define
  order-begin!
  (fn
    (env b oid at quote)
    (begin
      (order-create b oid at quote)
      (let
        ((id (order-susp-id (order-flow-start env oid))))
        (begin
          (order-reserve b oid (+ at 1) {})
          (order-flow-resume env id :reserved)
          id)))))

;; Settle a payment: record it, resume the flow past payment, service fulfil.
;; Idempotent — only acts when the flow is still waiting on payment, so a
;; replayed webhook returns :already-settled without double-charging.
(define
  order-settle!
  (fn
    (env b id oid ref at amount)
    (if
      (= (order-flow-waiting env id) "payment")
      (begin
        (order-pay b oid ref at amount)
        (order-flow-resume env id :paid)
        (order-fulfil b oid (+ at 1) {})
        (order-flow-resume env id :fulfilled)
        :settled)
      :already-settled)))

;; Simulate a process restart: export the flow store, reset the runtime, reload
;; the flow definition, reimport the store. Done entirely Scheme-side so the
;; (large) store is never marshalled across the boundary. The persist ledger is
;; a separate store and is unaffected. Suspended flows resume afterwards.
(define
  order-flow-restart!
  (fn
    (env)
    (flow-run-in
      env
      (str
        "(begin (define _saved (flow-store-export)) "
        flow-reset-src
        " "
        order-flow-src
        " (flow-store-import! _saved) #t)"))))
