// ---------------------------------------------------------------------------
// Metered execution stack: cloud's billing overlay over the execution seams.
//
// Cloud is the only host that meters executions, and BOTH of its execution
// planes do so: the HTTP `/api/*` executor plane (api/protected.ts) and the MCP
// session Durable Object (mcp/session-durable-object.ts). This module composes
// the four billing-free `CloudExecutionSeamsLayer` seams with an `EngineDecorator`
// that calls `AutumnService.trackExecution` after each execution.
//
// Keeping this in the cloud APP layer (not the neutral `engine/execution-stack.ts`)
// is the billing-boundary line: the seams module names no billing service; the
// metered overlay, provided ONLY here, does. Both planes import THIS layer and
// supply `AutumnService` from their own context (boot for the HTTP plane,
// `AutumnService.Default` locally for the DO).
// ---------------------------------------------------------------------------

import { Effect, Layer } from "effect";

import {
  CodeExecutorProvider,
  DbProvider,
  EngineDecorator,
  HostConfig,
  PluginsProvider,
  type EngineStackIdentity,
} from "@executor-js/api/server";

import { AutumnService } from "../extensions/billing/service";
import type { DbService } from "../db/db";
import { CloudExecutionSeamsLayer } from "../engine/execution-stack";
import { makeExecutionLimitGate } from "./execution-gate";
import { makeCloudExecutionRateLimiter } from "./execution-rate-limit";
import { withExecutionUsageTracking } from "./execution-usage";

// Usage-metering decorator bound to the billing service, plus the two
// pre-execution guards this layer owns, ordered cheapest first:
//   1. rate-limit backstop (counter DO, independent of billing)
//   2. execution balance gate (Autumn check, cached 60s, fails open)
//   3. usage tracking — fire-and-forget (`Effect.runFork`) so the billing
//      call can't stall a user-facing execution.
// The guards wrap OUTSIDE the tracker so a blocked execution is neither run
// nor tracked. One gate/limiter instance per layer build: the balance cache is
// shared by every engine this decorator produces (in the MCP session DO that's
// the session's engines; on the HTTP plane the boot layer builds it once).
export const CloudMeteringEngineDecorator: Layer.Layer<EngineDecorator, never, AutumnService> =
  Layer.effect(EngineDecorator)(
    Effect.map(AutumnService.asEffect(), (autumn): EngineDecorator["Service"] => {
      const balanceGate = makeExecutionLimitGate((organizationId) =>
        autumn.checkExecutionBalance(organizationId),
      );
      const rateLimiter = makeCloudExecutionRateLimiter();
      return {
        decorate: (engine, identity: EngineStackIdentity) =>
          rateLimiter.decorate(
            identity.organizationId,
            balanceGate.decorate(
              identity.organizationId,
              withExecutionUsageTracking(identity.organizationId, engine, (organizationId) =>
                Effect.runFork(autumn.trackExecution(organizationId)),
              ),
            ),
          ),
      };
    }),
  );

/**
 * The metered execution stack used by BOTH cloud planes (HTTP executor plane and
 * MCP session DO): the four billing-free `CloudExecutionSeamsLayer` seams plus
 * the billing decorator. Requires `DbService` (per-request Hyperdrive db) and
 * `AutumnService` (usage metering) from the surrounding context.
 */
export const CloudMeteredExecutionStackLayer: Layer.Layer<
  DbProvider | PluginsProvider | HostConfig | CodeExecutorProvider | EngineDecorator,
  never,
  AutumnService | DbService
> = Layer.merge(CloudExecutionSeamsLayer, CloudMeteringEngineDecorator);
