import { _ as isLabelPurchaseError, a as assertUuid, b as isVoucherExpiredError, c as DownloadNotFoundError, d as UnfulfillableLineError, f as VoucherAlreadyRedeemedError, g as isFulfillmentValidationError, h as isDownloadNotFoundError, i as assertPositiveInt, l as FulfillmentValidationError, m as VoucherWrongVendorError, n as UUID_RE, o as normalizeEmail, p as VoucherExpiredError, r as VOUCHER_STATES, s as ownerKeyOf, t as SHIPMENT_STATUSES, u as LabelPurchaseError, v as isUnfulfillableLineError, x as isVoucherWrongVendorError, y as isVoucherAlreadyRedeemedError } from "./types-Ao3zhbMU.js";
import { n as grantDigitalAccess, t as issueDownloadToken } from "./digital-DeJ4vYTG.js";
import { i as createShipment, n as handleCarrierWebhook, r as buyShippingLabel, t as trackShipment } from "./physical-Chw7nDaK.js";
import { n as decideRedemption, r as issueVoucher, t as redeemVoucher } from "./voucher-DzcvJygm.js";
import { sql } from "drizzle-orm";
import { ordersSchema, recordStep } from "@platform-modules/commerce-orders";
import { check, integer, jsonb, pgTable, text, timestamp, uniqueIndex, uuid } from "drizzle-orm/pg-core";
//#region src/fulfill.ts
function firstRow(res) {
	return ((Array.isArray(res) ? res : res.rows) ?? [])[0];
}
function allRows(res) {
	return (Array.isArray(res) ? res : res.rows) ?? [];
}
function isFulfillmentKind(kind) {
	return kind === "digital" || kind === "voucher" || kind === "physical";
}
function errorMessage(error) {
	if (error instanceof Error) return error.message;
	return String(error);
}
async function loadDigitalGrantId(tx, order, line) {
	const ownerKey = ownerKeyOf(order.buyerRef);
	const row = firstRow(await tx.execute(sql`
    SELECT id
    FROM access_grant
    WHERE order_id = ${order.id}::uuid
      AND item_id = ${line.variantId}
      AND owner_key = ${ownerKey}
  `));
	if (!row) throw new FulfillmentValidationError("grant");
	return String(row.id);
}
async function loadVoucherIds(tx, order, line) {
	const ids = allRows(await tx.execute(sql`
    SELECT id
    FROM voucher
    WHERE order_id = ${order.id}::uuid
      AND line_id = ${line.id}
    ORDER BY unit_index ASC
  `)).map((row) => String(row.id));
	if (ids.length === 0) throw new FulfillmentValidationError("voucher");
	return ids;
}
async function loadShipmentId(tx, order, line) {
	const lineIdsJson = JSON.stringify([line.id]);
	const row = firstRow(await tx.execute(sql`
    SELECT id
    FROM shipment
    WHERE order_id = ${order.id}::uuid
      AND line_ids @> ${lineIdsJson}::jsonb
    ORDER BY created_at ASC
    LIMIT 1
  `));
	if (!row) throw new FulfillmentValidationError("shipment");
	return String(row.id);
}
async function loadReplayOutcome(tx, order, line) {
	switch (line.kind) {
		case "digital": return {
			lineId: line.id,
			kind: "digital",
			grantId: await loadDigitalGrantId(tx, order, line)
		};
		case "voucher": return {
			lineId: line.id,
			kind: "voucher",
			voucherIds: await loadVoucherIds(tx, order, line)
		};
		case "physical": return {
			lineId: line.id,
			kind: "physical",
			shipmentId: await loadShipmentId(tx, order, line)
		};
		default: throw new UnfulfillableLineError({
			orderId: order.id,
			lineId: line.id,
			kind: line.kind
		});
	}
}
async function dispatchLine(tx, ports, order, line) {
	if (!isFulfillmentKind(line.kind)) throw new UnfulfillableLineError({
		orderId: order.id,
		lineId: line.id,
		kind: line.kind
	});
	switch (line.kind) {
		case "digital": {
			const blobKey = await ports.resolveBlobKey(line);
			if (!blobKey) throw new FulfillmentValidationError("blobKey");
			const grant = await grantDigitalAccess(tx, {
				orderId: order.id,
				itemId: line.variantId,
				ownerKey: ownerKeyOf(order.buyerRef),
				blobKey
			});
			return {
				lineId: line.id,
				kind: "digital",
				grantId: grant.id
			};
		}
		case "voucher": {
			const vouchers = await issueVoucher(tx, {
				orderId: order.id,
				lineId: line.id,
				qty: line.qty,
				vendorId: line.vendorId,
				expiresAt: null
			});
			return {
				lineId: line.id,
				kind: "voucher",
				voucherIds: vouchers.map((voucher) => voucher.id)
			};
		}
		case "physical": {
			const address = await ports.resolveShippingAddress(order);
			if (!address) throw new FulfillmentValidationError("address");
			const shipment = await createShipment(tx, {
				orderId: order.id,
				lineIds: [line.id],
				address
			});
			return {
				lineId: line.id,
				kind: "physical",
				shipmentId: shipment.id
			};
		}
		default: {
			const _exhaustive = line.kind;
			throw new UnfulfillableLineError({
				orderId: order.id,
				lineId: line.id,
				kind: String(_exhaustive)
			});
		}
	}
}
async function notifyForOutcome(ports, order, line, outcome) {
	if (outcome.kind === "digital") {
		await ports.notify({
			kind: "digital-grant",
			orderId: order.id,
			grantId: outcome.grantId,
			itemId: line.variantId
		});
		return;
	}
	if (outcome.kind === "voucher") await ports.notify({
		kind: "voucher-issued",
		orderId: order.id,
		lineId: line.id,
		codes: outcome.voucherIds
	});
}
async function deliverNotification(ports, order, line, outcome) {
	if (outcome.kind !== "digital" && outcome.kind !== "voucher") return;
	const stepId = `notify:${line.id}`;
	try {
		if (firstRow(await ports.db.execute(sql`
      SELECT 1
      FROM order_step
      WHERE order_id = ${order.id}::uuid
        AND step_id = ${stepId}
      LIMIT 1
    `))) return;
		await notifyForOutcome(ports, order, line, outcome);
		await ports.db.transaction(async (tx) => {
			await recordStep(tx, order.id, stepId, {
				kind: outcome.kind,
				lineId: line.id
			});
		});
	} catch {}
}
function computeOverall(lines) {
	const fulfilledCount = lines.filter((line) => line.kind !== "unfulfillable").length;
	if (fulfilledCount === lines.length) return "fulfilled";
	if (fulfilledCount === 0) return "unfulfillable";
	return "partial";
}
async function fulfillOrder(ports, order) {
	const lineOutcomes = [];
	for (const line of order.lines) {
		let outcome;
		try {
			outcome = await ports.db.transaction(async (tx) => {
				if (!await recordStep(tx, order.id, `fulfill:${line.id}`, {
					kind: line.kind,
					lineId: line.id
				})) return loadReplayOutcome(tx, order, line);
				return dispatchLine(tx, ports, order, line);
			});
		} catch (error) {
			outcome = isUnfulfillableLineError(error) ? {
				lineId: line.id,
				kind: "unfulfillable",
				error: error.message
			} : {
				lineId: line.id,
				kind: "unfulfillable",
				error: errorMessage(error)
			};
			lineOutcomes.push(outcome);
			continue;
		}
		lineOutcomes.push(outcome);
		await deliverNotification(ports, order, line, outcome);
	}
	return {
		orderId: order.id,
		overall: computeOverall(lineOutcomes),
		lines: lineOutcomes
	};
}
//#endregion
//#region src/migrate.ts
var FulfillmentMigrateError = class extends Error {
	detail;
	name = "FulfillmentMigrateError";
	code = "FULFILLMENT_MIGRATE";
	constructor(detail) {
		super(`fulfillment migrate: ${detail}`);
		this.detail = detail;
	}
};
function isFulfillmentMigrateError(e) {
	return typeof e === "object" && e !== null && e.name === "FulfillmentMigrateError" && e.code === "FULFILLMENT_MIGRATE";
}
const MIGRATION_STATEMENTS = [
	sql`
    CREATE TABLE IF NOT EXISTS access_grant (
      id uuid PRIMARY KEY,
      order_id uuid NOT NULL,
      item_id text NOT NULL,
      owner_key text NOT NULL,
      blob_key text NOT NULL,
      created_at timestamptz NOT NULL DEFAULT NOW()
    )
  `,
	sql`
    CREATE UNIQUE INDEX IF NOT EXISTS uq_access_grant_owner
    ON access_grant (order_id, item_id, owner_key)
  `,
	sql`
    CREATE TABLE IF NOT EXISTS voucher (
      id uuid PRIMARY KEY,
      order_id uuid NOT NULL,
      line_id text NOT NULL,
      unit_index integer NOT NULL,
      vendor_id text,
      state text NOT NULL DEFAULT 'UNREDEEMED',
      expires_at timestamptz,
      redeemed_at timestamptz,
      created_at timestamptz NOT NULL DEFAULT NOW(),
      CONSTRAINT voucher_state_chk CHECK (state IN ('UNREDEEMED', 'REDEEMED', 'EXPIRED', 'CANCELLED')),
      CONSTRAINT voucher_unit_index_chk CHECK (unit_index >= 0)
    )
  `,
	sql`
    CREATE UNIQUE INDEX IF NOT EXISTS uq_voucher_unit
    ON voucher (order_id, line_id, unit_index)
  `,
	sql`
    CREATE TABLE IF NOT EXISTS shipment (
      id uuid PRIMARY KEY,
      order_id uuid NOT NULL,
      line_ids jsonb NOT NULL,
      address jsonb NOT NULL,
      status text NOT NULL DEFAULT 'pending',
      carrier_kind text,
      label_id text,
      tracking_number text,
      created_at timestamptz NOT NULL DEFAULT NOW(),
      updated_at timestamptz NOT NULL DEFAULT NOW(),
      CONSTRAINT shipment_status_chk CHECK (status IN ('pending', 'labeled', 'in_transit', 'delivered', 'failed'))
    )
  `,
	sql`
    CREATE TABLE IF NOT EXISTS carrier_webhook_event (
      event_id text PRIMARY KEY,
      shipment_id uuid,
      received_at timestamptz NOT NULL DEFAULT NOW()
    )
  `
];
async function pushSchema(db) {
	try {
		for (const statement of MIGRATION_STATEMENTS) await db.execute(statement);
	} catch (e) {
		throw new FulfillmentMigrateError(e instanceof Error ? e.message : String(e));
	}
}
//#endregion
//#region src/schema.ts
const accessGrant = pgTable("access_grant", {
	id: uuid("id").primaryKey(),
	orderId: uuid("order_id").notNull(),
	itemId: text("item_id").notNull(),
	ownerKey: text("owner_key").notNull(),
	blobKey: text("blob_key").notNull(),
	createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
}, (t) => [uniqueIndex("uq_access_grant_owner").on(t.orderId, t.itemId, t.ownerKey)]);
const voucher = pgTable("voucher", {
	id: uuid("id").primaryKey(),
	orderId: uuid("order_id").notNull(),
	lineId: text("line_id").notNull(),
	unitIndex: integer("unit_index").notNull(),
	vendorId: text("vendor_id"),
	state: text("state").$type().notNull().default("UNREDEEMED"),
	expiresAt: timestamp("expires_at", { withTimezone: true }),
	redeemedAt: timestamp("redeemed_at", { withTimezone: true }),
	createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
}, (t) => [
	uniqueIndex("uq_voucher_unit").on(t.orderId, t.lineId, t.unitIndex),
	check("voucher_state_chk", sql`${t.state} IN ('UNREDEEMED', 'REDEEMED', 'EXPIRED', 'CANCELLED')`),
	check("voucher_unit_index_chk", sql`${t.unitIndex} >= 0`)
]);
const shipment = pgTable("shipment", {
	id: uuid("id").primaryKey(),
	orderId: uuid("order_id").notNull(),
	lineIds: jsonb("line_ids").$type().notNull(),
	address: jsonb("address").notNull(),
	status: text("status").$type().notNull().default("pending"),
	carrierKind: text("carrier_kind"),
	labelId: text("label_id"),
	trackingNumber: text("tracking_number"),
	createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
	updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
}, (t) => [check("shipment_status_chk", sql`${t.status} IN ('pending', 'labeled', 'in_transit', 'delivered', 'failed')`)]);
const carrierWebhookEvent = pgTable("carrier_webhook_event", {
	eventId: text("event_id").primaryKey(),
	shipmentId: uuid("shipment_id"),
	receivedAt: timestamp("received_at", { withTimezone: true }).notNull().defaultNow()
});
const fulfillmentSchema = {
	accessGrant,
	voucher,
	shipment,
	carrierWebhookEvent
};
const fulfillmentDbSchema = {
	...fulfillmentSchema,
	...ordersSchema
};
//#endregion
export { DownloadNotFoundError, FulfillmentMigrateError, FulfillmentValidationError, LabelPurchaseError, SHIPMENT_STATUSES, UUID_RE, UnfulfillableLineError, VOUCHER_STATES, VoucherAlreadyRedeemedError, VoucherExpiredError, VoucherWrongVendorError, accessGrant, assertPositiveInt, assertUuid, buyShippingLabel, carrierWebhookEvent, createShipment, decideRedemption, fulfillOrder, fulfillmentDbSchema, fulfillmentSchema, grantDigitalAccess, handleCarrierWebhook, isDownloadNotFoundError, isFulfillmentMigrateError, isFulfillmentValidationError, isLabelPurchaseError, isUnfulfillableLineError, isVoucherAlreadyRedeemedError, isVoucherExpiredError, isVoucherWrongVendorError, issueDownloadToken, issueVoucher, normalizeEmail, ownerKeyOf, pushSchema, redeemVoucher, shipment, trackShipment, voucher };
