// src/billing/index.ts
function createTcloudKeyProvisioner(client) {
  return {
    createKey: async (input) => {
      const created = await client.createKey(input);
      return { id: created.id, key: created.key };
    },
    revokeKey: (keyId) => client.revokeKey(keyId),
    getKey: async (keyId) => {
      const info = await client.getKey(keyId);
      return {
        budgetUsd: info.budgetUsd ?? void 0,
        budgetSpent: info.budgetSpent ?? void 0,
        expiresAt: info.expiresAt ?? null
      };
    }
  };
}
function nextPeriodEnd(now) {
  return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 1, 0, 0, 0, 0));
}
async function assertCryptoUsable(crypto) {
  const probe = "agent-app:key-manager:crypto-probe";
  let roundTripped;
  try {
    roundTripped = await crypto.decrypt(await crypto.encrypt(probe));
  } catch (err) {
    throw new Error(
      "Key encryption is misconfigured: the crypto seam threw before minting. Validate FIELD_ENCRYPTION_KEY (64-char hex) at startup. No platform key was minted.",
      { cause: err }
    );
  }
  if (roundTripped !== probe) {
    throw new Error(
      "Key encryption is misconfigured: encrypt/decrypt round-trip did not preserve the plaintext. No platform key was minted."
    );
  }
}
function createPlatformBalanceManager(opts) {
  const { client, planLimits, freePlan, productSlug } = opts;
  const getState = async (userId) => {
    const identity = await client.resolveIdentity(userId);
    if (!identity || !identity.apiKey) {
      const limits2 = planLimits[freePlan];
      return {
        platformUserId: identity?.platformUserId ?? null,
        plan: freePlan,
        monthlyBalanceUsd: limits2.monthlyBalanceUsd,
        remainingBalanceUsd: 0,
        lifetimeSpentUsd: 0,
        concurrency: limits2.concurrency,
        overageAllowed: limits2.overageAllowed
      };
    }
    const [plan, balance] = await Promise.all([client.getPlan(identity.apiKey), client.getBalance(identity.apiKey)]);
    const limits = planLimits[plan];
    return {
      platformUserId: identity.platformUserId,
      plan,
      monthlyBalanceUsd: limits.monthlyBalanceUsd,
      remainingBalanceUsd: balance.balance,
      lifetimeSpentUsd: balance.lifetimeSpent,
      concurrency: limits.concurrency,
      overageAllowed: limits.overageAllowed
    };
  };
  const canStartBillableTurn = async (userId) => {
    const state = await getState(userId);
    if (!state.platformUserId) return { allowed: false, state };
    const allowed = state.overageAllowed || state.remainingBalanceUsd > 0;
    return { allowed, state };
  };
  const deduct = async (userId, params) => {
    const identity = await client.resolveIdentity(userId);
    if (!identity) throw new Error("Shared billing requires a platform-linked user");
    await client.deduct({
      platformUserId: identity.platformUserId,
      amountUsd: params.amountUsd,
      type: params.type,
      description: params.description,
      referenceId: params.referenceId
    });
  };
  const getProductUsage = async (userId) => {
    const identity = await client.resolveIdentity(userId);
    if (!identity?.apiKey) return { spentUsd: 0, transactionCount: 0 };
    const rows = await client.getUsageByProduct(identity.apiKey);
    const product = rows.find((row) => row.product === productSlug);
    return { spentUsd: product?.totalSpent ?? 0, transactionCount: product?.count ?? 0 };
  };
  return { getState, canStartBillableTurn, deduct, getProductUsage };
}
function createWorkspaceKeyManager(opts) {
  const clock = opts.now ?? (() => /* @__PURE__ */ new Date());
  const product = opts.product ?? "router";
  const getUsage = async (workspaceId) => {
    const active = await opts.store.getActive(workspaceId);
    if (!active) return null;
    const info = await opts.provisioner.getKey(active.keyId);
    const budgetUsd = info.budgetUsd ?? active.budgetUsd;
    const budgetSpent = info.budgetSpent ?? 0;
    const budgetRemaining = Math.max(0, budgetUsd - budgetSpent);
    return {
      keyId: active.keyId,
      budgetUsd,
      budgetSpent,
      budgetRemaining,
      expiresAt: info.expiresAt ?? (active.expiresAt ? active.expiresAt.toISOString() : null),
      exhausted: budgetRemaining <= 0
    };
  };
  const rotateKey = async (workspaceId, ropts) => {
    const now = clock();
    const allowance = ropts?.budgetUsd ?? opts.defaultBudgetUsd;
    let budgetUsd = allowance;
    if (ropts?.rollover) {
      const prior = await getUsage(workspaceId).catch(() => null);
      budgetUsd = allowance + (prior?.budgetRemaining ?? 0);
      if (ropts.rolloverCapUsd != null) budgetUsd = Math.min(budgetUsd, ropts.rolloverCapUsd);
    }
    await assertCryptoUsable(opts.crypto);
    const expiresAt = nextPeriodEnd(now);
    const created = await opts.provisioner.createKey({ name: `ws:${workspaceId}`, product, budgetUsd, expiresAt: expiresAt.toISOString() });
    if (!created.key || !created.id) throw new Error("tcloud createKey returned no key");
    const mintedKeyId = created.id;
    let keyEncrypted;
    let priors;
    try {
      keyEncrypted = await opts.crypto.encrypt(created.key);
      priors = await opts.store.listActive(workspaceId);
      await opts.store.insert({ workspaceId, keyId: mintedKeyId, keyEncrypted, budgetUsd, expiresAt });
    } catch (err) {
      try {
        await opts.provisioner.revokeKey(mintedKeyId);
      } catch (revokeErr) {
        console.error(
          `[workspace-key-manager] FAILED to revoke orphaned child key ${mintedKeyId} for workspace ${workspaceId} after a post-mint failure \u2014 it now leaks parent-key budget. Revoke error:`,
          revokeErr
        );
      }
      throw err;
    }
    for (const p of priors) {
      await opts.store.markRevoked(p.id, now);
      try {
        await opts.provisioner.revokeKey(p.keyId);
      } catch {
      }
    }
    return created.key;
  };
  const ensureKey = async (workspaceId, eopts) => {
    const now = clock();
    const active = await opts.store.getActive(workspaceId);
    if (active && (!active.expiresAt || active.expiresAt.getTime() > now.getTime())) {
      return opts.crypto.decrypt(active.keyEncrypted);
    }
    return rotateKey(workspaceId, { budgetUsd: eopts?.budgetUsd });
  };
  return { ensureKey, rotateKey, getUsage };
}

export {
  createTcloudKeyProvisioner,
  createPlatformBalanceManager,
  createWorkspaceKeyManager
};
//# sourceMappingURL=chunk-GXE6KHTS.js.map