import { eq, sql } from "drizzle-orm";
import { integer, pgTable, text, timestamp, uniqueIndex, uuid } from "drizzle-orm/pg-core";
import { catalogSchema, getProductById, upsertProduct } from "@platform-modules/commerce-catalog";
import { listOrders, ordersSchema } from "@platform-modules/commerce-orders";
//#region src/errors.ts
var VendorNotFoundError = class extends Error {
	context;
	name = "VendorNotFoundError";
	code = "VENDOR_NOT_FOUND";
	httpStatus = 404;
	constructor(context = {}) {
		super(context.vendorId ? `vendor not found: ${context.vendorId}` : "vendor not found");
		this.context = context;
	}
};
var VendorProductNotFoundError = class extends Error {
	context;
	name = "VendorProductNotFoundError";
	code = "VENDOR_PRODUCT_NOT_FOUND";
	httpStatus = 404;
	constructor(context = {}) {
		super(context.productId ? `vendor product not found: ${context.productId}` : "vendor product not found");
		this.context = context;
	}
};
var VendorAuthorizationError = class extends Error {
	name = "VendorAuthorizationError";
	code = "VENDOR_FORBIDDEN";
	httpStatus = 403;
	constructor() {
		super("vendor authorization failed");
	}
};
var VendorStateError = class extends Error {
	context;
	name = "VendorStateError";
	code = "VENDOR_STATE";
	httpStatus = 409;
	constructor(context) {
		super(`vendor state transition invalid: ${context.from} -> ${context.to}`);
		this.context = context;
	}
};
var CommissionRateError = class extends Error {
	context;
	name = "CommissionRateError";
	code = "COMMISSION_RATE_INVALID";
	httpStatus = 422;
	constructor(context) {
		super(`commission rate invalid: ${context.bps}`);
		this.context = context;
	}
};
var VendorAlreadyExistsError = class extends Error {
	context;
	name = "VendorAlreadyExistsError";
	code = "VENDOR_ALREADY_EXISTS";
	httpStatus = 409;
	constructor(context) {
		super(`vendor already exists: ${context.ownerUserId}`);
		this.context = context;
	}
};
function isVendorNotFoundError(e) {
	return typeof e === "object" && e !== null && e.name === "VendorNotFoundError" && e.code === "VENDOR_NOT_FOUND";
}
function isVendorProductNotFoundError(e) {
	return typeof e === "object" && e !== null && e.name === "VendorProductNotFoundError" && e.code === "VENDOR_PRODUCT_NOT_FOUND";
}
function isVendorAuthorizationError(e) {
	return typeof e === "object" && e !== null && e.name === "VendorAuthorizationError" && e.code === "VENDOR_FORBIDDEN";
}
function isVendorStateError(e) {
	return typeof e === "object" && e !== null && e.name === "VendorStateError" && e.code === "VENDOR_STATE";
}
function isCommissionRateError(e) {
	return typeof e === "object" && e !== null && e.name === "CommissionRateError" && e.code === "COMMISSION_RATE_INVALID";
}
function isVendorAlreadyExistsError(e) {
	return typeof e === "object" && e !== null && e.name === "VendorAlreadyExistsError" && e.code === "VENDOR_ALREADY_EXISTS";
}
var SplitIntegrityError = class extends Error {
	context;
	name = "SplitIntegrityError";
	code = "SPLIT_INTEGRITY";
	httpStatus = 422;
	constructor(context) {
		super(`computeVendorSplits: ${context.reason}`);
		this.context = context;
	}
};
//#endregion
//#region src/splits.ts
function computeVendorSplits(input) {
	const { lines, subtotal, total, rates } = input;
	if (total < 0n || subtotal < 0n) throw new Error("computeVendorSplits: negative total or subtotal");
	for (const line of lines) if (line.lineTotal < 0n) throw new SplitIntegrityError({
		reason: "negative lineTotal",
		vendorId: line.vendorId ?? void 0
	});
	if (subtotal === 0n) {
		if (total === 0n) return [{
			vendorId: null,
			amount: 0n,
			funder: "platform"
		}];
		throw new Error("computeVendorSplits: total without lineable subtotal");
	}
	const bucketOrder = [];
	const bucketSums = /* @__PURE__ */ new Map();
	let lineSum = 0n;
	for (const line of lines) {
		const key = line.vendorId;
		if (!bucketSums.has(key)) {
			bucketOrder.push(key);
			bucketSums.set(key, 0n);
		}
		bucketSums.set(key, bucketSums.get(key) + line.lineTotal);
		lineSum += line.lineTotal;
	}
	if (lineSum !== subtotal) throw new Error("computeVendorSplits: line totals do not sum to subtotal");
	const floorShares = /* @__PURE__ */ new Map();
	let floorSum = 0n;
	const remainderRanks = [];
	for (let order = 0; order < bucketOrder.length; order++) {
		const key = bucketOrder[order];
		const product = total * bucketSums.get(key);
		const floorShare = product / subtotal;
		const remainder = product % subtotal;
		floorShares.set(key, floorShare);
		floorSum += floorShare;
		remainderRanks.push({
			key,
			remainder,
			order
		});
	}
	const shares = new Map(floorShares);
	let leftover = total - floorSum;
	remainderRanks.sort((a, b) => {
		if (a.remainder !== b.remainder) return a.remainder > b.remainder ? -1 : 1;
		return a.order - b.order;
	});
	for (let i = 0; i < Number(leftover); i++) {
		const key = remainderRanks[i].key;
		shares.set(key, (shares.get(key) ?? 0n) + 1n);
	}
	let totalCommission = 0n;
	const vendorSplits = [];
	for (const key of bucketOrder) {
		if (key === null) continue;
		const share = shares.get(key);
		const rate = rates.get(key) ?? 0;
		if (!Number.isInteger(rate) || rate < 0 || rate > 1e4) throw new Error(`computeVendorSplits: commission rate out of range for vendor ${key}: ${rate}`);
		const commission = share * BigInt(rate) / 10000n;
		totalCommission += commission;
		vendorSplits.push({
			vendorId: key,
			amount: share - commission,
			funder: "vendor"
		});
	}
	const platformOwnShare = shares.get(null) ?? 0n;
	return [...vendorSplits, {
		vendorId: null,
		amount: platformOwnShare + totalCommission,
		funder: "platform"
	}];
}
//#endregion
//#region src/schema.ts
const vendor = pgTable("vendor", {
	id: uuid("id").primaryKey().defaultRandom(),
	ownerUserId: text("owner_user_id").notNull(),
	name: text("name").notNull(),
	status: text("status").$type().notNull().default("pending"),
	commissionBps: integer("commission_bps").notNull(),
	createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
	updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
}, (t) => [uniqueIndex("vendor_owner_user_id_uq").on(t.ownerUserId)]);
const marketplaceSchema = { vendor };
const marketplaceDbSchema = {
	...marketplaceSchema,
	...catalogSchema,
	...ordersSchema
};
//#endregion
//#region src/types.ts
const DEFAULT_COMMISSION_BPS = 1e3;
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
//#endregion
//#region src/authz.ts
function toVendor$1(row) {
	return {
		id: row.id,
		ownerUserId: row.ownerUserId,
		name: row.name,
		status: row.status,
		commissionBps: row.commissionBps,
		createdAt: row.createdAt,
		updatedAt: row.updatedAt
	};
}
async function requireVendorForActor(q, actor) {
	if (!actor.userId) throw new VendorNotFoundError();
	const [row] = await q.select().from(vendor).where(eq(vendor.ownerUserId, actor.userId)).limit(1);
	if (!row) throw new VendorNotFoundError();
	return toVendor$1(row);
}
async function assertVendorOwnership(q, actor, vendorId) {
	if (actor.isAdmin) return;
	if (!UUID_RE.test(vendorId)) throw new VendorNotFoundError({ vendorId });
	const [row] = await q.select().from(vendor).where(eq(vendor.id, vendorId)).limit(1);
	if (!row || row.ownerUserId !== actor.userId) throw new VendorNotFoundError({ vendorId });
}
async function assertVendorCanSell(q, vendorId) {
	if (!UUID_RE.test(vendorId)) throw new VendorNotFoundError({ vendorId });
	const [row] = await q.select().from(vendor).where(eq(vendor.id, vendorId)).limit(1);
	if (!row) throw new VendorNotFoundError({ vendorId });
	if (row.status !== "approved") throw new VendorStateError({
		vendorId,
		from: row.status,
		to: "approved"
	});
}
//#endregion
//#region src/commission.ts
async function resolveCommissionRate(q, vendorId) {
	if (!UUID_RE.test(vendorId)) throw new VendorNotFoundError({ vendorId });
	const [row] = await q.select({ commissionBps: vendor.commissionBps }).from(vendor).where(eq(vendor.id, vendorId)).limit(1);
	if (!row) throw new VendorNotFoundError({ vendorId });
	return row.commissionBps;
}
async function setVendorCommissionRate(tx, vendorId, admin, bps) {
	if (!admin.isAdmin) throw new VendorAuthorizationError();
	if (!Number.isInteger(bps) || bps < 0 || bps > 1e4) throw new CommissionRateError({ bps });
	if (!UUID_RE.test(vendorId)) throw new VendorNotFoundError({ vendorId });
	const [row] = await tx.select({ id: vendor.id }).from(vendor).where(eq(vendor.id, vendorId)).limit(1);
	if (!row) throw new VendorNotFoundError({ vendorId });
	await tx.update(vendor).set({
		commissionBps: bps,
		updatedAt: /* @__PURE__ */ new Date()
	}).where(eq(vendor.id, vendorId));
}
//#endregion
//#region src/migrate.ts
var MarketplaceMigrateError = class extends Error {
	detail;
	name = "MarketplaceMigrateError";
	code = "MARKETPLACE_MIGRATE";
	constructor(detail) {
		super(`marketplace migrate: ${detail}`);
		this.detail = detail;
	}
};
function isMarketplaceMigrateError(e) {
	return typeof e === "object" && e !== null && e.name === "MarketplaceMigrateError" && e.code === "MARKETPLACE_MIGRATE";
}
const MIGRATION_STATEMENTS = [sql`
    CREATE TABLE IF NOT EXISTS vendor (
      id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
      owner_user_id text NOT NULL,
      name text NOT NULL,
      status text NOT NULL DEFAULT 'pending',
      commission_bps integer NOT NULL,
      created_at timestamptz NOT NULL DEFAULT NOW(),
      updated_at timestamptz NOT NULL DEFAULT NOW()
    )
  `, sql`
    CREATE UNIQUE INDEX IF NOT EXISTS vendor_owner_user_id_uq
    ON vendor (owner_user_id)
  `];
async function pushSchema(db) {
	try {
		for (const statement of MIGRATION_STATEMENTS) await db.execute(statement);
	} catch (e) {
		throw new MarketplaceMigrateError(e instanceof Error ? e.message : String(e));
	}
}
//#endregion
//#region src/orders.ts
async function getVendorOrders(q, actor, filter) {
	const v = await requireVendorForActor(q, actor);
	return listOrders(q, { vendorId: v.id }, filter);
}
//#endregion
//#region src/product.ts
async function createVendorProduct(tx, actor, input) {
	const v = await requireVendorForActor(tx, actor);
	await assertVendorCanSell(tx, v.id);
	const { id: _dropClientId, ...rest } = input;
	return upsertProduct(tx, {
		...rest,
		vendorId: v.id
	});
}
async function updateVendorProduct(tx, actor, productId, patch) {
	const catalogTx = tx;
	const product = await getProductById(catalogTx, productId, { audience: "admin" });
	if (!product) throw new VendorProductNotFoundError({ productId });
	if (product.vendorId == null) throw new VendorProductNotFoundError({ productId });
	if (!actor.isAdmin) {
		const v = await requireVendorForActor(tx, actor);
		if (v.id !== product.vendorId) throw new VendorProductNotFoundError({ productId });
		await assertVendorCanSell(tx, v.id);
	}
	const merged = {
		id: product.id,
		kind: product.kind,
		vendorId: product.vendorId,
		slug: patch.slug ?? product.slug,
		title: patch.title ?? product.title,
		description: patch.description !== void 0 ? patch.description : product.description,
		status: patch.status ?? product.status,
		media: patch.media ?? product.media,
		tags: patch.tags ?? product.tags,
		availableFrom: patch.availableFrom !== void 0 ? patch.availableFrom : product.availableFrom,
		availableUntil: patch.availableUntil !== void 0 ? patch.availableUntil : product.availableUntil
	};
	return upsertProduct(catalogTx, merged);
}
//#endregion
//#region src/vendor.ts
function isUniqueViolation(e) {
	let cur = e;
	while (cur) {
		const code = cur?.code;
		const msg = cur instanceof Error ? cur.message : String(cur);
		if (code === "23505" || /vendor_owner_user_id_uq|duplicate key|unique constraint/i.test(msg)) return true;
		cur = cur instanceof Error ? cur.cause : void 0;
	}
	return false;
}
function toVendor(row) {
	return {
		id: row.id,
		ownerUserId: row.ownerUserId,
		name: row.name,
		status: row.status,
		commissionBps: row.commissionBps,
		createdAt: row.createdAt,
		updatedAt: row.updatedAt
	};
}
function assertApproveTransition(from, to, vendorId) {
	if (from === "pending" && to === "approved") return;
	throw new VendorStateError({
		vendorId,
		from,
		to
	});
}
function assertSuspendTransition(from, to, vendorId) {
	if ((from === "pending" || from === "approved") && to === "suspended") return;
	throw new VendorStateError({
		vendorId,
		from,
		to
	});
}
async function applyAsVendor(tx, input) {
	try {
		const [row] = await tx.insert(vendor).values({
			ownerUserId: input.ownerUserId,
			name: input.name,
			status: "pending",
			commissionBps: DEFAULT_COMMISSION_BPS
		}).returning();
		if (!row) throw new Error("applyAsVendor: insert returned no row");
		return toVendor(row);
	} catch (e) {
		if (isUniqueViolation(e)) throw new VendorAlreadyExistsError({ ownerUserId: input.ownerUserId });
		throw e;
	}
}
async function approveVendor(tx, vendorId, admin) {
	if (!admin.isAdmin) throw new VendorAuthorizationError();
	if (!UUID_RE.test(vendorId)) throw new VendorNotFoundError({ vendorId });
	const [row] = await tx.select().from(vendor).where(eq(vendor.id, vendorId)).limit(1);
	if (!row) throw new VendorNotFoundError({ vendorId });
	const to = "approved";
	assertApproveTransition(row.status, to, vendorId);
	await tx.update(vendor).set({
		status: to,
		updatedAt: /* @__PURE__ */ new Date()
	}).where(eq(vendor.id, vendorId));
}
async function suspendVendor(tx, vendorId, admin) {
	if (!admin.isAdmin) throw new VendorAuthorizationError();
	if (!UUID_RE.test(vendorId)) throw new VendorNotFoundError({ vendorId });
	const [row] = await tx.select().from(vendor).where(eq(vendor.id, vendorId)).limit(1);
	if (!row) throw new VendorNotFoundError({ vendorId });
	const to = "suspended";
	assertSuspendTransition(row.status, to, vendorId);
	await tx.update(vendor).set({
		status: to,
		updatedAt: /* @__PURE__ */ new Date()
	}).where(eq(vendor.id, vendorId));
}
//#endregion
export { CommissionRateError, DEFAULT_COMMISSION_BPS, MarketplaceMigrateError, UUID_RE, VendorAlreadyExistsError, VendorAuthorizationError, VendorNotFoundError, VendorProductNotFoundError, VendorStateError, applyAsVendor, approveVendor, assertVendorCanSell, assertVendorOwnership, computeVendorSplits, createVendorProduct, getVendorOrders, isCommissionRateError, isMarketplaceMigrateError, isVendorAlreadyExistsError, isVendorAuthorizationError, isVendorNotFoundError, isVendorProductNotFoundError, isVendorStateError, marketplaceDbSchema, marketplaceSchema, pushSchema, requireVendorForActor, resolveCommissionRate, setVendorCommissionRate, suspendVendor, updateVendorProduct, vendor };
