import { i as isVestingInvariantError, n as VestingInvariantError } from "./errors-CvXxEMgH.js";
import { bigint, check, index, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
import { and, eq, inArray, isNull, lte, sql } from "drizzle-orm";
//#region src/vesting-schema.ts
/** 1:1 opt-in extension of ledgerEntries (entryId → ledgerEntries.id; cross-package, no drizzle relation). */
const ledgerEntryVesting = pgTable("ledger_entry_vesting", {
	entryId: uuid("entry_id").primaryKey(),
	matureAt: timestamp("mature_at", { withTimezone: true }),
	sweptAt: timestamp("swept_at", { withTimezone: true }),
	withdrawableAt: timestamp("withdrawable_at", { withTimezone: true })
}, (t) => [index("ledger_entry_vesting_mature_idx").on(t.matureAt).where(sql`${t.sweptAt} IS NULL`)]);
/** 1:1 opt-in extension of walletBalances (ownerId logical-maps to walletBalances.ownerId — NO cross-package FK; plain text PK). */
const walletVesting = pgTable("wallet_vesting", {
	ownerId: text("owner_id").primaryKey(),
	pendingMinor: bigint("pending_minor", { mode: "bigint" }).notNull().default(0n),
	maturedMinor: bigint("matured_minor", { mode: "bigint" }).notNull().default(0n),
	carriedDebtMinor: bigint("carried_debt_minor", { mode: "bigint" }).notNull().default(0n),
	withdrawableMinor: bigint("withdrawable_minor", { mode: "bigint" }).notNull().default(0n),
	lifetimeEarnedMinor: bigint("lifetime_earned_minor", { mode: "bigint" }).notNull().default(0n),
	updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
}, (t) => [
	check("wallet_vesting_pending_minor_nonneg", sql`${t.pendingMinor} >= 0`),
	check("wallet_vesting_matured_minor_nonneg", sql`${t.maturedMinor} >= 0`),
	check("wallet_vesting_carried_debt_minor_nonneg", sql`${t.carriedDebtMinor} >= 0`),
	check("wallet_vesting_withdrawable_minor_nonneg", sql`${t.withdrawableMinor} >= 0`)
]);
const vestingSchema = {
	ledgerEntryVesting,
	walletVesting
};
//#endregion
//#region src/vesting.ts
async function accrueVesting(tx, input) {
	if (input.amountMinor <= 0n) throw new Error("accrueVesting: amount must be positive");
	const now = /* @__PURE__ */ new Date();
	const isPending = input.matureAt > now;
	const state = isPending ? "pending" : "matured";
	await tx.insert(ledgerEntryVesting).values({
		entryId: input.entryId,
		matureAt: input.matureAt,
		sweptAt: isPending ? null : now
	});
	await tx.insert(walletVesting).values({
		ownerId: input.ownerId,
		pendingMinor: isPending ? input.amountMinor : 0n,
		maturedMinor: isPending ? 0n : input.amountMinor,
		lifetimeEarnedMinor: input.amountMinor,
		updatedAt: now
	}).onConflictDoUpdate({
		target: walletVesting.ownerId,
		set: {
			pendingMinor: isPending ? sql`${walletVesting.pendingMinor} + ${input.amountMinor}` : walletVesting.pendingMinor,
			maturedMinor: isPending ? walletVesting.maturedMinor : sql`${walletVesting.maturedMinor} + greatest(${input.amountMinor} - ${walletVesting.carriedDebtMinor}, 0)`,
			carriedDebtMinor: isPending ? walletVesting.carriedDebtMinor : sql`greatest(${walletVesting.carriedDebtMinor} - ${input.amountMinor}, 0)`,
			lifetimeEarnedMinor: sql`${walletVesting.lifetimeEarnedMinor} + ${input.amountMinor}`,
			updatedAt: now
		}
	});
	return { state };
}
async function promoteEntries(tx, input) {
	const ids = input.earnEntryIds.map((e) => e.entryId);
	if (new Set(ids).size !== ids.length) throw new VestingInvariantError("duplicate earnEntryId");
	const dOwners = input.perOwnerDeltas.map((d) => d.ownerId);
	if (new Set(dOwners).size !== dOwners.length) throw new VestingInvariantError("duplicate owner in perOwnerDeltas");
	if (input.perOwnerDeltas.some((d) => d.deltaMinor < 0n)) throw new VestingInvariantError("negative promotion delta");
	if ((await tx.update(ledgerEntryVesting).set({ sweptAt: sql`now()` }).where(and(inArray(ledgerEntryVesting.entryId, ids), isNull(ledgerEntryVesting.sweptAt), lte(ledgerEntryVesting.matureAt, sql`now()`))).returning({ entryId: ledgerEntryVesting.entryId })).length !== input.earnEntryIds.length) throw new VestingInvariantError("earn not found, not matured, or already swept");
	const earnOwners = new Set(input.earnEntryIds.map((e) => e.ownerId));
	const deltaOwners = new Set(dOwners);
	if (earnOwners.size !== deltaOwners.size || [...earnOwners].some((o) => !deltaOwners.has(o))) throw new VestingInvariantError("earn-owner / delta-owner set mismatch");
	for (const { ownerId, deltaMinor } of input.perOwnerDeltas) if ((await tx.update(walletVesting).set({
		pendingMinor: sql`${walletVesting.pendingMinor} - ${deltaMinor}`,
		maturedMinor: sql`${walletVesting.maturedMinor} + greatest(${deltaMinor} - ${walletVesting.carriedDebtMinor}, 0)`,
		carriedDebtMinor: sql`greatest(${walletVesting.carriedDebtMinor} - ${deltaMinor}, 0)`,
		updatedAt: sql`now()`
	}).where(eq(walletVesting.ownerId, ownerId)).returning({ ownerId: walletVesting.ownerId })).length !== 1) throw new VestingInvariantError("missing wallet_vesting row for owner " + ownerId);
}
async function settleClawback(tx, input) {
	if (input.amountMinor <= 0n) throw new VestingInvariantError("settleClawback: amount must be positive");
	const now = /* @__PURE__ */ new Date();
	await tx.insert(walletVesting).values({
		ownerId: input.ownerId,
		carriedDebtMinor: input.amountMinor,
		updatedAt: now
	}).onConflictDoUpdate({
		target: walletVesting.ownerId,
		set: {
			maturedMinor: sql`greatest(${walletVesting.maturedMinor} - ${input.amountMinor}, 0)`,
			carriedDebtMinor: sql`${walletVesting.carriedDebtMinor} + greatest(${input.amountMinor} - ${walletVesting.maturedMinor}, 0)`,
			updatedAt: now
		}
	});
}
/**
* Recomputes `withdrawableMinor` as max(0, eligible − paid) under the wallet_vesting owner lock.
*
* LOCK-THEN-COMPUTE (P5): upsert-then-lock the owner row FIRST, then call `compute()` so the
* caller reads eligible/paid under the held lock, then write the clamped withdrawable.
*
* DEADLOCK-AVOIDANCE: `compute()` MUST read matured `ledger_entry_vesting` rows PLAIN — never
* FOR UPDATE. Locking entry rows while holding the owner lock reverses lock order vs
* `promoteEntries`' sweep (entry→owner) and deadlocks. The owner lock alone serializes
* recompute against the T12 withdrawal debit (which locks the SAME wallet_vesting row first).
*/
async function recomputeWithdrawable(tx, target, compute) {
	const now = /* @__PURE__ */ new Date();
	const { ownerId } = target;
	await tx.insert(walletVesting).values({
		ownerId,
		pendingMinor: 0n,
		maturedMinor: 0n,
		withdrawableMinor: 0n,
		lifetimeEarnedMinor: 0n,
		updatedAt: now
	}).onConflictDoUpdate({
		target: walletVesting.ownerId,
		set: { updatedAt: now }
	}).returning();
	const { eligibleMinor, paidMinor } = await compute();
	const w = eligibleMinor - paidMinor;
	const withdrawableMinor = w > 0n ? w : 0n;
	await tx.update(walletVesting).set({
		withdrawableMinor,
		updatedAt: now
	}).where(eq(walletVesting.ownerId, ownerId));
}
async function setEntryWithdrawableAt(tx, input) {
	if ((await tx.update(ledgerEntryVesting).set({ withdrawableAt: input.withdrawableAt }).where(eq(ledgerEntryVesting.entryId, input.entryId)).returning({ entryId: ledgerEntryVesting.entryId })).length !== 1) throw new VestingInvariantError("missing ledger_entry_vesting row for entry " + input.entryId);
}
async function getVesting(tx, ownerId) {
	const [row] = await tx.select({
		pendingMinor: walletVesting.pendingMinor,
		maturedMinor: walletVesting.maturedMinor,
		carriedDebtMinor: walletVesting.carriedDebtMinor,
		withdrawableMinor: walletVesting.withdrawableMinor,
		lifetimeEarnedMinor: walletVesting.lifetimeEarnedMinor
	}).from(walletVesting).where(eq(walletVesting.ownerId, ownerId)).limit(1);
	const maturedMinor = row?.maturedMinor ?? 0n;
	const carriedDebtMinor = row?.carriedDebtMinor ?? 0n;
	return {
		pendingMinor: row?.pendingMinor ?? 0n,
		maturedMinor,
		carriedDebtMinor,
		netMinor: maturedMinor - carriedDebtMinor,
		withdrawableMinor: row?.withdrawableMinor ?? 0n,
		lifetimeEarnedMinor: row?.lifetimeEarnedMinor ?? 0n
	};
}
//#endregion
export { VestingInvariantError, accrueVesting, getVesting, isVestingInvariantError, ledgerEntryVesting, promoteEntries, recomputeWithdrawable, setEntryWithdrawableAt, settleClawback, vestingSchema, walletVesting };
