// `orders.fulfill` — EXECUTOR (server-only).
//
// Durable order-fulfillment pipeline. The default export is a factory
// `(ctx) => execute`; the CLI calls `workflow.toLayer(execute)` at boot
// so the executor closes over the live `AppContext` (store, events, …).
//
// The flow, every step durable (replayed from its journaled result on a
// crash/resume rather than re-run):
//
//   1. step 'reserve-stock' — flip the order to `reserved`.
//   2. sleep                — a (short) durable delay; the wake time is
//                             journaled in the engine. Bump to hours/days
//                             for a real "hold before charging" window.
//   3. awaitSignal 'approval' — PARK until a human sends the approval
//                             signal (via the `orders.approve` mutation
//                             or the dashboard "Send signal" button).
//   4. step 'ship' / mark rejected — branch on the decision.
//
// `withCompensation` wraps the reserve step with saga-style rollback:
// the finalizer runs only if the WHOLE workflow later fails, releasing
// the reservation so a downstream failure doesn't strand held stock.

import { step, sleep, awaitSignal, withCompensation } from '@voltro/workflow'
import { Effect, Schema } from 'effect'
import type { AppContext } from '@voltro/runtime'

const Decision = Schema.Struct({ approved: Schema.Boolean })

const buildExecute = (ctx: AppContext) =>
  (payload: { orderId: string; tenantId: string }, _executionId: string) =>
    Effect.gen(function* () {
      const { orderId } = payload

      // 1. Reserve stock. `withCompensation` registers an undo that runs
      //    only if the workflow as a whole fails after this point.
      yield* withCompensation(
        step({
          name:    'reserve-stock',
          input:   { orderId },
          success: Schema.Struct({ orderId: Schema.String }),
          execute: Effect.tryPromise({
            try:   async () => {
              await ctx.store.update('orders', orderId, { status: 'reserved' })
              return { orderId }
            },
            catch: (error) => error as never,
          }),
        }),
        // Saga rollback: release the reservation on whole-workflow failure.
        () =>
          Effect.promise(() =>
            ctx.store
              .update('orders', orderId, { status: 'placed' })
              .then(() => undefined),
          ),
      )

      // 2. Durable delay. The wake is journaled — survives a restart.
      yield* sleep({ name: 'settle-window', duration: '5 seconds' })

      // 3. Human-approval gate. The run parks here until an external
      //    caller sends a matching `approval` signal; the parsed payload
      //    is journaled, so a post-signal replay returns it instantly.
      const decision = yield* awaitSignal(ctx, {
        name:   'approval',
        schema: Decision,
        // pollIntervalMs defaults to 200 (backoff → 5s); timeoutMs 24h.
      })

      if (!decision.approved) {
        yield* step({
          name:    'mark-rejected',
          input:   { orderId },
          success: Schema.Struct({ orderId: Schema.String }),
          execute: Effect.tryPromise({
            try:   async () => {
              await ctx.store.update('orders', orderId, { status: 'rejected' })
              return { orderId }
            },
            catch: (error) => error as never,
          }),
        })
        return { orderId, outcome: 'rejected' as const }
      }

      // 4. Approved → ship.
      yield* step({
        name:    'ship',
        input:   { orderId },
        success: Schema.Struct({ orderId: Schema.String }),
        execute: Effect.tryPromise({
          try:   async () => {
            await ctx.store.update('orders', orderId, { status: 'shipped' })
            return { orderId }
          },
          catch: (error) => error as never,
        }),
      })

      return { orderId, outcome: 'shipped' as const }
    })

export default buildExecute
