import { A as isOrderNotChargeableError, C as RefundExceedsPaidError, D as isOrderIdempotencyConflictError, E as isOrderChargeConflictError, M as isOrderValidationError, N as isRefundExceedsPaidError, O as isOrderIntegrityError, S as OrderValidationError, T as isImmutableFieldError, _ as OrderIdempotencyConflictError, a as ORDER_STATUSES, b as OrderNotChargeableError, c as isOrderStatus, d as orderStep, f as ordersSchema, g as OrderChargeConflictError, h as ImmutableFieldError, i as rowToBuyerRef$1, j as isOrderNotFoundError, k as isOrderMigrateError, l as order, m as vendorSplit, n as loadOrderChildren, o as assertPositiveInt, p as refundIntent, r as loadOrdersChildrenBatch, s as assertUuid, t as assembleOrder, u as orderLine, v as OrderIntegrityError, x as OrderNotFoundError, y as OrderMigrateError } from "./order-assembly-4nLsQgCN.js";
import { and, desc, eq, exists, lt, or, sql } from "drizzle-orm";
//#region src/claim-for-charge.ts
function rowToBuyerRef(row) {
	if (row.buyerUserId !== null) return { userId: row.buyerUserId };
	return { guestEmail: row.buyerGuestEmail };
}
async function loadOrder(tx, orderId) {
	const [row] = await tx.select().from(order).where(eq(order.id, orderId));
	if (!row) throw new OrderNotChargeableError(orderId);
	const lineRows = await tx.select().from(orderLine).where(eq(orderLine.orderId, orderId));
	const splitRows = await tx.select().from(vendorSplit).where(eq(vendorSplit.orderId, orderId));
	const lines = lineRows.map((line) => ({
		id: line.id,
		orderId: line.orderId,
		variantId: line.variantId,
		kind: line.kind,
		qty: line.qty,
		unitPrice: line.unitPrice,
		lineTotal: line.lineTotal,
		vendorId: line.vendorId ?? null
	}));
	const splits = splitRows.map((split) => ({
		id: split.id,
		orderId: split.orderId,
		vendorId: split.vendorId ?? null,
		amount: split.amount,
		funder: split.funder
	}));
	return {
		id: row.id,
		buyerRef: rowToBuyerRef(row),
		status: row.status,
		currency: row.currency,
		priceMode: row.priceMode,
		subtotal: row.subtotal,
		tax: row.tax,
		discount: row.discount,
		total: row.total,
		lines,
		splits,
		fulfillmentState: { steps: {} }
	};
}
async function claimForCharge(tx, orderId) {
	assertUuid(orderId, "orderId");
	const now = /* @__PURE__ */ new Date();
	const [updated] = await tx.update(order).set({
		status: "charging",
		updatedAt: now
	}).where(and(eq(order.id, orderId), eq(order.status, "pending"))).returning();
	if (!updated) throw new OrderNotChargeableError(orderId);
	return loadOrder(tx, orderId);
}
//#endregion
//#region src/integrity.ts
function assertOrderIntegrity(o) {
	let lineSum = 0n;
	for (const line of o.lines) lineSum += line.lineTotal;
	if (lineSum !== o.subtotal) throw new OrderIntegrityError("lines");
	if (o.subtotal + o.tax - o.discount !== o.total) throw new OrderIntegrityError("total");
	let splitSum = 0n;
	for (const split of o.splits) splitSum += split.amount;
	if (splitSum !== o.total) throw new OrderIntegrityError("splits");
}
//#endregion
//#region src/create-order.ts
function parseBuyerRef(ref) {
	const hasUser = "userId" in ref && ref.userId !== void 0 && ref.userId !== "";
	const hasGuest = "guestEmail" in ref && ref.guestEmail !== void 0 && ref.guestEmail !== "";
	if (hasUser && hasGuest) throw new OrderValidationError("buyerRef");
	if (!hasUser && !hasGuest) throw new OrderValidationError("buyerRef");
	if (hasUser) {
		assertUuid(ref.userId, "userId");
		return {
			buyerUserId: ref.userId,
			buyerGuestEmail: null
		};
	}
	return {
		buyerUserId: null,
		buyerGuestEmail: ref.guestEmail
	};
}
function assertSingleCurrency(orderCurrency, lines) {
	for (const line of lines) if (line.currency !== orderCurrency) throw new OrderValidationError("currency");
}
function assertLineTotals(lines) {
	for (const line of lines) {
		assertUuid(line.variantId, "variantId");
		assertPositiveInt(line.qty, "qty");
		const expected = line.unitPrice * BigInt(line.qty);
		if (line.lineTotal !== expected) throw new OrderIntegrityError("lines");
	}
}
function assertIdempotencyKey(idempotencyKey) {
	if (idempotencyKey.length === 0) throw new OrderValidationError("idempotencyKey");
}
function bigintToDecimal(value) {
	return value.toString();
}
function computeRequestHash(input) {
	const payload = {
		buyerRef: input.buyerRef,
		currency: input.currency,
		priceMode: input.priceMode,
		subtotal: bigintToDecimal(input.subtotal),
		tax: bigintToDecimal(input.tax),
		discount: bigintToDecimal(input.discount),
		total: bigintToDecimal(input.total),
		lines: input.lines.map((line) => ({
			variantId: line.variantId,
			kind: line.kind,
			qty: line.qty,
			unitPrice: bigintToDecimal(line.unitPrice),
			lineTotal: bigintToDecimal(line.lineTotal),
			currency: line.currency,
			vendorId: line.vendorId ?? null
		})),
		splits: input.splits.map((split) => ({
			vendorId: split.vendorId,
			amount: bigintToDecimal(split.amount),
			funder: split.funder
		}))
	};
	return JSON.stringify(payload);
}
async function createOrder(tx, input) {
	assertIdempotencyKey(input.idempotencyKey);
	const buyer = parseBuyerRef(input.buyerRef);
	assertSingleCurrency(input.currency, input.lines);
	assertLineTotals(input.lines);
	assertOrderIntegrity(input);
	const requestHash = computeRequestHash(input);
	const now = /* @__PURE__ */ new Date();
	const [created] = await tx.insert(order).values({
		buyerUserId: buyer.buyerUserId,
		buyerGuestEmail: buyer.buyerGuestEmail,
		status: "pending",
		currency: input.currency,
		priceMode: input.priceMode,
		subtotal: input.subtotal,
		tax: input.tax,
		discount: input.discount,
		total: input.total,
		idempotencyKey: input.idempotencyKey,
		requestHash,
		createdAt: now,
		updatedAt: now
	}).onConflictDoNothing({ target: order.idempotencyKey }).returning();
	if (created) {
		const orderId = created.id;
		const insertedLines = input.lines.length > 0 ? await tx.insert(orderLine).values(input.lines.map((line) => ({
			orderId,
			variantId: line.variantId,
			kind: line.kind,
			qty: line.qty,
			unitPrice: line.unitPrice,
			lineTotal: line.lineTotal,
			vendorId: line.vendorId ?? null,
			createdAt: now
		}))).returning() : [];
		const insertedSplits = input.splits.length > 0 ? await tx.insert(vendorSplit).values(input.splits.map((split) => ({
			orderId,
			vendorId: split.vendorId,
			amount: split.amount,
			funder: split.funder,
			createdAt: now
		}))).returning() : [];
		const lines = insertedLines.map((row) => ({
			id: row.id,
			orderId: row.orderId,
			variantId: row.variantId,
			kind: row.kind,
			qty: row.qty,
			unitPrice: row.unitPrice,
			lineTotal: row.lineTotal,
			vendorId: row.vendorId ?? null
		}));
		const splits = insertedSplits.map((row) => ({
			id: row.id,
			orderId: row.orderId,
			vendorId: row.vendorId ?? null,
			amount: row.amount,
			funder: row.funder
		}));
		return assembleOrder(created, lines, splits, {});
	}
	const [existing] = await tx.select().from(order).where(eq(order.idempotencyKey, input.idempotencyKey));
	if (!existing) throw new OrderValidationError("idempotencyKey");
	if (existing.requestHash !== requestHash) throw new OrderIdempotencyConflictError({ idempotencyKey: input.idempotencyKey });
	const { lines, splits, steps } = await loadOrderChildren(tx, existing.id);
	return assembleOrder(existing, lines, splits, steps);
}
//#endregion
//#region src/fail-order.ts
async function failOrder(tx, orderId, reason) {
	assertUuid(orderId, "orderId");
	const now = /* @__PURE__ */ new Date();
	const [updated] = await tx.update(order).set({
		status: "failed",
		failureReason: reason,
		updatedAt: now
	}).where(and(eq(order.id, orderId), eq(order.status, "charging"))).returning({ id: order.id });
	if (updated) return;
	const [existing] = await tx.select().from(order).where(eq(order.id, orderId));
	if (!existing) throw new OrderNotChargeableError(orderId);
	if (existing.status === "failed") return;
	throw new OrderNotChargeableError(orderId);
}
//#endregion
//#region src/get-order-by-id.ts
async function getOrderById(q, id, actor) {
	assertUuid(id, "orderId");
	const [row] = await q.select().from(order).where(eq(order.id, id));
	if (!row) return null;
	const buyerRef = rowToBuyerRef$1(row);
	if (actor.isAdmin === true) {
		const { lines, splits, steps } = await loadOrderChildren(q, id);
		return assembleOrder(row, lines, splits, steps);
	}
	if (actor.userId !== void 0 && "userId" in buyerRef && buyerRef.userId === actor.userId) {
		const { lines, splits, steps } = await loadOrderChildren(q, id);
		return assembleOrder(row, lines, splits, steps);
	}
	if (actor.vendorId !== void 0) {
		const { lines, splits, steps } = await loadOrderChildren(q, id);
		if (lines.some((line) => line.vendorId === actor.vendorId)) return assembleOrder(row, lines, splits, steps);
	}
	return null;
}
//#endregion
//#region src/list-orders.ts
const CURSOR_SEP = "|";
/**
* Role precedence for list scope (A6 — applied in SQL WHERE):
* 1. isAdmin → unscoped (all orders).
* 2. Else userId and/or vendorId → OR of buyer_user_id match and vendor line EXISTS.
* 3. Else → empty page (no userId, vendorId, or admin).
*/
function actorHasListScope(actor) {
	return actor.isAdmin === true || actor.userId !== void 0 || actor.vendorId !== void 0;
}
function resolveLimit(limit) {
	const raw = limit ?? 20;
	if (!Number.isFinite(raw) || raw < 1) return 20;
	return Math.min(Math.floor(raw), 100);
}
function encodeCursor(createdAt, id) {
	return `${createdAt.toISOString()}${CURSOR_SEP}${id}`;
}
function decodeCursor(cursor) {
	const sep = cursor.lastIndexOf(CURSOR_SEP);
	if (sep <= 0) throw new OrderValidationError("cursor");
	const createdAtRaw = cursor.slice(0, sep);
	const id = cursor.slice(sep + 1);
	const createdAt = new Date(createdAtRaw);
	if (Number.isNaN(createdAt.getTime())) throw new OrderValidationError("cursor");
	return {
		createdAt,
		id
	};
}
function buildActorScope(q, actor) {
	if (actor.isAdmin === true) return;
	const clauses = [];
	if (actor.userId !== void 0) clauses.push(eq(order.buyerUserId, actor.userId));
	if (actor.vendorId !== void 0) clauses.push(exists(q.select({ one: sql`1` }).from(orderLine).where(and(eq(orderLine.orderId, order.id), eq(orderLine.vendorId, actor.vendorId)))));
	if (clauses.length === 0) return;
	return clauses.length === 1 ? clauses[0] : or(...clauses);
}
function buildCursorPredicate(cursor) {
	const { createdAt, id } = decodeCursor(cursor);
	return or(lt(order.createdAt, createdAt), and(eq(order.createdAt, createdAt), lt(order.id, id)));
}
async function listOrders(q, actor, filter = {}) {
	if (!actorHasListScope(actor)) return {
		items: [],
		nextCursor: null
	};
	if (filter.status !== void 0 && !isOrderStatus(filter.status)) throw new OrderValidationError("status");
	const limit = resolveLimit(filter.limit);
	const actorScope = buildActorScope(q, actor);
	const predicates = [];
	if (actorScope !== void 0) predicates.push(actorScope);
	if (filter.status !== void 0) predicates.push(eq(order.status, filter.status));
	if (filter.cursor !== void 0) predicates.push(buildCursorPredicate(filter.cursor));
	const whereClause = predicates.length === 0 ? void 0 : and(...predicates);
	const rows = await q.select().from(order).where(whereClause).orderBy(desc(order.createdAt), desc(order.id)).limit(limit + 1);
	const hasMore = rows.length > limit;
	const pageRows = hasMore ? rows.slice(0, limit) : rows;
	const orderIds = pageRows.map((row) => row.id);
	const { linesByOrderId, splitsByOrderId, stepsByOrderId } = await loadOrdersChildrenBatch(q, orderIds);
	const items = pageRows.map((row) => assembleOrder(row, linesByOrderId.get(row.id) ?? [], splitsByOrderId.get(row.id) ?? [], stepsByOrderId.get(row.id) ?? {}));
	const last = pageRows[pageRows.length - 1];
	return {
		items,
		nextCursor: hasMore && last !== void 0 ? encodeCursor(last.createdAt, last.id) : null
	};
}
//#endregion
//#region src/mark-paid.ts
async function markPaid(tx, orderId, chargeRef) {
	assertUuid(orderId, "orderId");
	const now = /* @__PURE__ */ new Date();
	const [updated] = await tx.update(order).set({
		status: "paid",
		chargeRef,
		updatedAt: now
	}).where(and(eq(order.id, orderId), eq(order.status, "charging"))).returning({ id: order.id });
	if (updated) return;
	const [existing] = await tx.select().from(order).where(eq(order.id, orderId));
	if (!existing) throw new OrderNotChargeableError(orderId);
	if (existing.chargeRef !== null && existing.chargeRef !== "") {
		if (existing.chargeRef === chargeRef) return;
		throw new OrderChargeConflictError({
			orderId,
			existingChargeRef: existing.chargeRef,
			attemptedChargeRef: chargeRef
		});
	}
	throw new OrderNotChargeableError(orderId);
}
//#endregion
//#region src/mark-unfulfillable.ts
async function markUnfulfillable(tx, orderId, reason) {
	assertUuid(orderId, "orderId");
	const now = /* @__PURE__ */ new Date();
	const [updated] = await tx.update(order).set({
		status: "unfulfillable",
		failureReason: reason,
		updatedAt: now
	}).where(and(eq(order.id, orderId), eq(order.status, "paid"))).returning({ id: order.id });
	if (updated) return;
	const [existing] = await tx.select().from(order).where(eq(order.id, orderId));
	if (!existing) throw new OrderNotChargeableError(orderId);
	if (existing.status === "unfulfillable") return;
	throw new OrderNotChargeableError(orderId);
}
//#endregion
//#region src/migrate.ts
const MIGRATION_STATEMENTS = [
	sql`
    CREATE TABLE IF NOT EXISTS "order" (
      id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
      buyer_user_id uuid,
      buyer_guest_email text,
      status text NOT NULL DEFAULT 'pending',
      currency text NOT NULL,
      price_mode text NOT NULL,
      subtotal bigint NOT NULL,
      tax bigint NOT NULL,
      discount bigint NOT NULL,
      total bigint NOT NULL,
      charge_ref text,
      failure_reason text,
      idempotency_key text NOT NULL,
      request_hash text NOT NULL,
      created_at timestamptz NOT NULL DEFAULT NOW(),
      updated_at timestamptz NOT NULL DEFAULT NOW(),
      CONSTRAINT order_buyer_chk CHECK (num_nonnulls(buyer_user_id, buyer_guest_email) = 1)
    )
  `,
	sql`
    CREATE TABLE IF NOT EXISTS order_line (
      id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
      order_id uuid NOT NULL,
      variant_id uuid NOT NULL,
      kind text NOT NULL,
      qty integer NOT NULL,
      unit_price bigint NOT NULL,
      line_total bigint NOT NULL,
      vendor_id text,
      created_at timestamptz NOT NULL DEFAULT NOW()
    )
  `,
	sql`
    CREATE TABLE IF NOT EXISTS vendor_split (
      id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
      order_id uuid NOT NULL,
      vendor_id text,
      amount bigint NOT NULL,
      funder text NOT NULL,
      created_at timestamptz NOT NULL DEFAULT NOW()
    )
  `,
	sql`
    CREATE TABLE IF NOT EXISTS order_step (
      id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
      order_id uuid NOT NULL,
      step_id text NOT NULL,
      result jsonb NOT NULL,
      created_at timestamptz NOT NULL DEFAULT NOW()
    )
  `,
	sql`
    CREATE TABLE IF NOT EXISTS refund_intent (
      id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
      order_id uuid NOT NULL,
      seq integer NOT NULL,
      amount bigint NOT NULL CONSTRAINT refund_intent_amount_chk CHECK (amount > 0),
      status text NOT NULL DEFAULT 'pending',
      refund_key text NOT NULL,
      provider_ref text,
      created_at timestamptz NOT NULL DEFAULT NOW(),
      updated_at timestamptz NOT NULL DEFAULT NOW()
    )
  `,
	sql`
    CREATE UNIQUE INDEX IF NOT EXISTS uq_order_step
    ON order_step (order_id, step_id)
  `,
	sql`
    CREATE UNIQUE INDEX IF NOT EXISTS uq_refund_intent_seq
    ON refund_intent (order_id, seq)
  `,
	sql`
    CREATE UNIQUE INDEX IF NOT EXISTS uq_order_idempotency_key
    ON "order" (idempotency_key)
  `,
	sql`
    CREATE INDEX IF NOT EXISTS ix_order_line_order
    ON order_line (order_id)
  `,
	sql`
    CREATE INDEX IF NOT EXISTS ix_refund_intent_order
    ON refund_intent (order_id)
    WHERE status IN ('pending', 'executed')
  `
];
async function pushSchema(db) {
	try {
		for (const statement of MIGRATION_STATEMENTS) await db.execute(statement);
	} catch (e) {
		throw new OrderMigrateError(e instanceof Error ? e.message : String(e));
	}
}
//#endregion
//#region src/record-step.ts
async function recordStep(tx, orderId, stepId, result) {
	assertUuid(orderId, "orderId");
	if (stepId.length === 0) throw new OrderValidationError("stepId");
	return (await tx.insert(orderStep).values({
		orderId,
		stepId,
		result
	}).onConflictDoNothing({ target: [orderStep.orderId, orderStep.stepId] }).returning({ id: orderStep.id })).length > 0;
}
//#endregion
export { ImmutableFieldError, ORDER_STATUSES, OrderChargeConflictError, OrderIdempotencyConflictError, OrderIntegrityError, OrderMigrateError, OrderNotChargeableError, OrderNotFoundError, OrderValidationError, RefundExceedsPaidError, assertOrderIntegrity, claimForCharge, createOrder, failOrder, getOrderById, isImmutableFieldError, isOrderChargeConflictError, isOrderIdempotencyConflictError, isOrderIntegrityError, isOrderMigrateError, isOrderNotChargeableError, isOrderNotFoundError, isOrderStatus, isOrderValidationError, isRefundExceedsPaidError, listOrders, markPaid, markUnfulfillable, order, orderLine, orderStep, ordersSchema, pushSchema, recordStep, refundIntent, vendorSplit };
