import { a as assertUuid, f as VoucherAlreadyRedeemedError, i as assertPositiveInt, l as FulfillmentValidationError, m as VoucherWrongVendorError, p as VoucherExpiredError } from "./types-Ao3zhbMU.js";
import { sql } from "drizzle-orm";
//#region src/voucher/issue.ts
function allRows(res) {
	return (Array.isArray(res) ? res : res.rows) ?? [];
}
function rowToVoucher$1(row) {
	const expiresAt = row.expires_at;
	const redeemedAt = row.redeemed_at;
	const createdAt = row.created_at;
	return {
		id: String(row.id),
		orderId: String(row.order_id),
		lineId: String(row.line_id),
		unitIndex: Number(row.unit_index),
		vendorId: row.vendor_id == null ? null : String(row.vendor_id),
		state: String(row.state),
		expiresAt: expiresAt == null ? null : expiresAt instanceof Date ? expiresAt : new Date(String(expiresAt)),
		redeemedAt: redeemedAt == null ? null : redeemedAt instanceof Date ? redeemedAt : new Date(String(redeemedAt)),
		createdAt: createdAt instanceof Date ? createdAt : new Date(String(createdAt))
	};
}
async function issueVoucher(tx, input) {
	assertUuid(input.orderId, "orderId");
	assertPositiveInt(input.qty, "qty");
	if (input.lineId.length === 0) throw new FulfillmentValidationError("lineId");
	const valueRows = Array.from({ length: input.qty }, (_, unitIndex) => {
		const id = crypto.randomUUID();
		return sql`(
      ${id}::uuid,
      ${input.orderId}::uuid,
      ${input.lineId},
      ${unitIndex},
      ${input.vendorId},
      'UNREDEEMED',
      ${input.expiresAt}
    )`;
	});
	await tx.execute(sql`
    INSERT INTO voucher (id, order_id, line_id, unit_index, vendor_id, state, expires_at)
    VALUES ${sql.join(valueRows, sql`, `)}
    ON CONFLICT (order_id, line_id, unit_index) DO NOTHING
    RETURNING id, order_id, line_id, unit_index, vendor_id, state, expires_at, redeemed_at, created_at
  `);
	return allRows(await tx.execute(sql`
    SELECT id, order_id, line_id, unit_index, vendor_id, state, expires_at, redeemed_at, created_at
    FROM voucher
    WHERE order_id = ${input.orderId}::uuid
      AND line_id = ${input.lineId}
    ORDER BY unit_index ASC
  `)).map(rowToVoucher$1).filter((v) => v.unitIndex >= 0 && v.unitIndex < input.qty);
}
//#endregion
//#region src/voucher/decide.ts
function decideRedemption(state, event) {
	const current = event.currentState ?? state;
	if (current === "REDEEMED") return {
		ok: false,
		reason: "ALREADY_REDEEMED"
	};
	if (current === "EXPIRED") return {
		ok: false,
		reason: "EXPIRED"
	};
	if (current === "CANCELLED") return {
		ok: false,
		reason: "INVALID"
	};
	if (current !== "UNREDEEMED") return {
		ok: false,
		reason: "INVALID"
	};
	if (event.expiresAt !== null && event.expiresAt <= event.at) return {
		ok: false,
		reason: "EXPIRED"
	};
	if (event.vendorId !== null && event.scanningVendorId !== void 0 && event.scanningVendorId !== event.vendorId) return {
		ok: false,
		reason: "WRONG_VENDOR"
	};
	return { ok: true };
}
//#endregion
//#region src/voucher/redeem.ts
function firstRow(res) {
	return ((Array.isArray(res) ? res : res.rows) ?? [])[0];
}
function rowToVoucher(row) {
	const expiresAt = row.expires_at;
	const redeemedAt = row.redeemed_at;
	const createdAt = row.created_at;
	return {
		id: String(row.id),
		orderId: String(row.order_id),
		lineId: String(row.line_id),
		unitIndex: Number(row.unit_index),
		vendorId: row.vendor_id == null ? null : String(row.vendor_id),
		state: String(row.state),
		expiresAt: expiresAt == null ? null : expiresAt instanceof Date ? expiresAt : new Date(String(expiresAt)),
		redeemedAt: redeemedAt == null ? null : redeemedAt instanceof Date ? redeemedAt : new Date(String(redeemedAt)),
		createdAt: createdAt instanceof Date ? createdAt : new Date(String(createdAt))
	};
}
function throwForDecision(voucherId, scanningVendorId, reason) {
	if (reason === "ALREADY_REDEEMED") throw new VoucherAlreadyRedeemedError(voucherId);
	if (reason === "EXPIRED") throw new VoucherExpiredError(voucherId);
	if (reason === "WRONG_VENDOR") throw new VoucherWrongVendorError({
		voucherId,
		scanningVendorId
	});
	throw new FulfillmentValidationError("voucher");
}
function classifyRedeemFailure(voucherId, scanningVendorId, serverNow, row) {
	const state = String(row.state);
	const vendorId = row.vendor_id == null ? null : String(row.vendor_id);
	const expiresAtRaw = row.expires_at;
	const expiresAt = expiresAtRaw == null ? null : expiresAtRaw instanceof Date ? expiresAtRaw : new Date(String(expiresAtRaw));
	if (state === "REDEEMED") throw new VoucherAlreadyRedeemedError(voucherId);
	if (expiresAt !== null && expiresAt <= serverNow) throw new VoucherExpiredError(voucherId);
	if (vendorId !== null && vendorId !== scanningVendorId) throw new VoucherWrongVendorError({
		voucherId,
		scanningVendorId
	});
	throw new VoucherAlreadyRedeemedError(voucherId);
}
async function redeemVoucher(tx, input) {
	assertUuid(input.voucherId, "voucherId");
	const snapshot = firstRow(await tx.execute(sql`
    SELECT id, order_id, line_id, unit_index, vendor_id, state, expires_at, redeemed_at, created_at,
           NOW() AS server_now
    FROM voucher
    WHERE id = ${input.voucherId}::uuid
  `));
	if (!snapshot) throw new FulfillmentValidationError("voucherId");
	const serverNow = new Date(String(snapshot.server_now));
	const current = rowToVoucher(snapshot);
	const advisory = decideRedemption(current.state, {
		currentState: current.state,
		at: serverNow,
		scanningVendorId: input.scanningVendorId,
		expiresAt: current.expiresAt,
		vendorId: current.vendorId
	});
	if (!advisory.ok) throwForDecision(input.voucherId, input.scanningVendorId, advisory.reason);
	const updated = firstRow(await tx.execute(sql`
    UPDATE voucher
    SET state = 'REDEEMED', redeemed_at = NOW()
    WHERE id = ${input.voucherId}::uuid
      AND state = 'UNREDEEMED'
      AND (expires_at IS NULL OR expires_at > NOW())
      AND (vendor_id IS NULL OR vendor_id = ${input.scanningVendorId})
    RETURNING id, order_id, line_id, unit_index, vendor_id, state, expires_at, redeemed_at, created_at
  `));
	if (updated) return rowToVoucher(updated);
	const reread = firstRow(await tx.execute(sql`
    SELECT id, order_id, line_id, unit_index, vendor_id, state, expires_at, redeemed_at, created_at,
           NOW() AS server_now
    FROM voucher
    WHERE id = ${input.voucherId}::uuid
  `));
	if (!reread) throw new FulfillmentValidationError("voucherId");
	const rereadServerNow = new Date(String(reread.server_now));
	classifyRedeemFailure(input.voucherId, input.scanningVendorId, rereadServerNow, reread);
}
//#endregion
export { decideRedemption as n, issueVoucher as r, redeemVoucher as t };
