import { sql } from "drizzle-orm";
import { integer, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
//#region src/errors.ts
var OversoldError = class extends Error {
	skuId;
	requested;
	name = "OversoldError";
	code = "OVERSOLD";
	constructor(skuId, requested) {
		super(`oversold: sku ${skuId}, requested ${requested}`);
		this.skuId = skuId;
		this.requested = requested;
	}
};
var InventoryValidationError = class extends Error {
	field;
	name = "InventoryValidationError";
	code = "INVENTORY_VALIDATION";
	constructor(field) {
		super(`inventory validation failed: ${field}`);
		this.field = field;
	}
};
var InventoryItemNotFoundError = class extends Error {
	skuId;
	name = "InventoryItemNotFoundError";
	code = "INVENTORY_ITEM_NOT_FOUND";
	constructor(skuId) {
		super(`inventory item not found: ${skuId}`);
		this.skuId = skuId;
	}
};
function isOversoldError(e) {
	return typeof e === "object" && e !== null && e.name === "OversoldError" && e.code === "OVERSOLD";
}
function isInventoryValidationError(e) {
	return typeof e === "object" && e !== null && e.name === "InventoryValidationError" && e.code === "INVENTORY_VALIDATION";
}
function isInventoryItemNotFoundError(e) {
	return typeof e === "object" && e !== null && e.name === "InventoryItemNotFoundError" && e.code === "INVENTORY_ITEM_NOT_FOUND";
}
//#endregion
//#region src/migrate.ts
var InventoryMigrateError = class extends Error {
	detail;
	name = "InventoryMigrateError";
	code = "INVENTORY_MIGRATE";
	constructor(detail) {
		super(`inventory migrate: ${detail}`);
		this.detail = detail;
	}
};
function isInventoryMigrateError(e) {
	return typeof e === "object" && e !== null && e.name === "InventoryMigrateError" && e.code === "INVENTORY_MIGRATE";
}
const MIGRATION_STATEMENTS = [
	sql`
    CREATE TABLE IF NOT EXISTS inventory_item (
      sku_id uuid PRIMARY KEY,
      vendor_id text,
      quantity_total integer,
      quantity_sold integer NOT NULL DEFAULT 0,
      created_at timestamptz NOT NULL DEFAULT NOW(),
      updated_at timestamptz NOT NULL DEFAULT NOW()
    )
  `,
	sql`
    CREATE TABLE IF NOT EXISTS stock_reservation (
      id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
      sku_id uuid NOT NULL,
      qty integer NOT NULL,
      holder_ref text NOT NULL,
      expires_at timestamptz NOT NULL,
      consumed_at timestamptz,
      released_at timestamptz,
      created_at timestamptz NOT NULL DEFAULT NOW()
    )
  `,
	sql`
    CREATE UNIQUE INDEX IF NOT EXISTS uq_active_holder
    ON stock_reservation (sku_id, holder_ref)
    WHERE consumed_at IS NULL AND released_at IS NULL
  `,
	sql`
    CREATE INDEX IF NOT EXISTS ix_active_sku
    ON stock_reservation (sku_id)
    WHERE consumed_at IS NULL AND released_at IS NULL
  `
];
async function pushSchema(db) {
	try {
		for (const statement of MIGRATION_STATEMENTS) await db.execute(statement);
	} catch (e) {
		throw new InventoryMigrateError(e instanceof Error ? e.message : String(e));
	}
}
//#endregion
//#region src/schema.ts
const inventoryItem = pgTable("inventory_item", {
	skuId: uuid("sku_id").primaryKey(),
	vendorId: text("vendor_id"),
	quantityTotal: integer("quantity_total"),
	quantitySold: integer("quantity_sold").notNull().default(0),
	createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
	updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
});
const stockReservation = pgTable("stock_reservation", {
	id: uuid("id").primaryKey().defaultRandom(),
	skuId: uuid("sku_id").notNull(),
	qty: integer("qty").notNull(),
	holderRef: text("holder_ref").notNull(),
	expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
	consumedAt: timestamp("consumed_at", { withTimezone: true }),
	releasedAt: timestamp("released_at", { withTimezone: true }),
	createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
});
const inventorySchema = {
	inventoryItem,
	stockReservation
};
//#endregion
//#region src/types.ts
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
function assertUuid(value, field) {
	if (!UUID_RE.test(value)) throw new InventoryValidationError(field);
}
function assertPositiveInt(qty, field = "qty") {
	if (!Number.isInteger(qty) || qty <= 0) throw new InventoryValidationError(field);
}
//#endregion
//#region src/set-inventory.ts
function firstRow$4(res) {
	return ((Array.isArray(res) ? res : res.rows) ?? [])[0];
}
async function setInventory(tx, item) {
	assertUuid(item.skuId, "skuId");
	if (item.quantityTotal !== null) {
		if (!Number.isInteger(item.quantityTotal) || item.quantityTotal < 0) throw new InventoryValidationError("quantityTotal");
	}
	const vendorId = item.vendorId ?? null;
	const quantityTotal = item.quantityTotal;
	await tx.execute(sql`
    INSERT INTO inventory_item (sku_id, vendor_id, quantity_total, quantity_sold)
    VALUES (${item.skuId}::uuid, ${vendorId}, ${quantityTotal}, 0)
    ON CONFLICT (sku_id) DO NOTHING
  `);
	const itemRes = await tx.execute(sql`
    SELECT quantity_sold
    FROM inventory_item
    WHERE sku_id = ${item.skuId}::uuid
    FOR UPDATE
  `);
	const sold = Number(firstRow$4(itemRes)?.quantity_sold ?? 0);
	const sumRes = await tx.execute(sql`
    SELECT COALESCE(SUM(qty), 0)::int AS reserved
    FROM stock_reservation
    WHERE sku_id = ${item.skuId}::uuid
      AND consumed_at IS NULL
      AND released_at IS NULL
  `);
	const reserved = Number(firstRow$4(sumRes)?.reserved ?? 0);
	if (quantityTotal !== null && quantityTotal < sold + reserved) throw new InventoryValidationError("quantityTotal");
	await tx.execute(sql`
    UPDATE inventory_item
    SET vendor_id = ${vendorId}, quantity_total = ${quantityTotal}, updated_at = now()
    WHERE sku_id = ${item.skuId}::uuid
  `);
}
//#endregion
//#region src/reserve.ts
function firstRow$3(res) {
	return ((Array.isArray(res) ? res : res.rows) ?? [])[0];
}
const DEFAULT_TTL_MS = 9e5;
async function reserve(tx, args) {
	assertPositiveInt(args.qty, "qty");
	assertUuid(args.skuId, "skuId");
	if (args.holderRef.trim().length === 0) throw new InventoryValidationError("holderRef");
	const ttlMs = args.ttlMs ?? DEFAULT_TTL_MS;
	const expiresAt = new Date(args.now.getTime() + ttlMs);
	const item = firstRow$3(await tx.execute(sql`
    SELECT quantity_total, quantity_sold
    FROM inventory_item
    WHERE sku_id = ${args.skuId}::uuid
    FOR UPDATE
  `));
	if (!item) throw new InventoryItemNotFoundError(args.skuId);
	const existing = firstRow$3(await tx.execute(sql`
    SELECT id
    FROM stock_reservation
    WHERE sku_id = ${args.skuId}::uuid
      AND holder_ref = ${args.holderRef}
      AND consumed_at IS NULL
      AND released_at IS NULL
    LIMIT 1
  `));
	if (existing?.id) return { reservationId: String(existing.id) };
	const sumRow = firstRow$3(await tx.execute(sql`
    SELECT COALESCE(SUM(qty), 0)::int AS reserved
    FROM stock_reservation
    WHERE sku_id = ${args.skuId}::uuid
      AND consumed_at IS NULL
      AND released_at IS NULL
  `));
	const reserved = Number(sumRow?.reserved ?? 0);
	const total = item.quantity_total === null || item.quantity_total === void 0 ? null : Number(item.quantity_total);
	const sold = Number(item.quantity_sold ?? 0);
	if (total !== null && total - sold - reserved < args.qty) throw new OversoldError(args.skuId, args.qty);
	const inserted = firstRow$3(await tx.execute(sql`
    INSERT INTO stock_reservation (sku_id, qty, holder_ref, expires_at)
    VALUES (${args.skuId}::uuid, ${args.qty}, ${args.holderRef}, ${expiresAt.toISOString()}::timestamptz)
    RETURNING id
  `));
	if (!inserted?.id) throw new OversoldError(args.skuId, args.qty);
	return { reservationId: String(inserted.id) };
}
//#endregion
//#region src/consume.ts
function firstRow$2(res) {
	return ((Array.isArray(res) ? res : res.rows) ?? [])[0];
}
async function consume(tx, reservationId) {
	assertUuid(reservationId, "reservationId");
	return firstRow$2(await tx.execute(sql`
    WITH mark AS (
      UPDATE stock_reservation
      SET consumed_at = now()
      WHERE id = ${reservationId}::uuid
        AND consumed_at IS NULL
        AND released_at IS NULL
      RETURNING sku_id, qty, id
    ),
    sold AS (
      UPDATE inventory_item ii
      SET quantity_sold = ii.quantity_sold + mark.qty, updated_at = now()
      FROM mark
      WHERE ii.sku_id = mark.sku_id
    )
    SELECT id FROM mark
  `))?.id !== void 0;
}
//#endregion
//#region src/release.ts
async function release(tx, reservationId) {
	assertUuid(reservationId, "reservationId");
	await tx.execute(sql`
    UPDATE stock_reservation
    SET released_at = now()
    WHERE id = ${reservationId}::uuid
      AND released_at IS NULL
      AND consumed_at IS NULL
  `);
}
//#endregion
//#region src/commit-stock.ts
function firstRow$1(res) {
	return ((Array.isArray(res) ? res : res.rows) ?? [])[0];
}
async function commitStock(tx, skuId, qty) {
	assertPositiveInt(qty, "qty");
	assertUuid(skuId, "skuId");
	const item = firstRow$1(await tx.execute(sql`
    SELECT quantity_total, quantity_sold
    FROM inventory_item
    WHERE sku_id = ${skuId}::uuid
    FOR UPDATE
  `));
	if (!item) throw new InventoryItemNotFoundError(skuId);
	const sumRow = firstRow$1(await tx.execute(sql`
    SELECT COALESCE(SUM(qty), 0)::int AS reserved
    FROM stock_reservation
    WHERE sku_id = ${skuId}::uuid
      AND consumed_at IS NULL
      AND released_at IS NULL
  `));
	const reserved = Number(sumRow?.reserved ?? 0);
	const total = item.quantity_total === null || item.quantity_total === void 0 ? null : Number(item.quantity_total);
	const sold = Number(item.quantity_sold ?? 0);
	if (total !== null && total - sold - reserved < qty) throw new OversoldError(skuId, qty);
	await tx.execute(sql`
    UPDATE inventory_item
    SET quantity_sold = quantity_sold + ${qty}, updated_at = now()
    WHERE sku_id = ${skuId}::uuid
  `);
}
//#endregion
//#region src/get-availability.ts
function firstRow(res) {
	return ((Array.isArray(res) ? res : res.rows) ?? [])[0];
}
async function getAvailability(q, skuId) {
	assertUuid(skuId, "skuId");
	const row = firstRow(await q.execute(sql`
    SELECT
      quantity_total,
      quantity_sold,
      (
        SELECT COALESCE(SUM(qty), 0)::int
        FROM stock_reservation
        WHERE sku_id = ${skuId}::uuid
          AND consumed_at IS NULL
          AND released_at IS NULL
      ) AS reserved
    FROM inventory_item
    WHERE sku_id = ${skuId}::uuid
  `));
	if (!row) throw new InventoryItemNotFoundError(skuId);
	if (row.quantity_total === null || row.quantity_total === void 0) return null;
	const total = Number(row.quantity_total);
	const sold = Number(row.quantity_sold ?? 0);
	const reserved = Number(row.reserved ?? 0);
	return total - sold - reserved;
}
//#endregion
//#region src/sweep.ts
function rows(res) {
	return (Array.isArray(res) ? res : res.rows) ?? [];
}
async function sweepStaleReservations(q, now) {
	const nowIso = now.toISOString();
	return { released: rows(await q.execute(sql`
    UPDATE stock_reservation
    SET released_at = ${nowIso}::timestamptz
    WHERE expires_at < ${nowIso}::timestamptz
      AND consumed_at IS NULL
      AND released_at IS NULL
    RETURNING id
  `)).length };
}
//#endregion
export { InventoryItemNotFoundError, InventoryMigrateError, InventoryValidationError, OversoldError, commitStock, consume, getAvailability, inventoryItem, inventorySchema, isInventoryItemNotFoundError, isInventoryMigrateError, isInventoryValidationError, isOversoldError, pushSchema, release, reserve, setInventory, stockReservation, sweepStaleReservations };
