import { env } from "cloudflare:workers";
import { accountDb, stmt } from "../scoped-db";
import type { ScopedStatement } from "../scoped-db";
import { locate, registerLocator } from "../scoped-db/locators";
import { randomId } from "../crypto";
import type { MeterId } from "~/lib/meters";
import { parsePlans } from "~/lib/plans";
import { CREDIT_POOLS, METER_PRICES, creditPoolFor } from "~/lib/pricing";
import type { CreditPoolId } from "~/lib/pricing";
/**
* Prepaid credits: a local ledger per account and credit pool (see CREDIT_POOLS in src/lib/pricing.ts).
* Compute credit is held in standard-1 runner seconds and spent by every priced compute meter at its price
* relative to standard-1; AI token credit is spent 1:1 by ai.tokens.
*
* Draw-down order for a creditable meter is plan-included first, then credits, then overage (paid plans) or
* the hard cap (free). Draws happen in the same account batch that inserts the usage event, keyed by the event's
* idempotency key, so a retried event never draws twice. reserveUsage counts the unspent balance as extra
* headroom inside its own atomic batch, so concurrent holds can never commit more than plan + credits.
*/
/** Whether usage on `meter` can be paid for with prepaid credit. */
export function isCreditMeter(meter: string): meter is MeterId {
return creditPoolFor(meter) !== null;
}
export function isCreditPool(pool: string): pool is CreditPoolId {
return Object.hasOwn(CREDIT_POOLS, pool);
}
/** Older ledgers and pack configs named the compute pool after its base meter. */
export function normalizePool(value: string): CreditPoolId | null {
if (isCreditPool(value)) return value;
return value.startsWith("compute.") && isCreditMeter(value) ? "compute" : null;
}
export class CreditError extends Error {}
/** Balances below this are rounding dust and count as empty. */
export const CREDIT_EPSILON = 1e-6;