import { C as RefundExceedsPaidError, P as isReturnTransitionError, S as OrderValidationError, b as OrderNotChargeableError, l as order, n as loadOrderChildren, p as refundIntent, s as assertUuid, w as ReturnTransitionError, x as OrderNotFoundError } from "./order-assembly-4nLsQgCN.js";
import { and, eq, sql } from "drizzle-orm";
//#region src/returns/machine.ts
const TERMINAL = /* @__PURE__ */ new Set(["refunded", "rejected"]);
function invalid(state, cmd) {
	throw new ReturnTransitionError({
		from: state.status,
		command: cmd.type
	});
}
function decideReturn(state, cmd) {
	if (TERMINAL.has(state.status)) invalid(state, cmd);
	switch (state.status) {
		case "requested":
			if (cmd.type === "vendor.approve") return [{ type: "return.approved" }];
			if (cmd.type === "vendor.reject") return [{ type: "return.rejected" }];
			if (cmd.type === "buyer.cancel") return [{ type: "return.cancelled" }];
			if (cmd.type === "sla.autoApprove") return [{ type: "return.approved" }];
			return invalid(state, cmd);
		case "approved":
			if (cmd.type === "buyer.cancel") return [{ type: "return.cancelled" }];
			if (cmd.type === "admin.override" && cmd.to === "refunded") return [{ type: "return.refunded" }];
			if (cmd.type === "admin.override") return [{
				type: "return.overridden",
				to: cmd.to
			}];
			return invalid(state, cmd);
		case "in_transit":
			if (cmd.type === "carrier.received") return [{ type: "return.received" }];
			if (cmd.type === "admin.override" && cmd.to === "refunded") return [{ type: "return.refunded" }];
			if (cmd.type === "admin.override") return [{
				type: "return.overridden",
				to: cmd.to
			}];
			return invalid(state, cmd);
		case "received":
			if (cmd.type === "admin.override" && cmd.to === "refunded") return [{ type: "return.refunded" }];
			if (cmd.type === "admin.override") return [{
				type: "return.overridden",
				to: cmd.to
			}];
			return invalid(state, cmd);
		default: return invalid(state, cmd);
	}
}
//#endregion
//#region src/returns/execute-refund.ts
const REFUNDABLE_STATUSES = /* @__PURE__ */ new Set([
	"paid",
	"partially_refunded",
	"fulfilled",
	"completed",
	"unfulfillable"
]);
function firstRow(res) {
	return ((Array.isArray(res) ? res : res.rows) ?? [])[0];
}
function sumRefundLines(lines) {
	let total = 0n;
	for (const line of lines) total += line.amount;
	return total;
}
function assertActorMayRefund(actor, orderLines, lines, orderId) {
	const lineById = new Map(orderLines.map((line) => [line.id, line]));
	for (const refundLine of lines) if (!lineById.has(refundLine.orderLineId)) throw new OrderNotFoundError(orderId);
	if (actor.isAdmin === true) return;
	if (typeof actor.vendorId === "string" && actor.vendorId.length > 0 && lines.every((refundLine) => {
		const line = lineById.get(refundLine.orderLineId);
		return line.vendorId === actor.vendorId && line.vendorId !== null;
	})) return;
	throw new OrderNotFoundError(orderId);
}
function assertRefundableStatus(status, orderId) {
	if (!REFUNDABLE_STATUSES.has(status)) throw new OrderNotChargeableError(orderId);
}
async function recomputeOrderRefundStatus(tx, orderId) {
	const orderRow = firstRow(await tx.execute(sql`
    SELECT total
    FROM "order"
    WHERE id = ${orderId}::uuid
    FOR UPDATE
  `));
	if (!orderRow) return;
	const sumRow = firstRow(await tx.execute(sql`
    SELECT COALESCE(SUM(amount), 0) AS refunded
    FROM refund_intent
    WHERE order_id = ${orderId}::uuid
      AND status = 'executed'
  `));
	const newStatus = BigInt(String(sumRow?.refunded ?? 0)) === BigInt(String(orderRow.total)) ? "refunded" : "partially_refunded";
	const now = /* @__PURE__ */ new Date();
	await tx.update(order).set({
		status: newStatus,
		updatedAt: now
	}).where(eq(order.id, orderId));
}
async function claimRefundIntentInTx(tx, orderId, thisRefund) {
	const orderRow = firstRow(await tx.execute(sql`
    SELECT total, charge_ref
    FROM "order"
    WHERE id = ${orderId}::uuid
    FOR UPDATE
  `));
	if (!orderRow) throw new OrderNotFoundError(orderId);
	const chargeRef = orderRow.charge_ref;
	if (chargeRef === null || chargeRef === void 0 || String(chargeRef).length === 0) throw new OrderNotChargeableError(orderId);
	const amountPaid = BigInt(String(orderRow.total));
	const priorRow = firstRow(await tx.execute(sql`
    SELECT COALESCE(SUM(amount), 0) AS prior
    FROM refund_intent
    WHERE order_id = ${orderId}::uuid
      AND status IN ('pending', 'executed')
  `));
	const prior = BigInt(String(priorRow?.prior ?? 0));
	if (prior + thisRefund > amountPaid) throw new RefundExceedsPaidError({
		orderId,
		requested: thisRefund,
		alreadyRefunded: prior,
		paid: amountPaid
	});
	const seqRow = firstRow(await tx.execute(sql`
    SELECT COALESCE(MAX(seq), 0) + 1 AS next_seq
    FROM refund_intent
    WHERE order_id = ${orderId}::uuid
  `));
	const seq = Number(seqRow?.next_seq ?? 1);
	const intentId = crypto.randomUUID();
	const refundKey = `refund:${intentId}`;
	const now = /* @__PURE__ */ new Date();
	await tx.insert(refundIntent).values({
		id: intentId,
		orderId,
		seq,
		amount: thisRefund,
		status: "pending",
		refundKey,
		createdAt: now,
		updatedAt: now
	});
	return {
		intentId,
		seq,
		refundKey,
		chargeRef: String(chargeRef),
		thisRefund
	};
}
async function settleRefundIntentInTx(tx, intentId, orderId, providerRef) {
	const now = /* @__PURE__ */ new Date();
	const [settled] = await tx.update(refundIntent).set({
		status: "executed",
		providerRef,
		updatedAt: now
	}).where(and(eq(refundIntent.id, intentId), eq(refundIntent.status, "pending"))).returning({ id: refundIntent.id });
	if (!settled) return false;
	await recomputeOrderRefundStatus(tx, orderId);
	return true;
}
async function executeRefund(db, orderId, lines, refund, actor) {
	assertUuid(orderId, "orderId");
	if (lines.length === 0) throw new OrderValidationError("lines");
	for (const line of lines) assertUuid(line.orderLineId, "orderLineId");
	const [orderRow] = await db.select().from(order).where(eq(order.id, orderId));
	if (!orderRow) throw new OrderNotFoundError(orderId);
	const { lines: orderLines } = await loadOrderChildren(db, orderId);
	assertActorMayRefund(actor, orderLines, lines, orderId);
	assertRefundableStatus(orderRow.status, orderId);
	for (const line of lines) if (line.amount <= 0n) throw new OrderValidationError("amount");
	const thisRefund = sumRefundLines(lines);
	if (thisRefund > BigInt(Number.MAX_SAFE_INTEGER)) throw new OrderValidationError("amount");
	const claimed = await db.transaction((tx) => claimRefundIntentInTx(tx, orderId, thisRefund));
	let res;
	res = await refund({
		refundKey: claimed.refundKey,
		chargeKey: claimed.chargeRef,
		refundId: claimed.intentId,
		amount: Number(thisRefund)
	});
	if (res.kind === "refunded") await db.transaction((tx) => settleRefundIntentInTx(tx, claimed.intentId, orderId, res.providerRef));
	return res;
}
//#endregion
//#region src/returns/reconcile-stuck-refunds.ts
const DEFAULT_LIMIT = 100;
async function reconcileStuckRefunds(db, refund, opts) {
	const limit = opts?.limit ?? DEFAULT_LIMIT;
	const conditions = [eq(refundIntent.status, "pending")];
	if (opts?.olderThan !== void 0) conditions.push(sql`${refundIntent.createdAt} < ${opts.olderThan}`);
	const pendingIntents = await db.select().from(refundIntent).where(and(...conditions)).orderBy(refundIntent.createdAt).limit(limit);
	let settled = 0;
	let stillPending = 0;
	const failures = [];
	for (const intent of pendingIntents) try {
		const [orderRow] = await db.select({ chargeRef: order.chargeRef }).from(order).where(eq(order.id, intent.orderId));
		if (!orderRow?.chargeRef) {
			failures.push({
				intentId: intent.id,
				orderId: intent.orderId,
				error: new OrderNotChargeableError(intent.orderId)
			});
			continue;
		}
		if (intent.amount > BigInt(Number.MAX_SAFE_INTEGER)) {
			failures.push({
				intentId: intent.id,
				orderId: intent.orderId,
				error: new OrderValidationError("amount")
			});
			continue;
		}
		const res = await refund({
			refundKey: intent.refundKey,
			chargeKey: orderRow.chargeRef,
			refundId: intent.id,
			amount: Number(intent.amount)
		});
		if (res.kind === "pending") {
			stillPending++;
			continue;
		}
		if (await db.transaction((tx) => settleRefundIntentInTx(tx, intent.id, intent.orderId, res.providerRef))) settled++;
	} catch (error) {
		failures.push({
			intentId: intent.id,
			orderId: intent.orderId,
			error
		});
	}
	return {
		scanned: pendingIntents.length,
		settled,
		stillPending,
		failures
	};
}
//#endregion
export { ReturnTransitionError, decideReturn, executeRefund, isReturnTransitionError, reconcileStuckRefunds };
