import { ScopeViolationError } from "./index.js";
import { and, eq, getTableColumns } from "drizzle-orm";
//#region src/isolation-fk.ts
function scopeViolation(tenantId, reason) {
	throw new ScopeViolationError(reason, tenantId);
}
function assertRegistered(table, registered, tenantId) {
	if (!registered.has(table)) scopeViolation(tenantId, "table is not registered for tenant scoping");
}
function findTenantColumn(table, columnName, tenantId) {
	const columns = getTableColumns(table);
	for (const col of Object.values(columns)) if (col.name === columnName) return col;
	scopeViolation(tenantId, `registered table is missing tenant column "${columnName}"`);
}
function tenantPropertyKey(table, columnName, tenantId) {
	const columns = getTableColumns(table);
	for (const [key, col] of Object.entries(columns)) if (col.name === columnName) return key;
	scopeViolation(tenantId, `registered table is missing tenant column "${columnName}"`);
}
function guardBuilder(builder, tenantId, tenantCol) {
	const joinProps = /* @__PURE__ */ new Set([
		"innerJoin",
		"leftJoin",
		"rightJoin",
		"fullJoin",
		"crossJoin",
		"union",
		"unionAll",
		"except",
		"intersect"
	]);
	return new Proxy(builder, { get(target, prop, receiver) {
		if (typeof prop === "string" && joinProps.has(prop)) scopeViolation(tenantId, `join shape "${prop}" cannot be proven tenant-scoped`);
		const value = Reflect.get(target, prop, receiver);
		if (prop === "where" && typeof value === "function") return (condition, ...rest) => {
			const merged = condition ? and(eq(tenantCol, tenantId), condition) : eq(tenantCol, tenantId);
			const next = value.apply(target, [merged, ...rest]);
			return typeof next === "object" && next !== null ? guardBuilder(next, tenantId, tenantCol) : next;
		};
		if (typeof value === "function") return (...args) => {
			const next = value.apply(target, args);
			return typeof next === "object" && next !== null ? guardBuilder(next, tenantId, tenantCol) : next;
		};
		return value;
	} });
}
function createScopeQuerier(q, tenantId, columnName, registered) {
	return {
		select(...args) {
			const builder = q.select(...args);
			return { from(table, ...rest) {
				assertRegistered(table, registered, tenantId);
				const tenantCol = findTenantColumn(table, columnName, tenantId);
				return guardBuilder(builder.from.call(builder, table, ...rest).where(eq(tenantCol, tenantId)), tenantId, tenantCol);
			} };
		},
		selectDistinct(...args) {
			const builder = q.selectDistinct(...args);
			return { from(table, ...rest) {
				assertRegistered(table, registered, tenantId);
				const tenantCol = findTenantColumn(table, columnName, tenantId);
				return guardBuilder(builder.from.call(builder, table, ...rest).where(eq(tenantCol, tenantId)), tenantId, tenantCol);
			} };
		},
		selectDistinctOn(...args) {
			const builder = q.selectDistinctOn(...args);
			return { from(table, ...rest) {
				assertRegistered(table, registered, tenantId);
				const tenantCol = findTenantColumn(table, columnName, tenantId);
				return guardBuilder(builder.from.call(builder, table, ...rest).where(eq(tenantCol, tenantId)), tenantId, tenantCol);
			} };
		},
		insert(table) {
			assertRegistered(table, registered, tenantId);
			const propertyKey = tenantPropertyKey(table, columnName, tenantId);
			const builder = q.insert(table);
			return { values(values) {
				const stamped = Array.isArray(values) ? values.map((row) => ({
					...row,
					[propertyKey]: tenantId
				})) : {
					...values,
					[propertyKey]: tenantId
				};
				return builder.values(stamped);
			} };
		},
		update(table) {
			assertRegistered(table, registered, tenantId);
			const tenantCol = findTenantColumn(table, columnName, tenantId);
			const propertyKey = tenantPropertyKey(table, columnName, tenantId);
			const builder = q.update(table);
			return { set(patch) {
				if (Object.prototype.hasOwnProperty.call(patch, propertyKey)) scopeViolation(tenantId, `cannot reassign the tenant column "${columnName}" through a scoped update`);
				return guardBuilder(builder.set(patch).where(eq(tenantCol, tenantId)), tenantId, tenantCol);
			} };
		},
		delete(table) {
			assertRegistered(table, registered, tenantId);
			const tenantCol = findTenantColumn(table, columnName, tenantId);
			return guardBuilder(q.delete(table).where(eq(tenantCol, tenantId)), tenantId, tenantCol);
		},
		get execute() {
			return scopeViolation(tenantId, "escape hatch \"execute\" is not available on a scoped querier");
		},
		get $client() {
			return scopeViolation(tenantId, "escape hatch \"$client\" is not available on a scoped querier");
		},
		get transaction() {
			return scopeViolation(tenantId, "escape hatch \"transaction\" is not available on a scoped querier");
		}
	};
}
function createFkIsolation(config) {
	const column = config.column ?? "tenant_id";
	const registered = new Set(config.tables);
	return { scopeQuerier(q, tenantId) {
		return createScopeQuerier(q, tenantId, column, registered);
	} };
}
//#endregion
export { createFkIsolation };
